/*
 * Agent Disco — Studio 54 meets observability dashboard.
 *
 * Palette:
 *   magenta  #ff2e8f  primary accent (CTAs, "groovy" word, highlights)
 *   cyan     #00e5ff  secondary accent (links, grade A strokes)
 *   gold     #ffd34d  tertiary accent (grade A fills, hover sheen)
 *   plum     #2a0f3f  deep aubergine for the dance-floor base
 *   bg       #0a0418  near-black underlay
 *   surface  #18102e  glass-card backing
 *   text     #f5f1ff  body, with a hint of lavender warmth
 *
 * Typography:
 *   display  Boldonse — single-weight free font, retro-disco character,
 *            used for the hero word "groovy", grade letters on /report,
 *            the leaderboard rank numerals, and the kickers (which get
 *            extra letter-spacing + uppercase to pace them out).
 *   body     DM Sans — geometric sans, weights 400 / 500 / 700 / 900;
 *            quiet enough to read long check descriptions, but set in
 *            its 900 weight for editorial pull-quote moments.
 *   mono     ui-monospace stack for code samples + numerical alignment
 *            in tabular contexts.
 *
 * 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 ------------------------------------------------------ */
    --disco-bg: #0a0418;
    --disco-bg-deep: #050210;
    --disco-plum: #2a0f3f;
    --disco-surface: #18102e;
    --disco-surface-hi: #221638;
    --disco-border: #34204e;
    --disco-border-bright: #4a2c6a;
    --disco-text: #f5f1ff;
    --disco-muted: #b1a7c8;
    --disco-magenta: #ff2e8f;
    --disco-magenta-hi: #ff5dab;
    --disco-cyan: #00e5ff;
    --disco-cyan-hi: #66f0ff;
    --disco-gold: #ffd34d;
    --disco-gold-hi: #ffe588;
    --disco-red: #e5004b;
    --disco-orange: #e85d2f;
    --disco-amber: #f5a623;

    /* The signature gradient — used on the brand wordmark, the "groovy"
       em, big buttons, focus rings on hot surfaces. Three stops, magenta
       through gold to cyan, which spans the whole palette in one sweep. */
    --disco-rainbow: linear-gradient(120deg,
        var(--disco-magenta) 0%,
        var(--disco-gold) 50%,
        var(--disco-cyan) 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 ------------------------------------------------------ */
    --shadow-card:
        0 1px 0 rgba(255, 255, 255, 0.04) inset,
        0 12px 30px rgba(2, 0, 8, 0.5);
    --shadow-card-hi:
        0 1px 0 rgba(255, 255, 255, 0.06) inset,
        0 18px 50px rgba(255, 46, 143, 0.18),
        0 8px 20px rgba(2, 0, 8, 0.55);
    --shadow-glow-magenta: 0 0 36px rgba(255, 46, 143, 0.45);
    --shadow-glow-gold:    0 0 36px rgba(255, 211, 77, 0.45);

    /* ---- 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 {
    /* Layered base: deep aubergine wash, then a static radial dance-floor
       wash anchored top-centre. The animated mesh of pulsing colour blobs
       lives on `body::before`; a fine grain layer on `body::after` sells
       the depth. Keeps content above on its own stacking context. */
    background-color: var(--disco-bg);
    background-image:
        radial-gradient(ellipse 60vw 40vh at 50% -5%, rgba(255, 46, 143, 0.20), transparent 60%),
        radial-gradient(ellipse 50vw 35vh at 100% 0%, rgba(0, 229, 255, 0.12), transparent 65%),
        radial-gradient(ellipse 80vw 60vh at 0% 100%, rgba(255, 211, 77, 0.10), transparent 60%),
        linear-gradient(180deg, var(--disco-bg) 0%, var(--disco-bg-deep) 100%);
    background-attachment: fixed;
    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;
}

/* Animated mesh of colour blobs that drifts behind everything — the
   dance-floor lighting effect. Three blobs at differing sizes/positions
   move at different speeds + scales to never quite repeat. Pinned with
   position: fixed so it doesn't scroll out — the atmosphere persists
   across long pages. */
body::before {
    content: "";
    position: fixed;
    inset: -20vmax;
    z-index: -2;
    pointer-events: none;
    background-image:
        radial-gradient(circle at 30% 30%, rgba(255, 46, 143, 0.35) 0%, transparent 35%),
        radial-gradient(circle at 80% 60%, rgba(0, 229, 255, 0.22) 0%, transparent 35%),
        radial-gradient(circle at 50% 90%, rgba(255, 211, 77, 0.20) 0%, transparent 35%);
    filter: blur(80px) saturate(120%);
    opacity: 0.85;
    animation: dance-floor 22s var(--ease-snap) infinite alternate;
}

/* Fine grain so the gradients don't look like an iOS lock screen.
   SVG-as-data-URL inline; tiny payload (~600 bytes) and zero extra
   network hop. The grain is multiplied with the layers below so it
   reads as texture, not as noise overlay. */
body::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    opacity: 0.06;
    mix-blend-mode: overlay;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' seed='7'/><feColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.6 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}

@keyframes dance-floor {
    0% {
        transform: translate3d(0, 0, 0) scale(1);
        filter: blur(80px) saturate(120%) hue-rotate(0deg);
    }
    50% {
        transform: translate3d(2vmax, -3vmax, 0) scale(1.08);
        filter: blur(90px) saturate(140%) hue-rotate(15deg);
    }
    100% {
        transform: translate3d(-2vmax, 2vmax, 0) scale(1.12);
        filter: blur(85px) saturate(130%) hue-rotate(-12deg);
    }
}

@media (prefers-reduced-motion: reduce) {
    body::before { animation: none; }
}

/* Better default focus ring across interactive elements without one of their
   own. Cyan outline on the dark surface reads as obviously-focused without
   the heavy black-on-white default. */
:where(a, button, [tabindex]):focus-visible {
    outline: 2px solid var(--disco-cyan);
    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;
}

a {
    color: var(--disco-cyan);
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
    transition: color var(--t-quick) var(--ease-snap);
}

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

/* 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 disco-themed 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. */
.kicker {
    display: inline-block;
    font-size: 0.78rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    font-weight: 700;
    background: var(--disco-rainbow);
    background-size: 220% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: kicker-shimmer 12s ease-in-out infinite;
}

@keyframes kicker-shimmer {
    0%   { background-position: 0% 50%; }
    50%  { background-position: 100% 50%; }
    100% { background-position: 0% 50%; }
}

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

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

/* ---------------- hero ---------------- */
/* The signature page. Disco ball front and centre, massive editorial
   headline below, scan form embedded in the spotlight. The whole stack
   sits on a layered backdrop: the body's animated gradient mesh +
   grain bleed through, plus a hero-specific sunburst that radiates out
   from the ball position. Everything fades in on a stagger so the page
   load reads as a "lights up" theatrical reveal. */

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

/* Sunburst beneath the ball — radial gradient from the ball's centre
   outwards, magenta→gold, that fades into the background mesh. Sized
   in vmax so it's prominent on tablet+ and tasteful on mobile. */
.hero::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: radial-gradient(
        ellipse 70% 55% at 50% 35%,
        rgba(255, 46, 143, 0.25) 0%,
        rgba(255, 211, 77, 0.10) 35%,
        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);
    background: var(--disco-rainbow);
    background-size: 220% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation:
        hero-fade-up 600ms var(--ease-snap) 200ms both,
        kicker-shimmer 12s ease-in-out 800ms infinite;
}

.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. Boldonse glyphs paint
       considerably taller than their em-box (the font's reported
       cap-height is generous, and its bounding glyph paint extends
       above + below that). `font-size: 0.85em` scales the type down
       inside the em so the painted glyph height roughly matches DM
       Sans's at 1em — the word still reads as a different voice
       (Boldonse vs DM Sans), but the geometry plays nicely with the
       parent line-height. `text-transform: uppercase` keeps lowercase
       y from emitting a descender. */
    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;
    background: var(--disco-rainbow);
    background-size: 240% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    /* Subtle text-shadow tries to bleed colour past the glyph silhouettes,
       reads as glow against the mesh backdrop. */
    filter: drop-shadow(0 0 22px rgba(255, 46, 143, 0.45));
    animation: groovy-shimmer 9s ease-in-out infinite;
    /* Vertical padding (top + bottom) reserves paint room on both
       edges of the em-box so the glyph silhouette never clips
       against the line above or below. */
    padding: 0.25em 0.15em;
    margin: 0 0.05em;
    line-height: 1;
    vertical-align: baseline;
}

@keyframes groovy-shimmer {
    0%   { background-position:   0% 50%; }
    50%  { background-position: 100% 50%; }
    100% { background-position:   0% 50%; }
}

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

.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;
}

/* 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;
    }
}

.hero__ball {
    /* Hero ball is the star — much bigger than the original 96-140px so
       it actually feels like the disco-ball-on-stage moment the brand
       implies. Fluid 180px on a 320px phone → 320px on desktop. */
    --ball-size: clamp(180px, 30vw, 320px);
    width: var(--ball-size);
    height: var(--ball-size);
    margin: 0 auto var(--space-6);
    position: relative;
    /* Pre-rendered sprite atlas (50 frames stacked, each a 3D projection
       of the ball at a different rotation around its vertical axis with
       per-tile lighting baked against a fixed light source). Driving
       it with steps(50) gives a real spinning disco ball — the flat
       SVG + rotateY couldn't, since the highlight rotated with the ball
       and the ball went edge-on at 90°. Sprite generated by
       tools/render-disco-ball-sprite.py — re-run if the look changes. */
    background-image: url("../images/disco-ball-sprite-zujyMnB.webp");
    background-repeat: no-repeat;
    /* Pixel-precise scaling so each frame in the displayed sprite is
       exactly --ball-size tall. Percentages here would land between
       frames mid-animation — see the keyframe note below. */
    background-size: var(--ball-size) calc(var(--ball-size) * 50);
    animation:
        disco-ball-spin-frames 24s steps(50) infinite,
        hero-ball-drop 1100ms var(--ease-bounce) both;
    filter:
        drop-shadow(0 30px 50px rgba(255, 46, 143, 0.35))
        drop-shadow(0 10px 20px rgba(0, 229, 255, 0.2));
}

/* Light beams shooting down from the disco ball — pseudo-element below
   the ball with a conic-gradient that fans out, masked at the edges so
   it dissolves into the page. Spins very slowly for a subtle, almost
   subliminal motion. */
.hero__ball::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 200%;
    height: 250%;
    transform: translate(-50%, -10%);
    z-index: -1;
    pointer-events: none;
    background: conic-gradient(
        from 0deg,
        transparent 0deg,
        rgba(255, 46, 143, 0.18) 30deg,
        transparent 60deg,
        rgba(0, 229, 255, 0.14) 90deg,
        transparent 120deg,
        rgba(255, 211, 77, 0.16) 150deg,
        transparent 180deg,
        rgba(255, 46, 143, 0.14) 210deg,
        transparent 240deg,
        rgba(0, 229, 255, 0.12) 270deg,
        transparent 300deg,
        rgba(255, 211, 77, 0.14) 330deg,
        transparent 360deg
    );
    mask-image: radial-gradient(ellipse 60% 60% at 50% 0%, #000 0%, transparent 70%);
    -webkit-mask-image: radial-gradient(ellipse 60% 60% at 50% 0%, #000 0%, transparent 70%);
    animation: light-beam-spin 80s linear infinite;
}

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

@keyframes light-beam-spin {
    from { transform: translate(-50%, -10%) rotate(0deg); }
    to   { transform: translate(-50%, -10%) rotate(360deg); }
}

/* The canonical sprite-strip pattern: animate background-position by the
   total sprite height (`--ball-size × 50`) and let `steps(50)` drop us
   on every frame boundary in turn. Earlier we used percentages
   (`0% → 100%`) which seemed equivalent — but `background-position: P%`
   places the sprite's P% point at the element's P% point, so the
   linear traversal lands on intermediate fractions of frames at every
   step except 0 and 100. The user saw two stitched-together half-balls
   wherever the animation paused mid-cycle. Pixel offsets via the
   `--ball-size` custom property fix this without giving up the fluid
   `clamp()` sizing. */
@keyframes disco-ball-spin-frames {
    from { background-position: 0 0; }
    to   { background-position: 0 calc(var(--ball-size) * -50); }
}

/* 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;
    /* Glass-card shell wrapping the whole input + button row so the form
       reads as a single luminous artefact rather than two stacked widgets.
       The shell carries the magenta border-glow; the input + button sit
       transparent on top so focus rings still work. */
    background: rgba(24, 16, 46, 0.65);
    backdrop-filter: blur(20px) saturate(140%);
    -webkit-backdrop-filter: blur(20px) saturate(140%);
    border: 1px solid var(--disco-border-bright);
    border-radius: 999px;
    padding: var(--space-2);
    box-shadow:
        0 1px 0 rgba(255, 255, 255, 0.06) inset,
        var(--shadow-glow-magenta),
        0 24px 60px rgba(2, 0, 8, 0.6);
}

.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-cyan);
    box-shadow:
        0 1px 0 rgba(255, 255, 255, 0.08) inset,
        0 0 0 3px rgba(0, 229, 255, 0.25),
        0 0 50px rgba(0, 229, 255, 0.35),
        0 24px 60px rgba(2, 0, 8, 0.6);
}

.scan-form__button {
    flex: 1 1 auto;
    position: relative;
    overflow: hidden;
    background: var(--disco-rainbow);
    background-size: 220% 100%;
    color: #1a0a2e;
    border: 0;
    border-radius: 999px;
    padding: var(--space-4) var(--space-6);
    font-family: var(--font-sans);
    font-size: 1rem;
    font-weight: 900;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    cursor: pointer;
    transition:
        transform var(--t-quick) var(--ease-snap),
        box-shadow var(--t-medium) ease,
        background-position var(--t-medium) ease;
    box-shadow:
        0 6px 20px rgba(255, 46, 143, 0.5),
        0 0 0 1px rgba(255, 255, 255, 0.18) inset;
}

.scan-form__button:hover:not(:disabled),
.scan-form__button:focus-visible {
    transform: translateY(-1px) scale(1.02);
    background-position: 100% 50%;
    box-shadow:
        0 12px 32px rgba(255, 46, 143, 0.7),
        0 0 60px rgba(255, 211, 77, 0.4),
        0 0 0 1px rgba(255, 255, 255, 0.3) inset;
    outline: none;
}

/* Sheen sweep on hover — a thin diagonal highlight band travels across
   the button face. Pure CSS, runs once per hover. */
.scan-form__button::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(120deg,
        transparent 35%,
        rgba(255, 255, 255, 0.42) 50%,
        transparent 65%);
    transform: translateX(-100%);
    transition: transform 700ms var(--ease-snap);
    pointer-events: none;
    border-radius: inherit;
}

.scan-form__button:hover::after {
    transform: translateX(100%);
}

.scan-form__button:focus-visible {
    outline: 2px solid var(--disco-gold);
    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;
    /* Glass card on the dance-floor backdrop. The backdrop-filter blurs
       the mesh underneath so the card has its own ambient glow halo
       that follows it as the body's animation drifts. */
    background: linear-gradient(180deg, rgba(34, 22, 56, 0.7), rgba(24, 16, 46, 0.55));
    backdrop-filter: blur(18px) saturate(140%);
    -webkit-backdrop-filter: blur(18px) saturate(140%);
    border: 1px solid var(--disco-border-bright);
    border-radius: var(--radius-4);
    padding: clamp(1.5rem, 3vw, 2.5rem);
    box-shadow: var(--shadow-card);
    position: relative;
    overflow: hidden;
}

/* Magenta-to-cyan ribbon along the top edge of every glass card. Ten-pixel
   bar with the rainbow gradient — a signature flourish that ties the
   landing example, the report cards, and the leaderboard table together. */
.example::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    background: var(--disco-rainbow);
    background-size: 200% 100%;
    animation: kicker-shimmer 12s ease-in-out infinite;
}

.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: linear-gradient(135deg, var(--disco-gold), var(--disco-magenta));
    color: #1a0a2e;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: 1.4rem;
    line-height: 1;
    padding: 0.45rem 0.9rem;
    border-radius: var(--radius-1);
    letter-spacing: 0;
    box-shadow: var(--shadow-glow-gold);
}

.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-gold); }
.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: linear-gradient(180deg, rgba(34, 22, 56, 0.6), rgba(24, 16, 46, 0.4));
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    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;
}

/* Glowing accent on the left edge — magenta ribbon with the same
   rainbow shimmer used on the example card and headers. */
.why__item::before {
    content: "";
    position: absolute;
    top: var(--space-5);
    bottom: var(--space-5);
    left: 0;
    width: 3px;
    background: var(--disco-rainbow);
    background-size: 100% 220%;
    border-radius: 0 3px 3px 0;
    opacity: 0.7;
    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: rgba(0, 229, 255, 0.4);
}

.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-cyan);
    box-shadow: 0 0 0 3px rgba(0, 229, 255, 0.25);
}

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

.contact-form__submit {
    position: relative;
    overflow: hidden;
    background: var(--disco-rainbow);
    background-size: 220% 100%;
    color: #1a0a2e;
    border: none;
    border-radius: 999px;
    padding: var(--space-4) var(--space-6);
    font-family: var(--font-sans);
    font-weight: 900;
    font-size: 0.95rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    cursor: pointer;
    justify-self: start;
    transition:
        transform var(--t-quick) var(--ease-snap),
        box-shadow var(--t-medium) ease,
        background-position var(--t-medium) ease;
    box-shadow:
        0 6px 20px rgba(255, 46, 143, 0.5),
        0 0 0 1px rgba(255, 255, 255, 0.18) inset;
}

.contact-form__submit:hover,
.contact-form__submit:focus-visible {
    transform: translateY(-1px) scale(1.02);
    background-position: 100% 50%;
    box-shadow:
        0 12px 32px rgba(255, 46, 143, 0.7),
        0 0 60px rgba(255, 211, 77, 0.4),
        0 0 0 1px rgba(255, 255, 255, 0.3) inset;
    outline: none;
}

/* Sheen sweep on hover — matches the homepage scan button. */
.contact-form__submit::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(120deg,
        transparent 35%,
        rgba(255, 255, 255, 0.4) 50%,
        transparent 65%);
    transform: translateX(-100%);
    transition: transform 600ms cubic-bezier(.2, .8, .2, 1);
    pointer-events: none;
}

.contact-form__submit:hover::after {
    transform: translateX(100%);
}

.contact-form__submit:focus-visible {
    outline: 2px solid var(--disco-gold);
    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%,
        rgba(42, 15, 63, 0.5) 30%,
        rgba(10, 4, 24, 0.85) 100%);
}

.site-footer::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: var(--disco-rainbow);
    background-size: 220% 100%;
    opacity: 0.5;
    animation: kicker-shimmer 18s ease-in-out infinite;
}

.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__office {
    font-size: 0.78rem;
    opacity: 0.8;
}

.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-rainbow);
    background-size: 200% 100%;
    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.22) 0%,
        rgba(255, 211, 77, 0.10) 30%,
        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 30px 50px rgba(255, 46, 143, 0.4))
        drop-shadow(0 10px 20px rgba(0, 229, 255, 0.18));
}

/* 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(255, 211, 77, 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__ball img {
    width: 100%;
    height: 100%;
    /* Spin only the ball image, not the wrapper, so the grade letter
       stays put + readable. The earlier wrapper-rotates-everything-
       grade-counter-rotates dance worked for a 2D rotate() spin but
       breaks for the rotateY spin used now: counter-rotating the
       letter around Y would make it go edge-on at 90° / 270°. */
    animation: disco-spin 18s linear infinite;
}

.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. */
    font-family: var(--font-display);
    font-weight: 400;
    font-size: clamp(5.5rem, 12vw, 9rem);
    color: #1a0a2e;
    text-shadow:
        0 2px 0 rgba(255, 211, 77, 0.9),
        0 0 30px rgba(255, 46, 143, 0.4);
    animation: grade-reveal 900ms var(--ease-bounce) both;
}

@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);
    background: var(--disco-rainbow);
    background-size: 220% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation:
        hero-fade-up 600ms var(--ease-snap) 200ms both,
        kicker-shimmer 12s ease-in-out 800ms infinite;
}

.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: linear-gradient(180deg, rgba(34, 22, 56, 0.55), rgba(24, 16, 46, 0.35));
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border: 1px solid var(--disco-border);
    border-left: 3px solid transparent;
    border-image: var(--disco-rainbow) 1;
    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, rgba(34, 22, 56, 0.6), rgba(24, 16, 46, 0.4));
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    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(0, 229, 255, 0.12);
    color: inherit;
}

.report-subscribe__button {
    display: inline-block;
    padding: var(--space-3) var(--space-5);
    background: var(--disco-cyan);
    color: #1a0a2e;
    border: 0;
    border-radius: 999px;
    font-family: var(--font-sans);
    font-weight: 800;
    font-size: 0.85rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    cursor: pointer;
    text-decoration: none;
    transition:
        transform var(--t-quick) var(--ease-snap),
        box-shadow var(--t-medium) ease,
        filter var(--t-quick) ease;
    box-shadow:
        0 6px 16px rgba(0, 229, 255, 0.35),
        0 0 0 1px rgba(255, 255, 255, 0.18) inset;
}

.report-subscribe__button:hover,
.report-subscribe__button:focus-visible {
    filter: brightness(1.08);
    transform: translateY(-1px);
    box-shadow:
        0 12px 24px rgba(0, 229, 255, 0.55),
        0 0 0 1px rgba(255, 255, 255, 0.3) inset;
    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-cyan);
    background: rgba(0, 229, 255, 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;
}

.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;
}

/* 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;
}

.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, rgba(34, 22, 56, 0.55), rgba(24, 16, 46, 0.35));
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    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);
    box-shadow: 0 0 12px rgba(255, 46, 143, 0.5);
}

.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.6rem;
    background: rgba(10, 4, 24, 0.65);
    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: linear-gradient(90deg,
        var(--disco-magenta) 0%,
        var(--disco-gold) 60%,
        var(--disco-cyan) 100%);
    background-size: 200% 100%;
    transition: width 800ms var(--ease-snap);
    box-shadow: 0 0 16px rgba(255, 46, 143, 0.35);
    /* The gradient pulses subtly inside the fill so a static bar still
       has a hint of movement without distracting from the data. */
    animation: bar-shimmer 8s ease-in-out infinite;
}

@keyframes bar-shimmer {
    0%, 100% { background-position: 0% 50%; }
    50%      { background-position: 100% 50%; }
}

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

.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;
    mix-blend-mode: difference;
    letter-spacing: 0.04em;
}

.findings {
    margin-bottom: var(--space-4);
    background: linear-gradient(180deg, rgba(34, 22, 56, 0.55), rgba(24, 16, 46, 0.4));
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    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);
}

.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(255, 211, 77, 0.18);
    color: var(--disco-gold);
    border-color: rgba(255, 211, 77, 0.4);
    box-shadow: 0 0 12px rgba(255, 211, 77, 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);
}

/* ---------------- 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 {
    background: var(--disco-rainbow);
    background-size: 200% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

.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: rgba(24, 16, 46, 0.55);
    backdrop-filter: blur(14px);
    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: rgba(5, 2, 16, 0.85);
    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);
}

.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(0, 229, 255, 0.1);
    border-color: rgba(0, 229, 255, 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 sprite as the homepage hero, smaller and quicker — a livelier
       feel while the user waits. */
    --ball-size: clamp(120px, 14vw, 160px);
    animation: disco-ball-spin-frames 18s steps(50) infinite;
    margin: 0;
    filter:
        drop-shadow(0 14px 28px rgba(0, 229, 255, 0.25))
        drop-shadow(0 6px 12px rgba(255, 46, 143, 0.18));
}

/* 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, rgba(34, 22, 56, 0.65), rgba(24, 16, 46, 0.45));
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    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(0, 229, 255, 0)   0%,
        rgba(0, 229, 255, 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;
}

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

/* 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(0, 229, 255, 0.4);
}

.scan-progress__row--running::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(120deg,
        transparent 30%,
        rgba(0, 229, 255, 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(255, 211, 77, 0.3);
    background: linear-gradient(180deg, var(--disco-bg) 0%, rgba(255, 211, 77, 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 {
        animation: none;
    }
}

.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: rgba(14, 14, 42, 0.4);
}

.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: rgba(24, 16, 46, 0.6);
    border: 1px solid var(--disco-border);
    color: var(--disco-cyan);
}

.grade-chip--a {
    background: linear-gradient(135deg, var(--disco-magenta), var(--disco-gold));
    color: #1a0a2e;
    border-color: var(--disco-gold);
    box-shadow: 0 0 18px rgba(255, 211, 77, 0.5);
}
.grade-chip--b {
    background: linear-gradient(135deg, rgba(0, 229, 255, 0.18), rgba(0, 229, 255, 0.04));
    color: var(--disco-cyan);
    border-color: rgba(0, 229, 255, 0.6);
    box-shadow: 0 0 14px rgba(0, 229, 255, 0.2);
}
.grade-chip--c {
    background: linear-gradient(135deg, rgba(245, 166, 35, 0.18), rgba(245, 166, 35, 0.04));
    color: var(--disco-amber);
    border-color: rgba(245, 166, 35, 0.5);
}
.grade-chip--d {
    background: linear-gradient(135deg, rgba(232, 93, 47, 0.18), rgba(232, 93, 47, 0.04));
    color: var(--disco-orange);
    border-color: rgba(232, 93, 47, 0.5);
}
.grade-chip--f {
    background: linear-gradient(135deg, rgba(229, 0, 75, 0.18), rgba(229, 0, 75, 0.04));
    color: var(--disco-red);
    border-color: rgba(229, 0, 75, 0.5);
}

.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(0, 229, 255, 0.15);
    color: var(--disco-cyan);
    border-color: rgba(0, 229, 255, 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;
    /* Match the homepage hero gradient — the status number reads as a
       brand moment instead of a sad muted-grey 404. */
    background: linear-gradient(120deg,
        var(--disco-magenta) 0%,
        var(--disco-gold) 35%,
        var(--disco-cyan) 70%,
        var(--disco-magenta) 100%);
    background-size: 200% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    letter-spacing: 0.04em;
    margin: 0 0 0.5rem;
    line-height: 1;
    animation: groovy-shimmer 8s ease-in-out infinite,
               hero-fade-up 700ms cubic-bezier(.34, 1.56, .64, 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: linear-gradient(135deg, var(--disco-gold), var(--disco-magenta));
    color: var(--disco-bg);
    font-weight: 600;
    border-radius: 0.4rem;
    text-decoration: none;
    transition: transform 160ms cubic-bezier(.2, .8, .2, 1),
                box-shadow 200ms ease,
                filter 120ms ease;
    box-shadow: 0 6px 16px -8px rgba(255, 211, 77, 0.5);
}

.error-page__primary:hover,
.error-page__primary:focus-visible {
    filter: brightness(1.1);
    transform: translateY(-1px);
    box-shadow: 0 12px 24px -10px rgba(255, 211, 77, 0.7);
    outline: none;
}

.error-page__primary::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(120deg,
        transparent 35%,
        rgba(255, 255, 255, 0.4) 50%,
        transparent 65%);
    transform: translateX(-100%);
    transition: transform 600ms cubic-bezier(.2, .8, .2, 1);
    pointer-events: none;
}

.error-page__primary:hover::after {
    transform: translateX(100%);
}

@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(0, 229, 255, 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 rgba(74, 44, 106, 0.4);
    background: rgba(10, 4, 24, 0.65);
    backdrop-filter: saturate(160%) blur(20px);
    -webkit-backdrop-filter: saturate(160%) blur(20px);
}

/* 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-rainbow);
    background-size: 220% 100%;
    opacity: 0.55;
    animation: kicker-shimmer 14s ease-in-out infinite;
}

.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;
    background: var(--disco-rainbow);
    background-size: 220% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: kicker-shimmer 12s ease-in-out infinite;
    /* 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;
}

/* The wider robot+ball lockup eats horizontal budget, so collapse the
   brand text on narrow viewports. The lockup itself carries the brand
   identity once it's small enough to need to. Screen-reader-only fallback
   keeps the brand name accessible even when visually hidden. */
@media (max-width: 640px) {
    .site-header__strapline { display: none; }

    /* Tighten the header gutter so brand + nav share the row more
       comfortably on phones. */
    .site-header__inner {
        gap: 0.85rem;
        padding-top: 0.7rem;
        padding-bottom: 0.7rem;
    }

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

    .site-header__auth {
        gap: 0.4rem;
    }

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

    /* 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-rainbow);
    background-size: 200% 100%;
    color: #1a0a2e;
    font-family: var(--font-sans);
    font-size: 0.85rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    text-decoration: none;
    border-radius: 999px;
    box-shadow:
        0 4px 12px rgba(255, 46, 143, 0.4),
        0 0 0 1px rgba(255, 255, 255, 0.15) inset;
    transition:
        background-position var(--t-medium) ease,
        transform var(--t-quick) var(--ease-snap),
        box-shadow var(--t-medium) ease;
}

.site-header__signup:hover,
.site-header__signup:focus-visible {
    color: #1a0a2e;
    background-position: 100% 50%;
    transform: translateY(-1px);
    box-shadow:
        0 8px 22px rgba(255, 46, 143, 0.6),
        0 0 30px rgba(255, 211, 77, 0.3),
        0 0 0 1px rgba(255, 255, 255, 0.25) inset;
    outline: none;
}

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

/* Narrow viewports: hide strapline (name alone holds the brand),
   collapse nav to wrap, drop logo spin (no hover on touch). */
@media (max-width: 640px) {
    .site-header__inner {
        gap: 0.75rem;
        padding-top: 0.6rem;
        padding-bottom: 0.6rem;
    }

    .site-header__logo {
        width: 34px;
        height: 34px;
    }

    .site-header__strapline {
        display: none;
    }

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

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

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

    .site-header__login {
        display: none; /* keep the CTA, drop the secondary text link */
    }

    .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;
    }

    .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(255, 211, 77, 0.08);
}

.report-compare__row--added,
.report-compare__row--removed,
.report-compare__row--changed {
    background: rgba(0, 229, 255, 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: #fff;
}

.landing__flash {
    margin: 0 0 1.5rem;
    padding: 0.6rem 0.85rem;
    border-radius: 4px;
    background: rgba(0, 229, 255, 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(255, 211, 77, 0.12);
    border: 1px solid rgba(255, 211, 77, 0.4);
}

.account__verify-banner--ok {
    background: rgba(0, 229, 255, 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(255, 211, 77, 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, rgba(34, 22, 56, 0.7), rgba(24, 16, 46, 0.55));
    backdrop-filter: blur(20px) saturate(140%);
    -webkit-backdrop-filter: blur(20px) saturate(140%);
    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-rainbow);
    background-size: 220% 100%;
    animation: kicker-shimmer 12s ease-in-out infinite;
}

.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);
    background: var(--disco-rainbow);
    background-size: 220% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: kicker-shimmer 12s ease-in-out infinite;
}

.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: rgba(14, 14, 42, 0.6);
    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-cyan);
    box-shadow: 0 0 0 3px rgba(0, 229, 255, 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 {
    position: relative;
    overflow: hidden;
    width: 100%;
    background: var(--disco-rainbow);
    background-size: 220% 100%;
    color: #1a0a2e;
    border: 0;
    border-radius: 999px;
    padding: var(--space-4) var(--space-5);
    font-family: var(--font-sans);
    font-size: 0.95rem;
    font-weight: 900;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
    transition:
        background-position var(--t-medium) ease,
        transform var(--t-quick) var(--ease-snap),
        box-shadow var(--t-medium) ease;
    box-shadow:
        0 6px 20px rgba(255, 46, 143, 0.5),
        0 0 0 1px rgba(255, 255, 255, 0.18) inset;
}

.auth-card__submit:hover:not(:disabled),
.auth-card__submit:focus-visible {
    transform: translateY(-1px) scale(1.01);
    background-position: 100% 50%;
    box-shadow:
        0 12px 32px rgba(255, 46, 143, 0.7),
        0 0 60px rgba(255, 211, 77, 0.4),
        0 0 0 1px rgba(255, 255, 255, 0.3) inset;
    outline: none;
}

/* Sheen sweep matches the rest of the primary CTAs across the site —
   scan, contact, auth all share the same hover-feel. */
.auth-card__submit::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(120deg,
        transparent 35%,
        rgba(255, 255, 255, 0.32) 50%,
        transparent 65%);
    transform: translateX(-100%);
    transition: transform 600ms cubic-bezier(.2, .8, .2, 1);
    pointer-events: none;
}

.auth-card__submit:hover::after {
    transform: translateX(100%);
}

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

.auth-card__submit:active:not(:disabled) {
    transform: translateY(1px);
    box-shadow: 0 4px 10px -6px rgba(255, 46, 143, 0.4);
}

.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(0, 229, 255, 0.2);
}

.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(0, 229, 255, 0.1);
    border: 1px solid rgba(0, 229, 255, 0.3);
    color: #d6f7ff;
}
