/* ==========================================================================
   DarkWeb Monitor — base
   --------------------------------------------------------------------------
   Reset, document, typography scale, links, focus, selection, scrollbars,
   the page background, the utility set and the reduced-motion contract.
   Every colour here resolves to a token in tokens.css, directly or through
   color-mix(); nothing below hard-codes a shade.
   ========================================================================== */

/* --------------------------------------------------------------- reset ---- */

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

/*
 * The page background lives on the root element, not on body.
 *
 * The animated graph is a canvas inside body at z-index -2. Painting order puts
 * negative-z-index descendants *before* the backgrounds of in-flow block boxes,
 * so an opaque background on body would cover the canvas completely. Declared
 * here it becomes the canvas background, painted beneath everything, and the
 * graph and the grid layer sit above it in that order.
 */
html {
    -webkit-text-size-adjust: 100%;
    /*
     * Flat colour. The glow moved to body::before.
     *
     * This used to be three radial-gradients with background-attachment: fixed.
     * Safari cannot GPU-composite a fixed background, so it repainted the whole
     * gradient stack on every scroll frame AND sized the backing store to the
     * scrollable document rather than the viewport. On a long listing that is a
     * very large buffer, and it is what produced Safari's "this webpage is
     * using significant memory" prompt and the blank page after its reload.
     *
     * The gradients render identically on a fixed, viewport-sized pseudo-element
     * -- which body::before already was -- for a fraction of the memory.
     */
    background: var(--bg);
    /* Reserve the scrollbar gutter always. Without it the masthead and the
       centred column slide sideways every time navigation moves between a page
       that scrolls and one that does not. */
    scrollbar-gutter: stable;
    scroll-behavior: smooth;
    /* The masthead is sticky, so an in-page anchor must stop below it. */
    scroll-padding-top: calc(var(--header-h) + 1.25rem);
    scrollbar-color: var(--border-strong) transparent;
    scrollbar-width: thin;
}

body {
    margin: 0;
    min-height: 100vh;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    font-family: var(--font-body);
    font-size: 15px;
    font-weight: 400;
    line-height: 1.65;
    color: var(--text);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    overflow-x: hidden;
}

/*
 * The texture layer: a faint engineering grid with a slow CRT scanline over
 * it, masked so it is strongest at the top of the viewport and gone by the
 * fold. It sits above the animated canvas (z-index -2) and below the content.
 */
/*
 * The glow. Viewport-sized and fixed, so it costs one screen of backing store
 * however long the page is.
 */
body::before {
    content: '';
    position: fixed;
    inset: 0;
    z-index: -3;
    pointer-events: none;
    background:
        radial-gradient(1150px 660px at 6% -14%, color-mix(in srgb, var(--accent-2) 24%, transparent), transparent 62%),
        radial-gradient(920px 560px at 96% -4%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 58%),
        radial-gradient(860px 720px at 50% 112%, color-mix(in srgb, var(--accent-2) 13%, transparent), transparent 60%);
}

/*
 * The texture layer, masked so it fades out by the fold.
 *
 * A mask on a full-viewport fixed element makes Safari keep a second offscreen
 * buffer for it, so this deliberately stays one element rather than being split
 * per pattern.
 */
body::after {
    content: '';
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background:
        linear-gradient(to right, var(--grid) 1px, transparent 1px) 0 0 / 68px 68px,
        linear-gradient(to bottom, var(--grid) 1px, transparent 1px) 0 0 / 68px 68px,
        repeating-linear-gradient(to bottom,
            color-mix(in srgb, var(--light) 3%, transparent) 0 1px,
            transparent 1px 3px);
    -webkit-mask-image: radial-gradient(120% 90% at 50% 0%, black 0%, transparent 78%);
    mask-image: radial-gradient(120% 90% at 50% 0%, black 0%, transparent 78%);
}

/* Scanlines are a dark-CRT idea. On the light theme they read as dirt, so the
   texture there is the grid alone. */
[data-theme='light'] body::after {
    background:
        linear-gradient(to right, var(--grid) 1px, transparent 1px) 0 0 / 68px 68px,
        linear-gradient(to bottom, var(--grid) 1px, transparent 1px) 0 0 / 68px 68px;
}

/* The animated intelligence graph. app.js injects this element if the layout
   has not already placed one. */
.bg-net {
    position: fixed;
    inset: 0;
    z-index: -2;
    display: block;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

img, svg, video, canvas, iframe { display: block; max-width: 100%; }
img, video { height: auto; }

button, input, select, textarea, optgroup {
    font: inherit;
    color: inherit;
    letter-spacing: inherit;
}
button { background: none; border: 0; padding: 0; cursor: pointer; }
textarea { resize: vertical; }

fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
legend { padding: 0; }

table { border-collapse: collapse; border-spacing: 0; }

/* ---------------------------------------------------------- typography ---- */

h1, h2, h3, h4, h5, h6 {
    margin: 0 0 .55em;
    color: var(--text);
    line-height: 1.22;
    text-wrap: balance;
}

/* h1 and h2 wear the terminal face; below that the body face keeps long
   headings readable at small sizes. */
h1, h2 {
    font-family: var(--font-display);
    font-weight: 400;
    letter-spacing: .015em;
}
h1 { font-size: clamp(1.7rem, 1.25rem + 1.9vw, 2.6rem); }
h2 { font-size: clamp(1.25rem, 1.05rem + 1vw, 1.7rem); }
h3, h4, h5, h6 { font-family: var(--font-body); font-weight: 700; }
h3 { font-size: 1.08rem; letter-spacing: .01em; }
h4 { font-size: .96rem; }
h5 { font-size: .86rem; letter-spacing: .04em; text-transform: uppercase; color: var(--text-dim); }
h6 { font-size: .74rem; letter-spacing: .14em; text-transform: uppercase; color: var(--text-faint); }

p { margin: 0 0 1rem; }
p:last-child { margin-bottom: 0; }

ul, ol { margin: 0 0 1rem; padding-left: 1.35rem; }
li { margin: .2rem 0; }
li::marker { color: var(--text-faint); }

dl { margin: 0 0 1rem; }
dt { font-weight: 700; color: var(--text-dim); }
dd { margin: 0 0 .6rem; }

small { font-size: .82em; }
strong, b { font-weight: 700; color: var(--text); }
abbr[title] { text-decoration: underline dotted; text-underline-offset: 3px; cursor: help; }

blockquote {
    margin: 0 0 1rem;
    padding: .2rem 0 .2rem 1rem;
    border-left: 2px solid color-mix(in srgb, var(--accent) 55%, transparent);
    color: var(--text-dim);
}

hr {
    border: 0;
    height: 1px;
    margin: 2rem 0;
    background: linear-gradient(90deg, transparent, var(--border-strong), transparent);
}

code, kbd, samp, pre { font-family: var(--font-mono); font-size: .9em; }
pre { margin: 0 0 1rem; overflow-x: auto; }

/* Numbers in tables and stats must line up column to column. */
time, .tnum { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- links --- */

a {
    color: var(--accent);
    text-decoration: none;
    text-underline-offset: .18em;
    text-decoration-thickness: 1px;
    transition: color var(--speed) var(--ease),
                text-decoration-color var(--speed) var(--ease);
}
a:hover { color: var(--text); text-decoration: underline; text-decoration-color: var(--accent); }

/* A link inside running prose is underlined from the start — it has no other
   affordance there. Links inside components are handled by the component. */
.prose a, p > a, li > a { text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent); }

/* ---------------------------------------------------- focus & selection --- */

:focus { outline: none; }

:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* Keyboard focus inside a dark panel needs a halo to stay visible against a
   surface that is already close to the accent in value. */
a:focus-visible, button:focus-visible, [role='button']:focus-visible,
summary:focus-visible, [tabindex]:focus-visible {
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 22%, transparent);
}

input:focus-visible, select:focus-visible, textarea:focus-visible {
    outline-offset: 0;
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
}

::selection {
    background: color-mix(in srgb, var(--accent-2) 55%, transparent);
    color: var(--light);
}

::placeholder { color: var(--text-faint); opacity: 1; }

/* ----------------------------------------------------------- scrollbars --- */

::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
    background: color-mix(in srgb, var(--text-faint) 45%, transparent);
    border: 3px solid transparent;
    background-clip: content-box;
    border-radius: var(--radius-pill);
}
::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--accent) 60%, transparent); background-clip: content-box; }
::-webkit-scrollbar-corner { background: transparent; }

/* ------------------------------------------------------------- document --- */

/* The skip link is the first focusable thing on the page. */
.skip-link {
    position: fixed;
    top: .5rem;
    left: -100vw;
    z-index: 200;
    padding: .6rem 1rem;
    background: var(--surface-3);
    color: var(--text);
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    font-family: var(--font-mono);
    font-size: .8rem;
    box-shadow: var(--shadow-lift);
}
.skip-link:focus-visible { left: .5rem; }

.wrap {
    width: 100%;
    max-width: var(--wrap);
    margin-inline: auto;
    padding-inline: clamp(1rem, 3vw, 2rem);
}
.wrap--narrow { max-width: 860px; }
.wrap--wide { max-width: 1600px; }

main { flex: 1 0 auto; }
.main { padding-block: clamp(1.4rem, 3vw, 2.6rem) clamp(3rem, 6vw, 5rem); }

/* ------------------------------------------------- derived semantic inks --- */

/*
 * --ok / --warn / --bad / --info / --violet are brand constants: §6 gives them
 * a single value shared by both themes. As a background or a border that is
 * fine, but as text on the light theme's white surfaces #3ddc84 and #ffc53d sit
 * far under 3:1, and a green "+12" becomes unreadable.
 *
 * These are not new colours. Each is the token itself, mixed toward --ink on
 * the light theme only, by just enough to clear the contrast bar. The dark
 * theme keeps the constants exactly as declared.
 */
:root {
    --ok-ink: var(--ok);
    --warn-ink: var(--warn);
    --bad-ink: var(--bad);
    --info-ink: var(--info);
    --violet-ink: var(--violet);
}
[data-theme='light'] {
    --ok-ink: color-mix(in srgb, var(--ok) 58%, var(--ink));
    --warn-ink: color-mix(in srgb, var(--warn) 55%, var(--ink));
    --bad-ink: color-mix(in srgb, var(--bad) 78%, var(--ink));
    --info-ink: color-mix(in srgb, var(--info) 72%, var(--ink));
    --violet-ink: color-mix(in srgb, var(--violet) 62%, var(--ink));
}

/* ------------------------------------------------------------ utilities --- */

.sr-only {
    position: absolute !important;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}
.sr-only-focusable:focus-visible {
    position: static !important;
    width: auto; height: auto;
    clip: auto; clip-path: none;
    margin: 0;
}

.is-hidden, [hidden] { display: none !important; }

.dim   { color: var(--text-dim); }
.faint { color: var(--text-faint); }
.mono  { font-family: var(--font-mono); }
.display { font-family: var(--font-display); letter-spacing: .02em; }
.nowrap { white-space: nowrap; }
.break  { overflow-wrap: anywhere; word-break: break-word; }
.center { text-align: center; }
.right  { text-align: right; }
.upper  { text-transform: uppercase; letter-spacing: .1em; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Clamp a description to n lines without measuring anything in JS. */
.clamp-2, .clamp-3 {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.clamp-2 { -webkit-line-clamp: 2; line-clamp: 2; }
.clamp-3 { -webkit-line-clamp: 3; line-clamp: 3; }

.txt-xs { font-size: .68rem; }
.txt-sm { font-size: .8rem; }
.txt-lg { font-size: 1.08rem; }
.lede {
    font-size: clamp(1rem, .95rem + .35vw, 1.16rem);
    line-height: 1.6;
    color: var(--text-dim);
    max-width: 66ch;
}

/* The section eyebrow: small monospace kicker above a heading. */
.eyebrow {
    display: inline-block;
    font-family: var(--font-mono);
    font-size: .66rem;
    font-weight: 600;
    letter-spacing: .3em;
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: .45rem;
}

/* Bracket motif borrowed from thugs.red: [ label ] in the terminal face. */
.brackets::before { content: '['; color: var(--text-faint); margin-right: .35em; }
.brackets::after  { content: ']'; color: var(--text-faint); margin-left: .35em; }

.rule {
    width: 68px; height: 2px; border: 0; margin: 1.1rem 0;
    background: linear-gradient(90deg, var(--accent-2), var(--accent));
    border-radius: 2px;
}
.rule--centre { margin-inline: auto; }

.divider {
    display: flex; align-items: center; gap: .8rem;
    margin: 2.2rem 0;
    color: var(--border-strong);
}
.divider::before, .divider::after {
    content: ''; flex: 1; height: 1px;
    background: linear-gradient(90deg, transparent, var(--border-strong), transparent);
}
.divider__gem {
    width: 9px; height: 9px; transform: rotate(45deg);
    border: 1px solid var(--accent);
    background: color-mix(in srgb, var(--accent) 18%, transparent);
}

/* A section head with the ## sigil and a trailing hairline. */
.section-head {
    display: flex; align-items: baseline; gap: .7rem;
    margin: 2rem 0 1rem;
}
.section-head > :first-child { margin-bottom: 0; }
.section-head::before {
    content: '##';
    font-family: var(--font-mono);
    font-size: .85rem;
    color: var(--accent);
}
.section-head::after {
    content: '';
    flex: 1; height: 1px; align-self: center;
    background: linear-gradient(90deg, var(--border-strong), transparent);
}
.section-head__aside { margin-left: auto; font-size: .78rem; color: var(--text-faint); }

/* layout primitives */
.stack > * + * { margin-top: var(--stack-gap, 1rem); }
.stack--sm { --stack-gap: .5rem; }
.stack--lg { --stack-gap: 1.75rem; }

.cluster {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--cluster-gap, .5rem);
}
.cluster--between { justify-content: space-between; }
.cluster--end { justify-content: flex-end; }
.cluster--tight { --cluster-gap: .3rem; }

.grid { display: grid; gap: var(--grid-gap, 1rem); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr)); }

.push { margin-left: auto; }
.fill { flex: 1 1 auto; }
.scroll-x { overflow-x: auto; overscroll-behavior-x: contain; }

.mt-0 { margin-top: 0; }   .mb-0 { margin-bottom: 0; }
.mt-1 { margin-top: .5rem; }  .mb-1 { margin-bottom: .5rem; }
.mt-2 { margin-top: 1rem; }   .mb-2 { margin-bottom: 1rem; }
.mt-3 { margin-top: 1.75rem; } .mb-3 { margin-bottom: 1.75rem; }
.mt-4 { margin-top: 2.75rem; } .mb-4 { margin-bottom: 2.75rem; }

/* Semantic text colours, for a value whose meaning is not a site state. */
.t-ok   { color: var(--ok-ink); }
.t-warn { color: var(--warn-ink); }
.t-bad  { color: var(--bad-ink); }
.t-info { color: var(--info-ink); }
.t-accent { color: var(--accent); }

/* prose: long-form copy on /about and /api-docs */
.prose { max-width: 74ch; }
.prose > h2 { margin-top: 2.2rem; }
.prose > h3 { margin-top: 1.6rem; }
.prose img { border-radius: var(--radius); border: 1px solid var(--border); }

/* ------------------------------------------------------------- printing --- */

@media print {
    body { background: none; color: black; }
    body::before, body::after, .bg-net, .masthead, .site-foot, .toasts { display: none !important; }
    a { color: black; text-decoration: underline; }
}

/*
 * Flipping the theme must not animate. Every component transitions its own
 * background and colour, so without this the whole page smears through 200ms
 * of intermediate shades at once. app.js sets this class on <html> for the
 * duration of the swap.
 */
.theme-switching, .theme-switching *, .theme-switching *::before, .theme-switching *::after {
    transition: none !important;
}

/* -------------------------------------------------------------- motion ---- */

/*
 * Reduced motion is a hard stop, not a shortened transition: the background
 * canvas is removed from the page entirely (app.js also refuses to start its
 * rAF loop), and everything that moves on its own stops moving.
 */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .bg-net { display: none !important; }
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
        scroll-behavior: auto !important;
    }
}
