/* /bots -- the half of the stylesheet an unauthenticated visitor is allowed to
 * see: the palette, the reset, and the login gate. Served straight off disk by
 * Caddy, because the login page needs it before there is a session.
 *
 * Everything that describes the dashboard itself -- chrome, tiles, charts,
 * tables, the botban unfold -- is in ../../private/bots/assets/dash.css and
 * comes through asset.php, which hands it over only to a request carrying a
 * session cookie. Class names are documentation too, and the login page says
 * nothing about what is behind it; a 19 KB stylesheet full of ".botban-verdict"
 * would have said plenty. dash.css relies on the variables declared here, so
 * the dashboard loads both, this one first.
 *
 * Standalone rather than built on /home/style.css + parts/head.php: that chrome
 * is the SEO/canonical/JSON-LD layer for public pages, and this page is a
 * login-gated tool that must stay out of search. It borrows the same CSS
 * variable NAMES and the same `theme` cookie, so it tracks the site's light/dark
 * palette without inheriting its page furniture.
 *
 * Chart colours live here as roles (--s1..--s8, --seq-*, --st-*) and are applied
 * through classes, never inlined by JS -- so flipping the theme recolours every
 * chart with no re-render. The categorical eight are the dataviz reference
 * palette's fixed slot order, validated with scripts/validate_palette.js against
 * THIS page's surfaces (#fff light, #1c1f25 dark): all checks pass in both
 * modes, with the documented light-mode sub-3:1 contrast on aqua/yellow/magenta
 * handled by the relief rule -- every chart there ships a legend with values and
 * a table of the same numbers.
 *
 * CSP is default-src 'none' with no 'unsafe-inline', so there are no style
 * attributes in the markup. JS sets geometry through element.style / SVG
 * attributes, which CSSOM allows.
 */

:root {
    color-scheme: light dark;

    --bg: #fff;
    --card-bg: #fff;
    --surface-2: #f7f8fa;

    --text: #333;
    --text-body: #444;
    --heading: #1a1a2e;
    --muted: #777;
    --faint: #aaa;

    --accent: #8c1916;
    --btn-bg: #8c1916;
    --btn-bg-hover: #701411;
    --btn-text: #fff;

    --border: #dde2ea;
    --border-soft: #e4e8ed;
    --shadow-1: rgba(0, 0, 0, 0.05);
    --shadow-3: rgba(0, 0, 0, 0.10);

    /* Categorical, light column. Fixed slot order; never cycled, never
       reassigned by rank within a filter. */
    --s1: #2a78d6;  /* blue    */
    --s2: #eb6834;  /* orange  */
    --s3: #1baf7a;  /* aqua    */
    --s4: #eda100;  /* yellow  */
    --s5: #e87ba4;  /* magenta */
    --s6: #008300;  /* green   */
    --s7: #4a3aa7;  /* violet  */
    --s8: #e34948;  /* red     */
    --s9: #9aa3ad;  /* "other" is deliberately not a 9th hue -- it is ink */

    /* Sequential: one hue, light -> dark. Heatmap magnitude only. */
    --seq-0: #eef2f6;
    --seq-1: #cde2fb;
    --seq-2: #9ec5f4;
    --seq-3: #6da7ec;
    --seq-4: #3987e5;
    --seq-5: #256abf;
    --seq-6: #184f95;
    --seq-7: #0d366b;

    /* Status: reserved, never reused as a series. Always paired with a label. */
    --st-good: #0ca30c;
    --st-warning: #fab219;
    --st-serious: #ec835a;
    --st-critical: #d03b3b;

    --grid: rgba(0, 0, 0, 0.08);
    --ring: #fff;  /* the 2px separator ring drawn between overlapping marks */
}

/* Dark values are declared twice on purpose, exactly as the main site does it:
   the media query covers the OS setting, the [data-theme] scope covers the
   toggle, and the toggle must win in both directions. */
@media (prefers-color-scheme: dark) {
    html:not([data-theme]) {
        color-scheme: dark;
        --bg: #15171b;
        --card-bg: #1c1f25;
        --surface-2: #21252c;
        --text: #d3d7dd;
        --text-body: #c6cbd2;
        --heading: #eef1f5;
        --muted: #99a1ab;
        --faint: #6f7883;
        --accent: #ef8781;
        --btn-bg: #a5231f;
        --btn-bg-hover: #c22b26;
        --border: #2b3038;
        --border-soft: #2b3038;
        --shadow-1: rgba(0, 0, 0, 0.40);
        --shadow-3: rgba(0, 0, 0, 0.55);
        --s1: #3987e5; --s2: #d95926; --s3: #199e70; --s4: #c98500;
        --s5: #d55181; --s6: #008300; --s7: #9085e9; --s8: #e66767;
        --s9: #767e88;
        --seq-0: #23272e;
        --seq-1: #184f95; --seq-2: #1c5cab; --seq-3: #256abf;
        --seq-4: #2a78d6; --seq-5: #3987e5; --seq-6: #5598e7; --seq-7: #86b6ef;
        --grid: rgba(255, 255, 255, 0.09);
        --ring: #1c1f25;
    }
}
html[data-theme="dark"] {
    color-scheme: dark;
    --bg: #15171b;
    --card-bg: #1c1f25;
    --surface-2: #21252c;
    --text: #d3d7dd;
    --text-body: #c6cbd2;
    --heading: #eef1f5;
    --muted: #99a1ab;
    --faint: #6f7883;
    --accent: #ef8781;
    --btn-bg: #a5231f;
    --btn-bg-hover: #c22b26;
    --border: #2b3038;
    --border-soft: #2b3038;
    --shadow-1: rgba(0, 0, 0, 0.40);
    --shadow-3: rgba(0, 0, 0, 0.55);
    --s1: #3987e5; --s2: #d95926; --s3: #199e70; --s4: #c98500;
    --s5: #d55181; --s6: #008300; --s7: #9085e9; --s8: #e66767;
    --s9: #767e88;
    --seq-0: #23272e;
    --seq-1: #184f95; --seq-2: #1c5cab; --seq-3: #256abf;
    --seq-4: #2a78d6; --seq-5: #3987e5; --seq-6: #5598e7; --seq-7: #86b6ef;
    --grid: rgba(255, 255, 255, 0.09);
    --ring: #1c1f25;
}

* { box-sizing: border-box; }

/* `display: flex` on a component beats the UA sheet's [hidden] { display: none },
   so a hidden flex container stays visible. Both the range selector and the
   tab panels are flex/block containers that get toggled with the attribute. */
[hidden] { display: none !important; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--text-body);
    font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    -webkit-text-size-adjust: 100%;
}

code, .mono, th.num, td.num, .cidr { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

/* ---------------------------------------------------------------- login gate */
/* The page is a single password field and nothing else -- no heading, no label,
   no hint, no button. So the field itself has to carry every state, and it does
   it without words: dimmed while the KDF runs, red + a shake when the server
   says no. Nothing here reveals what the page is or what is behind it. */

.gate {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}
.gate .card { width: 100%; max-width: 300px; }
.gate input {
    width: 100%; padding: 11px 13px; font: inherit; font-size: 15px;
    background: var(--card-bg); color: var(--text);
    border: 1px solid var(--border); border-radius: 7px;
    box-shadow: 0 1px 3px var(--shadow-1);
    transition: border-color 120ms, opacity 120ms;
}
.gate input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }
.gate input.is-busy { opacity: 0.55; cursor: progress; }
/* The click target under the field (asked for 2026-10-30; Enter still
   works). An arrow, not a word, so the page still reads as nothing. */
.gate .go {
    display: block; margin: 12px auto 0; padding: 6px 18px;
    font: inherit; font-size: 17px; line-height: 1;
    color: var(--btn-text); background: var(--btn-bg);
    border: 0; border-radius: 7px; cursor: pointer;
    box-shadow: 0 1px 3px var(--shadow-1);
    transition: background 120ms, opacity 120ms;
}
.gate .go:hover { background: var(--btn-bg-hover); }
.gate .go:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.gate input.is-busy + .go { opacity: 0.55; cursor: progress; }
.gate input.is-bad {
    border-color: var(--st-critical);
    animation: nope 260ms ease-in-out;
}
@keyframes nope {
    0%, 100% { transform: translateX(0); }
    20%      { transform: translateX(-6px); }
    40%      { transform: translateX(5px); }
    60%      { transform: translateX(-3px); }
    80%      { transform: translateX(2px); }
}

@media (prefers-reduced-motion: reduce) {
    /* The red border carries the whole message on its own. */
    .gate input.is-bad { animation: none; }
    .gate input, .gate .go { transition: none; }
}
