A modern (dark) theme for rule34.xxx.
// ==UserScript==
// @name Modern34 (Rule34 Modern Theme)
// @description A modern (dark) theme for rule34.xxx.
// @namespace Violentmonkey Scripts
// @match *://rule34.xxx/*
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @connect localhost
// @run-at document-end
// @version 1.0
// @license MIT
// ==/UserScript==
(function() {
"use strict";
//region Config
const CFG = {
defaultColumns: GM_getValue("defaultColumns", 0),
imageReplacement: GM_getValue("imageReplacement", true)
};
function cfgSet(key, val) {
CFG[key] = val;
GM_setValue(key, val);
}
function cfgToggle(key) {
cfgSet(key, !CFG[key]);
location.reload();
}
const flag = v => v ? "✅" : "❌";
GM_registerMenuCommand(`${flag(CFG.imageReplacement)} Image Replacement`, () => cfgToggle("imageReplacement"));
GM_registerMenuCommand("🔄 Reset Image Server Cache", () => {
const host = location.hostname.replace(/^www\./, "");
GM_setValue(`imageServerBase_${host}`, "");
GM_setValue(`imageServerExpiry_${host}`, 0);
location.reload();
});
GM_registerMenuCommand("⚙️ Set Default Columns...", () => {
const input = prompt(
"Default column count (1-8).\nSet 0 for automatic responsive breakpoints.\nCurrent: " + (CFG.defaultColumns || "auto"),
CFG.defaultColumns
);
if (input === null) return;
const n = parseInt(input, 10);
if (isNaN(n) || n < 0 || n > 8) { alert("Invalid value"); return; }
cfgSet("defaultColumns", n);
location.reload();
});
//endregion
if (CFG.defaultColumns > 0) {
document.documentElement.style.setProperty("--grid-columns", CFG.defaultColumns);
}
// Note: this css document was AI generated although meticulously edited to ensure it is valid + functional
GM_addStyle("/* ==========================================================================\n 1. Theme Variables & Universal Overrides\n ========================================================================== */\n:root,\n*,\n*::before,\n*::after {\n /* Dark Mode Palette Tokens */\n --bg-main: #121212;\n --bg-surface: #1e1e1e;\n --bg-surface-elevated: #282828;\n --bg-accent: #2c2c2c;\n --bg-hover: rgba(255, 255, 255, 0.06);\n --bg-active: rgba(255, 255, 255, 0.1);\n --bg-glass: rgba(20, 20, 20, 0.9);\n\n --text-main: #e0e0e0;\n --text-muted: #888888;\n --text-dim: #666666;\n --text-inverse: #121212;\n\n --accent-color: #8ab4f8;\n --accent-hover: #aecbfa;\n --border-color: #333333;\n --border-subtle: #242424;\n\n /* Tag Colors */\n --tag-general: #8ab4f8;\n --tag-artist: #f28b82;\n --tag-character: #81c995;\n --tag-copyright: #d7aefb;\n --tag-meta: #fdd663;\n\n /* Fonts, Shadows & Radii */\n --font-sans: system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n --font-mono: ui-monospace, SFMono-Regular, Consolas, \"Liberation Mono\", Menlo, monospace;\n --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);\n --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5);\n --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.65);\n --radius-xs: 2px;\n --radius-sm: 4px;\n --radius-md: 8px;\n --radius-lg: 12px;\n --radius-full: 9999px;\n\n --grid-columns: 3;\n\n /* Site Variable Overrides (replacing original green variables) */\n --c-bg: #121212 !important;\n --c-bg-alt: #1e1e1e !important;\n --c-bg-deep: #282828 !important;\n --c-bg-highlight: #2c2c2c !important;\n --c-bg-overlay: rgba(20, 20, 20, 0.9) !important;\n --c-text: #e0e0e0 !important;\n --c-text-soft: #888888 !important;\n --c-link: #8ab4f8 !important;\n --c-link-soft: #888888 !important;\n --c-link-artist: #f28b82 !important;\n --c-link-character: #81c995 !important;\n --c-link-copyright: #d7aefb !important;\n --c-link-metadata: #fdd663 !important;\n --ff-narrow: var(--font-sans) !important;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\n\nhtml {\n min-height: 100%;\n background-color: var(--bg-main) !important;\n background: var(--bg-main) !important;\n color-scheme: dark;\n}\n\nbody,\ndiv,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np,\nul,\nol,\nli,\ndd,\ndt {\n font-family: var(--font-sans);\n margin: 0;\n padding: 0;\n}\n\nbody {\n background-color: var(--bg-main) !important;\n background: var(--bg-main) !important;\n color: var(--text-main) !important;\n font-family: var(--font-sans);\n font-size: 14px;\n line-height: 1.5;\n min-height: 100vh;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n/* ==========================================================================\n 2. Base Typography, Links & Spans\n ========================================================================== */\na,\na:link,\na:visited {\n color: var(--accent-color);\n text-decoration: none;\n transition: color 0.15s ease;\n}\n\na:hover {\n color: var(--accent-hover);\n text-decoration: underline;\n}\n\na:active {\n color: var(--accent-color);\n}\n\na.last-page {\n color: var(--text-muted);\n}\n\na.original-file-changed#highres {\n color: var(--accent-color);\n font-weight: 600;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n color: #fff;\n font-weight: 600;\n margin-bottom: 0.5em;\n line-height: 1.3;\n}\n\nh1 { font-size: 1.75rem; }\nh2 { font-size: 1.5rem; }\nh3 { font-size: 1.25rem; }\nh4 { font-size: 1.1rem; }\nh5 { font-size: 1rem; }\nh6 { font-size: 0.9rem; }\n\nh2.wiki-title {\n color: var(--accent-color);\n font-size: 1.5rem;\n margin-bottom: 0.5rem;\n border-bottom: 1px solid var(--border-color);\n padding-bottom: 0.35rem;\n}\n\np,\nul,\nol {\n margin-bottom: 1em;\n}\n\nli {\n margin-right: 0.5em;\n margin-bottom: 0.25em;\n}\n\ncode,\ntextarea {\n font-family: var(--font-mono);\n}\n\ncode {\n font-size: 0.9em;\n background-color: var(--bg-surface-elevated);\n color: var(--accent-color);\n padding: 2px 6px;\n border-radius: var(--radius-sm);\n border: 1px solid var(--border-color);\n}\n\nimg {\n border: none;\n vertical-align: middle;\n max-width: 100%;\n}\n\naddr {\n display: block;\n margin-left: 1.5em;\n font-weight: 600;\n color: var(--text-muted);\n}\n\ndd {\n margin-bottom: 1em;\n margin-left: 1.5em;\n}\n\ndt {\n font-weight: 600;\n color: var(--text-main);\n}\n\ndt.bad {\n color: #ff4d4f;\n}\n\nlabel {\n font-weight: 600;\n white-space: nowrap;\n color: var(--text-main);\n}\n\nhr {\n border: 0;\n height: 1px;\n background: var(--border-color);\n margin: 1.5em 0;\n}\n\nhr.light {\n background: var(--border-subtle);\n}\n\n/* Spoilers */\n.spoiler,\na.spoiler,\nspan.spoiler {\n background: #333;\n border-radius: var(--radius-sm);\n color: transparent;\n cursor: pointer;\n padding: 0 4px;\n user-select: none;\n transition: color 0.15s ease, background-color 0.15s ease;\n}\n\n.spoiler:hover,\na.spoiler:hover,\nspan.spoiler:hover {\n color: #fff;\n background-color: var(--bg-accent);\n user-select: text;\n}\n\n/* Metadata Spans */\nspan.author {\n color: var(--accent-color);\n font-weight: 600;\n}\n\nspan.date {\n color: var(--text-muted);\n font-size: 0.85em;\n}\n\nspan.locked-topic {\n color: #ff4d4f;\n font-weight: 600;\n}\n\nspan.informational {\n cursor: help;\n border-bottom: 1px dotted var(--text-muted);\n}\n\nspan.post-count {\n color: var(--text-muted);\n font-size: 0.85em;\n}\n\nspan.plid {\n display: none;\n}\n\nspan.unread-topic {\n font-weight: bold;\n color: var(--accent-color);\n}\n\nstrong.divider {\n font-weight: 600;\n color: var(--border-color);\n margin: 0 6px;\n}\n\n/* Lists */\nul.bare-list {\n padding-left: 0;\n list-style: none;\n}\n\nul.bare-list li {\n list-style-type: none;\n}\n\nul.flat-list {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 12px;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\nul.flat-list li {\n display: inline-flex;\n align-items: center;\n list-style-type: none;\n font-weight: 500;\n padding: 0;\n margin: 0;\n}\n\nul.flat-list li a {\n font-weight: normal;\n color: var(--accent-color);\n}\n\nul.flat-list li a.forum-update {\n font-style: italic;\n color: var(--tag-meta);\n}\n\nul.link-page {\n font-size: 1.1em;\n font-weight: 600;\n padding: 0;\n list-style: none;\n}\n\nul.link-page > li {\n margin: 6px 0;\n padding: 0;\n list-style-type: none;\n}\n\n/* ==========================================================================\n 3. Forms & Inputs\n ========================================================================== */\ninput,\nselect,\ntextarea {\n font-family: inherit;\n font-size: 14px;\n color: var(--text-main);\n background-color: var(--bg-surface-elevated);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-sm);\n padding: 6px 10px;\n outline: none;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n}\n\ninput[type=\"text\"],\ninput[type=\"password\"],\ninput[type=\"search\"] {\n background-color: var(--bg-surface-elevated);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-sm);\n color: var(--text-main);\n font-family: inherit;\n font-size: 14px;\n padding: 6px 10px;\n outline: none;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n}\n\ninput[type=\"text\"]:focus,\ninput[type=\"password\"]:focus,\ninput[type=\"search\"]:focus,\ninput[type=password]:focus,\ninput[type=text]:focus,\nselect:focus,\ntextarea:focus {\n border-color: var(--accent-color);\n box-shadow: 0 0 0 2px rgba(138, 180, 248, 0.2);\n background-color: var(--bg-surface-elevated);\n}\n\ntextarea {\n display: block;\n font-family: var(--font-mono);\n font-size: 14px;\n padding: 8px 10px;\n line-height: 1.5;\n width: 100%;\n box-sizing: border-box;\n}\n\ntextarea.no-block {\n display: inline;\n}\n\ninput[type=\"submit\"],\ninput[type=\"button\"],\nbutton,\n.button {\n -webkit-appearance: none !important;\n -moz-appearance: none !important;\n appearance: none !important;\n background-color: var(--bg-surface-elevated) !important;\n background: var(--bg-surface-elevated) !important;\n border: 1px solid var(--border-color) !important;\n border-radius: var(--radius-sm);\n color: var(--text-main) !important;\n cursor: pointer;\n font-family: var(--font-sans);\n font-size: 14px;\n font-weight: 500;\n padding: 6px 14px;\n transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;\n}\n\ninput[type=\"submit\"]:hover,\ninput[type=\"button\"]:hover,\nbutton:hover,\n.button:hover {\n background-color: #383838 !important;\n background: #383838 !important;\n border-color: #555555 !important;\n}\n\ninput[type=\"submit\"]:active,\ninput[type=\"button\"]:active,\nbutton:active,\n.button:active {\n transform: translateY(1px);\n}\n\n.tradio {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n}\n\ndiv#upload-form table,\ndiv#edit-form table {\n width: 100%;\n max-width: 850px;\n}\n\n/* ==========================================================================\n 4. Autocomplete (Awesomplete & Legacy)\n ========================================================================== */\n.awesomplete {\n position: relative;\n width: 100%;\n}\n\n.awesomplete [hidden] {\n display: none;\n}\n\n.awesomplete .visually-hidden {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n}\n\n.awesomplete > input {\n width: 100%;\n display: block;\n}\n\n.awesomplete > ul {\n background: var(--bg-surface-elevated);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-sm);\n box-shadow: var(--shadow-md);\n box-sizing: border-box;\n left: 0;\n list-style: none;\n margin: 4px 0 0;\n max-height: 300px;\n min-width: 100%;\n overflow-y: auto;\n padding: 4px 0;\n position: absolute;\n z-index: 1000;\n}\n\n.awesomplete > ul:empty {\n display: none;\n}\n\n@supports (transform: scale(0)) {\n .awesomplete > ul {\n transform-origin: top left;\n transition: opacity 0.15s ease, transform 0.15s ease;\n }\n\n .awesomplete > ul[hidden],\n .awesomplete > ul:empty {\n opacity: 0;\n transform: scale(0.95);\n display: none;\n }\n}\n\n.awesomplete > ul:before {\n content: \"\";\n position: absolute;\n top: -5px;\n left: 1.25rem;\n width: 8px;\n height: 8px;\n background-color: var(--bg-surface-elevated);\n border-left: 1px solid var(--border-color);\n border-top: 1px solid var(--border-color);\n transform: rotate(45deg);\n}\n\n.awesomplete > ul > li,\n.awesomeplete > ul > li {\n color: var(--text-main);\n cursor: pointer;\n padding: 6px 12px;\n position: relative;\n font-size: 14px;\n transition: background-color 0.15s ease;\n}\n\n.awesomplete > ul > li:hover,\n.awesomplete > ul > li[aria-selected=\"true\"] {\n background-color: var(--accent-color);\n color: var(--text-inverse);\n}\n\n.awesomplete mark {\n background: transparent;\n color: var(--tag-meta);\n font-weight: bold;\n}\n\n.awesomplete > ul > li:hover mark,\n.awesomplete > ul > li[aria-selected=\"true\"] mark {\n color: var(--text-inverse);\n}\n\n/* Legacy AutoComplete */\ndiv.auto_complete {\n width: 350px;\n background-color: var(--bg-surface-elevated);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-sm);\n box-shadow: var(--shadow-md);\n z-index: 1000;\n}\n\ndiv.auto_complete ul {\n border: none;\n margin: 0;\n padding: 4px 0;\n width: 100%;\n list-style-type: none;\n}\n\ndiv.auto_complete ul li {\n margin: 0;\n padding: 6px 12px;\n color: var(--text-main);\n cursor: pointer;\n}\n\ndiv.auto_complete ul li.selected {\n background-color: var(--accent-color);\n color: var(--text-inverse);\n}\n\ndiv.auto_complete ul strong.highlight {\n color: var(--tag-meta);\n font-weight: bold;\n}\n\n/* ==========================================================================\n 5. Booru Layout Structure & Sidebars\n ========================================================================== */\n#content,\ndiv#content {\n display: flex;\n gap: 20px;\n padding: 20px;\n max-width: 1600px;\n margin: 0 auto;\n}\n\ndiv.content {\n margin: 0 1rem;\n}\n\n.sidebar,\ndiv.sidebar {\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-md);\n box-shadow: var(--shadow-sm);\n flex-shrink: 0;\n padding: 16px;\n width: 220px;\n min-width: 200px;\n max-width: 260px;\n box-sizing: border-box;\n}\n\n.sidebar li,\ndiv.sidebar li {\n list-style: none;\n margin-bottom: 4px;\n}\n\ndiv#post-view,\ndiv#post-list,\n.flexi,\ndiv.flexi {\n display: flex;\n gap: 20px;\n width: 100%;\n}\n\ndiv#right-col {\n flex: 1 1 auto;\n min-width: 0;\n}\n\ndiv.postListSidebarRight {\n margin-left: 20px;\n width: 200px;\n min-height: 400px;\n box-sizing: border-box;\n}\n\ndiv.postViewSidebarRight {\n margin-left: 20px;\n width: 320px;\n min-height: 400px;\n box-sizing: border-box;\n}\n\n/* Search Sidebar Form */\n.tag-search,\ndiv.tag-search {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin-bottom: 1rem;\n width: 100%;\n}\n\ndiv.tag-search small {\n display: none;\n}\n\ndiv.tag-search form br {\n display: none;\n}\n\ndiv.tag-search input[type=\"text\"] {\n width: 100%;\n}\n\ndiv.tag-search input[type=\"submit\"] {\n width: 100%;\n}\n\ndiv.page,\ndiv.help {\n max-width: 900px;\n margin: 0 auto;\n padding: 20px;\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-md);\n box-shadow: var(--shadow-sm);\n}\n\ndiv.verticalFlexWithMargins {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\ndiv.verticalFlexWithMargins > * {\n margin: 0;\n}\n\ndiv.horizontalFlexWithMargins {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 10px;\n}\n\ndiv.horizontalFlexWithMargins > * {\n margin: 0;\n}\n\n/* Static Home Index */\ndiv#static-index {\n text-align: center;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 3rem 1.5rem;\n gap: 1.5rem;\n}\n\ndiv#static-index > div#links {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n gap: 10px;\n}\n\ndiv#static-index > div#links > a {\n display: inline-flex;\n align-items: center;\n padding: 6px 14px;\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-full);\n color: var(--text-main);\n font-weight: 500;\n transition: all 0.15s ease;\n}\n\ndiv#static-index > div#links > a:hover {\n background-color: var(--bg-surface-elevated);\n border-color: var(--accent-color);\n color: var(--accent-color);\n text-decoration: none;\n transform: translateY(-1px);\n}\n\ndiv#static-more {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 20px;\n width: 100%;\n max-width: 1000px;\n margin: 2rem auto;\n}\n\ndiv#static-more > div {\n float: none;\n width: auto;\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-md);\n padding: 16px;\n}\n\ndiv#static-more > div > ul {\n margin-top: 8px;\n padding-left: 0;\n}\n\ndiv#static-more > div > ul > li {\n list-style-type: none;\n margin: 6px 0;\n}\n\n/* Related & Tag Columns */\ndiv#related {\n display: flex;\n flex-wrap: wrap;\n gap: 20px;\n margin: 0 0 1.5rem 0;\n}\n\ndiv#related > div.tag-column {\n float: none;\n margin: 0;\n min-width: 160px;\n max-width: 220px;\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-md);\n padding: 16px;\n}\n\ndiv#related > div.tag-column > a {\n display: block;\n margin-bottom: 6px;\n}\n\ndiv#tag-list {\n text-align: center;\n margin-bottom: 2em;\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n gap: 8px;\n}\n\ndiv#tag-list > a {\n padding: 4px 8px;\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-sm);\n}\n\n.history-search-row {\n display: flex;\n align-items: center;\n gap: 12px;\n margin: 10px 0;\n clear: both;\n}\n\n.history-search-label {\n min-width: 100px;\n float: none;\n font-weight: 600;\n color: var(--text-muted);\n}\n\n/* ==========================================================================\n 6. Post Grid & Thumbnails (JS Grid Masonry)\n ========================================================================== */\ndiv.image-list {\n display: grid !important;\n grid-template-columns: repeat(var(--grid-columns, 3), 1fr) !important;\n grid-auto-rows: 1px !important;\n row-gap: 0 !important;\n column-gap: 16px !important;\n width: 100% !important;\n padding: 8px 0 !important;\n}\n\n.thumb,\nspan.thumb {\n position: relative !important;\n align-self: start !important; /* Prevents grid item from stretching and creating empty gaps */\n width: 100% !important;\n height: max-content !important;\n display: block !important;\n margin: 0 !important;\n padding: 0 !important;\n overflow: hidden;\n grid-row-end: span 250; /* Default span of 250px until JS takes over */\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-md);\n box-shadow: var(--shadow-sm);\n box-sizing: border-box;\n transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;\n}\n\ndiv.image-list > span[style]:not(.thumb),\ndiv#content > div[style*=\"float: left\"] span[style*=\"display: grid\"]:not(.thumb) {\n display: contents !important;\n}\n\n.thumb:hover,\nspan.thumb:hover {\n box-shadow: var(--shadow-md);\n border-color: var(--accent-color);\n transform: translateY(-2px);\n}\n\n/* Primary Thumbnail Media Link */\n.thumb a,\nspan.thumb a,\n.thumb > a:has(img, video),\nspan.thumb > a:has(img, video),\n.thumb > a:first-of-type,\nspan.thumb > a:first-of-type {\n display: block !important;\n width: 100% !important;\n height: auto !important;\n min-height: 150px !important;\n position: relative;\n overflow: hidden;\n border-radius: var(--radius-md);\n}\n\n/* Secondary Action Links & Remove Buttons inside .thumb or outer wrapper */\n.thumb > a[href*=\"remove\"]:not(:has(img, video)),\n.thumb > a[href*=\"delete\"]:not(:has(img, video)),\n.thumb > a[onclick*=\"remove\"]:not(:has(img, video)),\n.thumb > a[onclick*=\"delete\"]:not(:has(img, video)),\nspan.thumb > a[href*=\"remove\"]:not(:has(img, video)),\nspan.thumb > a[href*=\"delete\"]:not(:has(img, video)),\nspan.thumb > a[onclick*=\"remove\"]:not(:has(img, video)),\nspan.thumb > a[onclick*=\"delete\"]:not(:has(img, video)),\nspan[style] > a[href*=\"remove\"]:not(:has(img, video)),\nspan[style] > a[href*=\"delete\"]:not(:has(img, video)),\nspan[style] > a[onclick*=\"remove\"]:not(:has(img, video)),\nspan[style] > a[onclick*=\"delete\"]:not(:has(img, video)),\n.thumb > input[type=\"submit\"],\nspan.thumb > input[type=\"submit\"] {\n position: absolute !important;\n top: 8px !important;\n right: 8px !important;\n z-index: 10 !important;\n display: inline-flex !important;\n align-items: center !important;\n justify-content: center !important;\n width: auto !important;\n height: auto !important;\n min-height: 0 !important;\n padding: 4px 10px !important;\n background-color: rgba(239, 68, 68, 0.9) !important;\n color: #ffffff !important;\n font-size: 11px !important;\n font-weight: 600 !important;\n text-decoration: none !important;\n border: none !important;\n border-radius: var(--radius-sm) !important;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4) !important;\n backdrop-filter: blur(4px) !important;\n transition: all 0.15s ease !important;\n cursor: pointer !important;\n}\n\n.thumb > a[href*=\"remove\"]:not(:has(img, video)) b,\n.thumb > a[href*=\"delete\"]:not(:has(img, video)) b,\n.thumb > a[onclick*=\"remove\"]:not(:has(img, video)) b,\n.thumb > a[onclick*=\"delete\"]:not(:has(img, video)) b,\nspan.thumb > a[href*=\"remove\"]:not(:has(img, video)) b,\nspan.thumb > a[href*=\"delete\"]:not(:has(img, video)) b,\nspan.thumb > a[onclick*=\"remove\"]:not(:has(img, video)) b,\nspan.thumb > a[onclick*=\"delete\"]:not(:has(img, video)) b,\nspan[style] > a[href*=\"remove\"]:not(:has(img, video)) b,\nspan[style] > a[href*=\"delete\"]:not(:has(img, video)) b,\nspan[style] > a[onclick*=\"remove\"]:not(:has(img, video)) b,\nspan[style] > a[onclick*=\"delete\"]:not(:has(img, video)) b {\n color: #ffffff !important;\n font-size: 11px !important;\n font-weight: 600 !important;\n}\n\n.thumb > a:not(:has(img, video)):not(:first-of-type):hover,\nspan.thumb > a:not(:has(img, video)):not(:first-of-type):hover,\n.thumb > a[href*=\"remove\"]:not(:has(img, video)):hover,\n.thumb > a[href*=\"delete\"]:not(:has(img, video)):hover,\n.thumb > a[onclick*=\"remove\"]:not(:has(img, video)):hover,\n.thumb > a[onclick*=\"delete\"]:not(:has(img, video)):hover,\nspan.thumb > a[href*=\"remove\"]:not(:has(img, video)):hover,\nspan.thumb > a[href*=\"delete\"]:not(:has(img, video)):hover,\nspan.thumb > a[onclick*=\"remove\"]:not(:has(img, video)):hover,\nspan.thumb > a[onclick*=\"delete\"]:not(:has(img, video)):hover,\nspan[style] > a[href*=\"remove\"]:not(:has(img, video)):hover,\nspan[style] > a[href*=\"delete\"]:not(:has(img, video)):hover,\nspan[style] > a[onclick*=\"remove\"]:not(:has(img, video)):hover,\nspan[style] > a[onclick*=\"delete\"]:not(:has(img, video)):hover,\n.thumb > input[type=\"submit\"]:hover,\nspan.thumb > input[type=\"submit\"]:hover {\n background-color: rgba(220, 38, 38, 1) !important;\n color: #ffffff !important;\n transform: scale(1.05) !important;\n box-shadow: 0 4px 10px rgba(220, 38, 38, 0.5) !important;\n}\n\n/* Hide line breaks inside .thumb */\n.thumb br,\nspan.thumb br {\n display: none !important;\n}\n\n.thumb img,\n.thumb video,\nspan.thumb img,\nspan.thumb video {\n width: 100% !important;\n height: auto !important;\n max-width: 100% !important;\n max-height: unset !important;\n object-fit: contain;\n border-radius: var(--radius-md);\n transition: transform 0.15s ease;\n}\n\n/* Ensure images without inline display:none are displayed as block */\n.thumb img:not([style*=\"display: none\"]),\n.thumb video:not([style*=\"display: none\"]),\nspan.thumb img:not([style*=\"display: none\"]),\nspan.thumb video:not([style*=\"display: none\"]) {\n display: block !important;\n}\n\n.thumb:hover img,\n.thumb:hover video {\n transform: scale(1.02);\n}\n\n/* Status Badges on Thumbs */\nimg.flagged { outline: 3px solid #ff4d4f; border-color: #ff4d4f; }\nimg.has-children { outline: 3px solid #52c41a; border-color: #52c41a; }\nimg.has-parent { outline: 3px solid #faad14; border-color: #faad14; }\nimg.pending { outline: 3px solid #1890ff; border-color: #1890ff; }\nimg.video { outline: 3px solid var(--accent-color); border-color: var(--accent-color); }\n.webm-thumb { border: 2px solid var(--accent-color); }\n\nimg.preview {\n margin: auto;\n display: block;\n max-width: 100%;\n border-radius: var(--radius-md);\n box-shadow: var(--shadow-md);\n}\n\nspan.blacklisted-image {\n background: var(--bg-surface-elevated) url(/blacklisted.png) no-repeat center;\n background-size: contain;\n display: inline-block;\n vertical-align: top;\n border-radius: var(--radius-md);\n}\n\ndiv#post-popular > h3 {\n text-align: center;\n margin-bottom: 1.5rem;\n font-size: 1.5rem;\n}\n\ndiv#post-add p#scale {\n font-style: italic;\n color: var(--text-muted);\n}\n\n/* ==========================================================================\n 7. Booru Tag Categories\n ========================================================================== */\n.tag-type-artist > a, .tag-type-artist { color: var(--tag-artist) !important; }\n.tag-type-artist > a:hover, .tag-type-artist:hover { color: #ffb4ab !important; text-decoration: underline; }\n\n.tag-type-character > a, .tag-type-character { color: var(--tag-character) !important; }\n.tag-type-character > a:hover, .tag-type-character:hover { color: #a8f5bb !important; text-decoration: underline; }\n\n.tag-type-copyright > a, .tag-type-copyright { color: var(--tag-copyright) !important; }\n.tag-type-copyright > a:hover, .tag-type-copyright:hover { color: #eed5ff !important; text-decoration: underline; }\n\n.tag-type-metadata > a, .tag-type-metadata { color: var(--tag-meta) !important; }\n.tag-type-metadata > a:hover, .tag-type-metadata:hover { color: #ffe89e !important; text-decoration: underline; }\n\n.tag-count {\n color: var(--text-muted);\n font-size: 0.85em;\n margin-left: 0.3em;\n}\n\n.blacklisted-tags-disabled {\n color: var(--text-dim) !important;\n text-decoration: line-through;\n opacity: 0.6;\n}\n\n.obsolete-tag-change {\n color: var(--text-dim);\n text-decoration: line-through;\n}\n\n.added-tags { color: #52c41a !important; font-weight: 600; }\n.removed-tags { color: #ff4d4f !important; font-weight: 600; text-decoration: line-through; }\n.unchanged-tags { font-size: 0.85em; color: var(--text-muted); }\n\n/* ==========================================================================\n 8. Navigation, Header & Pagination\n ========================================================================== */\n#header,\ndiv#header {\n background-color: var(--bg-surface);\n border-bottom: 1px solid var(--border-color);\n padding: 10px 20px;\n margin-bottom: 1.5rem;\n}\n\n#site-title,\ndiv#header #site-title {\n padding: 10px 0 6px 0;\n background-image: none;\n}\n\n#site-title a {\n font-size: 1.75rem;\n font-weight: 800;\n color: #fff;\n text-decoration: none;\n}\n\n#site-title a:hover {\n color: var(--accent-color);\n}\n\nlabel[for=\"nav-trigger\"],\n.nav-trigger {\n display: none;\n}\n\n#header ul#navbar,\ndiv#header ul#navbar,\n#header ul#subnavbar,\ndiv#header ul#subnavbar {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 4px 0;\n}\n\n#header ul#navbar li a,\ndiv#header ul#navbar li a,\n#header ul#subnavbar li a,\ndiv#header ul#subnavbar li a {\n border-radius: var(--radius-sm);\n color: var(--text-muted);\n font-weight: 500;\n padding: 4px 10px;\n display: inline-block;\n transition: color 0.15s ease, background-color 0.15s ease;\n}\n\n#header ul#navbar li.current-page a,\n#navbar li.current-page a,\ndiv#header ul#navbar li.current-page a,\n#header ul#navbar li a:hover,\ndiv#header ul#navbar li a:hover {\n background-color: var(--bg-surface-elevated);\n color: #fff;\n text-decoration: none;\n}\n\ndiv#header ul#navbar li.current-page,\n#navbar li.current-page {\n background-image: none;\n}\n\n#header ul#subnavbar,\ndiv#header ul#subnavbar {\n background-color: var(--bg-surface-elevated);\n border-radius: var(--radius-sm);\n padding: 4px 8px;\n margin-top: 6px;\n}\n\n#header ul#subnavbar li a:hover,\ndiv#header ul#subnavbar li a:hover {\n color: var(--accent-color);\n}\n\n/* Pagination */\n#paginator,\ndiv#paginator,\n.pagination,\ndiv#paginator .pagination {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n justify-content: center;\n align-items: center;\n margin: 2.5rem 0 2rem 0;\n padding: 8px 0;\n clear: both;\n}\n\n#paginator a,\n#paginator span,\n#paginator b,\ndiv#paginator a,\ndiv#paginator span,\ndiv#paginator b,\n.pagination a,\n.pagination span,\n.pagination b {\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-sm);\n color: var(--text-main);\n padding: 6px 12px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 36px;\n height: 36px;\n font-size: 14px;\n font-weight: 500;\n line-height: 1;\n text-align: center;\n box-sizing: border-box;\n margin: 0;\n text-decoration: none;\n transition: all 0.15s ease;\n user-select: none;\n}\n\n#paginator a:hover,\ndiv#paginator a:hover,\n.pagination a:hover {\n background-color: var(--accent-color);\n border-color: var(--accent-color);\n color: var(--text-inverse);\n text-decoration: none;\n transform: translateY(-1px);\n box-shadow: var(--shadow-sm);\n}\n\n/* Current active page indicator */\n#paginator b,\ndiv#paginator b,\n.pagination b,\n#paginator span.current,\ndiv#paginator span.current,\ndiv#paginator a.current,\n.pagination span.current,\n.pagination a.current {\n background-color: var(--accent-color) !important;\n border: 1px solid var(--accent-color) !important;\n color: var(--text-inverse) !important;\n font-weight: 700 !important;\n cursor: default;\n box-shadow: 0 0 10px rgba(138, 180, 248, 0.3);\n transform: none !important;\n}\n\n/* Manual Page Chooser Form */\n.manual-page-chooser,\nform#manualpage,\ndiv#paginator form.manual-page-chooser {\n display: inline-flex !important;\n align-items: center;\n gap: 6px;\n margin: 0 0 0 12px;\n}\n\n.manual-page-chooser > input[type=\"text\"],\n.manual-page-chooser input[type=\"text\"],\n#manualpage input[type=\"text\"],\n#pageid {\n -webkit-appearance: none !important;\n -moz-appearance: none !important;\n appearance: none !important;\n width: 4.5rem;\n height: 36px;\n text-align: center;\n padding: 6px 8px;\n background-color: var(--bg-surface-elevated) !important;\n background: var(--bg-surface-elevated) !important;\n border: 1px solid var(--border-color) !important;\n border-radius: var(--radius-sm) !important;\n color: var(--text-main) !important;\n box-sizing: border-box;\n}\n\n.manual-page-chooser > input[type=\"submit\"],\n.manual-page-chooser input[type=\"submit\"],\n#manualpage input[type=\"submit\"],\nform#manualpage input[type=\"submit\"] {\n -webkit-appearance: none !important;\n -moz-appearance: none !important;\n appearance: none !important;\n height: 36px;\n padding: 6px 14px;\n background-color: var(--bg-surface-elevated) !important;\n background: var(--bg-surface-elevated) !important;\n border: 1px solid var(--border-color) !important;\n border-radius: var(--radius-sm) !important;\n color: var(--text-main) !important;\n cursor: pointer !important;\n font-weight: 600;\n box-sizing: border-box;\n transition: all 0.15s ease;\n}\n\n.manual-page-chooser > input[type=\"submit\"]:hover,\n.manual-page-chooser input[type=\"submit\"]:hover,\n#manualpage input[type=\"submit\"]:hover,\nform#manualpage input[type=\"submit\"]:hover {\n background-color: var(--accent-color) !important;\n background: var(--accent-color) !important;\n border-color: var(--accent-color) !important;\n color: var(--text-inverse) !important;\n}\n\n/* ==========================================================================\n 9. Tables & Data\n ========================================================================== */\ntable {\n border-collapse: collapse;\n margin-bottom: 2rem;\n width: 100%;\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-md);\n overflow: hidden;\n}\n\ntable th,\ntable td {\n border-bottom: 1px solid var(--border-color);\n padding: 10px 12px;\n text-align: left;\n}\n\ntable th,\ntr.tableheader,\nthead tr {\n background-color: var(--bg-surface) !important;\n color: #fff;\n font-weight: 600;\n background-image: none !important;\n}\n\ntable tr:nth-child(even) {\n background-color: rgba(255, 255, 255, 0.02);\n}\n\ntable tr:nth-child(odd) {\n background-color: transparent;\n}\n\ntable.highlightable {\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntable.highlightable th {\n color: #fff;\n}\n\ntable.highlightable > tbody > tr:hover,\ntable.highlightable tbody tr:hover {\n background-color: var(--bg-surface-elevated);\n}\n\ntable.highlightable td {\n border: none;\n border-bottom: 1px solid var(--border-color);\n}\n\ntable tr.good {\n background-color: rgba(82, 196, 26, 0.15) !important;\n}\n\ntable tr.selected {\n background-color: rgba(138, 180, 248, 0.18) !important;\n}\n\ntable tr.highlight {\n background-color: rgba(250, 173, 20, 0.15) !important;\n}\n\ntable tr.pending-tag {\n background-color: rgba(24, 144, 255, 0.15) !important;\n}\n\ntable.form {\n width: 100%;\n max-width: 900px;\n}\n\ntable.form th {\n color: #fff;\n white-space: normal;\n padding-right: 1.5rem;\n text-align: right;\n width: 25%;\n}\n\ntable.form p {\n font-size: 0.85em;\n font-weight: normal;\n color: var(--text-muted);\n margin-top: 4px;\n}\n\ndiv#artist-index > table td {\n white-space: nowrap;\n}\n\ndiv#user-record > table > tbody > tr.positive-record {\n background-color: rgba(82, 196, 26, 0.15) !important;\n}\n\ndiv#user-record > table > tbody > tr.negative-record {\n background-color: rgba(255, 77, 79, 0.15) !important;\n}\n\ndiv#forum > table th:first-child,\ndiv#forum > table td:first-child {\n text-align: left;\n}\n\n/* ==========================================================================\n 10. Notices, Blocked, Notes & Image Overlays\n ========================================================================== */\ndiv.notice,\ndiv.status-notice {\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-left: 4px solid var(--accent-color);\n border-radius: var(--radius-sm);\n margin: 1rem 0;\n padding: 12px 16px;\n color: var(--text-main);\n}\n\ndiv.status-notice p {\n margin: 0;\n}\n\ndiv.blocked {\n background-color: rgba(255, 77, 79, 0.1);\n border: 1px solid #ff4d4f;\n border-radius: var(--radius-sm);\n color: #ff4d4f;\n padding: 1rem;\n font-weight: 600;\n text-align: center;\n}\n\ndiv.quote {\n margin: 1rem 0;\n padding: 12px 16px;\n font-style: italic;\n font-size: 0.95rem;\n background-color: var(--bg-surface-elevated);\n border: 1px solid var(--border-color);\n border-left: 4px solid var(--accent-color);\n border-radius: var(--radius-sm);\n color: var(--text-main);\n}\n\ndiv.tips {\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-left: 4px solid var(--accent-color);\n padding: 10px 16px;\n font-size: 0.9rem;\n color: var(--text-main);\n border-radius: var(--radius-sm);\n margin-bottom: 1rem;\n}\n\ndiv#info {\n font-style: italic;\n color: var(--text-muted);\n}\n\n/* Note Overlay on Image */\n#note-container,\ndiv#note-container {\n position: absolute;\n}\n\n#note-container .note-box,\ndiv#note-container > div.note-box {\n border: 1px dashed #fff;\n background-color: rgba(255, 255, 255, 0.08);\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);\n cursor: move;\n position: absolute;\n width: 150px;\n height: 150px;\n border-radius: var(--radius-xs);\n z-index: 90;\n}\n\ndiv#note-container > div.note-box > div.note-corner {\n width: 8px;\n height: 8px;\n position: absolute;\n bottom: -4px;\n right: -4px;\n background-color: var(--accent-color);\n border: 1px solid var(--bg-main);\n cursor: se-resize;\n}\n\n#note-container .note-body,\ndiv#note-container > div.note-body {\n background: rgba(20, 20, 20, 0.9);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-sm);\n box-shadow: var(--shadow-md);\n color: #fff;\n padding: 8px 12px;\n position: absolute;\n max-width: 320px;\n min-width: 160px;\n backdrop-filter: blur(8px);\n cursor: pointer;\n overflow: auto;\n display: none;\n z-index: 100;\n}\n\ndiv#note-container > div.note-body > p.tn,\ntn {\n font-size: 0.8em;\n color: var(--text-muted);\n}\n\ndiv#note-container > div.unsaved {\n border: 1px dashed #ff4d4f;\n background-color: rgba(255, 77, 79, 0.15);\n}\n\ndiv#note-container > div.unsaved > div.note-corner {\n background-color: #ff4d4f;\n}\n\ndiv#edit-box {\n background-color: var(--bg-surface-elevated);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-sm);\n padding: 1rem;\n}\n\ndiv#post-view > div#right-col > div#edit {\n margin-bottom: 2rem;\n padding: 1rem;\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-md);\n}\n\n/* ==========================================================================\n 11. Comments, Forums & Response Lists\n ========================================================================== */\ndiv#post-comments {\n max-width: 850px;\n margin-top: 1.5rem;\n}\n\ndiv#post-comments div#comment-list > div,\ndiv#comments div#comment-list > div.post {\n display: flex;\n flex-direction: column;\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-md);\n padding: 16px;\n margin-bottom: 1rem;\n box-shadow: var(--shadow-sm);\n float: none;\n clear: both;\n}\n\ndiv#post-comments div#comment-list > div > div.col1,\ndiv#comments div#comment-list > div.post > div.col1 {\n float: none;\n clear: none;\n margin-bottom: 8px;\n}\n\ndiv#post-comments div#comment-list > div > div.col2,\ndiv#comments div#comment-list > div.post > div.col2 {\n float: none;\n width: 100%;\n margin-top: 0;\n}\n\ndiv#post-comments div#comment-list > div > div.col2 > div.header,\ndiv#comments div#comment-list > div.post > div.col2 > div.header {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin-bottom: 8px;\n padding-bottom: 8px;\n border-bottom: 1px solid var(--border-subtle);\n}\n\ndiv#post-comments div#comment-list > div > div.col2 > div.header > div > span.info,\ndiv#comments div#comment-list > div.post > div.col2 > div.header > div > span.info {\n margin-right: 8px;\n color: var(--text-muted);\n font-size: 0.85rem;\n}\n\ndiv#post-comments div#comment-list > div > div.col2 > div.header > div.tags > span,\ndiv#comments div#comment-list > div.post > div.col2 > div.header > div.tags > span {\n margin-right: 8px;\n}\n\ndiv.response-list {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\ndiv.response-list > div.post {\n display: flex;\n flex-direction: row;\n gap: 20px;\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-md);\n padding: 16px;\n box-shadow: var(--shadow-sm);\n margin-bottom: 0;\n}\n\ndiv.response-list > div.post > div.author {\n float: none;\n width: 180px;\n flex: 0 0 180px;\n padding-right: 0;\n border-right: 1px solid var(--border-subtle);\n overflow-x: hidden;\n}\n\ndiv.response-list > div.post > div.author > h6 {\n font-size: 1.1rem;\n color: var(--accent-color);\n margin-bottom: 6px;\n}\n\ndiv.response-list > div.post > div.content {\n float: none;\n flex: 1 1 auto;\n width: auto;\n padding: 0;\n margin: 0;\n}\n\ndiv.response-list > div.post > div.content > div.footer {\n margin-top: 12px;\n padding-top: 8px;\n border-top: 1px solid var(--border-subtle);\n color: var(--text-muted);\n font-size: 0.85rem;\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\ndiv.response-list > div.post > div.content > div.footer a {\n color: var(--text-muted);\n}\n\ndiv.response-list > div.post > div.content > div.footer a:hover {\n color: var(--accent-color);\n}\n\n/* ==========================================================================\n 12. Wiki, Diffs, Help & Mail\n ========================================================================== */\ndiv.wiki > h2.title {\n color: var(--accent-color);\n margin-bottom: 12px;\n}\n\ndiv#wiki-show > div#body {\n line-height: 1.7;\n max-width: 900px;\n width: 100%;\n text-align: left;\n float: none;\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-md);\n padding: 20px;\n}\n\ndiv#wiki-show > div#body ul {\n line-height: 1.6;\n margin: 8px 0 8px 24px;\n}\n\ndiv#wiki-show > div#body > ul {\n margin-bottom: 1em;\n}\n\ndiv#wiki-show > div#body > div#byline {\n font-style: italic;\n color: var(--text-muted);\n margin-top: 20px;\n padding-top: 8px;\n border-top: 1px solid var(--border-subtle);\n font-size: 0.9em;\n}\n\ndiv#wiki-diff > div#body {\n font-family: var(--font-mono);\n font-size: 14px;\n margin-top: 1rem;\n padding: 16px;\n background-color: var(--bg-surface);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-md);\n line-height: 1.6;\n}\n\ndiv#wiki-diff del {\n text-decoration: line-through;\n background-color: rgba(255, 77, 79, 0.25);\n color: #ff4d4f;\n padding: 2px 6px;\n border-radius: var(--radius-xs);\n}\n\ndiv#wiki-diff ins {\n text-decoration: none;\n background-color: rgba(82, 196, 26, 0.25);\n color: #52c41a;\n padding: 2px 6px;\n border-radius: var(--radius-xs);\n}\n\ndiv.help {\n margin-top: 1.5rem;\n}\n\ndiv.help a.code {\n font-family: var(--font-mono);\n}\n\ndiv.help div.code {\n border: 1px solid var(--border-color);\n background-color: var(--bg-surface-elevated);\n margin: 1rem 0;\n padding: 14px 18px;\n overflow-x: auto;\n font-family: var(--font-mono);\n border-radius: var(--radius-sm);\n color: var(--accent-color);\n}\n\ndiv.help > div.section {\n margin-bottom: 2rem;\n}\n\ndiv.help > div.section > div.section {\n margin-bottom: 1.25rem;\n}\n\ndiv.help h1,\ndiv.help h2,\ndiv.help h3,\ndiv.help h4 {\n margin: 0 0 0.5em 0;\n}\n\ndiv.help h4 {\n color: var(--accent-color);\n}\n\n/* Mail */\ndiv.mail .received {\n color: #52c41a;\n font-weight: 600;\n}\n\ndiv.mail .sent {\n color: var(--accent-color);\n font-weight: 600;\n}\n\ndiv.has-mail {\n border: 1px solid #52c41a;\n background-color: rgba(82, 196, 26, 0.15);\n color: #52c41a;\n padding: 14px 20px;\n margin: 1rem 0;\n font-size: 1.1rem;\n font-weight: 600;\n text-align: center;\n border-radius: var(--radius-sm);\n}\n\ndiv#mail-show div.message {\n margin-bottom: 2rem;\n}\n\ndiv.mailmessage {\n max-width: 800px;\n width: 100%;\n}\n\ndiv.mailbuttons {\n font-size: 14px;\n color: var(--text-muted);\n text-align: center;\n margin-top: 1rem;\n display: flex;\n justify-content: center;\n gap: 8px;\n}\n\ndiv.mailbuttons :not(:last-child):after {\n content: \"|\";\n margin-left: 8px;\n color: var(--border-color);\n}\n\ndiv.mailhint {\n padding: 14px 20px;\n font-weight: 600;\n background-color: var(--bg-surface-elevated);\n border-radius: var(--radius-sm);\n margin-bottom: 1rem;\n border: 1px solid var(--border-color);\n}\n\ndiv.mailheader {\n margin-bottom: 8px;\n}\n\ndiv.mailbody {\n margin: 8px 0;\n border: 1px solid var(--border-color);\n background-color: var(--bg-surface);\n border-radius: var(--radius-sm);\n padding: 16px;\n line-height: 1.6;\n}\n\ndiv.mailfooter {\n margin-top: 8px;\n}\n\ndiv.mailtitle {\n font-weight: 400;\n font-size: 1.15rem;\n}\n\ndiv.mailtitle > div {\n font-weight: 700;\n padding-left: 14px;\n color: var(--accent-color);\n}\n\n/* ==========================================================================\n 13. GDPR Consent Banner & Footer\n ========================================================================== */\n.gdprcontainer {\n position: fixed;\n bottom: 24px;\n left: 0;\n right: 0;\n width: 100%;\n display: flex;\n justify-content: center;\n z-index: 10000;\n pointer-events: none;\n}\n\n.gdprinner {\n pointer-events: auto;\n width: auto;\n max-width: 600px;\n min-width: 320px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 16px;\n background-color: var(--bg-glass) !important;\n background: var(--bg-glass) !important;\n border: 1px solid var(--border-color);\n border-radius: var(--radius-md);\n padding: 12px 20px;\n box-shadow: var(--shadow-lg);\n backdrop-filter: blur(12px);\n}\n\n.gdprtext {\n font-size: 14px;\n color: var(--text-main);\n}\n\n.gdprtext a {\n font-weight: 600;\n color: var(--accent-color);\n}\n\n.gdprbutton {\n text-align: center;\n flex-shrink: 0;\n}\n\n.gdprbutton button {\n font-size: 14px;\n padding: 6px 16px;\n border-radius: var(--radius-sm);\n}\n\n/* Footer */\ndiv#footer {\n margin-top: 3rem;\n padding: 2rem 1rem;\n text-align: center;\n font-size: 14px;\n color: var(--text-muted);\n border-top: 1px solid var(--border-subtle);\n clear: both;\n}\n\ndiv#footer > .footer-disabled {\n color: var(--text-dim);\n}\n\ndiv#footer > #label {\n color: var(--text-dim);\n}\n\n\n/* ==========================================================================\n Responsive Grid Columns\n ========================================================================== */\n@media (min-width: 2560px) { :root { --grid-columns: 6; } }\n@media (max-width: 2559px) and (min-width: 1920px) { :root { --grid-columns: 5; } }\n@media (max-width: 1919px) and (min-width: 1600px) { :root { --grid-columns: 4; } }\n@media (max-width: 1599px) and (min-width: 1200px) { :root { --grid-columns: 4; } }\n@media (max-width: 1199px) and (min-width: 992px) { :root { --grid-columns: 3; } }\n@media (max-width: 991px) and (min-width: 768px) { :root { --grid-columns: 3; } }\n@media (max-width: 767px) and (min-width: 576px) { :root { --grid-columns: 2; } }\n@media (max-width: 575px) { :root { --grid-columns: 1; } }\n");
setTimeout(() => {
ImageReplacement.init();
MasonryLayout.init();
}, 50);
//region Utilities
const Utils = {
debounce: (fn, ms) => { let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); }; },
log: (...args) => console.log("[Modern34]", ...args)
};
const currentHost = location.hostname.replace(/^www\./, "");
const SiteConfig = {
fallbackImageServer: `https://${currentHost}/images`,
thumbsOnlyServers: []
};
const ImageServer = {
CACHE_TTL: 7 * 24 * 60 * 60 * 1000,
resolve() {
const cacheKey = `imageServerBase_${currentHost}`;
const expiryKey = `imageServerExpiry_${currentHost}`;
const cached = GM_getValue(cacheKey, "");
const expiry = GM_getValue(expiryKey, 0);
if (cached && Date.now() < expiry) {
Utils.log("Image server (cached):", cached);
return cached;
}
const fromPage = this.detectFromPage();
if (fromPage) { this.cache(fromPage); return fromPage; }
const fallback = SiteConfig.fallbackImageServer;
this.cache(fallback);
return fallback;
},
detectFromPage() {
const thumb = document.querySelector('img[src*="/thumbnails/"]');
if (!thumb) return null;
try {
const url = new URL(thumb.src);
const base = `${url.protocol}//${url.host}/images`;
Utils.log("Image server (page-detected):", base);
return base;
} catch { return null; }
},
cache(base) {
GM_setValue(`imageServerBase_${currentHost}`, base);
GM_setValue(`imageServerExpiry_${currentHost}`, Date.now() + this.CACHE_TTL);
}
};
const ImageReplacement = {
processedImages: new Set(),
extCache: new Map(),
retryQueue: new Map(),
serverBase: "",
observer: null,
MAX_RETRIES: 3,
RETRY_DELAYS: [2000, 5000, 12000],
init() {
if (!CFG.imageReplacement) return;
this.serverBase = ImageServer.resolve();
Utils.log("Using image server:", this.serverBase);
this.blockSampleLoads();
this.observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
this.replaceThumbnail(img);
}
});
},
{ rootMargin: "300px 0px" }
);
this.observeAll();
new MutationObserver(() => this.observeAll())
.observe(document.body, { childList: true, subtree: true });
},
blockSampleLoads() {
document.querySelectorAll('img[src*="/thumbnails/"]').forEach(img => {
if (img.style.display === "none") return;
img.loading = "lazy";
img.decoding = "async";
if (!img.dataset.thumbSrc) {
img.dataset.thumbSrc = img.src;
}
});
},
observeAll() {
document.querySelectorAll('img[src*="/thumbnails/"]').forEach(img => {
if (img.style.display === "none") return;
if (!img.dataset.observed) {
img.dataset.observed = "true";
img.loading = "lazy";
img.decoding = "async";
if (!img.dataset.thumbSrc) img.dataset.thumbSrc = img.src;
this.observer.observe(img);
}
});
},
extractHash(url) { const m = url.match(/thumbnail_([a-f0-9]+)/); return m ? m[1] : null; },
extractDirectory(url) { const m = url.match(/thumbnails\/+(\d+)\//); return m ? m[1] : null; },
extractID(url) { const m = url.match(/thumbnail_[a-f0-9]+\.\w+\?(\d+)/); return m ? m[1] : null; },
createBaseUrl(thumbUrl) {
const hash = this.extractHash(thumbUrl);
const dir = this.extractDirectory(thumbUrl);
if (!hash || !dir) return null;
return { base: `${this.serverBase}/${dir}/${hash}`, hash };
},
replaceThumbnail(img) {
const src = img.dataset.thumbSrc || img.src;
if (this.processedImages.has(src) || img.dataset.processing === "true" || img.dataset.replaced) return;
const data = this.createBaseUrl(src);
if (!data) return;
const { base, hash } = data;
this.processedImages.add(src);
img.dataset.processing = "true";
const id = this.extractID(src);
if (this.extCache.has(hash)) {
const ext = this.extCache.get(hash);
this.loadImage(img, `${base}.${ext}`, hash, ext, src, id);
return;
}
this.tryExtensions(img, base, hash, src, id, 0);
},
tryExtensions(img, base, hash, thumbSrc, id, extIdx) {
const exts = ["jpg", "png", "jpeg", "gif"];
if (extIdx >= exts.length) {
Utils.log("All extensions failed for", hash, "– scheduling retry");
img.dataset.processing = "false";
this.processedImages.delete(thumbSrc);
this.scheduleRetry(img, thumbSrc);
return;
}
const ext = exts[extIdx];
const url = id ? `${base}.${ext}?${id}` : `${base}.${ext}`;
const probe = new Image();
probe.onload = () => {
this.extCache.set(hash, ext);
img.src = url;
img.dataset.replaced = "loaded";
img.dataset.processing = "false";
this.cancelRetry(thumbSrc);
img.onload = () => {
const thumb = img.closest(".thumb, span.thumb");
if (thumb) MasonryLayout.measureThumb(thumb);
};
};
probe.onerror = () => {
this.tryExtensions(img, base, hash, thumbSrc, id, extIdx + 1);
};
probe.src = url;
},
loadImage(img, url, hash, ext, thumbSrc, id) {
const fullUrl = id ? `${url}?${id}` : url;
const probe = new Image();
probe.onload = () => {
img.src = fullUrl;
img.dataset.replaced = "cached";
img.dataset.processing = "false";
this.cancelRetry(thumbSrc);
img.onload = () => {
const thumb = img.closest(".thumb, span.thumb");
if (thumb) MasonryLayout.measureThumb(thumb);
};
};
probe.onerror = () => {
Utils.log("Cached extension failed for", hash, "– retrying");
this.extCache.delete(hash);
img.dataset.processing = "false";
this.processedImages.delete(thumbSrc);
this.scheduleRetry(img, thumbSrc);
};
probe.src = fullUrl;
},
scheduleRetry(img, thumbSrc) {
const existing = this.retryQueue.get(thumbSrc);
const retries = existing ? existing.retries : 0;
if (retries >= this.MAX_RETRIES) return;
const delay = this.RETRY_DELAYS[retries] || this.RETRY_DELAYS[this.RETRY_DELAYS.length - 1];
const timer = setTimeout(() => {
this.retryQueue.delete(thumbSrc);
const r = img.getBoundingClientRect();
if (r.top < window.innerHeight + 500 && r.bottom > -500) {
this.replaceThumbnail(img);
}
}, delay);
this.retryQueue.set(thumbSrc, { img, retries: retries + 1, timer });
},
cancelRetry(thumbSrc) {
const entry = this.retryQueue.get(thumbSrc);
if (entry) {
clearTimeout(entry.timer);
this.retryQueue.delete(thumbSrc);
}
}
};
//endregion
//region Masonry Layout
const MasonryLayout = {
gridGap: 16, // vertical gap
gridRowHeight: 1, // 1px precision rows
init() {
const imageList = document.querySelector("div.image-list");
if (!imageList) return;
// inital layout
setTimeout(() => this.layoutAll(imageList), 250);
imageList.addEventListener("load", (e) => { // reload when necessary
if (e.target.tagName === "IMG") {
this.measureThumb(e.target.closest(".thumb, span.thumb"));
}
}, true);
window.addEventListener("resize", Utils.debounce(() => { // on resize
this.layoutAll(imageList);
}, 200));
// watcher
new MutationObserver(() => {
this.layoutAll(imageList);
}).observe(imageList, { childList: true, subtree: true });
},
layoutAll(container) {
if (!container) container = document.querySelector("div.image-list");
if (!container) return;
this.fixRemoveButtons(container);
const thumbs = container.querySelectorAll(".thumb, span.thumb");
thumbs.forEach(thumb => this.measureThumb(thumb));
},
fixRemoveButtons(container) {
const target = container || document;
target.querySelectorAll('span[style] > a[onclick*="delete"], span[style] > a[onclick*="remove"], span[style] > a[href*="delete"], span[style] > a[href*="remove"]').forEach(btn => {
const thumb = btn.parentElement.querySelector('.thumb, span.thumb');
if (thumb && btn.parentElement !== thumb) {
thumb.appendChild(btn);
}
});
},
measureThumb(thumb) {
if (!thumb) return;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
const content = thumb.querySelector("a") || thumb;
const img = thumb.querySelector("img, video");
if (img && img.tagName === "IMG" && !img.complete) {
if (!img.dataset.masonryBound) {
img.dataset.masonryBound = "true";
img.addEventListener("load", () => this.measureThumb(thumb), { once: true });
}
return;
}
const h = thumb.scrollHeight;
if (h <= 0) return;
const span = Math.ceil((h + this.gridGap) / this.gridRowHeight);
thumb.style.gridRowEnd = `span ${span}`;
});
});
}
};
//endregion
})();