/* ===========================================================
   What is my IP — old-school CRT console theme
   Phosphor green on black, subtle scanlines + glow.
   Switch palette by editing the variables below.
   =========================================================== */

:root {
    --bg:            #020602;   /* deep black-green CRT background */
    --surface:       #050d05;   /* sidebar / panels               */
    --surface-2:     #07120a;   /* code blocks / inputs           */
    --border:        #114d22;   /* phosphor hairline borders      */
    --text:          #33ff66;   /* primary phosphor green         */
    --text-bright:   #9dffb4;   /* highlights                     */
    --text-dim:      #1f9c44;   /* secondary / muted              */
    --glow:          rgba(51, 255, 102, .45);
    --text-err:      #ff5f5f;   /* console error lines             */
    --glow-err:      rgba(255, 95, 95, .45);
    --warn:          #ffcc66;   /* caution: held, pending, planned */
    --radius:        4px;
    --sidebar-width: 250px;
    /* PIX-VIS-019: three width tokens used consistently instead of ad hoc
       360/640/940px scattered per page. --field-w matches the existing
       single-line field width (unchanged everywhere it already applied),
       --field-w-wide matches the existing textarea/input max-width,
       --field-w-full matches the existing output-box width. */
    --field-w:       360px;
    --field-w-wide:  640px;
    --field-w-full:  940px;
}

/* ---------- Theme-palettes (overschrijven de :root variabelen) ---------- */
/* green = standaard (gelijk aan :root), dus geen overrides nodig */
body.theme-amber {
    --bg:          #0a0600;
    --surface:     #140d02;
    --surface-2:   #1a1205;
    --border:      #5a3d00;
    --text:        #ffb000;
    --text-bright: #ffd27f;
    --text-dim:    #b37700;
    --glow:        rgba(255, 176, 0, .40);
    --text-err:    #ff6a4d;
    --glow-err:    rgba(255, 106, 77, .40);
    --warn:        #fff1c2;
}
body.theme-dos {
    --bg:          #000000;
    --surface:     #0c0c0c;
    --surface-2:   #151515;
    --border:      #3a3a3a;
    --text:        #cfcfcf;
    --text-bright: #ffffff;
    --text-dim:    #8a8a8a;
    --glow:        rgba(255, 255, 255, .12);
    --text-err:    #ff5555;
    --glow-err:    rgba(255, 85, 85, .30);
    --warn:        #ffd966;
}
body.theme-c64 {
    --bg:          #3e31a2;
    --surface:     #352a8f;
    --surface-2:   #2f2580;
    --border:      #7b71d6;
    --text:        #b6acff;
    --text-bright: #ffffff;
    /* PIX-VIS-026: #8d82e0 measured 2.96:1 against --bg / 3.41:1 against
       --surface (pixel-sampled 2.33:1 in the audit); #b0a6ff measures
       4.50:1 against --bg and 5.19:1 against --surface (WCAG relative
       luminance formula), clearing the 4.5:1 target on both. */
    --text-dim:    #b0a6ff;
    --glow:        rgba(150, 135, 255, .35);
    --text-err:    #ff9a9a;
    --glow-err:    rgba(255, 154, 154, .35);
    --warn:        #f7e08a;
}
/* notepad = white paper, black ink, Courier New */
body.theme-notepad {
    --bg:          #ffffff;
    --surface:     #f5f5f5;
    --surface-2:   #ebebeb;
    /* PIX-VIS-027: #b8b8b8 on #ffffff measured 1.98:1 (below the 3:1
       component-boundary guideline). #8a8a8a measures 3.45:1 (WCAG relative
       luminance formula) against the page background. */
    --border:      #8a8a8a;
    --text:        #1a1a1a;
    --text-bright: #000000;
    --text-dim:    #666666;
    --glow:        rgba(0, 0, 0, 0);
    --text-err:    #c00000;
    --glow-err:    rgba(0, 0, 0, 0);
    --warn:        #8a5a00;
    font-family: "Courier New", Courier, monospace;
}
/* disable scanlines and vignette on a white background */
body.theme-notepad::before,
body.theme-notepad::after {
    display: none;
}

/* funky = neon synthwave: magenta tekst, cyaan accenten, diep paars */
body.theme-funky {
    --bg:          #1c0633;
    --surface:     #2a0a4d;
    --surface-2:   #36105f;
    --border:      #ff2bd6;
    --text:        #ff5ed8;
    --text-bright: #5ef9ff;
    --text-dim:    #b06bff;
    --glow:        rgba(255, 94, 216, .60);
    --text-err:    #ffe14d;
    --glow-err:    rgba(255, 225, 77, .55);
    --warn:        #ffa63d;
}

*, *::before, *::after {
    box-sizing: border-box;
}

body {
    font-family: "Cascadia Mono", "Consolas", "Lucida Console", "Courier New", monospace;
    margin: 0;
    padding: 0;
    display: flex;
    min-height: 100vh;
    background-color: var(--bg);
    color: var(--text);
    line-height: 1.55;
    letter-spacing: .5px;
    text-shadow: 0 0 4px var(--glow);   /* soft phosphor glow */
    /* Scrollbar volgt het thema: de variabelen staan op body via de theme-class,
       en moderne browsers propageren body's scrollbar-color naar de viewport. */
    scrollbar-color: var(--border) var(--bg);
    scrollbar-width: thin;
}

/* WebKit-fallback voor de scrollbar (oudere Chrome/Safari) */
body::-webkit-scrollbar { width: 12px; height: 12px; }
body::-webkit-scrollbar-track { background: var(--bg); }
body::-webkit-scrollbar-thumb {
    background: var(--border);
    border: 3px solid var(--bg);
    border-radius: 6px;
}
body::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }

/* ---------- CRT scanline + flicker overlays (subtle) ---------- */
body::before {
    /* horizontal scanlines */
    content: "";
    position: fixed;
    inset: 0;
    z-index: 9999;
    pointer-events: none;
    background: repeating-linear-gradient(
        to bottom,
        rgba(0, 0, 0, 0)   0px,
        rgba(0, 0, 0, 0)   2px,
        rgba(0, 0, 0, .28) 3px,
        rgba(0, 0, 0, .28) 3px
    );
    background-size: 100% 3px;
}

body::after {
    /* gentle vignette so it reads like a curved tube */
    content: "";
    position: fixed;
    inset: 0;
    z-index: 9998;
    pointer-events: none;
    background: radial-gradient(ellipse at center,
        rgba(0, 0, 0, 0) 60%,
        rgba(0, 0, 0, .55) 100%);
}

/* ---------- Sidebar (terminal menu) ---------- */
.sidebar {
    width: var(--sidebar-width);
    background-color: var(--surface);
    height: 100vh;
    position: fixed;
    top: 0;
    left: 0;
    display: flex;
    flex-direction: column;
    padding-top: 16px;
    border-right: 1px solid var(--border);
}

.sidebar a {
    color: var(--text);
    padding: 11px 20px;
    text-decoration: none;
    text-align: left;
    display: block;
    font-size: 15px;
    text-transform: uppercase;
    text-shadow: 0 0 6px var(--glow);
    transition: color .12s ease, background-color .12s ease;
}

/* Scoped to .nav-links (not .sidebar a) so the "> " prefix never reaches the
   .brand link: a selector that only ever matched nav items cannot be beaten
   by a lower-specificity .brand::before override (OD-01). */
.nav-links a::before {
    content: "> ";
    color: var(--text-dim);
}

.sidebar a:hover {
    background-color: var(--surface-2);
    color: var(--text-bright);
    text-shadow: 0 0 8px var(--glow);
}

.nav-links a:hover::before {
    color: var(--text);
}

/* admin-only nav item */
.nav-admin {
    opacity: .6;
    font-size: 13px !important;
}
.nav-admin::before {
    content: "$ " !important;
}

/* visitor counter at sidebar bottom */
.sidebar-footer {
    margin-top: auto;
    padding: 12px 20px;
    border-top: 1px dashed var(--border);
    color: var(--text-dim);
    font-size: 11px;
    letter-spacing: .5px;
    text-shadow: 0 0 4px var(--glow);
}

/* A second copy of the palette switcher lives inside the mobile menu (see
   the max-width: 720px block below); on desktop the standalone switcher
   above the sidebar already covers it. This base rule has to sit BEFORE that
   media block: a later same-specificity base rule would beat the mobile
   `display: block` at every width, which is exactly the bug that hid the
   switcher on mobile entirely (no base rule may follow the 720px block for
   any selector this file also sets inside it). */
.nav-theme-switcher { display: none; }

/* hamburger button — hidden on desktop */
.nav-toggle {
    display: none;
}

/* nav-links wrapper — fills sidebar on desktop */
.nav-links {
    display: flex;
    flex-direction: column;
    flex: 1;
}

/* ---------- Skip link ---------- */
.skip-link {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 10000;
    padding: 8px 16px;
    background: var(--surface);
    color: var(--text);
    text-decoration: none;
    /* PIX-VIS-025 round 2: translateY(-100%) moves the box up by its OWN
       border-box height, so a 1px border on every side put the BOTTOM
       border of the translated box exactly back at y:0 -- still a hairline
       sliver, just from the border instead of from the old `top: -40px`.
       No border at rest (round 1 kept `border: 1px solid var(--border)`
       here, which is exactly what was still visible) -- the border only
       exists in :focus, and the lift adds 4px on top of -100% so rounding
       cannot put any pixel of the focused-state border back at y:0 either. */
    border: none;
    transform: translateY(calc(-100% - 4px));
}

.skip-link:focus {
    outline: none;
    border: 1px solid var(--text);
    box-shadow: 0 0 10px var(--glow);
    transform: translateY(0);
}

/* ---------- Content ---------- */
.content {
    margin-left: var(--sidebar-width);
    padding: 0;
    width: calc(100% - var(--sidebar-width));
}

.content-body {
    padding: 26px 30px;
    width: 100%;
    max-width: 1000px;
}

/* ---------- Header ---------- */
.header {
    background-color: var(--surface);
    color: var(--text-bright);
    /* The fixed palette switcher sits top-right; clear it with right padding
       instead of a tall top padding, so the header stays a normal height
       (OD-01: a more compact public header). */
    padding: 16px 200px 16px 30px;
    text-align: left;
    border-bottom: 1px solid var(--border);
}

/* Desktop: the content-body padding already separates the page from the
   header, so the first heading does not add its own top margin on top of it
   (owner feedback 28-09: less whitespace under the header). Mobile keeps its
   own spacing, set in the 720px block below. */
@media (min-width: 721px) {
    .content-body > :first-child { margin-top: 0; }
}

.header h1 {
    margin: 0;
    font-size: 24px;
    font-weight: normal;
    text-transform: none;
    letter-spacing: 1px;
}

.header h1::before {
    content: "root@kali:~$ ";
    color: var(--text-dim);
    text-transform: none;
}

@media (max-width: 480px) {
    .header h1::before { content: "~$ "; }
}

/* blinking cursor after the title */
.header h1::after {
    content: "_";
    margin-left: 4px;
    animation: blink 1.1s steps(1) infinite;
}

@keyframes blink {
    50% { opacity: 0; }
}

/* ---------- Typography ---------- */
h2 {
    font-size: 18px;
    font-weight: normal;
    text-transform: uppercase;
    letter-spacing: 1px;
    margin: 26px 0 14px;
    color: var(--text-bright);
}

h3 {
    font-size: 15px;
    font-weight: normal;
    margin: 26px 0 10px;
    color: var(--text-bright);
    text-transform: uppercase;
    letter-spacing: 1px;
    padding-bottom: 7px;
    border-bottom: 1px dashed var(--border);
}

h3::before {
    content: "// ";
    color: var(--text-dim);
}

a {
    color: var(--text-bright);
}
a:hover {
    text-shadow: 0 0 8px var(--glow);
}

/* ---------- Code / data blocks ---------- */
pre {
    background-color: var(--surface-2);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 14px 16px;
    margin: 0 0 18px;
    font-family: inherit;
    font-size: 14px;
    line-height: 1.5;
    /* PIX-VIS-019: the shared "full column output" box -- a named cap
       (--field-w-full) instead of an accidental one inherited from
       .content-body's own 1000px max-width. */
    max-width: var(--field-w-full);
    /* lange whois/geo-regels afbreken i.p.v. horizontaal scrollen */
    white-space: pre-wrap;
    word-wrap: break-word;
    overflow-wrap: break-word;
}

/* A client-side tool's output <pre> starts empty (no server render), and its
   boxed, bordered style then reads as broken or failed output rather than
   "nothing here yet" -- three blank bars on /ipcalc, four on /cron before any
   input (PIX-VIS-017). /subnet already avoids printing an empty box; this
   makes every tool do the same without each page having to remember to. The
   element (and its id) stays in the DOM either way, so JS bindings and the
   Wp10PublicContractTest id list are untouched -- only its box disappears
   while textContent is empty. */
pre:empty {
    display: none;
}

/* PIX-VIS-043: one error component and one neutral "no result" component,
   shared by every public tool and form instead of four different
   treatments (grey-on-grey, red-on-grey, red without a box, red-bordered).
   .msg-error keeps the "[!] " marker the terminal identity already uses
   elsewhere; .msg-empty is deliberately marker-free so a real error can
   never be mistaken for "nothing found". Both stay <pre>-compatible (same
   box as the rest of a tool's output) so existing markup can just gain the
   class instead of being rewritten. */
.msg-error,
pre.msg-error {
    color: var(--text-err);
    border-color: var(--text-err);
    text-shadow: 0 0 4px var(--glow-err);
}
.msg-empty,
pre.msg-empty {
    color: var(--text-dim);
}

/* PIX-VIS-023: one small status vocabulary -- pass / info / warn / fail --
   text plus symbol so colour is never the only signal, reused wherever a
   page shows a pass/fail/warning state instead of four different ad hoc
   treatments (plain text, an info-only marker, a neutral grey box for a
   warning, colour alone for a checksum match). Warnings get the same
   --warn accent already used elsewhere ("held, pending, planned"); pass is
   the ordinary text colour (a good state should not read as alarming);
   fail is --text-err. */
.status-pass, .status-info, .status-warn, .status-fail {
    font-weight: normal;
}
.status-pass { color: var(--text); }
.status-pass::before { content: "[+] "; }
.status-info { color: var(--text-dim); }
.status-info::before { content: "[i] "; }
.status-warn { color: var(--warn); }
.status-warn::before { content: "[*] "; }
.status-fail { color: var(--text-err); text-shadow: 0 0 4px var(--glow-err); }
.status-fail::before { content: "[!] "; }

/* A warning banner (PAN-OS high-findings notice, cron's "Frequent ..."
   caution) gets the same amber-ish box as .status-warn's text colour,
   instead of the neutral output box every other message uses. */
.status-warn-box {
    border-left: 3px solid var(--warn);
    color: var(--warn);
    background: var(--surface-2);
    padding: 8px 12px;
}

/* ---------- Inline-form row (flex input + button) ---------- */
.input-row {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-bottom: 0;
}

.input-row .input-flex {
    flex: 1;
    max-width: var(--field-w);
}

.input-row input[type="text"] {
    margin-bottom: 0;
}

.input-row button {
    margin-top: 0;
}

/* ---------- Forms (IP Extractor) ---------- */
form {
    margin-bottom: 18px;
}

label {
    display: block;
    margin-bottom: 8px;
    color: var(--text-dim);
    font-size: 14px;
    text-transform: uppercase;
    letter-spacing: .5px;
}

textarea,
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
select {
    display: block;
    width: 100%;
    max-width: var(--field-w-wide);
    margin-bottom: 16px;
    background-color: var(--surface-2);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 11px 14px;
    font-family: inherit;
    font-size: 14px;
    line-height: 1.5;
    text-shadow: 0 0 4px var(--glow);
    transition: border-color .12s ease, box-shadow .12s ease;
    caret-color: var(--text);
}

textarea {
    resize: vertical;
}

/* PIX-VIS-011 / PIX-VIS-020: native selects, number inputs and checkboxes
   used to render entirely unstyled (system sans-serif, white fill, browser
   default blue checkbox) next to the styled text fields around them --
   about half the height of a text field on mobile, and off-palette in
   every theme. Folding select/number into the shared rule above gives them
   the same font, height, fill and border as text fields; accent-color
   pulls the checkbox/radio tick itself from the palette instead of the
   browser default blue. */
select {
    cursor: pointer;
}
select option {
    background: var(--surface);
    color: var(--text);
}
input[type="checkbox"],
input[type="radio"] {
    width: 18px;
    height: 18px;
    accent-color: var(--text);
    cursor: pointer;
    vertical-align: middle;
}

textarea:focus,
input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
input[type="number"]:focus,
select:focus {
    outline: none;
    border-color: var(--text);
    box-shadow: 0 0 0 1px var(--text), 0 0 10px var(--glow);
}

/* PIX-VIS-043 round 2: field marked invalid by a server-rendered or
   client-side error message next to it (aria-invalid="true" on the
   input/textarea). This has to sit *after* the two rules above and match
   at least their specificity, or it silently loses: a plain
   `input[aria-invalid="true"]` (0,1,1) is the same weight as
   `input[type="text"]` (0,1,1) -- same weight means "whichever comes
   later in the file" wins, which is how the unfocused /dns and /ssl
   inputs kept their ordinary grey border. The type+aria-invalid pair below
   (0,2,1) matches the *focus* rule's own weight, so placing it after that
   rule is enough for the unfocused case; the second block adds :focus
   itself (0,3,1), which outweighs the focus rule outright regardless of
   order. No !important needed either way. */
input[type="text"][aria-invalid="true"],
input[type="email"][aria-invalid="true"],
input[type="password"][aria-invalid="true"],
input[type="number"][aria-invalid="true"],
textarea[aria-invalid="true"] {
    border-color: var(--text-err);
}
input[type="text"][aria-invalid="true"]:focus,
input[type="email"][aria-invalid="true"]:focus,
input[type="password"][aria-invalid="true"]:focus,
input[type="number"][aria-invalid="true"]:focus,
textarea[aria-invalid="true"]:focus {
    outline: none;
    border-color: var(--text-err);
    box-shadow: 0 0 0 1px var(--text-err), 0 0 10px var(--glow-err);
}

/* Captcha-veld smaller dan de rest */
#captcha {
    max-width: 120px;
}

/* Honeypot-veld: onzichtbaar voor mensen, wel in de DOM voor bots.
   In style.css i.p.v. inline: de CSP staat inline style-attributen alleen bewust
   en beperkt toe (zie SecurityHeaders), dus vaste regels horen hier. */
.hp-field {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

button {
    margin-top: 14px;
    background-color: transparent;
    color: var(--text);
    border: 1px solid var(--text);
    border-radius: var(--radius);
    padding: 10px 22px;
    font-family: inherit;
    font-size: 14px;
    text-transform: uppercase;
    letter-spacing: 1px;
    cursor: pointer;
    text-shadow: 0 0 5px var(--glow);
    transition: background-color .12s ease, color .12s ease, box-shadow .12s ease;
}

/* PIX-VIS-024: focused text inputs already get a clear border+glow (see
   the textarea/input :focus rule above); focused buttons and other
   non-field controls only went from a 1px to a 2px border, which barely
   moves on an already-outlined button. A dedicated focus-visible outline
   (offset, so it does not sit flush on the button's own border) gives every
   interactive element the same clearly-visible ring, in the palette's own
   accent colour, keyboard-only (mouse clicks do not trigger
   :focus-visible). */
a:focus-visible,
button:focus-visible,
.example-list button.example:focus-visible,
.theme-switcher .theme-btn:focus-visible {
    outline: 2px solid var(--text-bright);
    outline-offset: 3px;
}

/* PIX-VIS-022: primary/secondary/danger, on top of the plain `button` rule
   above (which stays the "secondary" look -- most buttons on a page are not
   the one primary action). Applied narrowly (e.g. /panos "Analyze") rather
   than rewritten across every tool in this batch; see the batch report for
   what did and did not get a variant applied in time. */
button.btn-primary {
    background-color: var(--text);
    color: var(--bg);
    text-shadow: none;
}
button.btn-primary:hover {
    background-color: var(--text-bright);
    color: var(--bg);
}
button.btn-danger {
    border-color: var(--text-err);
    color: var(--text-err);
}
button.btn-danger:hover {
    background-color: var(--text-err);
    color: var(--bg);
    box-shadow: 0 0 12px var(--glow-err);
}

button:hover {
    background-color: var(--text);
    color: var(--bg);
    box-shadow: 0 0 12px var(--glow);
    text-shadow: none;
}
button:active {
    transform: translateY(1px);
}

/* ---------- Lists ---------- */
ul {
    list-style: none;
    padding: 0;
    margin: 14px 0;
}

li {
    background-color: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 8px 14px;
    margin-bottom: 7px;
    font-family: inherit;
    font-size: 14px;
    width: fit-content;
    max-width: 100%;
    /* Prose wraps at word boundaries; break-all split ordinary words
       mid-letter ("collapsi|ng") on /tools, related-tools cards and /cron
       examples. overflow-wrap only breaks a word when it would not fit on
       its own, which still keeps a long, unbreakable technical token (an
       IP, a hash) from overflowing its box (PIX-VIS-005). */
    overflow-wrap: break-word;
    word-break: normal;
}

li::before {
    content: "[+] ";
    color: var(--text-dim);
}

/* ---------- Inline label prefix in input-row ---------- */
.input-prefix {
    white-space: nowrap;
    color: var(--text-dim);
    font-size: 14px;
}

/* ---------- Read-only display inputs ---------- */
input[readonly] {
    opacity: .75;
    cursor: default;
}
input[readonly]:focus {
    border-color: var(--border);
    box-shadow: none;
}

/* ---------- Primary IP answer (home page) ---------- */
/* The visitor's IP is the one thing most people come to the page for, but a
   plain readonly text input made it look exactly like the editable lookup
   field below it, with no more visual weight than a section heading
   (PIX-VIS-039). It is still the same <input readonly id="detected-ip">
   element -- copy-ip.js and the tests bind to that id -- only its chrome and
   size change: no field background/border (so it stops reading as "editable"),
   large bright text (so it reads as the answer), still fully selectable and
   copyable text either way. */
#detected-ip {
    background: transparent;
    border: none;
    border-radius: 0;
    box-shadow: none;
    opacity: 1;
    padding: 4px 0;
    max-width: none;
    color: var(--text-bright);
    font-size: 28px;
    letter-spacing: 1px;
    text-shadow: 0 0 8px var(--glow);
}
#detected-ip:focus {
    outline: none;
    border: none;
    box-shadow: none;
}
@media (max-width: 480px) {
    #detected-ip { font-size: 22px; }
}

/* ---------- Hint text ---------- */
/* PIX-VIS-031: hint paragraphs ran to ~120 characters per line in this
   small monospace; capped at ~75ch measure like the rest of this batch's
   long-text fixes. */
.hint {
    color: var(--text-dim);
    font-size: 13px;
    max-width: 75ch;
}

/* ---------- AJAX loading state ---------- */
.ajax-loading {
    opacity: .65;
    position: relative;
}
.ajax-loading::after {
    content: '_';
    animation: blink 1.1s steps(1) infinite;
}


/* ---------- Geo map ---------- */
.geo-map {
    display: none;
    height: 220px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    margin-bottom: 18px;
    box-shadow: 0 0 10px var(--glow);
    overflow: hidden;
}

/* The OpenStreetMap tiles are light only; the dark palettes invert them so the
   map stays in the page's colour scheme (PIX-089B). Only the tile pane is
   filtered: the marker and the controls keep the theme's own colours. */
.geo-map--dark .leaflet-tile-pane {
    filter: invert(1) hue-rotate(180deg) brightness(0.85) contrast(0.9);
}

.leaflet-control-attribution {
    background: var(--surface) !important;
    color: var(--text-dim) !important;
    font-size: 10px !important;
    border-radius: 0 !important;
    border: none !important;
}

.leaflet-control-attribution a {
    color: var(--text-dim) !important;
}

.leaflet-control-zoom a {
    background: var(--surface) !important;
    color: var(--text) !important;
    border-color: var(--border) !important;
    font-family: inherit !important;
}

.leaflet-control-zoom a:hover {
    background: var(--surface-2) !important;
}

/* ---------- Pong canvas ---------- */
#pong-canvas {
    display: block;
    width: 100%;
    max-width: 800px;
    border: 1px solid var(--border);
    box-shadow: 0 0 18px var(--glow);
    cursor: none;
    border-radius: var(--radius);
    margin-bottom: 10px;
}

/* ---------- Theme-switcher (rechtsboven) ---------- */
.theme-switcher {
    position: fixed;
    top: 10px;
    right: 12px;
    z-index: 10000;
    display: flex;
    gap: 6px;
}

.theme-switcher .theme-btn {
    /* overschrijft de algemene button-stijl */
    margin: 0;
    padding: 5px 9px 5px 7px;
    background-color: #000000;
    color: var(--text-dim);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    font-family: inherit;
    font-size: 11px;
    letter-spacing: .5px;
    text-transform: uppercase;
    text-shadow: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 6px;
    line-height: 1;
    transition: color .12s ease, border-color .12s ease, background-color .12s ease;
}

/* kleurstip vóór het label, per thema */
.theme-switcher .theme-btn::before {
    content: "";
    width: 10px;
    height: 10px;
    border-radius: 50%;
    display: inline-block;
    box-shadow: 0 0 4px currentColor;
}
.theme-switcher .theme-btn[data-theme="green"]::before { background: #33ff66; }
.theme-switcher .theme-btn[data-theme="amber"]::before { background: #ffb000; }
.theme-switcher .theme-btn[data-theme="dos"]::before   { background: #cfcfcf; }
.theme-switcher .theme-btn[data-theme="c64"]::before   { background: #6c5fd0; }
.theme-switcher .theme-btn[data-theme="notepad"]::before { background: #000000; box-shadow: none; }
.theme-switcher .theme-btn[data-theme="funky"]::before {
    background: conic-gradient(#ff2bd6, #5ef9ff, #b06bff, #ffe14d, #ff2bd6);
}

.theme-switcher .theme-btn:hover {
    color: var(--text-bright);
    border-color: var(--text-dim);
    background-color: #1a1a1a;
}
.theme-switcher .theme-btn[data-theme="notepad"] {
    background-color: #ffffff;
    color: #555555;
    border-color: #888888;
}
.theme-switcher .theme-btn[data-theme="notepad"]:hover {
    background-color: #f0f0f0;
    color: #000000;
    border-color: #444444;
}

.theme-switcher .theme-btn.active {
    color: var(--text-bright);
    border-color: var(--text);
    box-shadow: 0 0 8px var(--glow);
}

/* Theme labels hidden — dots only */
.theme-switcher .theme-btn span { display: none; }
.theme-switcher .theme-btn { padding: 6px; }

/* Op smalle schermen: eigen balk bovenaan i.p.v. zwevend over de titel. */
@media (max-width: 720px) {
    .theme-switcher {
        position: static;
        width: 100%;
        justify-content: flex-end;
        padding: 8px 12px;
        background-color: var(--surface);
        border-bottom: 1px solid var(--border);
    }
    .theme-switcher .theme-btn::before { width: 12px; height: 12px; }
}

/* ---------- Funky theme: bewegende regenboog-header ---------- */
body.theme-funky .header {
    background: linear-gradient(90deg, #ff2bd6, #5ef9ff, #b06bff, #ffe14d, #ff2bd6);
    background-size: 300% 100%;
    animation: funky-shift 6s linear infinite;
}
@keyframes funky-shift {
    to { background-position: 300% 0; }
}
body.theme-funky .header h1,
body.theme-funky .header h1::before {
    color: #ffffff;
    text-shadow: 0 0 4px #000, 0 0 12px var(--glow);
}
body.theme-funky .sidebar a:hover {
    background-color: rgba(255, 43, 214, .18);
}
@media (prefers-reduced-motion: reduce) {
    body.theme-funky .header { animation: none; }
}

/* ---------- Responsive ---------- */
@media (max-width: 720px) {
    body {
        flex-direction: column;
    }
    /* One compact header row (hamburger + brand) instead of a stack of full
       rows: the two sit side by side, and only the nav dropdown wraps onto
       its own row below them (PIX-VIS-004). */
    .sidebar {
        position: static;
        width: 100%;
        height: auto;
        display: flex;
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
        padding-top: 0;
        border-right: none;
        border-bottom: 1px solid var(--border);
    }

    /* hamburger button */
    .nav-toggle {
        display: flex;
        flex-direction: column;
        justify-content: center;
        gap: 6px;
        background: none;
        border: 1px solid var(--border);
        border-radius: var(--radius);
        cursor: pointer;
        padding: 10px 14px;
        margin: 8px 4px 8px 12px;
        align-self: center;
        order: 1;
        transition: border-color .12s ease, box-shadow .12s ease;
    }
    .nav-toggle:hover,
    .nav-toggle:focus-visible {
        border-color: var(--text);
        box-shadow: 0 0 6px var(--glow);
        outline: none;
    }
    .nav-toggle span {
        display: block;
        width: 20px;
        height: 2px;
        background: var(--text);
        box-shadow: 0 0 4px var(--glow);
        transition: transform .2s ease, opacity .2s ease;
        transform-origin: center;
    }

    /* bars → ✕ when open */
    .sidebar.open .nav-toggle span:nth-child(1) {
        transform: translateY(8px) rotate(45deg);
    }
    .sidebar.open .nav-toggle span:nth-child(2) {
        opacity: 0;
        transform: scaleX(0);
    }
    .sidebar.open .nav-toggle span:nth-child(3) {
        transform: translateY(-8px) rotate(-45deg);
    }

    /* brand shares the hamburger's row on mobile instead of stacking under it.
       Scoped as ".sidebar .brand" (0,2,0), not ".brand" (0,1,0): the base
       ".brand" rule lives further down the file in the pre-existing "brand
       and tool navigation" section, so a same-specificity mobile override
       here would lose to it on source order alone and silently keep the
       desktop padding/border on mobile — the same bug class flagged for the
       theme-switcher below. */
    .sidebar .brand {
        order: 2;
        flex: 1 1 auto;
        min-width: 0;
        padding: 6px 12px 6px 0;
        border-bottom: none;
        margin-bottom: 0;
    }

    /* dropdown — collapsed by default. Capped relative to the viewport with
       its own scroll instead of a fixed 400px, so every destination is
       reachable on any phone (PIX-VIS-001: the fixed cap clipped the menu
       after "PALO ALTO" and could not be scrolled). */
    .nav-links {
        max-height: 0;
        overflow: hidden;
        overflow-y: auto;
        transition: max-height .25s ease;
        order: 3;
        flex-basis: 100%;
    }
    .sidebar.open .nav-links {
        max-height: calc(100vh - 70px);
        overflow-y: auto;
    }

    /* The visitor row and the palette switcher used to be their own full
       rows above the page content; they now only appear inside the opened
       menu, so the collapsed header stays to the hamburger + brand row
       (PIX-VIS-004 / OD-01). */
    .sidebar-footer {
        display: none;
        border-top: none;
        padding: 6px 16px 10px;
        text-align: right;
    }
    .sidebar.open .sidebar-footer {
        display: block;
        order: 4;
        flex-basis: 100%;
    }

    /* The standalone switcher above the sidebar would be a second full-width
       bar on public pages; it moves into the opened menu instead. Admin and
       auth pages are not built from .sidebar and keep the standalone bar
       from the rule above untouched. */
    body:not(.admin-body) > .theme-switcher {
        display: none;
    }
    .nav-theme-switcher {
        display: block;
        padding: 10px 20px 6px;
        border-top: 1px dashed var(--border);
        margin-top: 6px;
    }
    .nav-theme-switcher .theme-switcher {
        position: static;
        width: auto;
        padding: 0;
        background: none;
        border-bottom: none;
        flex-wrap: wrap;
        gap: 10px;
        justify-content: flex-start;
    }
    /* >=32px hit area for the palette buttons on mobile (PIX-VIS-033) */
    .nav-theme-switcher .theme-btn {
        padding: 9px;
    }
    .nav-theme-switcher .theme-btn::before {
        width: 14px;
        height: 14px;
    }

    /* input + button rows stack vertically on small screens */
    .input-row {
        flex-direction: column;
        align-items: stretch;
    }
    .input-row .input-flex {
        max-width: 100%;
    }
    .input-row button {
        width: 100%;
    }

    .content {
        margin-left: 0;
        width: 100%;
    }
    /* The palette switcher no longer floats over the header on mobile (it
       lives in the menu), so the header just needs its own small padding. */
    .header {
        padding: 16px 18px;
    }
    .header h1 {
        font-size: 19px;
    }
    .content-body {
        padding: 18px 16px;
    }
}

/* ---------- Encoder/decoder <select> ---------- */
select#encode-scheme {
    display: block;
    max-width: 260px;
    margin-bottom: 16px;
    background-color: var(--surface-2);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 10px 12px;
    font-family: inherit;
    font-size: 14px;
    text-shadow: 0 0 4px var(--glow);
    cursor: pointer;
}
select#encode-scheme:focus {
    outline: none;
    border-color: var(--text);
    box-shadow: 0 0 0 1px var(--text), 0 0 10px var(--glow);
}
select#encode-scheme option { background: var(--surface); color: var(--text); }

.encode-actions {
    flex-wrap: wrap;
    margin-bottom: 16px;
}
.encode-actions button { margin-top: 0; }

#encode-output { caret-color: transparent; }

/* ---------- Interactive console ---------- */
.term {
    background-color: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: 0 0 14px var(--glow);
    padding: 14px 16px;
    height: min(62vh, 560px);
    overflow-y: auto;
    font-size: 14px;
    line-height: 1.5;
    cursor: text;
    margin-bottom: 10px;
}
.term:focus-within {
    border-color: var(--text);
    box-shadow: 0 0 0 1px var(--text), 0 0 14px var(--glow);
}

.term-output { white-space: pre-wrap; word-break: break-word; }

.term-line { white-space: pre-wrap; word-break: break-word; min-height: 1.2em; }
.term-cmd     { color: var(--text-bright); }
.term-dim     { color: var(--text-dim); }
.term-bright  { color: var(--text-bright); text-shadow: 0 0 8px var(--glow); }
.term-err     { color: var(--text-err); text-shadow: 0 0 6px var(--glow-err); }

.term-prompt { color: var(--text-dim); }

.term-inputline {
    display: flex;
    align-items: center;
    gap: 8px;
}
.term-input {
    flex: 1;
    width: auto !important;
    max-width: none !important;
    margin: 0 !important;
    padding: 2px 0 !important;
    background: transparent !important;
    border: none !important;
    box-shadow: none !important;
    color: var(--text-bright) !important;
    font-family: inherit;
    font-size: 14px;
    caret-color: var(--text);
}
.term-input:focus { outline: none; box-shadow: none !important; }

/* ---------- 404 page ---------- */
/* The 404 used to hide .sidebar and .content-body entirely, which also hid
   the template's own "[!] The requested page does not exist." message and
   its Home / All tools links -- a visitor who followed a stale link had no
   way back into the site but the browser's own controls (PIX-VIS-003). The
   body still carries the page-404 class (Wp10HomePageTest pins that), but it
   no longer changes the layout: the 404 now renders through the exact same
   shell as every other page, sidebar and all, and the 43px of empty mobile
   scroll that came from centering an otherwise-hidden page disappears with
   it. */

/* ===========================================================
   Pixtria — brand and tool navigation
   Added when the product became a toolbox instead of a single
   page: the sidebar carries every tool, so it needs category headings and
   a scroll of its own.
   =========================================================== */

/* ---------- Brand block ---------- */
.brand {
    display: block;
    padding: 0 20px 10px;
    border-bottom: 1px dashed var(--border);
    margin-bottom: 6px;
    text-decoration: none;
}
.brand:hover { background: none; }

/* ".sidebar a" (0,1,1) outranks ".brand" (0,1,0), so on desktop the brand
   silently took the nav links' 11px top padding on top of the sidebar's own
   16px (owner feedback 30-09: too much space above PIXTRIA). Restoring the
   intended 0 puts the brand on the same 16px line as the header text.
   Desktop only: mobile has its own ".sidebar .brand" padding above. */
@media (min-width: 721px) {
    .sidebar .brand { padding-top: 0; }
}

.brand-name {
    display: block;
    font-size: 20px;
    letter-spacing: 3px;
    text-transform: uppercase;
    color: var(--text-bright);
    text-shadow: 0 0 10px var(--glow);
}
.brand-tagline {
    display: block;
    font-size: 10px;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    color: var(--text-dim);
    margin-top: 2px;
}

/* ---------- Category headings in the sidebar ---------- */
.nav-links { overflow-y: auto; }         /* dozens of tools must not clip */

.nav-cat {
    display: block;
    padding: 12px 20px 3px;
    /* PIX-VIS-026: was 10px + opacity: .75, which pixel-sampled at
       2.33-3.21:1 across palettes (the opacity blends the already-dim
       token further toward the surface colour). Removing the opacity and
       going to 11px keeps --text-dim's own contrast (>=4.5:1 in every
       palette after the c64 token fix above) instead of diluting it, and
       clears the ~11-12px minimum for small labels. */
    font-size: 11px;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: var(--text-dim);
}
.nav-cat:first-child { padding-top: 4px; }

.sidebar .nav-links a {
    padding: 6px 20px 6px 26px;
    font-size: 13px;
    text-transform: none;
    letter-spacing: .3px;
}
.sidebar .nav-links a.active {
    background-color: var(--surface-2);
    color: var(--text-bright);
}
.sidebar .nav-links a.active::before { content: "* "; color: var(--text); }

/* ---------- Tool lists (/tools) ---------- */
.tool-search {
    max-width: var(--field-w-full);
    margin: 0 0 28px;
}
.tool-search input {
    box-sizing: border-box;
    max-width: 100%;
    width: 100%;
}
.tool-search-status {
    color: var(--text-dim);
    margin: 8px 0 0;
}
.tool-list { list-style: none; padding: 0; margin: 0 0 18px; }
/* PIX-VIS-030: the base `li` rule (width: fit-content) still won on the
   width property -- .tool-list li never redeclared it, so every card
   shrank to its own content and the right edge was ragged, with
   description text sitting flush against nothing (no right padding).
   Full width plus matching left/right padding fixes both. */
.tool-list li {
    display: block;
    width: 100%;
    box-sizing: border-box;
    padding: 8px 14px;
    border-left: 1px solid var(--border);
    margin-bottom: 2px;
}
.tool-list li a {
    color: var(--text-bright);
    text-shadow: 0 0 6px var(--glow);
}
.tool-desc {
    display: block;
    color: var(--text-dim);
    font-size: 12px;
    margin-top: 1px;
}
.tool-local {
    color: var(--text-dim);
    font-size: 11px;
    margin-left: 6px;
}
.example-list { list-style: none; padding: 0; margin: 0; }
.example-list li { margin-bottom: 4px; color: var(--text-dim); font-size: 13px; }
.example-list button.example {
    background: var(--surface-2);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    font: inherit;
    font-size: 13px;
    padding: 2px 7px;
    cursor: pointer;
    margin-right: 6px;
}
.example-list button.example:hover { color: var(--text-bright); border-color: var(--text-dim); }

/* ---------- Cron page ---------- */
.cron-summary {
    color: var(--text-bright);
    text-shadow: 0 0 8px var(--glow);
    font-size: 15px;
}
/* PIX-VIS-043: both already used red text in a box (the shared pre style);
   the missing piece was a red border, so a real error reads as an error box
   instead of red text sitting in the ordinary neutral box. */
.cron-summary.cron-bad,
#ipc-info.ipc-bad {
    color: var(--text-err);
    border-color: var(--text-err);
    text-shadow: 0 0 8px var(--glow-err);
}

/* PIX-VIS-041: NORMALIZE's wrapping .input-row has margin-bottom: 0 (it is
   meant for a field+button row with nothing below it elsewhere), so the
   result boxes sat flush against the button with no gap at all. */
#ipc-list-stats {
    margin-top: 16px;
}

/* ---------- Textareas on the new tool pages ---------- */
#ipc-list {
    width: 100%;
    background: var(--surface-2);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    font: inherit;
    font-size: 13px;
    padding: 8px 10px;
    resize: vertical;
}

/* The hamburger now sits inline with the brand in the same row at
   max-width: 720px (see the Responsive block above), so it no longer needs
   separate right-hand clearance here. */

/* ---------- PAN-OS Object Toolkit (/panos) ---------- */
#pan-input {
    width: 100%;
    background: var(--surface-2);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    font: inherit;
    font-size: 13px;
    padding: 8px 10px;
    resize: vertical;
}

/* Four states, told apart at a glance: a config that parsed cleanly must not
   look like one that failed, or people stop reading the banner. */
.pan-status {
    white-space: pre-wrap;
    border-left: 3px solid var(--border);
    background: var(--surface-2);
    padding: 8px 12px;
    margin: 12px 0 4px;
    font-size: 13px;
}
.pan-status.pan-ok    { border-left-color: var(--text); color: var(--text); }
/* PIX-VIS-023: was --text-bright, the same "just brighter output" colour
   used elsewhere for good/neutral emphasis -- a warning needs its own
   accent so it does not read as ordinary output. Uses --warn, the token
   already reserved for caution states. */
.pan-status.pan-warn  { border-left-color: var(--warn); color: var(--warn); }
.pan-status.pan-error { border-left-color: var(--text-err); color: var(--text-err); }

/* white-space: pre overrides the global <pre> rule (pre-wrap), which is what
   was soft-wrapping every ASCII-table row onto 2-3 lines and breaking the
   dashed separator line along with it (PIX-VIS-009): these blocks are
   fixed-width text tables, not prose, so a line has to stay one line. The
   boxes already scroll (overflow: auto), so the wide lines just scroll
   sideways instead of wrapping and losing column alignment. */
#pan-objects, #pan-groups, #pan-findings, #pan-unparsed,
#pan-setcommands, #pan-xml {
    max-height: 46vh;
    overflow: auto;
    white-space: pre;
}

/* The rules table is wide by nature — a firewall rule has ten columns and they
   all matter. Let it scroll sideways inside its own box rather than squeezing
   the columns until they are unreadable. */
#pan-rules, #pan-summary { max-height: 46vh; overflow: auto; white-space: pre; }

/* ---------- TOTP Generator (/totp) ---------- */
/* The code is the whole point of the page, so it is the one thing that is
   bigger than everything around it; the settings stay ordinary form controls. */
.totp-result {
    display: flex;
    align-items: center;
    gap: 18px;
    flex-wrap: wrap;
    margin: 10px 0 4px;
}

.totp-code {
    font-size: 34px;
    letter-spacing: 4px;
    color: var(--text-bright);
    text-shadow: 0 0 10px var(--glow);
}

.totp-timer {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1;
    min-width: 140px;
    max-width: 260px;
}

.totp-bar {
    flex: 1;
    height: 8px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}

.totp-bar span {
    display: block;
    height: 100%;
    width: 0;
    background: var(--text);
    box-shadow: 0 0 8px var(--glow);
    transition: width .25s linear;
}

.totp-seconds {
    color: var(--text-dim);
    font-size: 13px;
    min-width: 34px;
    text-align: right;
}

.totp-result button { margin-top: 0; }

.totp-status { font-size: 13px; margin: 8px 0 0; min-height: 1.2em; }
/* PIX-VIS-043: /totp used to be the one tool with red text and no box at
   all; the border makes it match the shared .msg-error treatment other
   tools use for the same kind of message. */
.totp-status.totp-error {
    color: var(--text-err);
    border: 1px solid var(--text-err);
    border-radius: var(--radius);
    padding: 6px 10px;
    text-shadow: 0 0 6px var(--glow-err);
}
.totp-status.totp-ok    { color: var(--text); }

.totp-meta {
    color: var(--text-dim);
    font-size: 13px;
    margin: 4px 0 0;
    overflow-wrap: break-word;
}

.totp-settings {
    display: flex;
    gap: 18px;
    flex-wrap: wrap;
    align-items: flex-end;
}

.totp-settings select,
.totp-settings input[type="number"] {
    background: var(--surface-2);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 10px 12px;
    font: inherit;
    font-size: 14px;
    max-width: 140px;
}

.totp-settings select:focus,
.totp-settings input[type="number"]:focus {
    outline: none;
    border-color: var(--text);
    box-shadow: 0 0 0 1px var(--text), 0 0 10px var(--glow);
}

.totp-settings option { background: var(--surface); color: var(--text); }

/* An otpauth link owns its own settings, so the controls show them read-only
   rather than pretending an edit would survive. */
.totp-settings select:disabled,
.totp-settings input[type="number"]:disabled { opacity: .5; cursor: not-allowed; }

@media (max-width: 800px) {
    .totp-code { font-size: 28px; letter-spacing: 3px; }
    .totp-timer { max-width: none; }
}

/* PIX-VIS-041: at the same breakpoint the shared .input-row rule makes
   CLEAR full-width (it lives in an .input-row), but COPY CODE lives in
   .totp-result (a different flex row) and stayed content-width, so the
   two ended up different widths on the same page. Same breakpoint, same
   full-width treatment. */
@media (max-width: 720px) {
    .totp-result { flex-direction: column; align-items: stretch; }
    .totp-result button { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
    .totp-bar span { transition: none; }
}
