Modern34 (Rule34 Modern Theme)

A modern (dark) theme for rule34.xxx.

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey oder Violentmonkey installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey oder Violentmonkey installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey oder Userscripts installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey installieren.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

Um dieses Design zu installieren, müssen Sie eine Erweiterung wie Stylus installieren.

Um dieses Design zu installieren, müssen Sie eine Erweiterung wie Stylus installieren.

Um dieses Design zu installieren, müssen Sie eine Erweiterung wie Stylus installieren.

Um diesen Stil zu installieren, müssen Sie eine Erweiterung für den Benutzerstil Verwaltung installieren.

Um diesen Stil zu installieren, müssen Sie eine Erweiterung für den Benutzerstil Verwaltung installieren.

Um diesen Stil zu installieren, müssen Sie eine Erweiterung für den Benutzerstil Verwaltung installieren.

(Ich habe bereits einen Benutzerstil Verwaltung, ich möchte ihn installieren!)

// ==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
})();