/*
 * Agent Disco — sleek dark, disco in the accents. (2026-06 refresh.)
 *
 * The room is dark and quiet; the disco is what catches the light.
 * Near-black NEUTRAL base (no purple wash), flat surfaces with 1px
 * borders, generous whitespace, and exactly one signature accent —
 * magenta — spent deliberately: the primary CTA, the active state,
 * the brand moments. Cyan is demoted to informational states, gold
 * is reserved for grade-A moments, green/amber/red carry statuses.
 *
 * Rules of the system:
 *   - One accent per surface. A card may have a magenta button OR a
 *     gold grade chip, not both fighting.
 *   - No glow, no glass. Elevation is borders + restrained shadow.
 *   - Motion is rare, smooth, and interruptible: the hero ball's
 *     sheen, a hover transition. Nothing shimmers on a timer.
 *
 * Palette:
 *   magenta  #ff2e8f  the signature (CTAs, active nav, selection)
 *   cyan     #5ad7e6  informational (running states, info chips)
 *   gold     #f6c945  grade-A + leaderboard crowns only
 *   green    #3ecf8e  pass
 *   amber    #eaa83c  warn
 *   red      #ff4d6d  fail / error
 *   bg       #0b0b10  neutral near-black
 *   surface  #131318  card backing
 *   text     #f4f4f6  body
 *
 * Typography:
 *   display  Boldonse — single-weight, retro-disco character. Spent
 *            sparingly: grade letters, leaderboard rank numerals,
 *            the hero "groovy". Everything else is DM Sans.
 *   body     DM Sans — geometric sans, weights 400 / 500 / 700 / 900.
 *   mono     ui-monospace stack for code + tabular numerals.
 *
 * Self-hosted under `assets/fonts/` so CSP can stay tight (no
 * `font-src https://fonts.gstatic.com` exception) and the first paint
 * isn't gated on a third-party DNS lookup.
 */

@font-face {
    font-family: 'Boldonse';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("../fonts/boldonse-regular-V1UU-jL.ttf") format('truetype');
}
@font-face {
    font-family: 'DM Sans';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("../fonts/dmsans-400-TwALNhk.ttf") format('truetype');
}
@font-face {
    font-family: 'DM Sans';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("../fonts/dmsans-500-p7KnYtS.ttf") format('truetype');
}
@font-face {
    font-family: 'DM Sans';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("../fonts/dmsans-700-P0NHek-.ttf") format('truetype');
}
@font-face {
    font-family: 'DM Sans';
    font-style: normal;
    font-weight: 900;
    font-display: swap;
    src: url("../fonts/dmsans-900-6QBPP2U.ttf") format('truetype');
}

:root {
    /* ---- palette ------------------------------------------------------ */
    /* Variable names are the stable API every page section styles
       against; the 2026-06 refresh repoints the VALUES to the neutral
       system. `--disco-plum` survives as the deep-neutral step because
       ~30 rules reference it for backdrops. */
    --disco-bg: #0b0b10;
    --disco-bg-deep: #08080c;
    --disco-plum: #15151c;
    --disco-surface: #131318;
    --disco-surface-hi: #1a1a22;
    --disco-border: #25252d;
    --disco-border-bright: #34343e;
    --disco-text: #f4f4f6;
    --disco-muted: #9c9ca8;
    --disco-magenta: #ff2e8f;
    --disco-magenta-hi: #ff5dab;
    /* Ink for text sitting ON a solid magenta/red fill. White on
       magenta is only 3.5:1 — below WCAG AA for button-sized type —
       so filled controls use a near-black plum instead (5.7:1+),
       mirroring the dark-ink-on-gold grade-A chip. */
    --disco-btn-ink: #16060e;
    --disco-cyan: #5ad7e6;
    --disco-cyan-hi: #8ce4ef;
    --disco-gold: #f6c945;
    --disco-gold-hi: #ffdf80;
    --disco-green: #3ecf8e;
    --disco-green-hi: #6fe0ab;
    --disco-red: #ff4d6d;
    --disco-orange: #ff8a5c;
    --disco-amber: #eaa83c;

    /* The signature "gradient" is now a two-stop magenta ramp — every
       legacy rainbow usage (buttons, brand moments) calms down to the
       single accent without each rule needing a rewrite. */
    --disco-rainbow: linear-gradient(120deg,
        var(--disco-magenta) 0%,
        var(--disco-magenta-hi) 100%);

    /* ---- type --------------------------------------------------------- */
    --font-display: 'Boldonse', 'Times New Roman', serif;
    --font-sans: 'DM Sans', ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;

    /* Headings get an editorial size-step (1.25 modular) so a page reads
       like a magazine spread, not a SaaS settings panel. */
    --type-display-xl: clamp(2.6rem, 9vw, 6.25rem);    /* hero pull */
    --type-display-l:  clamp(2.0rem, 6vw, 3.75rem);    /* page H1 */
    --type-display-m:  clamp(1.5rem, 3.5vw, 2.25rem);  /* section H2 */
    --type-display-s:  clamp(1.15rem, 2.2vw, 1.4rem);  /* card H3 */

    /* ---- spacing (8pt grid) ------------------------------------------ */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-5: 1.5rem;
    --space-6: 2rem;
    --space-7: 3rem;
    --space-8: 4rem;
    --space-9: 6rem;

    /* ---- radii -------------------------------------------------------- */
    --radius-1: 0.375rem;
    --radius-2: 0.625rem;
    --radius-3: 1rem;
    --radius-4: 1.5rem;

    /* ---- shadows ------------------------------------------------------ */
    /* Flat system: elevation comes from the 1px border; shadows only
       separate a card from the page, never advertise it. The legacy
       glow vars survive as soft accent shadows for the few rules that
       still reference them (primary CTA, grade chip). */
    --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.35);
    --shadow-card-hi: 0 2px 6px rgba(0, 0, 0, 0.4);
    --shadow-glow-magenta: 0 4px 16px rgba(255, 46, 143, 0.25);
    --shadow-glow-gold:    0 4px 16px rgba(246, 201, 69, 0.2);

    /* ---- transitions -------------------------------------------------- */
    --ease-snap: cubic-bezier(0.2, 0.8, 0.2, 1);
    --ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
    --t-quick: 180ms;
    --t-medium: 320ms;
    --t-slow: 600ms;

    color-scheme: dark;
}

* {
    box-sizing: border-box;
}

html,
body {
    margin: 0;
    padding: 0;
}

html {
    /* Smooth scroll for in-page anchors (e.g. footer "Back to top",
       skip-link). Honour reduced-motion below. */
    scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
}

body {
    /* Flat neutral room. The single faint magenta breath at the very
       top of the page is the only colour in the backdrop — everything
       else is content. No animated mesh, no grain, no fixed-attachment
       repaint cost. */
    background-color: var(--disco-bg);
    background-image:
        radial-gradient(ellipse 70vw 30vh at 50% -10%, rgba(255, 46, 143, 0.07), transparent 70%);
    color: var(--disco-text);
    font-family: var(--font-sans);
    font-size: 16px;
    line-height: 1.55;
    min-height: 100vh;
    /* Hard-stop on horizontal scroll. Some long inline content (raw URLs in
       fix-hint blocks, monospace API examples) would otherwise push the
       viewport wider than the device; `clip` is preferable to `hidden`
       because it doesn't establish a new containing block, so position:
       sticky/fixed elsewhere keep working. */
    overflow-x: clip;
    position: relative;
    isolation: isolate;
}

/* Focus ring in the signature accent — obvious on the neutral dark
   surfaces without resorting to the heavy black-on-white default. */
:where(a, button, [tabindex]):focus-visible {
    outline: 2px solid var(--disco-magenta);
    outline-offset: 3px;
    border-radius: 0.25rem;
}

/* Selection highlight uses the brand magenta so copying URLs / quoting check
   notes feels deliberately on-brand instead of the OS default. */
::selection {
    background: rgba(255, 46, 143, 0.45);
    color: #fff;
}

/* Links read as text first, accent on intent: light ink with a quiet
   magenta-tinted underline, full magenta on hover. One affordance,
   used everywhere. */
a {
    color: var(--disco-text);
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
    text-decoration-color: rgba(255, 46, 143, 0.55);
    transition: color var(--t-quick) var(--ease-snap);
}

a:hover,
a:focus-visible {
    color: var(--disco-magenta-hi);
    text-decoration-color: currentColor;
}

/* Page-level headings get the editorial size step. Section h2s slot in
   between the hero and a wall of cards/tables, so they need just enough
   weight to anchor without competing with the bigger type above.
   Letter-spacing is dialled tight (-0.02em) so big DM Sans Black reads
   like a premium magazine deck rather than a dashboard label. */
h1, h2, h3, h4 {
    font-family: var(--font-sans);
    font-weight: 900;
    letter-spacing: -0.02em;
    line-height: 1.1;
    color: var(--disco-text);
}

h1 { font-size: var(--type-display-l); }
h2 { font-size: var(--type-display-m); }
h3 { font-size: var(--type-display-s); }

/* Generic kicker label — the small uppercase label that introduces a
   section. Used on the hero, the report breakdown, the leaderboard.
   Solid muted ink; the section heading below it carries the weight. */
.kicker {
    display: inline-block;
    font-size: 0.78rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    font-weight: 700;
    color: var(--disco-muted);
}

.container {
    max-width: 1100px;
    margin: 0 auto;
    padding: 0 var(--space-5);
}

/* ---------------- hero ---------------- */
/* The signature page. Mirror ball front and centre, massive editorial
   headline below, scan form in the spotlight. The room stays dark —
   one soft pool of magenta light behind the ball is the only stage
   dressing. The stack fades in on a short stagger. */

.hero {
    position: relative;
    padding: clamp(3.5rem, 9vw, 6rem) 0 clamp(2rem, 5vw, 4rem);
    text-align: center;
    isolation: isolate;
}

/* The pool of light the ball hangs in — one quiet radial, no sunburst. */
.hero::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: radial-gradient(
        ellipse 55% 40% at 50% 28%,
        rgba(255, 46, 143, 0.12) 0%,
        transparent 70%
    );
}

.hero__kicker {
    display: inline-block;
    font-family: var(--font-sans);
    font-size: 0.78rem;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    font-weight: 700;
    margin-bottom: var(--space-5);
    color: var(--disco-muted);
    animation: hero-fade-up 600ms var(--ease-snap) 200ms both;
}

.hero__headline {
    font-family: var(--font-sans);
    font-weight: 900;
    /* Massive editorial pull. Floor sized so a 320px phone never sees
       a one-word-per-line wrap; ceiling sized so 1440px+ desktops feel
       cinematic. Generous line-height (1.45) is required because the
       em below renders in Boldonse, whose glyph height substantially
       exceeds its em-box — at tighter line-heights the next line's
       text overlaps the bottom of the GROOVY letterforms. */
    font-size: clamp(2.2rem, 8.5vw, 5.5rem);
    line-height: 1.45;
    letter-spacing: -0.035em;
    margin: 0 auto var(--space-5);
    max-width: 22ch;
    /* Prevents the headline overflowing on narrow viewports if a single
       word (e.g. an inline `<code>`) is wider than the column. */
    overflow-wrap: anywhere;
    animation: hero-fade-up 700ms var(--ease-snap) 280ms both;
}

.hero__headline em {
    font-style: normal;
    /* "groovy" gets the Boldonse display face — the one word on the
       page allowed to wear the accent. Boldonse glyphs paint
       considerably taller than their em-box, so `font-size: 0.85em`
       scales the type down inside the em until the painted glyph
       height roughly matches DM Sans's at 1em, and the vertical
       padding reserves paint room so the silhouette never clips
       against the lines above/below. `text-transform: uppercase`
       keeps lowercase y from emitting a descender. Solid magenta —
       no gradient fill, no glow, no shimmer. */
    font-family: var(--font-display);
    font-weight: 400; /* Boldonse only ships at one weight; the type IS the weight */
    font-size: 0.85em;
    letter-spacing: 0.02em; /* Boldonse glyphs are chunky; positive tracking lets V + Y breathe */
    text-transform: uppercase;
    display: inline-block;
    color: var(--disco-magenta);
    padding: 0.25em 0.15em;
    margin: 0 0.05em;
    line-height: 1;
    vertical-align: baseline;
}

.hero__blurb {
    color: var(--disco-muted);
    font-size: clamp(1rem, 1.8vw, 1.2rem);
    line-height: 1.5;
    max-width: 44rem;
    margin: 0 auto var(--space-7);
    animation: hero-fade-up 800ms var(--ease-snap) 380ms both;
}

/* Account/auth pages reuse .hero__blurb as the intro line under a
   left-aligned headline; cancel the landing hero's centring there so
   the line (and its "back to account" link) aligns with the heading. */
.auth .hero__blurb {
    margin-left: 0;
}

/* Soft staggered entrance for the hero stack. Stops short at reduced-
   motion so the page settles instantly. */
@keyframes hero-fade-up {
    from {
        opacity: 0;
        transform: translateY(8px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@media (prefers-reduced-motion: reduce) {
    .hero__kicker,
    .hero__headline,
    .hero__blurb {
        animation: none;
    }
}

/* The mirror ball, rebuilt in pure CSS. A stack of background layers
   on one round div: edge vignette → fixed specular highlight → two
   coloured bounce-lights (the room's magenta + cyan reflecting off
   the mirrors) → two repeating facet grids → spherical base shading.
   The spin is the longitude grid sliding horizontally behind the
   FIXED lighting — exactly what a real ball does: tiles move, light
   stays put. Driven by a registered custom property so the slide
   interpolates smoothly (no sprite, no steps(), no frame rate).

   Fallback: browsers without @property don't interpolate the custom
   property, and because the travel distance is a whole number of
   facet periods the from/to states render identically — they simply
   see a beautiful static ball. */
@property --ball-shift {
    syntax: '<length>';
    inherits: false;
    initial-value: 0px;
}

.hero__ball,
.report-hero__orb {
    --ball-size: clamp(150px, 20vw, 220px);
    --facet: calc(var(--ball-size) / 11);
    width: var(--ball-size);
    height: var(--ball-size);
    margin: 0 auto var(--space-6);
    position: relative;
    border-radius: 50%;
    background-image:
        radial-gradient(circle at 50% 50%, transparent 56%, rgba(8, 8, 12, 0.55) 92%),
        radial-gradient(circle at 35% 28%, rgba(255, 255, 255, 0.85) 0%, rgba(255, 255, 255, 0) 26%),
        radial-gradient(circle at 72% 74%, rgba(255, 46, 143, 0.32) 0%, transparent 38%),
        radial-gradient(circle at 20% 68%, rgba(90, 215, 230, 0.22) 0%, transparent 34%),
        repeating-linear-gradient(90deg, rgba(8, 8, 12, 0.5) 0 1.5px, transparent 1.5px var(--facet)),
        repeating-linear-gradient(0deg, rgba(8, 8, 12, 0.4) 0 1.5px, transparent 1.5px var(--facet)),
        radial-gradient(circle at 38% 30%, #fafafd 0%, #cfcfdb 22%, #8b8b9c 48%, #3a3a47 75%, #1b1b23 100%);
    background-position: 0 0, 0 0, 0 0, 0 0, var(--ball-shift) 0, 0 0, 0 0;
    animation:
        ball-roll 16s linear infinite,
        hero-ball-drop 900ms var(--ease-snap) both;
    filter:
        drop-shadow(0 18px 40px rgba(0, 0, 0, 0.5))
        drop-shadow(0 8px 32px rgba(255, 46, 143, 0.16));
}

/* The wire it hangs from. */
.hero__ball::before {
    content: "";
    position: absolute;
    bottom: 100%;
    left: 50%;
    transform: translateX(-50%);
    width: 2px;
    height: clamp(2.5rem, 7vw, 5rem);
    background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.28));
}

/* One facet-width per ~1.45s, travelling a whole number of grid
   periods so the loop point is seamless. */
@keyframes ball-roll {
    from { --ball-shift: 0px; }
    to   { --ball-shift: calc(var(--facet) * -11); }
}

@keyframes hero-ball-drop {
    from {
        opacity: 0;
        transform: translateY(-14px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* Disco balls hang from the top fixture and rotate around their vertical
   axis. The /report grade backdrop still uses this rotateY animation —
   acceptable there because the foreground grade letter holds the eye and
   the brief edge-on phase reads as "back of the ball" with the letter
   covering the gap. */
@keyframes disco-spin {
    from { transform: rotateY(0deg); }
    to { transform: rotateY(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .hero__ball {
        animation: none;
    }
}

/* ---------------- form ---------------- */

.scan-form {
    max-width: 38rem;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    animation: hero-fade-up 850ms var(--ease-snap) 480ms both;
}

.scan-form__row {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    /* One flat pill wrapping input + button so the form reads as a
       single artefact. Elevation is the border; no glass, no glow. */
    background: var(--disco-surface);
    border: 1px solid var(--disco-border-bright);
    border-radius: 999px;
    padding: var(--space-2);
    box-shadow: var(--shadow-card);
}

.scan-form__label {
    position: absolute;
    left: -9999px;
}

.scan-form__input {
    flex: 1 1 14rem;
    min-width: 0;
    background: transparent;
    color: var(--disco-text);
    border: 0;
    padding: var(--space-4) var(--space-5);
    font-size: 1.05rem;
    font-family: var(--font-mono);
    line-height: 1.4;
}

.scan-form__input::placeholder {
    color: var(--disco-muted);
    opacity: 0.7;
}

.scan-form__input:focus {
    outline: none;
}

.scan-form__input:focus-visible {
    outline: none;
}

/* When the input is focused we light up the surrounding shell instead
   of giving the input itself a focus ring — the whole row feels like
   the focused element. */
.scan-form__row:focus-within {
    border-color: var(--disco-magenta);
    box-shadow:
        0 0 0 3px rgba(255, 46, 143, 0.18),
        var(--shadow-card);
}

/* THE button on the site. Solid magenta, white type, no gradient
   sweep, no sheen — it earns attention by being the only saturated
   block in the viewport. */
.scan-form__button {
    flex: 1 1 auto;
    position: relative;
    background: var(--disco-magenta);
    color: var(--disco-btn-ink);
    border: 0;
    border-radius: 999px;
    padding: var(--space-4) var(--space-6);
    font-family: var(--font-sans);
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    cursor: pointer;
    transition:
        background var(--t-quick) var(--ease-snap),
        transform var(--t-quick) var(--ease-snap),
        box-shadow var(--t-quick) ease;
    box-shadow: var(--shadow-glow-magenta);
}

.scan-form__button:hover:not(:disabled),
.scan-form__button:focus-visible {
    background: var(--disco-magenta-hi);
    transform: translateY(-1px);
    outline: none;
}

.scan-form__button:focus-visible {
    outline: 2px solid var(--disco-text);
    outline-offset: 3px;
}

.scan-form__button:disabled {
    cursor: not-allowed;
    opacity: 0.6;
}

.scan-form__button[data-loading="true"] .scan-form__button-label::after {
    content: '…';
}

.scan-form__error {
    color: var(--disco-red);
    font-size: 0.95rem;
    min-height: 1.3rem;
    margin: 0;
}

/* ---------------- example teaser ---------------- */

.example {
    margin: clamp(3rem, 7vw, 6rem) auto clamp(2rem, 5vw, 5rem);
    max-width: 60rem;
    background: var(--disco-surface);
    border: 1px solid var(--disco-border);
    border-radius: var(--radius-3);
    padding: clamp(1.5rem, 3vw, 2.5rem);
    box-shadow: var(--shadow-card);
    position: relative;
    overflow: hidden;
}

.example__title {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    margin: 0 0 var(--space-5);
    font-family: var(--font-sans);
    font-size: 0.78rem;
    color: var(--disco-muted);
    letter-spacing: 0.18em;
    text-transform: uppercase;
    font-weight: 700;
}

.example__grade {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    background: var(--disco-gold);
    color: #16130a;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: 1.2rem;
    line-height: 1;
    padding: 0.45rem 0.9rem;
    border-radius: var(--radius-1);
    letter-spacing: 0;
}

.example__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
    gap: 0.6rem 2rem;
}

.example__item {
    display: flex;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-2) 0;
    border-bottom: 1px dashed var(--disco-border);
    font-size: 0.95rem;
}

.example__item:last-child {
    border-bottom: none;
}

.example__item-name {
    color: var(--disco-text);
    font-family: var(--font-mono);
    font-size: 0.9rem;
}

.example__item-status {
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    font-size: 0.8rem;
}

.example__item-status--pass { color: var(--disco-green); }
.example__item-status--warn { color: var(--disco-amber); }
.example__item-status--fail { color: var(--disco-red); }
.example__item-status--skip { color: var(--disco-muted); }
.example__item-status--error { color: var(--disco-red); }

.example__footnote {
    margin: 1.25rem 0 0;
    color: var(--disco-muted);
    font-size: 0.85rem;
}

/* ---------------- why Agent Disco ---------------- */
/* AD-36: three-bullet positioning section between hero and example.
   Answers "how is this different from an SEO audit?" in the first
   viewport so a scan-submitting visitor understands the product. */

.why {
    margin: clamp(3rem, 7vw, 6rem) auto 0;
    max-width: 60rem;
    padding: 0 var(--space-4);
}

.why__title {
    font-family: var(--font-sans);
    font-weight: 900;
    font-size: var(--type-display-m);
    line-height: 1.1;
    letter-spacing: -0.025em;
    margin: 0 0 var(--space-6);
    color: var(--disco-text);
    text-align: center;
    /* Pull-quote treatment — sized big to match the editorial feel
       set by the hero, given the section a moment of its own. */
}

.why__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
    gap: var(--space-4);
}

.why__item {
    /* Glass card with a colour-tinged left border that brightens on
       hover. Hover lifts + tilts ever so slightly so the card feels
       three-dimensional. */
    position: relative;
    background: var(--disco-surface);
    border: 1px solid var(--disco-border);
    border-radius: var(--radius-3);
    padding: var(--space-5) var(--space-5);
    line-height: 1.55;
    color: var(--disco-muted);
    box-shadow: var(--shadow-card);
    transition:
        transform var(--t-medium) var(--ease-snap),
        border-color var(--t-medium) ease,
        box-shadow var(--t-medium) ease;
    overflow: hidden;
}

/* Accent on the left edge — a quiet solid magenta hairline. */
.why__item::before {
    content: "";
    position: absolute;
    top: var(--space-5);
    bottom: var(--space-5);
    left: 0;
    width: 2px;
    background: var(--disco-magenta);
    border-radius: 0 2px 2px 0;
    opacity: 0.55;
    transition: opacity var(--t-medium) ease;
}

.why__item:hover {
    transform: translateY(-4px);
    border-color: var(--disco-border-bright);
    box-shadow: var(--shadow-card-hi);
}

.why__item:hover::before {
    opacity: 1;
}

/* Scroll-driven lift on supporting browsers (Chrome 115+, etc.). Cards
   slide gently up as they enter the viewport. Opacity is intentionally
   pinned at 1 — earlier we used a fade-in on the timeline, but that
   left cards invisible until first scroll, which broke users who landed
   directly on a deep anchor and screenshot/headless-browser flows.
   Lift-only is more subtle and always visible. */
@supports (animation-timeline: view()) {
    .why__item {
        animation: card-reveal linear both;
        animation-timeline: view();
        animation-range: entry 0% entry 50%;
    }
}

@keyframes card-reveal {
    from { transform: translateY(28px); }
    to   { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
    .why__item { animation: none; }
}

.why__item strong {
    color: var(--disco-text);
    display: block;
    margin-bottom: 0.25rem;
}

/* ---------------- /contact form ---------------- */
/* Form-based contact mailer. Sober like /terms — this is a utility page,
   not a marketing moment. The `.contact-form__honeypot` row keeps the
   visually-hidden input rendered (bots see it, real users never encounter
   it) without the non-accessible `display: none` that some anti-spam
   stacks sniff out. */

.contact {
    max-width: 44rem;
    padding: 2rem 1rem;
}

.contact__body h1 {
    font-size: 1.8rem;
    margin-bottom: 0.25rem;
}

.contact__lead {
    color: var(--disco-muted);
    line-height: 1.6;
    margin-top: 0;
}

.contact__body h2 {
    font-size: 1.15rem;
    margin-top: 2rem;
    color: var(--disco-muted);
}

.contact__body p {
    line-height: 1.65;
}

.contact__flash {
    margin: 1rem 0;
    padding: 0.75rem 1rem;
    border-radius: 6px;
    line-height: 1.45;
}

.contact__flash--success {
    background: rgba(255, 214, 102, 0.12);
    border: 1px solid var(--disco-gold);
    color: var(--disco-gold);
}

.contact__flash--error {
    background: rgba(220, 53, 69, 0.12);
    border: 1px solid var(--disco-red);
    color: var(--disco-red);
}

.contact-form {
    display: grid;
    gap: 1rem;
    margin: 1.25rem 0 2rem;
}

.contact-form > div {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.contact-form label {
    font-size: 0.85rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--disco-muted);
}

.contact-form input[type="text"],
.contact-form input[type="email"],
.contact-form textarea {
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid var(--disco-border);
    border-radius: 6px;
    color: var(--disco-text);
    font: inherit;
    padding: 0.6rem 0.8rem;
    transition: border-color 160ms ease, box-shadow 160ms ease;
}

.contact-form input[type="text"]:hover,
.contact-form input[type="email"]:hover,
.contact-form textarea:hover {
    border-color: var(--disco-border-bright);
}

.contact-form textarea {
    min-height: 10rem;
    resize: vertical;
}

/* Match the scan-form + auth-card focus-glow pattern: cyan ring + cyan
   border, no chunky outline. Sitewide visual consistency. */
.contact-form input:focus-visible,
.contact-form textarea:focus-visible {
    outline: none;
    border-color: var(--disco-magenta);
    box-shadow: 0 0 0 3px rgba(255, 46, 143, 0.18);
}

.contact-form .form-error-message,
.contact-form ul li {
    color: var(--disco-red);
    font-size: 0.85rem;
    margin: 0;
}

.contact-form__submit {
    background: var(--disco-magenta);
    color: var(--disco-btn-ink);
    border: none;
    border-radius: 999px;
    padding: var(--space-4) var(--space-6);
    font-family: var(--font-sans);
    font-weight: 700;
    font-size: 0.95rem;
    letter-spacing: 0.02em;
    cursor: pointer;
    justify-self: start;
    transition:
        background var(--t-quick) var(--ease-snap),
        transform var(--t-quick) var(--ease-snap);
    box-shadow: var(--shadow-glow-magenta);
}

.contact-form__submit:hover,
.contact-form__submit:focus-visible {
    background: var(--disco-magenta-hi);
    transform: translateY(-1px);
    outline: none;
}

.contact-form__submit:focus-visible {
    outline: 2px solid var(--disco-text);
    outline-offset: 2px;
}

/* Honeypot: visually hidden from humans, still present in the DOM so
   the bot-side DOM walker encounters it. `display: none` would be
   simpler but some anti-spam stacks fingerprint that; a position-
   based hide is more resilient. */
.contact-form__honeypot {
    position: absolute !important;
    left: -9999px !important;
    width: 1px !important;
    height: 1px !important;
    overflow: hidden !important;
}

/* ---------------- legal pages ---------------- */
/* AD-37: sober two-column-free layout for /terms, /privacy.
   Operators reading ToS want to see serious, not cute — resist the
   disco maximalism here. */

.legal {
    max-width: 44rem;
    padding: 2rem 1rem;
}

.legal__body h1 {
    font-size: 1.8rem;
    margin-bottom: 0.25rem;
}

.legal__body h2 {
    font-size: 1.15rem;
    margin-top: 2rem;
    color: var(--disco-muted);
}

.legal__body p,
.legal__body ul {
    line-height: 1.65;
}

.legal__updated {
    color: var(--disco-muted);
    font-size: 0.85rem;
    margin-top: 0;
}

.legal__email {
    font-size: 1.1rem;
    margin: 1.5rem 0;
}

/* ---------------- /bot page ---------------- */
/* AD-36: operator-facing explanation of the scanner. Shares the sober
   tone of /terms; no hero, no disco flair — it's the page people read
   before deciding to allow-list us. */

.bot-page {
    max-width: 44rem;
    padding: 2rem 1rem;
}

.bot-page__header h1 {
    font-size: 1.8rem;
    margin-bottom: 0.25rem;
}

.bot-page__lead {
    color: var(--disco-muted);
    line-height: 1.6;
    margin-top: 0;
}

.bot-page__body h2 {
    font-size: 1.15rem;
    margin-top: 2rem;
    color: var(--disco-muted);
}

.bot-page__body p,
.bot-page__body ul {
    line-height: 1.65;
}

.bot-page__code {
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid var(--disco-border);
    border-radius: 6px;
    padding: 0.75rem 1rem;
    overflow-x: auto;
    font-size: 0.9rem;
    margin: 0.5rem 0 1rem;
}

/* ---------------- footer ---------------- */
/* AD-37: shared site-wide footer. UK Companies Act attribution is
   required on every page; keeps it sober and out of the way of the
   disco hero. */

.site-footer {
    position: relative;
    margin-top: clamp(4rem, 8vw, 7rem);
    padding: clamp(3rem, 6vw, 5rem) var(--space-5) clamp(2.5rem, 5vw, 4rem);
    color: var(--disco-muted);
    font-size: 0.88rem;
    text-align: center;
    line-height: 1.7;
    /* "Lights coming up at the end of the night" — a deeper aubergine
       wash on the footer so it feels separate from the dance floor.
       The animated rainbow ribbon at the very top of the footer ties
       it back to the rest of the brand. */
    background: linear-gradient(180deg,
        transparent 0%,
        var(--disco-surface-hi) 30%,
        var(--disco-bg-deep) 100%);
}

.site-footer::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: var(--disco-magenta);
    opacity: 0.3;
}

.site-footer__legal {
    max-width: 60rem;
    margin: 0 auto;
}

.site-footer__legal p {
    margin: var(--space-2) 0;
}

.site-footer__company strong {
    color: var(--disco-text);
    font-weight: 700;
    font-size: 0.92rem;
}

.site-footer__nav {
    margin: var(--space-6) 0 var(--space-2);
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-2) var(--space-4);
    font-size: 0.85rem;
    letter-spacing: 0.02em;
}

.site-footer__nav a,
.site-footer__contact a {
    color: var(--disco-muted);
    text-decoration: none;
    position: relative;
    padding: 2px 0;
    transition: color var(--t-quick) ease;
}

.site-footer__nav a::after,
.site-footer__contact a::after {
    content: "";
    position: absolute;
    inset: auto 0 0 0;
    height: 1px;
    background: var(--disco-magenta);
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform var(--t-medium) var(--ease-snap);
}

.site-footer__nav a:hover,
.site-footer__nav a:focus-visible,
.site-footer__contact a:hover,
.site-footer__contact a:focus-visible {
    color: var(--disco-text);
    outline: none;
}

.site-footer__nav a:hover::after,
.site-footer__nav a:focus-visible::after,
.site-footer__contact a:hover::after,
.site-footer__contact a:focus-visible::after {
    transform: scaleX(1);
}

/* ---------------- report page ---------------- */

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.report {
    padding-bottom: 4rem;
}

.report-hero {
    display: flex;
    align-items: center;
    gap: clamp(1.5rem, 4vw, 3.5rem);
    padding: clamp(2.5rem, 6vw, 5rem) 0 clamp(1.5rem, 3vw, 3rem);
    flex-wrap: wrap;
    position: relative;
    isolation: isolate;
}

/* Spotlight wash behind the report hero — radial gradient anchored
   under the ball position. Lower amplitude than the landing hero so
   the report stays focused on data, not theatre. */
.report-hero::before {
    content: "";
    position: absolute;
    inset: -2rem -10% auto;
    height: 100%;
    z-index: -1;
    pointer-events: none;
    background: radial-gradient(
        ellipse 50% 65% at 18% 50%,
        rgba(255, 46, 143, 0.1) 0%,
        transparent 60%
    );
}

.report-hero__ball {
    position: relative;
    /* Bigger than the original 180px so the grade letter has room to
       breathe at editorial scale (8rem cap on the letter). */
    --ball-size: clamp(200px, 28vw, 320px);
    width: var(--ball-size);
    height: var(--ball-size);
    flex: 0 0 var(--ball-size);
    filter: drop-shadow(0 22px 44px rgba(0, 0, 0, 0.5));
}

/* The orb behind the grade letter shares the hero ball's paint (see
   the .hero__ball / .report-hero__orb block); here it just fills the
   wrapper and rolls more slowly so the letter stays the focus. */
.report-hero__orb {
    --ball-size: clamp(200px, 28vw, 320px);
    position: absolute;
    inset: 0;
    margin: 0;
    animation: ball-roll 22s linear infinite;
}

/* Grade-A celebration — six gold stars twinkle in sequence around the
   ball, positioned just outside the 180 px disc. Each star is a 4-point
   asterisk via clip-path; the keyframe scales 0 → 1 → 0 with opacity
   crossfade so the stars wink rather than blink hard. Six × 0.4 s
   stagger gives a 2.4 s loop where roughly one star is at peak at any
   given moment, like a twinkle around the ball's halo. */
.report-hero__sparkles {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.report-hero__sparkles i {
    position: absolute;
    width: 14px;
    height: 14px;
    background: var(--disco-gold);
    clip-path: polygon(
        50%   0%, 58%  42%, 100% 50%, 58%  58%,
        50% 100%, 42%  58%,   0% 50%, 42%  42%
    );
    opacity: 0;
    transform-origin: center;
    filter: drop-shadow(0 0 8px rgba(246, 201, 69, 0.8));
    animation: sparkle-twinkle 2.4s ease-in-out infinite;
}

.report-hero__sparkles i:nth-child(1) { top: -10px;    left: calc(50% - 7px); animation-delay: 0s;   }
.report-hero__sparkles i:nth-child(2) { top: 22%;      right: -8px;           animation-delay: 0.4s; }
.report-hero__sparkles i:nth-child(3) { bottom: 22%;   right: -8px;           animation-delay: 0.8s; }
.report-hero__sparkles i:nth-child(4) { bottom: -10px; left: calc(50% - 7px); animation-delay: 1.2s; }
.report-hero__sparkles i:nth-child(5) { bottom: 22%;   left: -8px;            animation-delay: 1.6s; }
.report-hero__sparkles i:nth-child(6) { top: 22%;      left: -8px;            animation-delay: 2.0s; }

@keyframes sparkle-twinkle {
    0%, 100% {
        opacity: 0;
        transform: scale(0) rotate(0deg);
    }
    50% {
        opacity: 0.9;
        transform: scale(1) rotate(45deg);
    }
}

@media (prefers-reduced-motion: reduce) {
    .report-hero__sparkles i { animation: none; opacity: 0.6; transform: scale(1); }
}

.report-hero__grade {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Boldonse for the grade letter — same display family as the hero
       "groovy", so the scanned site's grade reads as a continuation of
       the brand voice rather than a generic dashboard chip. Colour
       comes from the per-grade modifiers below; the dark shadow lifts
       the letter off the mirror tiles. */
    font-family: var(--font-display);
    font-weight: 400;
    font-size: clamp(5.5rem, 12vw, 9rem);
    color: var(--disco-text);
    text-shadow: 0 3px 14px rgba(0, 0, 0, 0.65);
    animation: grade-reveal 900ms var(--ease-bounce) both;
}

/* The one place each grade's colour gets a hero moment. */
.report-hero--a .report-hero__grade { color: var(--disco-gold); }
.report-hero--b .report-hero__grade { color: var(--disco-green); }
.report-hero--c .report-hero__grade { color: var(--disco-amber); }
.report-hero--d .report-hero__grade { color: var(--disco-orange); }
.report-hero--f .report-hero__grade { color: var(--disco-red); }

@keyframes grade-reveal {
    0% {
        opacity: 0;
        transform: scale(0.3);
        filter: blur(4px);
    }
    60% {
        opacity: 1;
        filter: blur(0);
    }
    100% {
        opacity: 1;
        transform: scale(1);
        filter: blur(0);
    }
}

@media (prefers-reduced-motion: reduce) {
    .report-hero__grade { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
    .report-hero__ball img {
        animation: none;
    }
}

.report-hero__meta {
    flex: 1 1 22rem;
}

.report-hero__kicker {
    display: inline-block;
    font-family: var(--font-sans);
    font-size: 0.78rem;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    font-weight: 700;
    margin-bottom: var(--space-3);
    color: var(--disco-muted);
    animation: hero-fade-up 600ms var(--ease-snap) 200ms both;
}

.report-hero__title {
    font-family: var(--font-mono);
    font-size: clamp(1.8rem, 5vw, 3.5rem);
    font-weight: 700;
    line-height: 1.05;
    margin: 0 0 var(--space-4);
    word-break: break-all;
    color: var(--disco-text);
    animation: hero-fade-up 700ms var(--ease-snap) 280ms both;
}

.report-hero__score {
    font-family: var(--font-sans);
    font-size: clamp(1.05rem, 1.6vw, 1.25rem);
    color: var(--disco-muted);
    margin: 0 0 var(--space-2);
    animation: hero-fade-up 800ms var(--ease-snap) 360ms both;
}

.report-hero__score strong {
    color: var(--disco-gold);
    font-family: var(--font-sans);
    font-weight: 900;
    font-size: 1.4em;
    /* Use Boldonse-fed weight for the score numeral. The score-ticker
       Stimulus controller animates this from 0 → score on first paint. */
}

.report-hero__when {
    color: var(--disco-muted);
    font-size: 0.92rem;
    margin: 0;
    animation: hero-fade-up 800ms var(--ease-snap) 440ms both;
}

@media (prefers-reduced-motion: reduce) {
    .report-hero__kicker,
    .report-hero__title,
    .report-hero__score,
    .report-hero__when {
        animation: none;
    }
}

.report-hero--pending .report-hero__grade {
    color: var(--disco-muted);
    text-shadow: none;
    animation: none;
}

.report-section {
    padding: clamp(2rem, 4vw, 3rem) 0;
    border-top: 1px solid var(--disco-border);
}

.report-section:first-of-type {
    border-top: none;
    padding-top: var(--space-6);
}

/* AD-51: the one-paragraph "why this grade" summary that lives between
   the hero and the quick-wins. Treated as an editorial pull-quote in
   Fraunces-ish weight; punchy enough to hold its own near the hero
   grade, gentle enough not to compete with the data sections below. */

.report-why {
    margin: var(--space-6) 0 var(--space-7);
    padding: var(--space-5) var(--space-6);
    background: var(--disco-surface);
    border: 1px solid var(--disco-border);
    border-left: 3px solid var(--disco-magenta);
    border-radius: 0 var(--radius-3) var(--radius-3) 0;
    position: relative;
}

.report-why::before {
    content: "“";
    position: absolute;
    top: -0.5rem;
    left: var(--space-3);
    font-family: var(--font-display);
    font-size: 4rem;
    color: var(--disco-magenta);
    opacity: 0.4;
    line-height: 1;
}

.report-why__title {
    margin: 0 0 var(--space-3);
    font-family: var(--font-sans);
    font-size: 0.7rem;
    color: var(--disco-magenta);
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.18em;
}

.report-why__body {
    margin: 0;
    font-size: clamp(1.05rem, 1.6vw, 1.2rem);
    line-height: 1.55;
    max-width: 50rem;
    color: var(--disco-text);
    font-weight: 500;
}

/* M8 subscription alerts: the subscribe / unsubscribe card immediately
   after "Why this grade". Uses an accent border to feel optional-but-
   inviting — not a blocking CTA, not a hidden tool. */

.report-subscribe {
    margin: var(--space-6) 0;
    padding: var(--space-5) var(--space-6);
    background: linear-gradient(180deg, var(--disco-surface-hi), var(--disco-surface));
    border: 1px solid var(--disco-border);
    border-left: 3px solid var(--disco-cyan);
    border-radius: var(--radius-2);
}

.report-subscribe__title {
    margin: 0 0 0.5rem;
    font-size: 1.1rem;
}

.report-subscribe__prose {
    margin: 0 0 1rem;
    max-width: 42rem;
    color: var(--disco-muted);
}

.report-subscribe__form {
    margin: 0;
}

.report-subscribe__cta {
    margin: 0;
}

.report-subscribe__flash {
    margin: 0 0 1rem;
    padding: 0.5rem 0.75rem;
    border-radius: 4px;
    background: rgba(90, 215, 230, 0.12);
    color: inherit;
}

.report-subscribe__button {
    display: inline-block;
    padding: var(--space-3) var(--space-5);
    background: var(--disco-magenta);
    color: var(--disco-btn-ink);
    border: 0;
    border-radius: 999px;
    font-family: var(--font-sans);
    font-weight: 700;
    font-size: 0.85rem;
    letter-spacing: 0.02em;
    cursor: pointer;
    text-decoration: none;
    transition:
        background var(--t-quick) var(--ease-snap),
        transform var(--t-quick) var(--ease-snap);
}

.report-subscribe__button:hover,
.report-subscribe__button:focus-visible {
    background: var(--disco-magenta-hi);
    transform: translateY(-1px);
    outline: none;
}

.report-subscribe__button--secondary {
    background: transparent;
    color: var(--disco-muted);
    box-shadow: none;
    border: 1px solid var(--disco-border-bright);
}

.report-subscribe__button--secondary:hover,
.report-subscribe__button--secondary:focus-visible {
    color: var(--disco-text);
    border-color: var(--disco-magenta);
    background: rgba(90, 215, 230, 0.06);
    box-shadow: none;
}

.report-subscribe__select {
    padding: 0.45rem 0.7rem;
    background: var(--disco-surface, #16162e);
    color: inherit;
    border: 1px solid var(--disco-border);
    border-radius: 4px;
    font: inherit;
    margin-right: 0.5rem;
}

/* /account "Your subscriptions" panel. Same table treatment as the
   report history table so the two views feel related. */

.account__subscriptions {
    margin-top: 1rem;
}

.account__subs-title {
    margin: 0 0 0.5rem;
    font-size: 1.2rem;
}

.account__subs-subtitle {
    margin: 1.25rem 0 0.5rem;
    font-size: 1rem;
    color: var(--disco-muted);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.account__subs-table {
    width: 100%;
    border-collapse: collapse;
    margin: 0.5rem 0 0;
}

.account__subs-table th,
.account__subs-table td {
    padding: 0.5rem 0.75rem;
    text-align: left;
    border-bottom: 1px solid var(--disco-border);
    vertical-align: middle;
}

.account__subs-score {
    color: var(--disco-muted);
    margin-left: 0.4rem;
    font-size: 0.9rem;
}

.account__subs-unsub {
    text-align: right;
}

/* Bulk-unsubscribe checkbox column — narrow, vertically aligned to
   the row's content. The label gives the checkbox a generous tap
   target on touch screens without bloating the column width. */
.account__subs-checkbox-col {
    width: 2.5rem;
    text-align: center;
}

.account__subs-checkbox-label {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.4rem;
    cursor: pointer;
}

.account__subs-checkbox {
    width: 1rem;
    height: 1rem;
    cursor: pointer;
}

.account__subs-bulk-row {
    margin-top: 0.75rem;
    text-align: right;
}

/* Client-side host filter above subscriptions / schedules tables.
   Driven by the table-filter Stimulus controller — without JS the
   input is an inert text field and every row stays visible. */
.account__filter {
    margin-top: 0.5rem;
}

.account__filter-label {
    display: block;
    font-size: 0.85rem;
    color: var(--disco-muted);
    margin-bottom: 0.25rem;
}

.account__filter-input {
    width: 100%;
    max-width: 24rem;
    margin-bottom: 0.75rem;
}

.account__filter-empty {
    margin: 0.75rem 0 0;
    font-style: italic;
    color: var(--disco-muted);
}

.account__subs-unsub-button {
    background: transparent;
    color: inherit;
    border: 1px solid var(--disco-border);
    padding: 0.35rem 0.8rem;
    border-radius: 4px;
    font: inherit;
    font-size: 0.9rem;
    cursor: pointer;
}

.account__subs-unsub-button:hover,
.account__subs-unsub-button:focus-visible {
    border-color: var(--disco-magenta);
    color: var(--disco-magenta);
    outline: none;
}

/* "Disconnect" sits just under the "Linked …" line of a sign-in method,
   so give it a little breathing room from the text above it. */
.account__disconnect-form {
    margin-top: 0.5rem;
}

/* Webhook create-form layout — stacked label/input pairs so the host
   + URL inputs each get the full row width rather than fighting on a
   single line. */
.account__webhook-form {
    margin: 1rem 0;
    display: grid;
    gap: 0.4rem 1rem;
    max-width: 42rem;
}

/* Grid items stretch by default, which turns the submit button into a
   42rem-wide magenta bar. Inputs should fill the column; buttons
   shouldn't. */
.account__webhook-form .report-subscribe__button {
    justify-self: start;
}

.account__webhook-label {
    font-size: 0.85rem;
    color: var(--disco-muted);
    margin-top: 0.5rem;
}

.account__webhook-input {
    width: 100%;
    flex: none;
}

/* Inline warning badge in subscription / webhook rows when delivery
   is showing trouble. Two variants:
   - default (amber): consecutiveFailures > 0 but under threshold
   - --paused (red, dimmer): consecutiveFailures >= threshold (auto-
     paused; row is no longer firing). */
.account__failure-badge {
    display: inline-block;
    margin-left: 0.5rem;
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    background: rgba(245, 166, 35, 0.18);
    color: var(--disco-amber);
    border: 1px solid rgba(245, 166, 35, 0.35);
    font-size: 0.78rem;
    font-weight: 600;
    cursor: help;
    vertical-align: 0.05em;
}

.account__failure-badge--paused {
    background: rgba(229, 0, 75, 0.15);
    color: var(--disco-red);
    border-color: rgba(229, 0, 75, 0.35);
}

.report-section__title {
    margin: 0 0 var(--space-2);
    font-family: var(--font-sans);
    font-weight: 900;
    font-size: var(--type-display-m);
    letter-spacing: -0.025em;
    line-height: 1.1;
}

.report-section__lead {
    color: var(--disco-muted);
    margin: 0 0 var(--space-5);
    font-size: 1rem;
    line-height: 1.55;
    max-width: 50rem;
}

.quick-wins,
.blocking-issues {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    gap: var(--space-3);
}

.quick-win,
.blocking-issue {
    display: grid;
    gap: var(--space-1);
    padding: var(--space-4) var(--space-5);
    background: linear-gradient(180deg, var(--disco-surface-hi), var(--disco-surface));
    border: 1px solid var(--disco-border);
    border-radius: var(--radius-2);
    position: relative;
    transition:
        transform var(--t-medium) var(--ease-snap),
        border-color var(--t-medium) ease;
}

.quick-win::before,
.blocking-issue::before {
    content: "";
    position: absolute;
    left: 0;
    top: var(--space-3);
    bottom: var(--space-3);
    width: 3px;
    border-radius: 0 3px 3px 0;
    background: var(--disco-gold);
}

.blocking-issue::before {
    background: var(--disco-magenta);
}

.quick-win:hover,
.blocking-issue:hover {
    transform: translateY(-2px);
    border-color: var(--disco-border-bright);
}

.quick-win__label,
.blocking-issue__label {
    font-weight: 600;
}

.quick-win__hint,
.blocking-issue__notes {
    color: var(--disco-muted);
    font-size: 0.92rem;
}

.breakdown {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.95rem;
}

.breakdown th,
.breakdown td {
    padding: var(--space-4) var(--space-3);
    text-align: left;
    border-bottom: 1px solid var(--disco-border);
}

.breakdown thead th {
    color: var(--disco-muted);
    font-weight: 700;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.18em;
    border-bottom-color: var(--disco-border-bright);
}

.breakdown__cat {
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--disco-text);
}

.breakdown__num {
    font-family: var(--font-mono);
    color: var(--disco-muted);
    width: 8rem;
    font-size: 0.92rem;
    font-variant-numeric: tabular-nums;
}

.breakdown__bar-header,
.breakdown__bar-cell {
    width: 55%;
}

.bar {
    position: relative;
    height: 1.25rem;
    background: var(--disco-bg-deep);
    border: 1px solid var(--disco-border);
    border-radius: 999px;
    overflow: hidden;
    box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.3);
}

.bar__fill {
    position: absolute;
    inset: 0 auto 0 0;
    background: rgba(255, 46, 143, 0.7);
    border-radius: inherit;
    transition: width 800ms var(--ease-snap);
}

.bar__label {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    height: 100%;
    padding-right: var(--space-3);
    color: var(--disco-text);
    font-size: 0.78rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.04em;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

.findings {
    margin-bottom: var(--space-4);
    background: var(--disco-surface);
    border: 1px solid var(--disco-border);
    border-radius: var(--radius-3);
    padding: var(--space-3) var(--space-5);
    transition: border-color var(--t-medium) ease;
}

.findings:hover {
    border-color: var(--disco-border-bright);
}

.findings__summary {
    cursor: pointer;
    font-family: var(--font-sans);
    font-weight: 800;
    font-size: 1rem;
    letter-spacing: 0.02em;
    padding: var(--space-3) 0;
    color: var(--disco-text);
    list-style: none;
}

.findings__summary::-webkit-details-marker { display: none; }
.findings__summary::marker { content: ""; }

.findings__summary::after {
    content: "▾";
    float: right;
    color: var(--disco-muted);
    transition: transform var(--t-medium) var(--ease-snap);
    margin-left: var(--space-3);
}

.findings[open] .findings__summary::after {
    transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
    /* Keep the rotated arrow as an open/closed indicator, but don't
       animate the rotation. Matches the per-component motion gating used
       across the rest of this sheet. */
    .findings__summary::after { transition: none; }
}

.findings__summary:focus-visible {
    outline: 2px solid var(--disco-cyan);
    outline-offset: 2px;
    border-radius: var(--radius-1);
}

.findings__table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.92rem;
    margin-top: var(--space-3);
}

.findings__table th,
.findings__table td {
    padding: var(--space-3) var(--space-2);
    border-bottom: 1px solid var(--disco-border);
    text-align: left;
    vertical-align: top;
}

.findings__table thead th {
    color: var(--disco-muted);
    font-weight: 700;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.18em;
    border-bottom-color: var(--disco-border-bright);
}

.findings__key {
    font-family: var(--font-mono);
    font-size: 0.88rem;
    color: var(--disco-text);
    font-weight: 500;
}

.findings__points {
    font-family: var(--font-mono);
    color: var(--disco-muted);
    width: 6rem;
    font-variant-numeric: tabular-nums;
}

.findings__notes {
    color: var(--disco-muted);
    font-size: 0.9rem;
}

.status {
    display: inline-block;
    padding: 0.25rem 0.7rem;
    border-radius: 999px;
    font-family: var(--font-sans);
    font-size: 0.7rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    border: 1px solid transparent;
}

.status--pass {
    background: rgba(246, 201, 69, 0.18);
    color: var(--disco-gold);
    border-color: rgba(246, 201, 69, 0.4);
    box-shadow: 0 0 12px rgba(246, 201, 69, 0.15);
}
.status--warn {
    background: rgba(245, 166, 35, 0.16);
    color: var(--disco-amber);
    border-color: rgba(245, 166, 35, 0.4);
}
.status--fail {
    background: rgba(229, 0, 75, 0.16);
    color: var(--disco-red);
    border-color: rgba(229, 0, 75, 0.4);
}
.status--skip {
    background: rgba(177, 167, 200, 0.14);
    color: var(--disco-muted);
    border-color: rgba(177, 167, 200, 0.3);
}
.status--error {
    background: rgba(232, 93, 47, 0.16);
    color: var(--disco-orange);
    border-color: rgba(232, 93, 47, 0.4);
}
/* Informational — reported for context, excluded from the grade. Muted
   blue-grey so it reads as neutral, not pass/fail. */
.status--info {
    background: rgba(120, 150, 210, 0.14);
    color: var(--disco-muted);
    border-color: rgba(120, 150, 210, 0.32);
}

/* ---------------- leaderboard ---------------- */
/* The leaderboard is editorial — a "top 100 agent-friendly sites"
   chart. Big rank numerals in Boldonse, host name as the editorial
   headline, score as a tabular numeric block. Each row hovers to
   reveal a thin magenta sweep on the left and a subtle lift. */

.leaderboard {
    padding-bottom: clamp(3rem, 6vw, 5rem);
}

.leaderboard__table {
    width: 100%;
    border-collapse: collapse;
    font-size: 1rem;
    /* Reset the inherited `.findings__table` layout — leaderboard rows
       want airier padding + bigger numerical type. */
}

.leaderboard__table thead th {
    padding: var(--space-4) var(--space-3);
    color: var(--disco-muted);
    font-family: var(--font-sans);
    font-weight: 700;
    font-size: 0.7rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    text-align: left;
    border-bottom: 1px solid var(--disco-border-bright);
}

.leaderboard__table tbody tr {
    transition: background var(--t-quick) ease;
    position: relative;
}

.leaderboard__table tbody tr:hover {
    background: rgba(255, 46, 143, 0.05);
}

.leaderboard__table tbody td {
    padding: var(--space-4) var(--space-3);
    border-bottom: 1px solid var(--disco-border);
    vertical-align: middle;
}

.leaderboard__rank {
    width: 5rem;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: clamp(1.4rem, 3vw, 2rem);
    color: var(--disco-muted);
    line-height: 1;
}

.leaderboard__table tbody tr:hover .leaderboard__rank {
    color: var(--disco-magenta);
}

.leaderboard__table tbody td a {
    color: var(--disco-text);
    text-decoration: none;
    font-weight: 700;
    font-size: 1.05rem;
    transition: color var(--t-quick) ease;
}

.leaderboard__table tbody td a:hover {
    color: var(--disco-cyan);
}

.leaderboard__empty {
    margin: var(--space-7) auto;
    padding: var(--space-6);
    background: var(--disco-surface);
    border: 1px dashed var(--disco-border-bright);
    border-radius: var(--radius-3);
    text-align: center;
    color: var(--disco-muted);
    max-width: 40rem;
}

.embed-snippet {
    background: var(--disco-bg-deep);
    border: 1px solid var(--disco-border);
    border-radius: var(--radius-2);
    padding: var(--space-4) var(--space-5);
    overflow-x: auto;
    font-family: var(--font-mono);
    font-size: 0.88rem;
    line-height: 1.6;
    color: var(--disco-text);
    margin: 0;
}

.embed-snippet code {
    white-space: pre;
}

/* Live badge preview above the snippets — gives the user proof
   that the badge actually exists for this host before they paste
   the markup into their README. */
.embed__preview {
    margin: 0.75rem 0 1.25rem;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.embed__preview img {
    display: block;
}

.embed__snippet {
    margin: 0 0 1rem;
    border: 1px solid var(--disco-border);
    border-radius: 0.5rem;
    overflow: hidden;
}

.embed__snippet .embed-snippet {
    margin: 0;
    border: 0;
    border-radius: 0;
    background: var(--disco-bg);
}

/* Muted one-liner pointing at the PNG alternative under the snippets. */
.embed__note {
    margin: 0.25rem 0 0;
    font-size: 0.9rem;
    color: var(--disco-text-muted, #9c9ca8);
}

.embed__snippet-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.45rem 0.75rem;
    background: rgba(255, 255, 255, 0.03);
    border-bottom: 1px solid var(--disco-border);
}

.embed__snippet-label {
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--disco-muted);
}

.embed-copy-button {
    background: transparent;
    color: var(--disco-cyan);
    border: 1px solid var(--disco-border);
    border-radius: 4px;
    padding: 0.25rem 0.7rem;
    font-size: 0.8rem;
    font-weight: 500;
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}

.embed-copy-button:hover,
.embed-copy-button:focus-visible {
    background: rgba(90, 215, 230, 0.1);
    border-color: rgba(90, 215, 230, 0.5);
    color: #fff;
    outline: none;
}

.embed-copy-button--copied {
    background: rgba(10, 122, 42, 0.18);
    border-color: rgba(10, 122, 42, 0.5);
    color: #b9efc7;
}

/* ---------------- scan-progress page ---------------- */

.scan-progress-page {
    padding-bottom: 4rem;
}

.scan-progress-hero {
    display: flex;
    align-items: center;
    gap: var(--space-6);
    padding: clamp(2.5rem, 5vw, 4rem) 0 var(--space-5);
    flex-wrap: wrap;
}

.scan-progress-hero .hero__ball {
    /* Same CSS ball as the homepage hero, smaller and quicker — a
       livelier feel while the user waits. */
    --ball-size: clamp(120px, 14vw, 160px);
    animation: ball-roll 10s linear infinite;
    margin: 0;
    filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.5));
}

/* No light beams on the smaller scan-progress hero — keeps the layout
   tight and signals "we're working" rather than "this is a moment". */
.scan-progress-hero .hero__ball::after {
    display: none;
}

.scan-progress-hero__title {
    font-family: var(--font-mono);
    font-size: clamp(1.4rem, 3vw, 2rem);
    font-weight: 700;
    margin: var(--space-2) 0;
    word-break: break-all;
    color: var(--disco-text);
    animation: hero-fade-up 600ms var(--ease-snap) both;
}

.scan-progress-hero__url {
    color: var(--disco-muted);
    margin: 0;
    font-size: 0.9rem;
    word-break: break-all;
    animation: hero-fade-up 700ms cubic-bezier(.2, .8, .2, 1) 80ms both;
}

@media (prefers-reduced-motion: reduce) {
    .scan-progress-hero__title,
    .scan-progress-hero__url {
        animation: none;
    }
}

.scan-progress-frame {
    display: block;
}

.scan-progress {
    background: linear-gradient(180deg, var(--disco-surface-hi), var(--disco-surface));
    border: 1px solid var(--disco-border-bright);
    border-radius: var(--radius-3);
    padding: var(--space-5) var(--space-6);
    box-shadow: var(--shadow-card);
}

.scan-progress__state {
    margin: 0 0 1rem;
    color: var(--disco-muted);
    font-size: 0.95rem;
}

.scan-progress__state strong {
    color: var(--disco-text);
    letter-spacing: 0.06em;
}

/* Small inline spinner shown next to the QUEUED / RUNNING status text.
   Conic gradient + rotation = circular loader. Cyan-tinted so it reads
   as "in progress" rather than alarming red/orange. The element is
   only rendered while the scan is non-terminal, and the polling loop
   removes it on the next response when the status flips. */
.scan-progress__spinner {
    display: inline-block;
    width: 0.95em;
    height: 0.95em;
    margin-left: 0.4em;
    vertical-align: -0.15em;
    border-radius: 50%;
    background: conic-gradient(
        from 0deg,
        rgba(90, 215, 230, 0)   0%,
        rgba(90, 215, 230, 0.9) 75%,
        var(--disco-cyan)      100%
    );
    /* Mask the centre so the spinner is a 2px-thick ring rather than a
       filled disc — reads as a loading indicator at small sizes. */
    -webkit-mask: radial-gradient(circle at center, transparent 55%, #000 56%);
    mask: radial-gradient(circle at center, transparent 55%, #000 56%);
    animation: scan-spinner-rotate 800ms linear infinite;
}

@keyframes scan-spinner-rotate {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .scan-progress__spinner { animation: none; opacity: 0.6; }
}

.scan-progress__list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    gap: 0.4rem;
}

.scan-progress__row {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.6rem 0.75rem;
    background: var(--disco-bg);
    border: 1px solid var(--disco-border);
    border-radius: 0.4rem;
    overflow: hidden;
    transition: background 160ms ease, border-color 160ms ease, opacity 200ms ease;
}

/* Pending rows breathe gently — implies "we'll get to this". */
.scan-progress__row--pending {
    opacity: 0.55;
    animation: scan-row-pending-breathe 2.4s ease-in-out infinite;
}

/* Negative delays start neighbouring rows mid-cycle, so the list
   breathes out of phase rather than as one synchronised sweep. */
.scan-progress__row--pending:nth-child(2n) { animation-delay: -0.8s; }
.scan-progress__row--pending:nth-child(3n) { animation-delay: -1.6s; }

@keyframes scan-row-pending-breathe {
    0%, 100% { opacity: 0.45; }
    50%      { opacity: 0.7;  }
}

/* The "pending" pill grows marching dots ( .  ..  ... ) so something
   visibly progresses even when the row's breathe is mid-fade. The
   fixed-width box stops the pill resizing as dots appear. */
.scan-progress__row--pending .status--pending::after {
    content: '';
    display: inline-block;
    width: 1.2em;
    text-align: left;
    animation: scan-pending-dots 1.6s steps(1, end) infinite;
}

@keyframes scan-pending-dots {
    0%   { content: ''; }
    25%  { content: '.'; }
    50%  { content: '..'; }
    75%  { content: '...'; }
    100% { content: ''; }
}

/* Running rows get a cyan sheen sweep — the row currently being scanned
   reads as actively-doing-something rather than a static "running" pill.
   Pseudo-element runs once every 2 s; it's lightweight enough to leave
   on continuously. */
.scan-progress__row--running {
    border-color: rgba(90, 215, 230, 0.45);
}

.scan-progress__row--running::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(120deg,
        transparent 30%,
        rgba(90, 215, 230, 0.18) 50%,
        transparent 70%);
    pointer-events: none;
    animation: scan-row-running-sheen 2s ease-in-out infinite;
}

@keyframes scan-row-running-sheen {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(100%);  }
}

/* Passing rows tint subtly gold; failing rows subtly red. Gives the
   user a peripheral-vision read on how the scan is going without
   needing to read each pill. */
.scan-progress__row--pass {
    border-color: rgba(246, 201, 69, 0.3);
    background: linear-gradient(180deg, var(--disco-bg) 0%, rgba(246, 201, 69, 0.04) 100%);
}

.scan-progress__row--fail {
    border-color: rgba(229, 0, 75, 0.3);
    background: linear-gradient(180deg, var(--disco-bg) 0%, rgba(229, 0, 75, 0.05) 100%);
}

@media (prefers-reduced-motion: reduce) {
    .scan-progress__row--pending,
    .scan-progress__row--running::after,
    .scan-progress__row--pending .status--pending::after {
        animation: none;
    }

    .scan-progress__row--pending .status--pending::after {
        content: '…';
    }
}

.scan-progress__check {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
}

.scan-progress__cat {
    color: var(--disco-cyan);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.scan-progress__label {
    font-size: 0.95rem;
}

.scan-progress__status {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-shrink: 0;
}

.scan-progress__points {
    font-family: var(--font-mono);
    color: var(--disco-muted);
    font-size: 0.85rem;
}

.status--pending {
    background: rgba(157, 157, 180, 0.15);
    color: var(--disco-muted);
}

.scan-progress__complete,
.scan-progress__failed {
    margin-top: 1.5rem;
    padding-top: 1.25rem;
    border-top: 1px dashed var(--disco-border);
}

.scan-progress__complete p,
.scan-progress__failed p {
    margin: 0.25rem 0;
}

.scan-progress__complete {
    color: var(--disco-gold);
}

.scan-progress__failed h2 {
    margin: 0 0 0.5rem;
    color: var(--disco-red);
}

.scan-progress__link {
    font-weight: 600;
}

/* ---- AD-21 scan history ---------------------------------------------- */

.history {
    width: 100%;
    border-collapse: collapse;
    margin-top: 0.75rem;
}

.history thead th {
    text-align: left;
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--disco-muted);
    padding: 0.35rem 0.5rem;
    border-bottom: 1px solid var(--disco-border);
}

.history__row td {
    padding: 0.5rem;
    border-bottom: 1px dashed var(--disco-border);
    vertical-align: middle;
}

.history__when {
    color: var(--disco-muted);
}

/* AD-52: expandable "what flipped" panel under each history row. No
   border — it visually belongs to the row above. Defaults to <details open>
   so the panel is visible without JS; Stimulus can strip the attribute
   later to make it click-to-expand. */

.history__flips-cell {
    padding: 0 0.5rem 0.75rem;
    border-bottom: 1px solid var(--disco-border);
    background: var(--disco-bg-deep);
}

.history__flips-summary {
    font-size: 0.85rem;
    color: var(--disco-muted);
    cursor: pointer;
    padding: 0.25rem 0;
}

.history__flips-list {
    margin: 0.4rem 0 0;
    padding-left: 0;
    list-style: none;
}

.history__flip {
    display: flex;
    gap: 0.5rem;
    font-size: 0.9rem;
    line-height: 1.4;
    padding: 0.15rem 0;
}

.history__flip-icon {
    width: 1rem;
    font-weight: bold;
    text-align: center;
    flex-shrink: 0;
}

.history__flip--pass .history__flip-icon { color: var(--disco-gold); }
.history__flip--fail .history__flip-icon { color: var(--disco-red); }
.history__flip--more { color: var(--disco-muted); font-style: italic; }

.history__flips-empty {
    margin: 0.4rem 0 0;
    color: var(--disco-muted);
    font-style: italic;
    font-size: 0.9rem;
}

.history__score,
.history__checks {
    font-variant-numeric: tabular-nums;
}

.history__link a {
    color: var(--disco-cyan);
    text-decoration: none;
    font-weight: 600;
}

.history__link a:hover,
.history__link a:focus {
    text-decoration: underline;
}

.history__more {
    margin: 0.75rem 0 0;
}

/* Old narrow sparkline used in the history section header. Kept
   for backwards compat in case any template still references it
   (none today after the score-trend section landed). */
.history-sparkline {
    display: block;
    margin-bottom: 0.5rem;
    max-width: 100%;
    height: auto;
}

/* "Score over time" trend section — promoted up from the old
   inline-with-history sparkline. Wide chart on the left, summary
   stats on the right. Stacks on narrow viewports. */
.score-trend__layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 1.5rem;
    align-items: center;
}

.score-trend__chart {
    width: 100%;
    height: 120px;
    display: block;
}

.score-trend__stats {
    display: grid;
    grid-template-columns: repeat(2, auto);
    gap: 0.6rem 1.25rem;
    margin: 0;
    padding: 0;
    font-size: 0.9rem;
}

.score-trend__stats > div {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

.score-trend__stats dt {
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--disco-muted);
    margin: 0;
}

.score-trend__stats dd {
    margin: 0;
    font-family: var(--font-mono);
    font-size: 1.2rem;
    color: var(--disco-text);
    font-weight: 600;
}

.score-trend__delta {
    display: inline-block;
    margin-left: 0.3rem;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 600;
    font-family: var(--font-sans);
    letter-spacing: 0;
}

.score-trend__delta--up {
    background: rgba(10, 122, 42, 0.18);
    color: #b9efc7;
}

.score-trend__delta--down {
    background: rgba(229, 0, 75, 0.18);
    color: #ffd6e0;
}

.score-trend__delta--flat {
    color: var(--disco-muted);
    background: transparent;
}

/* Switch to single-column at 768px (standard tablet portrait breakpoint)
   so iPad-portrait gets the stacked layout rather than a cramped two-
   column squeeze. Was 720px which fell between common breakpoints. */
@media (max-width: 768px) {
    .score-trend__layout {
        grid-template-columns: 1fr;
    }
    .score-trend__stats {
        grid-template-columns: repeat(4, auto);
        justify-content: start;
    }
}

.grade-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2.4rem;
    height: 2.4rem;
    padding: 0 0.6rem;
    border-radius: 999px;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: 1.1rem;
    line-height: 1;
    text-align: center;
    background: var(--disco-surface);
    border: 1px solid var(--disco-border);
    color: var(--disco-muted);
}

/* One colour per grade, flat tint + matching ink. Gold is reserved
   for A — the only chip that earns a solid fill. */
.grade-chip--a {
    background: var(--disco-gold);
    color: #16130a;
    border-color: var(--disco-gold);
}
.grade-chip--b {
    background: rgba(62, 207, 142, 0.12);
    color: var(--disco-green);
    border-color: rgba(62, 207, 142, 0.45);
}
.grade-chip--c {
    background: rgba(234, 168, 60, 0.12);
    color: var(--disco-amber);
    border-color: rgba(234, 168, 60, 0.45);
}
.grade-chip--d {
    background: rgba(255, 138, 92, 0.12);
    color: var(--disco-orange);
    border-color: rgba(255, 138, 92, 0.45);
}
.grade-chip--f {
    background: rgba(255, 77, 109, 0.12);
    color: var(--disco-red);
    border-color: rgba(255, 77, 109, 0.45);
}

.arrow {
    display: inline-block;
    font-weight: 700;
}

.arrow--up { color: var(--disco-cyan); }
.arrow--down { color: var(--disco-red); }
.arrow--same { color: var(--disco-muted); }
.arrow--none { color: var(--disco-border); }

/* ---- AD-22 checks catalogue ----------------------------------------- */
/* The catalogue is a magazine spread: every category gets its own
   editorial section heading, the checks themselves sit in airy
   tabular rows with mono-font keys, weight numerals, and a short
   description that justifies the weight. */

.checks {
    width: 100%;
    border-collapse: collapse;
    margin-top: var(--space-3);
}

.checks thead th {
    text-align: left;
    font-family: var(--font-sans);
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.18em;
    font-weight: 700;
    color: var(--disco-muted);
    padding: var(--space-3) var(--space-3);
    border-bottom: 1px solid var(--disco-border-bright);
}

.checks tbody tr {
    transition: background var(--t-quick) ease;
}

.checks tbody tr:hover {
    background: rgba(255, 46, 143, 0.04);
}

.checks tbody td {
    padding: var(--space-4) var(--space-3);
    vertical-align: top;
    border-bottom: 1px solid var(--disco-border);
}

.checks__label a {
    color: var(--disco-text);
    font-weight: 700;
    font-size: 1.02rem;
    text-decoration: none;
    transition: color var(--t-quick) ease;
}

.checks__label a:hover,
.checks__label a:focus {
    color: var(--disco-cyan);
}

.checks__key {
    display: block;
    font-family: var(--font-mono);
    font-size: 0.78rem;
    color: var(--disco-muted);
    margin-top: var(--space-1);
    letter-spacing: 0.02em;
}

.checks__weight {
    font-family: var(--font-display);
    font-weight: 400;
    font-size: 1.4rem;
    line-height: 1;
    color: var(--disco-gold);
    width: 5rem;
}

.checks__desc {
    color: var(--disco-muted);
    max-width: 38rem;
    line-height: 1.55;
}

.phase {
    display: inline-flex;
    align-items: center;
    padding: 0.25rem 0.7rem;
    border-radius: 999px;
    font-family: var(--font-sans);
    font-size: 0.7rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    border: 1px solid transparent;
}

.phase--passive {
    background: rgba(90, 215, 230, 0.15);
    color: var(--disco-cyan);
    border-color: rgba(90, 215, 230, 0.4);
}
.phase--active  {
    background: rgba(255, 46, 143, 0.16);
    color: var(--disco-magenta);
    border-color: rgba(255, 46, 143, 0.4);
}

.check-detail {
    color: var(--disco-text);
    line-height: 1.6;
    max-width: 48rem;
}

.check-detail h1,
.check-detail h2,
.check-detail h3 {
    color: var(--disco-gold);
    margin-top: 1.5rem;
}

.check-detail a {
    color: var(--disco-cyan);
}

.check-detail code {
    background: #1a1a2e;
    padding: 0.1rem 0.35rem;
    border-radius: 0.3rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-size: 0.9em;
}

.check-detail pre {
    background: #1a1a2e;
    padding: 0.75rem;
    border-radius: 0.5rem;
    overflow-x: auto;
}

.check-detail table {
    border-collapse: collapse;
    margin: 0.5rem 0;
}

.check-detail th,
.check-detail td {
    border: 1px solid var(--disco-border);
    padding: 0.35rem 0.6rem;
    text-align: left;
}

/* ---------------- error pages (404 / 500 / generic) ---------------- */
/* Sober layout, deliberately without the disco-ball artwork — an error
   response isn't a moment for celebration. Shares the container + base
   palette so the site footer + header chrome remain consistent. */

.error-page {
    text-align: center;
    padding: clamp(2.5rem, 7vw, 5rem) 1rem 6rem;
    max-width: 40rem;
}

.error-page__status {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: clamp(4rem, 14vw, 7rem);
    font-weight: 800;
    /* Solid signature accent — the status number is the one brand
       moment an error page gets. */
    color: var(--disco-magenta);
    letter-spacing: 0.04em;
    margin: 0 0 0.5rem;
    line-height: 1;
    animation: hero-fade-up 700ms cubic-bezier(.2, .8, .2, 1) both;
}

.error-page__headline {
    font-size: clamp(1.5rem, 4vw, 2rem);
    margin: 0 0 1.5rem;
    animation: hero-fade-up 600ms cubic-bezier(.2, .8, .2, 1) 120ms both;
}

.error-page__body {
    color: var(--disco-muted);
    font-size: 1.05rem;
    line-height: 1.55;
    margin: 0 0 2rem;
    animation: hero-fade-up 700ms cubic-bezier(.2, .8, .2, 1) 200ms both;
}

.error-page__actions {
    font-size: 1rem;
    display: flex;
    gap: 0.5rem 1rem;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    animation: hero-fade-up 800ms cubic-bezier(.2, .8, .2, 1) 280ms both;
}

.error-page__primary {
    position: relative;
    overflow: hidden;
    display: inline-block;
    padding: 0.6rem 1.2rem;
    background: var(--disco-magenta);
    color: var(--disco-btn-ink);
    font-weight: 700;
    border-radius: 999px;
    text-decoration: none;
    transition: background 160ms cubic-bezier(.2, .8, .2, 1),
                transform 160ms cubic-bezier(.2, .8, .2, 1);
}

.error-page__primary:hover,
.error-page__primary:focus-visible {
    background: var(--disco-magenta-hi);
    transform: translateY(-1px);
    outline: none;
}

@media (prefers-reduced-motion: reduce) {
    .error-page__status,
    .error-page__headline,
    .error-page__body,
    .error-page__actions {
        animation: none;
    }
    .error-page__status {
        background-position: 0% 50%;
    }
}

/* ---------------- table scroll wrapper ---------------- */
/* Generic fallback: tables inside this wrapper become horizontally
   scrollable on narrow viewports rather than squishing rows to
   illegibility. Used by the findings and history tables, where the
   column count + note text make them too wide to realistically
   card-ify. */

.table-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

/* ---------------- mobile viewport (report page) ---------------- */
/* Pre-deploy-followups item 13: the report page's wide tables break
   below ~640px. This block carries the narrow-viewport overrides —
   breakdown becomes a card list, history tables scroll horizontally,
   the hero disco-ball scales down. Anything wider than 640px keeps
   the existing desktop layout. */

@media (max-width: 640px) {
    .report-hero {
        gap: 1.25rem;
        padding: 1.5rem 0 1rem;
    }

    .report-hero__ball {
        width: 120px;
        height: 120px;
        flex: 0 0 120px;
    }

    .report-hero__grade {
        font-size: 3.25rem;
    }

    .report-hero__title {
        font-size: 1.5rem;
        word-break: break-word;
    }

    /* Breakdown → card list. thead hidden (visually, not from a11y
       tree — relies on the data-label pseudo-element for each cell
       to carry its column meaning). Each row becomes a stacked block;
       the bar stays full-width inside its card. */
    .breakdown thead {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        white-space: nowrap;
    }

    .breakdown,
    .breakdown tbody,
    .breakdown tr,
    .breakdown th,
    .breakdown td {
        display: block;
        width: auto;
    }

    .breakdown tr {
        border: 1px solid var(--disco-border);
        border-radius: 0.5rem;
        padding: 0.75rem;
        margin-bottom: 0.6rem;
        background: var(--disco-surface);
    }

    .breakdown th,
    .breakdown td {
        border-bottom: 0;
        padding: 0.3rem 0;
    }

    .breakdown th::before,
    .breakdown td::before {
        content: attr(data-label) ": ";
        color: var(--disco-muted);
        font-size: 0.75rem;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        display: block;
        margin-bottom: 0.15rem;
    }

    .breakdown__cat {
        font-size: 1.05rem;
    }

    .breakdown__num {
        width: auto;
    }

    /* Embed snippet — long URL overflows. Let it break. */
    .embed-snippet code {
        white-space: pre-wrap;
        word-break: break-all;
    }

    /* Quick-wins + blocking-issues: narrower padding, the label already
       wraps naturally. Mostly fine as-is; this is the only nudge. */
    .quick-win,
    .blocking-issue {
        padding: 0.6rem;
    }

    /* /checks and /leaderboard reuse the same table → card-list trick the
       /report breakdown table uses. The Description column on /checks is
       150+ characters per row, which won't fit on a 320px phone no matter
       how we shrink the type — stacking each row's cells wins. */
    .checks,
    .leaderboard__table {
        display: block;
    }

    .checks thead,
    .leaderboard__table thead {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        white-space: nowrap;
    }

    .checks tbody,
    .leaderboard__table tbody,
    .checks tr,
    .leaderboard__table tr,
    .checks td,
    .leaderboard__table td {
        display: block;
        width: auto;
    }

    .checks tr,
    .leaderboard__table tr {
        border: 1px solid var(--disco-border);
        border-radius: 0.5rem;
        padding: 0.85rem;
        margin-bottom: 0.6rem;
        background: var(--disco-surface);
    }

    .checks td,
    .leaderboard__table td {
        border-bottom: 0;
        padding: 0.25rem 0;
    }

    /* Same data-label trick — the table's column meaning travels into the
       card via a `data-label="Check"` attribute on each `<td>`. */
    .checks td::before,
    .leaderboard__table td::before {
        content: attr(data-label);
        display: block;
        color: var(--disco-muted);
        font-size: 0.7rem;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        margin-bottom: 0.15rem;
    }

    /* The leaderboard's rank cell would have an awkward "Rank" label
       repeated 100 times — promote it to a corner badge instead. */
    .leaderboard__rank {
        display: inline-block;
        background: rgba(90, 215, 230, 0.12);
        color: var(--disco-cyan);
        padding: 0.15rem 0.55rem;
        border-radius: 0.4rem;
        font-size: 0.8rem;
        margin-bottom: 0.4rem;
    }
    .leaderboard__rank::before { display: none; }
}

/* ---------------- sitewide header ---------------- */
/* Three-column flex: brand on the left, nav in the middle, auth
   links on the right. Palette cues: the site-name gradient echoes
   the landing-page hero's magenta→gold sweep so the header doesn't
   feel like a separate layer bolted on top, and the border-bottom
   uses --disco-border for a subtle separation from the hero. */

.skip-link {
    position: absolute;
    top: -40rem;
    left: 0;
    background: var(--disco-gold);
    color: var(--disco-bg);
    padding: 0.5rem 1rem;
    font-weight: 600;
    text-decoration: none;
    z-index: 100;
}

.skip-link:focus-visible {
    top: 0.5rem;
    left: 0.5rem;
    outline: 2px solid var(--disco-magenta);
    outline-offset: 2px;
}

#main-content:focus {
    outline: none;
}

.site-header {
    position: sticky;
    top: 0;
    z-index: 50;
    border-bottom: 1px solid var(--disco-border);
    background: var(--disco-bg-deep);
}

/* Animated rainbow underline beneath the header — same drift as the
   .kicker shimmer, ties everything together. Subtle (1px) but the
   gradient lives. */
.site-header::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -1px;
    height: 1px;
    background: var(--disco-border);
}

.site-header__inner {
    display: flex;
    align-items: center;
    gap: var(--space-5);
    padding-top: var(--space-3);
    padding-bottom: var(--space-3);
    max-width: 1100px;
}

.site-header__brand {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    color: var(--disco-text);
    text-decoration: none;
    flex: 0 0 auto;
}

.site-header__brand:hover,
.site-header__brand:focus-visible {
    color: var(--disco-text);
    outline: none;
}

.site-header__logo {
    display: block;
    width: 99px;
    height: 44px;
    /* Subtle lift on hover — rotation worked for the round-only mark
       but the wide robot+ball lockup looks awkward when tilted, so we
       scale up a hair instead. */
    transition: transform 400ms cubic-bezier(.2, .8, .2, 1);
    filter: drop-shadow(0 4px 10px rgba(255, 46, 143, 0.25));
}

.site-header__brand:hover .site-header__logo,
.site-header__brand:focus-visible .site-header__logo {
    transform: scale(1.05);
}

.site-header__brand-text {
    display: flex;
    flex-direction: column;
    /* Boldonse paints taller than its em-box; without generous
       line-height the brand wordmark below clips at the bottom edge
       of the header row. */
    line-height: 1.4;
}

.site-header__name {
    font-family: var(--font-display);
    font-size: 1rem;
    font-weight: 400;
    letter-spacing: 0.02em;
    color: var(--disco-text);
    /* Match the em treatment in the hero: a hair of vertical padding
       so the glyph paint doesn't touch the strapline below. */
    padding: 0.1em 0;
}

.site-header__strapline {
    font-family: var(--font-sans);
    font-size: 0.7rem;
    color: var(--disco-muted);
    letter-spacing: 0.18em;
    text-transform: uppercase;
    font-weight: 500;
    margin-top: 0.2rem;
}

/* (Header narrow-viewport rules live in one consolidated block after
   the header component styles below.) */
@media (max-width: 640px) {
    /* Auth card breathes a touch tighter on narrow viewports — the
       2 rem all-around padding feels disproportionate on a 360 phone. */
    .auth-card {
        margin: 1.5rem auto 2rem;
        padding: 1.5rem 1.25rem 1.25rem;
        border-radius: 12px;
    }
}
@media (max-width: 480px) {
    .site-header__brand-text {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        white-space: nowrap;
    }

    /* Centre the hero ball more tightly to the headline below it on
       phones; the desktop 1.5rem bottom margin opens an unnecessary gap
       at the top of the page. */
    .hero {
        padding-top: 1.5rem;
    }
}

.site-header__nav {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    margin-left: auto; /* push nav + auth to the right of brand */
    flex-wrap: wrap;
}

.site-header__nav a {
    color: var(--disco-text);
    font-size: 0.95rem;
    font-weight: 500;
    text-decoration: none;
    padding: 0.3rem 0;
    position: relative;
    transition: color 150ms ease;
}

.site-header__nav a::after {
    /* Cyan under-glow on hover — fast + subtle, nods to the
       disco-ball mirror facets without dominating. */
    content: '';
    position: absolute;
    inset: auto 0 -2px 0;
    height: 2px;
    background: var(--disco-cyan);
    transform: scaleX(0);
    transform-origin: center;
    transition: transform 180ms ease;
}

.site-header__nav a:hover,
.site-header__nav a:focus-visible {
    color: var(--disco-cyan);
    outline: none;
}

.site-header__nav a:hover::after,
.site-header__nav a:focus-visible::after {
    transform: scaleX(1);
}

.site-header__auth {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex: 0 0 auto;
}

.site-header__login,
.site-header__account {
    color: var(--disco-muted);
    font-size: 0.95rem;
    font-weight: 500;
    text-decoration: none;
    transition: color 150ms ease;
}

.site-header__login:hover,
.site-header__login:focus-visible,
.site-header__account:hover,
.site-header__account:focus-visible {
    color: var(--disco-cyan);
    outline: none;
}

/* Explicit — some user agents render `[hidden]` with a browser
   default that a later selector could override. Belt-and-braces so
   the Stimulus swap has a predictable starting state. */
.site-header__auth [hidden] {
    display: none !important;
}

.site-header__signup {
    display: inline-flex;
    align-items: center;
    padding: 0.5rem 1.1rem;
    background: var(--disco-magenta);
    color: var(--disco-btn-ink);
    font-family: var(--font-sans);
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-decoration: none;
    border-radius: 999px;
    transition:
        background var(--t-quick) var(--ease-snap),
        transform var(--t-quick) var(--ease-snap);
}

.site-header__signup:hover,
.site-header__signup:focus-visible {
    color: var(--disco-btn-ink);
    background: var(--disco-magenta-hi);
    transform: translateY(-1px);
    outline: none;
}

.site-header__signup:focus-visible {
    outline: 2px solid var(--disco-text);
    outline-offset: 3px;
}

/* Narrow viewports: hide the strapline (the name alone holds the
   brand), tighten gaps, keep the signup CTA and drop the secondary
   log-in text link (it stays reachable in the footer). */
@media (max-width: 640px) {
    .site-header__inner {
        gap: 0.75rem;
        padding-top: 0.6rem;
        padding-bottom: 0.6rem;
    }

    /* Lockup is 99×44 on desktop; shrink to 78×35 so the brand row
       leaves space for nav + auth. The aspect ratio is preserved. */
    .site-header__logo {
        width: 78px;
        height: 35px;
    }

    .site-header__strapline {
        display: none;
    }

    .site-header__name {
        font-size: 1rem;
    }

    .site-header__nav {
        gap: 0.75rem;
    }

    .site-header__nav a {
        font-size: 0.85rem;
    }

    .site-header__login {
        display: none;
    }

    .site-header__signup {
        padding: 0.35rem 0.8rem;
        font-size: 0.85rem;
    }
}

/* Really narrow (<420px): stack brand above nav so nothing overflows. */
@media (max-width: 420px) {
    .site-header__inner {
        flex-wrap: wrap;
    }

    /* The nav carries the desktop `margin-left: auto`; once it wraps
       to its own row the auth group must push itself right instead. */
    .site-header__auth {
        margin-left: auto;
    }

    .site-header__nav {
        order: 3;
        width: 100%;
        justify-content: center;
        border-top: 1px solid var(--disco-border);
        padding-top: 0.5rem;
    }
}


/* /report/{host}/compare — side-by-side scan diff. Reuses
   `.findings__table` for the per-check rows; this block adds the
   summary header + per-row diff highlights. */

.report-compare__hero {
    padding: 2rem 0 0.5rem;
}

.report-compare__columns {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 1rem;
    margin: 1rem 0;
    padding: 1rem 1.25rem;
    border: 1px solid var(--disco-border);
    border-radius: 6px;
    background: var(--disco-surface, rgba(255, 255, 255, 0.03));
}

.report-compare__column {
    text-align: center;
}

.report-compare__when {
    margin: 0 0 0.4rem;
    color: var(--disco-muted);
    font-size: 0.9rem;
}

.report-compare__grade {
    margin: 0;
    font-size: 1.1rem;
}

.report-compare__arrow {
    font-size: 2rem;
    color: var(--disco-muted);
}

.report-compare__delta {
    text-align: center;
    color: var(--disco-muted);
    margin: 0.5rem 0 1.5rem;
}

.report-compare__delta--up { color: #0a7a2a; }
.report-compare__delta--down { color: var(--disco-red); }

.report-compare__flips-subtitle {
    font-size: 1rem;
    margin: 1rem 0 0.4rem;
    color: var(--disco-muted);
}

.report-compare__row--regressed {
    background: rgba(229, 0, 75, 0.08);
}

.report-compare__row--improved {
    background: rgba(246, 201, 69, 0.08);
}

.report-compare__row--added,
.report-compare__row--removed,
.report-compare__row--changed {
    background: rgba(90, 215, 230, 0.06);
}

/* GDPR "Privacy & data" panel on /account — export + delete. The
   delete control is intentionally double-gated (a <details> the user
   has to expand and an email re-confirm) so a stray click can't
   trigger the irreversible action. */

.account__privacy {
    margin-top: 1rem;
}

.account__privacy-form {
    margin: 0.5rem 0 0;
}

.account__privacy-delete {
    margin-top: 1.25rem;
    padding: 0.75rem 1rem;
    border: 1px solid var(--disco-border);
    border-radius: 4px;
    background: rgba(229, 0, 75, 0.04);
}

.account__privacy-delete > summary {
    cursor: pointer;
    font-weight: 600;
}

.account__privacy-delete-button {
    background: var(--disco-red);
    color: var(--disco-btn-ink);
}

.landing__flash {
    margin: 0 0 1.5rem;
    padding: 0.6rem 0.85rem;
    border-radius: 4px;
    background: rgba(90, 215, 230, 0.12);
    color: inherit;
}

.landing__flash--error {
    background: rgba(229, 0, 75, 0.14);
    border: 1px solid rgba(229, 0, 75, 0.35);
}

/* "Verify your email" banner on /account. Persists until the user
   clicks the verification link. The pending variant is amber so it
   reads as "needs attention" rather than "broken". */

.account__verify-banner {
    margin: 0 0 1.5rem;
    padding: 0.75rem 1rem;
    border-radius: 4px;
}

.account__verify-banner--pending {
    background: rgba(246, 201, 69, 0.12);
    border: 1px solid rgba(246, 201, 69, 0.4);
}

.account__verify-banner--ok {
    background: rgba(90, 215, 230, 0.12);
}

.account__verify-banner--error {
    background: rgba(229, 0, 75, 0.1);
}

.account__verify-banner p {
    margin: 0 0 0.5rem;
}

/* ---------------- webhook deliveries debug page ---------------- */

.webhook-deliveries__status {
    display: inline-block;
    padding: 0.15rem 0.5rem;
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: 0.85rem;
    font-weight: 600;
}

.webhook-deliveries__status--ok {
    background: rgba(10, 122, 42, 0.18);
    color: #b9efc7;
}

.webhook-deliveries__status--fail {
    background: rgba(229, 0, 75, 0.18);
    color: #ffd6e0;
}

.webhook-deliveries__status--transport {
    background: rgba(246, 201, 69, 0.14);
    color: var(--disco-amber);
    font-style: italic;
}

.webhook-deliveries__row--fail {
    background: rgba(229, 0, 75, 0.04);
}

.webhook-deliveries__snippet {
    margin: 0;
    padding: 0.4rem 0.6rem;
    background: var(--disco-bg);
    border: 1px solid var(--disco-border);
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: 0.78rem;
    line-height: 1.4;
    max-height: 6rem;
    overflow: auto;
    white-space: pre-wrap;
    word-break: break-word;
}

.webhook-deliveries__muted {
    color: var(--disco-muted);
    font-style: italic;
}

/* ---------------- auth card ----------------
 * Centered card that hosts /login, /register, /forgot-password,
 * and /reset-password. Within the disco palette but visually a
 * distinct moment from the public landing — narrower, slightly
 * lifted, focused on the form rather than marketing prose.
 */

.auth-card {
    max-width: 30rem;
    margin: clamp(3rem, 6vw, 5rem) auto 3rem;
    padding: var(--space-7) var(--space-6) var(--space-6);
    background: linear-gradient(180deg, var(--disco-surface-hi), var(--disco-surface));
    border: 1px solid var(--disco-border-bright);
    border-radius: var(--radius-4);
    box-shadow: var(--shadow-card-hi);
    position: relative;
    overflow: hidden;
}

.auth-card::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    background: var(--disco-magenta);
    opacity: 0.6;
}

.auth-card__header {
    text-align: center;
    margin-bottom: var(--space-6);
}

.auth-card__kicker {
    display: inline-block;
    font-family: var(--font-sans);
    font-size: 0.72rem;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    font-weight: 800;
    margin-bottom: var(--space-3);
    color: var(--disco-muted);
}

.auth-card__title {
    font-family: var(--font-sans);
    font-weight: 900;
    font-size: clamp(1.7rem, 3.5vw, 2.2rem);
    line-height: 1.1;
    letter-spacing: -0.025em;
    margin: 0 0 var(--space-3);
}

.auth-card__subtitle {
    color: var(--disco-muted);
    font-size: 1rem;
    line-height: 1.55;
    margin: 0;
}

.auth-card__form {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.auth-card__field {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.auth-card__label {
    font-size: 0.85rem;
    color: var(--disco-muted);
    letter-spacing: 0.02em;
}

.auth-card__input {
    width: 100%;
    background: var(--disco-bg-deep);
    color: var(--disco-text);
    border: 1px solid var(--disco-border);
    border-radius: 8px;
    padding: 0.75rem 0.9rem;
    font-size: 1rem;
    font-family: var(--font-sans);
    transition: border-color 120ms ease, box-shadow 120ms ease;
}

.auth-card__input::placeholder {
    color: var(--disco-muted);
    opacity: 0.7;
}

.auth-card__input:focus-visible {
    outline: none;
    border-color: var(--disco-magenta);
    box-shadow: 0 0 0 3px rgba(255, 46, 143, 0.18);
}

.auth-card__remember {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.9rem;
    color: var(--disco-muted);
    cursor: pointer;
}

.auth-card__remember input {
    accent-color: var(--disco-cyan);
}

.auth-card__submit {
    width: 100%;
    background: var(--disco-magenta);
    color: var(--disco-btn-ink);
    border: 0;
    border-radius: 999px;
    padding: var(--space-4) var(--space-5);
    font-family: var(--font-sans);
    font-size: 0.95rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    cursor: pointer;
    transition:
        background var(--t-quick) var(--ease-snap),
        transform var(--t-quick) var(--ease-snap);
    box-shadow: var(--shadow-glow-magenta);
}

.auth-card__submit:hover:not(:disabled),
.auth-card__submit:focus-visible {
    background: var(--disco-magenta-hi);
    transform: translateY(-1px);
    outline: none;
}

.auth-card__submit:focus-visible {
    outline: 2px solid var(--disco-text);
    outline-offset: 3px;
}

.auth-card__submit:active:not(:disabled) {
    transform: translateY(1px);
}

.auth-card__inline-link {
    margin-top: -0.3rem;
    font-size: 0.85rem;
    text-align: right;
}

.auth-card__inline-link a {
    color: var(--disco-muted);
    text-decoration: none;
}

.auth-card__inline-link a:hover,
.auth-card__inline-link a:focus-visible {
    color: var(--disco-cyan);
    text-decoration: underline;
    text-underline-offset: 3px;
}

.auth-card__footer {
    margin-top: 1.5rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--disco-border);
    text-align: center;
    color: var(--disco-muted);
    font-size: 0.9rem;
}

.auth-card__footer a {
    color: var(--disco-text);
    font-weight: 500;
}

/* OAuth button: GitHub-style "Continue with X" treatment.
 * Subtle white-tinted surface so it reads as a third-party login
 * shortcut rather than the primary CTA. The hover-brighten matches
 * the colony.cc reference Jack pointed to — low-contrast at rest,
 * lifts on focus. */

.auth-card__oauth {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin-bottom: 1.25rem;
}

.auth-oauth-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    width: 100%;
    padding: 0.75rem 1rem;
    color: rgba(232, 232, 244, 0.92);
    background: rgba(232, 232, 244, 0.04);
    border: 1px solid rgba(232, 232, 244, 0.18);
    border-radius: 8px;
    font-size: 0.95rem;
    font-weight: 600;
    text-decoration: none;
    transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}

.auth-oauth-btn:hover,
.auth-oauth-btn:focus-visible {
    background: rgba(232, 232, 244, 0.1);
    border-color: rgba(232, 232, 244, 0.42);
    color: #fff;
    outline: none;
}

.auth-oauth-btn:focus-visible {
    box-shadow: 0 0 0 3px rgba(255, 46, 143, 0.18);
}

.auth-oauth-btn__icon {
    width: 20px;
    height: 20px;
    flex: 0 0 auto;
}

/* "or continue with email" divider between the OAuth buttons and
 * the local form. Two horizontal lines with the label in between.
 */

.auth-card__divider {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    color: var(--disco-muted);
    font-size: 0.78rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    margin: 0 0 1.25rem;
}

.auth-card__divider::before,
.auth-card__divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--disco-border);
}

/* Reuse the existing .auth__error / .auth__success classes for
 * flash + form errors (tests assert on those selectors). Tweaked
 * to sit nicely inside the card. */

.auth-card .auth__error,
.auth-card .auth__success {
    margin: 0 0 1rem;
    padding: 0.6rem 0.8rem;
    border-radius: 6px;
    font-size: 0.9rem;
}

.auth-card .auth__error {
    background: rgba(229, 0, 75, 0.12);
    border: 1px solid rgba(229, 0, 75, 0.3);
    color: #ffd6e0;
}

.auth-card .auth__success {
    background: rgba(90, 215, 230, 0.1);
    border: 1px solid rgba(90, 215, 230, 0.3);
    color: #d6f7ff;
}

/* 2FA recovery-code one-shot display + setup-page QR. The
   `--codes` panel is urgent-styled (save these now!) on the manage
   page; the QR centres on the setup page with a white inner
   background so the dark-themed page doesn't murder the QR's
   contrast for a phone camera. */
.account__twofa-codes {
    border: 1px solid rgba(246, 201, 69, 0.45);
    background: rgba(246, 201, 69, 0.06);
}

.account__twofa-code-list {
    list-style: none;
    margin: 0.75rem 0 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
    gap: 0.5rem;
}

.account__twofa-code-list code {
    display: block;
    padding: 0.5rem 0.75rem;
    background: rgba(0, 0, 0, 0.4);
    border: 1px solid var(--disco-border);
    border-radius: 6px;
    font-size: 1rem;
    letter-spacing: 0.04em;
    text-align: center;
    user-select: all;
}

.account__twofa-qr {
    display: flex;
    justify-content: center;
    margin: 1rem 0 0.5rem;
}

.account__twofa-qr svg {
    background: #ffffff;
    border-radius: 8px;
    padding: 0.5rem;
    width: 220px;
    height: 220px;
    max-width: 100%;
}

/* /account/activity table — same dark-card pattern as the rest of
   the account page, plus a dimmer secondary row for the metadata
   blurb and a smaller monospace UA so a long browser/OS string
   doesn't blow out the column. */
.account__activity-table .account__activity-meta {
    color: var(--disco-muted);
    font-size: 0.8rem;
    word-break: break-word;
}

.account__activity-table .account__activity-ua {
    color: var(--disco-muted);
    font-size: 0.78rem;
    font-family: var(--font-mono, monospace);
    word-break: break-all;
    display: inline-block;
    max-width: 24rem;
}

.account__activity-pager {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    margin-top: 1rem;
    font-size: 0.9rem;
    color: var(--disco-muted);
}

.account__activity-pager a {
    color: var(--disco-cyan);
    text-decoration: none;
    font-weight: 600;
}

.account__activity-pager a:hover,
.account__activity-pager a:focus {
    text-decoration: underline;
}

.account__activity-pager-disabled {
    color: var(--disco-border);
    cursor: not-allowed;
}

/* /account section nav: mobile-first horizontal pill bar at the top
   of the content column, promoted to a sticky vertical side-nav at
   ≥920px. Same nav element, same anchors; only the layout changes
   across the breakpoint. Smooth scroll is already enabled site-wide
   on `html`, so anchor jumps are animated for free. */
.account__layout {
    display: block;
}

/* Mobile: horizontal scrollable pill bar, sticky to the viewport top
   so the user can jump from anywhere in the section list. The solid
   background matters — without it, content scrolling underneath would
   be visible through the sticky bar. */
.account__nav {
    position: sticky;
    top: 0;
    z-index: 5;
    margin: 0 -1rem 1rem;
    padding: 0.5rem 1rem;
    background: var(--disco-bg);
    border-bottom: 1px solid var(--disco-border);
    /* Edge-fade hint that more pills are scrollable to the right.
       Mask is supported by every shipped browser at this point. */
    -webkit-mask-image: linear-gradient(to right, black 0, black calc(100% - 2rem), transparent 100%);
            mask-image: linear-gradient(to right, black 0, black calc(100% - 2rem), transparent 100%);
}

.account__nav-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    gap: 0.4rem;
    overflow-x: auto;
    /* Hide the horizontal scrollbar — the edge-fade above hints at
       overflow without the visual chrome. */
    scrollbar-width: none;
}
.account__nav-list::-webkit-scrollbar { display: none; }

.account__nav a {
    display: inline-block;
    flex-shrink: 0;
    padding: 0.4rem 0.85rem;
    color: var(--disco-muted);
    text-decoration: none;
    font-size: 0.85rem;
    /* Pill shape; ample tap target for thumbs (≥38px effective). */
    border-radius: 999px;
    border: 1px solid var(--disco-border);
    background: rgba(255, 255, 255, 0.02);
    white-space: nowrap;
    transition: color 160ms ease, background 160ms ease, border-color 160ms ease;
}

.account__nav a:hover,
.account__nav a:focus-visible {
    color: var(--disco-text);
    background: rgba(90, 215, 230, 0.08);
    border-color: var(--disco-magenta);
    outline: none;
}

/* Active section pill on mobile. Magenta because cyan is the hover
   colour above — the two states need to be distinguishable when the
   user is moused-over the active item. */
.account__nav a.account__nav-link--active {
    color: var(--disco-text);
    background: rgba(255, 46, 143, 0.18);
    border-color: var(--disco-magenta, #ff2e8f);
    font-weight: 600;
}

/* Group labels divide the flat nav list into Profile / Security /
   Notifications / Privacy clusters on desktop. Hidden on mobile —
   the pill bar stays a flat horizontal scroll there. */
.account__nav-group-label {
    display: none;
}

@media (min-width: 920px) {
    .account__layout {
        display: grid;
        grid-template-columns: 13rem minmax(0, 1fr);
        gap: 2rem;
        align-items: start;
    }

    /* Desktop: reset every mobile-pill rule and re-establish the
       sticky vertical column. Group the resets here so the
       breakpoint-specific intent is obvious. */
    .account__nav {
        position: sticky;
        top: 1rem;
        max-height: calc(100vh - 2rem);
        margin: 0;
        padding: 0.5rem 0;
        background: transparent;
        border-bottom: 0;
        overflow-y: auto;
        z-index: auto;
        -webkit-mask-image: none;
                mask-image: none;
    }

    .account__nav-list {
        flex-direction: column;
        flex-wrap: nowrap;
        gap: 0.15rem;
        overflow-x: visible;
    }

    .account__nav a {
        display: block;
        flex-shrink: 1;
        padding: 0.45rem 0.75rem;
        font-size: 0.92rem;
        border-radius: 6px;
        border: 0;
        border-left: 2px solid transparent;
        background: transparent;
        white-space: normal;
    }

    .account__nav a:hover,
    .account__nav a:focus-visible {
        background: rgba(90, 215, 230, 0.06);
        border-color: transparent;
        border-left-color: var(--disco-cyan);
    }

    .account__nav a.account__nav-link--active {
        background: rgba(255, 46, 143, 0.08);
        border-color: transparent;
        border-left-color: var(--disco-magenta, #ff2e8f);
    }

    /* Group labels: small-caps dividers above each cluster. Margin
       on the first label is collapsed so the nav doesn't lead with a
       double-spacing gap. */
    .account__nav-group-label {
        display: block;
        margin: 0.85rem 0 0.15rem;
        padding: 0 0.75rem;
        font-size: 0.7rem;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        color: var(--disco-muted);
    }

    .account__nav-list > .account__nav-group-label:first-child {
        margin-top: 0;
    }
}

/* Anchor-jump landing: keep a little air above the section so the
   heading doesn't collide with the viewport edge. On mobile the
   sticky pill nav takes ~3.5rem at the top of the viewport, so we
   need to clear that too — otherwise the section heading lands
   under the nav after a tap. */
.account__content section[id] {
    scroll-margin-top: 4rem;
}

@media (min-width: 920px) {
    .account__content section[id] {
        scroll-margin-top: 1rem;
    }
}
