/* =============================================================================
   ImproveCV pre-launch waitlist page
   =============================================================================
   Self-contained: styles.css is NOT loaded here (the page is served in
   maintenance mode, where the rest of the site is gated), so the dark-theme
   tokens below are copied from the [data-theme="dark"] block in styles.css.
   Keep them in sync if the brand palette moves.

   THE ONE HARD CONSTRAINT: everything fits one screen, no scrollbars. That is
   why every size derives from --u, a single unit clamped against BOTH viewport
   axes. Shrink the window and the whole page scales down together instead of
   overflowing. Below 900px wide (or on a genuinely short window) the layout
   stacks and scrolling is allowed, because clipping content is worse than a
   scrollbar.
   ========================================================================== */

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

:root {
    /* Brand — dark palette */
    --brand: #38bdf8;
    --brand-deep: #0ea5e9;
    --brand-08: rgba(56, 189, 248, 0.08);
    --brand-15: rgba(56, 189, 248, 0.15);
    --brand-30: rgba(56, 189, 248, 0.30);

    /* Solid CTA triple. The canonical button values from DESIGN.md §5: the
       deeper blue reads as a button against a dark surface, where the lighter
       accent (--brand) reads as a highlight. Hover/active are the canonical
       #0284c7 / #0369a1, never the teal --brand-primary-dark. */
    --cta: #0ea5e9;
    --cta-hover: #0284c7;
    --cta-active: #0369a1;

    /* Surfaces — the index page's dark tiers, one for one. body.landing-page
       sits on --bg-offwhite and its cards on --bg-white, so the whole ramp
       shifts up a step from what this page used before. */
    --bg-page: #22242b;   /* --bg-offwhite */
    --bg-card: #282c33;   /* --bg-white    */
    --bg-tile: #32363e;   /* --bg-gray     */
    --bg-tile-hi: #3d434c;/* --bg-slate    */

    /* Text */
    --text: #f7f9fc;
    --text-2: #ccd5e0;
    --text-3: #b3bdc9;
    --text-4: #828c9b;

    /* Lines */
    --line: rgba(255, 255, 255, 0.08);
    --line-strong: rgba(255, 255, 255, 0.14);

    --ease-out: cubic-bezier(0.23, 1, 0.32, 1);

    /* The scale unit. min() ties it to whichever axis is tighter, so a short
       window shrinks type just as a narrow one does.
       The vh coefficient is the page's real height budget: the card's contents
       are all multiples of --u, while the height it has to fit in is a fraction
       of the viewport, so this number decides whether the score card fits. It
       was 1.66 and left nothing spare on a 768px-tall laptop; the four checks
       are set a size larger now, so it comes down to buy that back. Anything
       higher clips the last fix row inside the card on common laptop screens. */
    --u: clamp(11px, min(1.56vh, 1.04vw), 21px);

    --fs-display: clamp(28px, min(4.6vw, 8.6vh), 70px);
    --fs-lead: clamp(13px, min(1.25vw, 2.47vh), 18px);
    --fs-body: calc(var(--u) * 0.95);
    --fs-small: calc(var(--u) * 0.82);
    --fs-micro: calc(var(--u) * 0.72);

    /* Fixed pixel radii, NOT scaled with --u. These are the site's button and
       card radii (DESIGN.md §5 and landing-styles.css); scaling them made the
       same button read as a pill on a big screen and a box on a small one. */
    --radius-btn: 10px;         /* standard button, matches .demo-improve-btn */
    --radius-btn-lg: 14px;      /* primary CTA, matches .hero-primary-cta */
    --radius: 10px;             /* inputs, tiles */
    --radius-lg: 16px;          /* cards, matches --radius-lg site-wide */

    /* One gutter for the top bar, both columns and the footer. */
    --wl-gutter: max(32px, calc((100vw - 1400px) / 2 + 32px));
}

html, body {
    height: 100%;
    /* The promise of the page: one screen, nothing hidden below the fold. */
    overflow: hidden;
}

body {
    background: var(--bg-page);
    color: var(--text);
    font-family: 'Schibsted Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    font-size: var(--fs-body);
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

/* Faint dot grid, the same background texture the real landing page uses. */
body::before {
    content: '';
    position: fixed;
    inset: 0;
    background-image: radial-gradient(circle, rgba(255, 255, 255, 0.02) 1px, transparent 1px);
    background-size: 24px 24px;
    pointer-events: none;
    z-index: 0;
}

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

/* ── Shell ────────────────────────────────────────────────────────────────── */

.wl-shell {
    position: relative;
    z-index: 1;
    height: 100dvh;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr) auto;
    gap: calc(var(--u) * 1.4);
    /* The site's content container: .nav-inner and .hero-grid are both
       `max-width: 1400px; padding: 0 32px`, which is what puts the hero headline
       and the navbar logo on the same edge. Expressed as padding rather than a
       max-width + margin so the full-bleed page background still reaches the
       viewport edges, and so the top bar, the two columns and the footer all
       share one gutter. Below 1464px wide it collapses to a flat 32px, exactly
       as the centred container does. */
    padding: 0 var(--wl-gutter) calc(var(--u) * 1.5);
}

.wl-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--u);
    /* .nav-inner is 72px tall; the separator under it spans the viewport, so the
       bar breaks out of the container gutter and re-applies it inside. */
    height: min(72px, 9.9vh);
    margin: 0 calc(var(--wl-gutter) * -1);
    padding: 0 var(--wl-gutter);
    border-bottom: 1px solid var(--line-strong);
}

.wl-logo img {
    /* --logo-wordmark-h on the index page. */
    height: min(32px, 4.4vh);
    width: auto;
    display: block;
}

.wl-tag {
    font-size: var(--fs-micro);
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--brand);
    background: var(--brand-08);
    border: 1px solid var(--brand-15);
    border-radius: 999px;
    padding: calc(var(--u) * 0.3) calc(var(--u) * 0.85);
}

/* Mirrors .hero-grid: pitch left, panel right, columns near-even with the panel
   a shade wider, both vertically centred against each other. */
.wl-main {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.04fr);
    gap: clamp(28px, 6.1vw, 88px);
    align-items: center;
    min-height: 0;
}

/* ── Left column: pitch ───────────────────────────────────────────────────── */

/* On the hero the headline nearly fills its column, so centring the CTA in the
   column centres it under the text. This headline is shorter, so the block gets
   an explicit cap tied to the headline size to reproduce the same relationship.
   It changes only what the CTA centres against, not where anything renders. */
.wl-pitch {
    min-width: 0;
    align-self: center;
    max-width: calc(var(--fs-display) * 7.6);
    transform: translateY(-30px);
}

/* Weight, tracking and leading copied from .headline on the landing hero. */
.wl-headline {
    font-size: var(--fs-display);
    font-weight: 700;
    line-height: 1.12;
    letter-spacing: -0.03em;
}

.wl-headline-accent { color: var(--brand); }

.wl-sub {
    margin-top: calc(var(--u) * 0.55);
    font-size: var(--fs-lead);
    line-height: 1.6;
    color: var(--text-3);
    max-width: 52ch;
}

.wl-sub strong { color: var(--text); font-weight: 600; }

/* Stacks state variants in one grid cell: the container is as tall as the
   tallest variant, and switching states cannot change its size. Hidden variants
   keep their space but leave the tab order and the accessibility tree. */
.wl-stack {
    display: grid;
}

.wl-stack > * {
    grid-area: 1 / 1;
    visibility: hidden;
}

.wl-stack > .is-shown {
    visibility: visible;
}

/* ── CTA area (replaces the old dropzone box) ─────────────────────────────── */

/* Mirrors .hero-cta-area: headline and sub stay left-aligned, everything from
   the button down is centred in the column. */
.wl-cta {
    margin-top: calc(var(--u) * 1.7);
    text-align: center;
    /* The nudge from .hero-cta-area: pulls the centred CTA back under the text
       block instead of the full column. */
    transform: translateX(calc(var(--u) * -1.5 - 20px));
    display: grid;
    /* TOP-aligned, not centred. Every state leads with the same button in the
       same box, so pinning the tops of the variants pins the button: choosing a
       file relabels it and swaps what sits underneath, and nothing else on the
       page moves. Centring made the shorter variant float down and the button
       jumped the moment a file was picked. */
    align-items: start;
}

.wl-cta-idle,
.wl-cta-ready,
.wl-cta-reading {
    grid-area: 1 / 1;
    align-self: start;
    justify-self: center;
    visibility: hidden;
}

/* A column, matching the idle block: button first, then the file where the two
   hint lines were. A long file name ellipsises inside the chip, so the block's
   height never depends on what the visitor called their CV. */
.wl-cta-ready {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: clamp(10px, 2.47vh, 18px);
    max-width: 100%;
}
.wl-cta[data-state="idle"] .wl-cta-idle,
.wl-cta[data-state="ready"] .wl-cta-ready,
.wl-cta[data-state="busy"] .wl-cta-ready,
.wl-cta[data-state="reading"] .wl-cta-reading { visibility: visible; }

.wl-cta-hint {
    margin-top: clamp(10px, 2.47vh, 18px);
    font-size: clamp(11.5px, min(0.94vw, 1.85vh), 13.5px);
    color: var(--text-4);
}

.wl-cta-micro {
    margin-top: clamp(7px, 1.92vh, 14px);
    font-size: clamp(11px, min(0.9vw, 1.79vh), 13px);
    font-weight: 500;
    color: var(--text-4);
}

/* display:flex is load-bearing, not cosmetic: .wl-spinner-inline resets the
   spinner to position:static, and an inline span ignores width/height, so
   without a flex parent the spinner renders as a zero-size dot. */
.wl-cta-reading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: calc(var(--u) * 0.7);
    font-size: var(--fs-body);
    font-weight: 600;
    color: var(--text-3);
    /* Sits where the button does, so the state change reads as the button being
       replaced rather than the block collapsing. */
    padding-top: clamp(11px, 2.06vh, 15px);
}

/* Chosen file, as a compact centred chip rather than a full-width row. */
.wl-file {
    display: flex;
    align-items: center;
    gap: calc(var(--u) * 0.7);
    flex: 0 1 auto;
    min-width: 0;
    max-width: 100%;
    padding: calc(var(--u) * 0.45) calc(var(--u) * 0.5) calc(var(--u) * 0.45) calc(var(--u) * 0.55);
    background: var(--bg-tile);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    text-align: left;
}

.wl-file-thumb {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: calc(var(--u) * 1.9);
    height: calc(var(--u) * 1.9);
    border-radius: calc(var(--u) * 0.4);
    background: var(--bg-tile-hi);
    color: var(--text-3);
}

.wl-file-thumb svg { width: calc(var(--u) * 1); height: calc(var(--u) * 1); }

.wl-file-body { min-width: 0; flex: 1; }

.wl-file-name {
    display: block;
    font-weight: 600;
    font-size: var(--fs-small);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.wl-file-meta {
    display: block;
    font-size: var(--fs-micro);
    color: var(--text-4);
}

/* Full-page drop target, shown only while a file is over the window. */
.wl-dropzone {
    position: fixed;
    inset: 0;
    z-index: 50;
    display: grid;
    place-items: center;
    background: rgba(27, 29, 35, 0.9);
    color: var(--brand);
    font-size: var(--fs-lead);
    font-weight: 600;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.14s var(--ease-out);
}

body.is-dragging .wl-dropzone { opacity: 1; }

/* ── Buttons ──────────────────────────────────────────────────────────────── */

.wl-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: calc(var(--u) * 0.5);
    font-family: inherit;
    font-size: var(--fs-body);
    font-weight: 600;
    line-height: 1;
    border: none;
    border-radius: var(--radius-btn);
    padding: calc(var(--u) * 0.85) calc(var(--u) * 1.4);
    cursor: pointer;
    white-space: nowrap;
    transition: background-color 0.1s ease, color 0.1s ease,
                transform 0.16s var(--ease-out);
}

/* Proportioned like .hero-primary-cta: inline width, generous horizontal
   padding, the 14px hero radius. Sized to its label, never stretched. */
.wl-btn-hero {
    font-size: clamp(15px, min(1.39vw, 2.75vh), 20px);
    padding: clamp(11px, 2.06vh, 15px) clamp(28px, 3.2vw, 46px);
    border-radius: var(--radius-btn-lg);
    gap: 12px;
    /* EXPLICIT height, not one inferred from content. The idle button's content
       is a 1.05em glyph beside the label; the ready button's is the label alone,
       so "Score my CV" came out 1px shorter than "Upload" AND 24px wider, and the
       pair of those read as the button shrinking the instant a file was picked.
       Height must not depend on what is inside, so it is stated once here and
       both states inherit it. The 1.3em content band also buys back the presence
       the wider label loses: a 214px button needs more than 50px of height to
       carry the same weight as a 190px one. */
    min-height: calc(clamp(11px, 2.06vh, 15px) * 2 + 1.3em);
}

.wl-btn-primary {
    background: var(--cta);
    color: #ffffff;
}

/* Flat in every state. The brand glow is reserved for the landing hero CTA and
   nothing else (DESIGN.md, Shadow Vocabulary). */
.wl-btn-primary:hover:not(:disabled) {
    background: var(--cta-hover);
}

.wl-btn-primary:active:not(:disabled) {
    background: var(--cta-active);
    transform: scale(0.97);
}



.wl-btn-ghost {
    background: transparent;
    color: var(--text-3);
    padding: calc(var(--u) * 0.5) calc(var(--u) * 0.7);
    font-weight: 500;
    border-radius: var(--radius-btn);
}

.wl-btn-ghost:hover { background: var(--brand-08); color: var(--brand); }
.wl-btn-ghost:active { transform: scale(0.97); }

.wl-btn:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
}

.wl-btn:disabled { cursor: default; opacity: 0.75; }

/* Busy: label swaps for a spinner without the button changing width. */
.wl-spinner {
    position: absolute;
    width: calc(var(--u) * 1.05);
    height: calc(var(--u) * 1.05);
    border: 2px solid rgba(255, 255, 255, 0.35);
    border-top-color: #ffffff;
    border-radius: 50%;
    opacity: 0;
    animation: wl-spin 0.7s linear infinite;
}

.wl-btn.is-busy .wl-btn-label { opacity: 0; }
.wl-btn.is-busy .wl-spinner { opacity: 1; }

/* The button spinner is absolutely positioned inside its button; inline uses
   need it back in flow. */
.wl-spinner-inline {
    position: static;
    opacity: 1;
    border-color: var(--brand-30);
    border-top-color: var(--brand);
}

@keyframes wl-spin { to { transform: rotate(360deg); } }

.wl-error {
    margin-top: calc(var(--u) * 0.8);
    font-size: var(--fs-small);
    color: #f87171;
    line-height: 1.45;
    /* Always in the layout, empty or not: a message that appears and disappears
       must not push the column it sits in. */
    min-height: calc(var(--u) * 2.4);
}

/* ── Score card ───────────────────────────────────────────────────────────── */

/* Right column wrapper: eyebrow + card, mirroring .hero-right. */
.wl-panel {
    min-width: 0;
    display: flex;
    flex-direction: column;
    min-height: 0;
    max-height: 100%;
    /* Deliberately NOT stretched to the row. Stretching pinned the card's outer
       edges, but the body then centred its content inside whatever was left over
       and the card carried a band of empty space above the ring and below the
       fixes in every state. Sized to its content the card is tight everywhere;
       the only movement left is the terminal "sent" step, and the min-height on
       the offer box keeps that to a small settle rather than a jump. */
}

/* Matches .hero-demo-eyebrow on the landing page. */
.wl-eyebrow {
    flex: none;
    text-align: center;
    font-size: var(--fs-micro);
    font-weight: 700;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    color: var(--text-4);
    margin-bottom: calc(var(--u) * 0.6);
}

.wl-card {
    /* Fills the column in every state. The panel must never change size as the
       visitor moves through upload -> scoring -> scored -> sent; a box that
       resizes under the cursor at each step is the thing that felt broken. */
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--bg-card);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

/* Window chrome, same device as the hero's demo card. */
.wl-window-bar {
    flex: none;
    display: flex;
    align-items: center;
    gap: 6px;
    padding: calc(var(--u) * 0.55) calc(var(--u) * 1.1);
    background: var(--bg-tile-hi);
    border-bottom: 1px solid var(--line);
}

.wl-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--line-strong);
}

.wl-dot:nth-child(2) { opacity: 0.7; }
.wl-dot:nth-child(3) { opacity: 0.5; }

.wl-card-head {
    flex: none;
    display: flex;
    align-items: center;
    gap: calc(var(--u) * 0.8);
    padding: calc(var(--u) * 0.78) calc(var(--u) * 1.25);
    border-bottom: 1px solid var(--line);
}

.wl-card-mark {
    width: calc(var(--u) * 1.7);
    height: auto;
    display: block;
}

/* One line, no strapline. "Four checks, out of 100" restated what the four rows
   and the ring already say, and cost the card a whole line of height. */
.wl-card-title {
    display: block;
    font-size: calc(var(--u) * 1.15);
    font-weight: 700;
    letter-spacing: -0.01em;
}

.wl-card-body {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    /* `safe` matters: a plain centered flex column that overflows spills off BOTH
       ends, so the ring would be cut off the top with no way to reach it. `safe`
       falls back to start-alignment the moment content stops fitting. */
    justify-content: safe center;
    gap: calc(var(--u) * 0.7);
    padding: calc(var(--u) * 0.8) calc(var(--u) * 1.25);
    /* Last line of defence for the no-scroll promise: if a long fix title or a
       wrapped consent line ever exceeds the budget, it clips inside the card
       rather than pushing the page past the viewport. The stacked layout below
       900px turns scrolling back on, so nothing is permanently unreachable. */
    overflow: hidden;
}

/* ── Ring + headline readout ──────────────────────────────────────────────── */

.wl-readout {
    display: flex;
    align-items: center;
    gap: calc(var(--u) * 1.4);
}

/* The readout keeps the same row shape in every state. Flipping it to a centred
   column while empty meant the ring jumped left and resized the moment a score
   arrived. */

.wl-ring {
    position: relative;
    flex: none;
    width: calc(var(--u) * 6.4);
    height: calc(var(--u) * 6.4);
}

.wl-ring svg {
    width: 100%;
    height: 100%;
    transform: rotate(-90deg);
    overflow: visible;
}

.wl-ring circle {
    fill: none;
    stroke-width: 7;
    stroke-linecap: round;
}

.wl-ring-track { stroke: var(--bg-tile-hi); }

.wl-ring-value {
    stroke: var(--brand);
    /* 2πr for r=52 */
    stroke-dasharray: 326.73;
    stroke-dashoffset: calc(326.73 - 326.73 * var(--pct, 0));
    transition: stroke-dashoffset 1.1s var(--ease-out);
}

/* Scoring: an indeterminate arc chasing the ring, so the wait reads as work. */
.wl-card[data-state="scoring"] .wl-ring-value {
    stroke-dasharray: 70 260;
    stroke-dashoffset: 0;
    transition: none;
    transform-origin: 50% 50%;
    animation: wl-ring-sweep 1.15s linear infinite;
}

@keyframes wl-ring-sweep { to { transform: rotate(360deg); } }

.wl-ring-inner {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    text-align: center;
}

.wl-ring-empty {
    font-size: var(--fs-micro);
    font-weight: 600;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: var(--text-4);
    max-width: 70%;
    line-height: 1.25;
}

.wl-ring-score { display: none; }
.wl-ring-score b {
    display: block;
    font-size: calc(var(--u) * 2.3);
    font-weight: 800;
    line-height: 1;
    letter-spacing: -0.04em;
    font-variant-numeric: tabular-nums;
}
/* Sized to clear the ring's inner diameter, not to a type scale: "OUT OF 100"
   is the widest thing inside the circle, and at the old size it ran under the
   stroke on both sides once the ring came down from 7u. */
.wl-ring-score i {
    display: block;
    font-style: normal;
    font-size: calc(var(--u) * 0.53);
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-4);
    margin-top: calc(var(--u) * 0.15);
}

.wl-card[data-state="scored"] .wl-ring-empty,
.wl-card[data-state="joined"] .wl-ring-empty { display: none; }
.wl-card[data-state="scored"] .wl-ring-score,
.wl-card[data-state="joined"] .wl-ring-score { display: block; }

.wl-card[data-state="scoring"] .wl-ring-empty { color: var(--brand); }

.wl-readout-copy { min-width: 0; }

.wl-readout-empty,
.wl-readout-busy,
.wl-readout-scored { display: none; }

.wl-card[data-state="empty"] .wl-readout-empty { display: block; }
.wl-card[data-state="scoring"] .wl-readout-busy { display: block; }
.wl-card[data-state="scored"] .wl-readout-scored,
.wl-card[data-state="joined"] .wl-readout-scored { display: block; }

.wl-readout-empty,
.wl-readout-busy {
    font-size: var(--fs-small);
    color: var(--text-4);
    line-height: 1.6;
}

.wl-readout-empty strong { color: var(--text-2); font-weight: 700; }

/* Now the only line in the scored readout, so it carries the weight the removed
   "N points short of 90" lead used to. */
.wl-readout-detail {
    display: block;
    font-size: calc(var(--u) * 1.15);
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--text-2);
    line-height: 1.35;
}

/* ── The four checks ──────────────────────────────────────────────────────── */

.wl-dims { list-style: none; }

.wl-dim {
    position: relative;
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    gap: calc(var(--u) * 0.5);
    padding: calc(var(--u) * 0.52) 0;
    border-bottom: 1px solid var(--line);
}

/* The four checks are the substance of the card, so they are set at body size
   rather than the small size the supporting copy uses. The height this costs is
   paid for by the card strapline and the readout lead, both removed. */
.wl-dim-label {
    font-size: var(--fs-body);
    font-weight: 600;
    color: var(--text-2);
}

.wl-dim-value {
    font-size: var(--fs-body);
    font-weight: 700;
    color: var(--text-4);
    font-variant-numeric: tabular-nums;
    transition: color 0.2s ease;
}

.wl-card[data-state="scored"] .wl-dim-value,
.wl-card[data-state="joined"] .wl-dim-value { color: var(--text); }

/* The track is the row's own bottom border, lit from the left. */
.wl-dim-track {
    grid-column: 1 / -1;
    position: absolute;
    left: 0;
    right: 0;
    bottom: -1px;
    height: 2px;
    border-radius: 2px;
    overflow: hidden;
}

.wl-dim-track i {
    display: block;
    height: 100%;
    width: calc(var(--pct, 0) * 100%);
    background: var(--brand);
    border-radius: 2px;
    /* Staggered per row in the markup via --delay, so the four fill in sequence. */
    transition: width 0.9s var(--ease-out) var(--delay, 0s);
}

/* Empty: the bar breathes so the row does not read as broken. */
.wl-card[data-state="empty"] .wl-dim-track,
.wl-card[data-state="scoring"] .wl-dim-track {
    background: var(--bg-tile-hi);
}

.wl-card[data-state="scoring"] .wl-dim-track {
    animation: wl-dim-pulse 1.4s var(--ease-out) infinite;
    animation-delay: var(--delay, 0s);
}

@keyframes wl-dim-pulse {
    0%, 100% { background: var(--bg-tile-hi); }
    50%      { background: var(--brand-30); }
}

/* ── Teaser fixes ─────────────────────────────────────────────────────────── */

.wl-fixes { display: flex; flex-direction: column; gap: calc(var(--u) * 0.45); }

/* Placeholder rows hold the slot open before a score exists, so the card is the
   same height throughout. They name what will appear, never a fake finding. */
.wl-fixes .wl-stack > .wl-fix-list { visibility: hidden; }
.wl-card[data-state="empty"] .wl-fix-placeholder,
.wl-card[data-state="scoring"] .wl-fix-placeholder,
.wl-card[data-state="scored"] #wlFixList,
.wl-card[data-state="joined"] #wlFixList { visibility: visible; }

.wl-fix.is-placeholder {
    background: transparent;
    border-style: dashed;
}

.wl-fix.is-placeholder .wl-fix-dot { background: var(--line-strong); }
.wl-fix.is-placeholder .wl-fix-title { color: var(--text-4); font-weight: 500; }

/* The "N more fixes" line only exists once there is a score, but its space is
   reserved either way so the offer box below it never moves. */
/* Derived from the line it reserves, not guessed: font-size x the body's 1.5
   line-height IS the rendered line box, so the slot is exactly the right height
   whether the line is there or not. A round number in --u was 4px short, and
   that 4px was the whole card growing the moment a real score replaced the
   placeholders. */
.wl-fix-more { min-height: calc(var(--fs-small) * 1.5); }

.wl-fix-list { list-style: none; display: flex; flex-direction: column; gap: calc(var(--u) * 0.45); }


.wl-fix {
    display: flex;
    align-items: center;
    gap: calc(var(--u) * 0.6);
    background: var(--bg-tile);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: calc(var(--u) * 0.52) calc(var(--u) * 0.8);
}

.wl-fix-dot {
    flex: none;
    width: calc(var(--u) * 0.42);
    height: calc(var(--u) * 0.42);
    border-radius: 50%;
    background: #f87171;
}

.wl-fix-title {
    flex: 1;
    min-width: 0;
    font-size: var(--fs-body);
    font-weight: 600;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.wl-fix-section {
    flex: none;
    font-size: var(--fs-small);
    color: var(--text-4);
}

.wl-fix-more {
    font-size: var(--fs-small);
    color: var(--text-4);
}

/* ── Offer / email capture ────────────────────────────────────────────────── */

.wl-offer {
    flex: none;
    margin: 0 calc(var(--u) * 1.25) calc(var(--u) * 0.85);
    background: var(--bg-tile);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: calc(var(--u) * 0.8);
    /* Stacked: the box is always as tall as the email form, its tallest state,
       so it neither grows when the score arrives nor shrinks when the report is
       sent. */
    display: grid;
    align-items: center;
}

/* stretch, NOT center. The box is held to the email form's height, so the two
   shorter states were a block of content floating in a tall box with dead space
   above and below it (108px of it in "joined" at 1512x924, out of 153px). They
   fill the cell now and lay their own content out inside it. This changes only
   what happens INSIDE the reserved height; the box is still sized by the form,
   so the card's height is untouched. */
.wl-offer-empty,
.wl-offer-form,
.wl-offer-done {
    grid-area: 1 / 1;
    align-self: stretch;
    visibility: hidden;
}

/* Title and copy stay together at the top; only the note drops to the bottom
   (margin-top: auto), so the slack lands in one deliberate gap instead of being
   split evenly above and below the whole block.

   Nudged off the top edge and set LARGER than the shared defaults below. This
   state carries the entire offer and has room the form does not, so it was the
   one variant reading as a small block of text with a hole under it. Type does
   the filling; the padding just stops it sitting on the ceiling.

   CEILING: like .wl-offer-done, this must stay shorter than .wl-offer-form or it
   becomes the grid's tallest child and the card grows. Measured at 1512/1440/
   1366/1280 after every size change here. */
.wl-offer-empty {
    display: flex;
    flex-direction: column;
    padding-top: calc(var(--u) * 0.45);
}

.wl-offer-empty .wl-offer-title { font-size: calc(var(--u) * 1.12); }

.wl-offer-empty .wl-offer-copy {
    font-size: calc(var(--u) * 0.9);
    margin-top: calc(var(--u) * 0.34);
}

.wl-card[data-state="empty"] .wl-offer-empty,
.wl-card[data-state="scoring"] .wl-offer-empty,
.wl-card[data-state="scored"] .wl-offer-form,
.wl-card[data-state="joined"] .wl-offer-done { visibility: visible; }

/* All four states are held to the email form's height, "sent" included. Letting
   the confirmation size itself shrank the box by a third, and because the card
   is sized by its content that shrank the CARD: its top and bottom edges both
   moved the moment the report was sent. A panel that resizes at the end of the
   journey is worse than a panel with generous padding in one state, so the box
   keeps its height and the confirmation is centred in it instead. */

.wl-offer-title {
    font-size: var(--fs-body);
    font-weight: 700;
    letter-spacing: -0.01em;
}

.wl-offer-copy {
    margin-top: calc(var(--u) * 0.25);
    font-size: var(--fs-small);
    color: var(--text-4);
    line-height: 1.5;
}

.wl-offer-copy strong { color: var(--text-2); font-weight: 700; }

.wl-offer-note {
    /* auto, so it sits on the floor of the box. padding-top keeps a real gap if
       a viewport ever leaves no slack to absorb. */
    margin-top: auto;
    padding-top: calc(var(--u) * 0.7);
    font-size: var(--fs-micro);
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--brand);
}

.wl-field {
    display: flex;
    gap: calc(var(--u) * 0.6);
    margin-top: calc(var(--u) * 0.7);
}

.wl-field input {
    flex: 1;
    min-width: 0;
    font-family: inherit;
    font-size: var(--fs-body);
    color: var(--text);
    background: var(--bg-page);
    border: 1.5px solid var(--line-strong);
    border-radius: var(--radius);
    padding: calc(var(--u) * 0.8) calc(var(--u) * 0.95);
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

.wl-field input::placeholder { color: var(--text-4); }

.wl-field input:focus {
    outline: none;
    border-color: var(--brand);
    box-shadow: 0 0 0 3px var(--brand-15);
}

.wl-field input[aria-invalid="true"] { border-color: #f87171; }

.wl-consent {
    margin-top: calc(var(--u) * 0.5);
    font-size: var(--fs-micro);
    line-height: 1.5;
    color: var(--text-4);
}

.wl-consent a { color: var(--text-3); text-decoration: underline; text-underline-offset: 2px; }
.wl-consent a:hover { color: var(--brand); }

.wl-offer-form .wl-error { margin-top: calc(var(--u) * 0.35); min-height: calc(var(--u) * 0.95); }

/* A centred column, not a row. As a row this was a 44px strip sitting in a 153px
   box: two thirds of the panel was empty at the one moment the visitor is being
   told the thing worked. Stacked and centred, with a tick sized to be seen, it
   fills the height it was already reserving.
   HARD CEILING: this state must stay SHORTER than .wl-offer-form. The box is a
   grid sized by its tallest child, so the moment "joined" overtakes the form it
   starts driving the height, the box grows, and because the card is sized by its
   content both card edges move when the report sends. Re-measure all four states
   after any size change here. */
.wl-offer-done {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: calc(var(--u) * 0.5);
    padding: calc(var(--u) * 0.15) 0;
}

.wl-offer-done-icon {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: calc(var(--u) * 2.3);
    height: calc(var(--u) * 2.3);
    border-radius: 50%;
    background: rgba(52, 211, 153, 0.15);
    color: #34d399;
}

.wl-offer-done-icon svg { width: calc(var(--u) * 1.15); height: calc(var(--u) * 1.15); }

/* Held in from the box edges so the second line breaks somewhere sensible rather
   than leaving one orphaned word against the border. */
.wl-offer-done-body { min-width: 0; max-width: 42ch; }

.wl-offer-done-body strong {
    display: block;
    font-size: var(--fs-body);
    font-weight: 700;
}

.wl-offer-done-body span {
    display: block;
    margin-top: calc(var(--u) * 0.15);
    font-size: var(--fs-small);
    color: var(--text-4);
    line-height: 1.45;
}

/* ── Footer ───────────────────────────────────────────────────────────────── */

.wl-foot {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: calc(var(--u) * 1.2);
    /* Breaks out of the gutter like .wl-top, so both rules run edge to edge
       instead of one spanning the viewport and the other the container. */
    margin: 0 calc(var(--wl-gutter) * -1);
    padding: calc(var(--u) * 1.1) var(--wl-gutter) 0;
    border-top: 1px solid var(--line-strong);
}

.wl-foot-label {
    font-size: var(--fs-micro);
    font-weight: 600;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: var(--text-4);
    line-height: 1.4;
}

.wl-foot-list {
    list-style: none;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: calc(var(--u) * 0.7);
}

.wl-foot-list li {
    background: var(--bg-card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: calc(var(--u) * 0.7) calc(var(--u) * 0.9);
    min-width: 0;
}

.wl-foot-list strong {
    display: block;
    font-size: var(--fs-small);
    font-weight: 700;
}

.wl-foot-list span {
    display: block;
    margin-top: calc(var(--u) * 0.1);
    font-size: var(--fs-micro);
    color: var(--text-4);
    line-height: 1.4;
}

.wl-foot-legal {
    font-size: var(--fs-micro);
    color: var(--text-4);
    white-space: nowrap;
}

.wl-foot-legal a { color: var(--text-3); text-decoration: none; }
.wl-foot-legal a:hover { color: var(--brand); text-decoration: underline; }

/* ── Narrow / short viewports ─────────────────────────────────────────────────
   The one-screen promise holds on a normal desktop window. Once the layout has
   to stack (or the window is genuinely short), scrolling is the honest answer:
   a clipped score card is worse than a scrollbar. */

@media (max-width: 900px), (max-height: 640px) {
    html, body { overflow: auto; }

    :root {
        --u: clamp(13px, 1.05vw + 0.6vh, 17px);
        /* ONE number for the shell padding, the top bar and the footer. The bar
           and the footer break out by exactly --wl-gutter and re-pad by it, so
           when the shell switched to its own padding here and the gutter stayed
           at the desktop 32px, both rules hung 11px off each edge of the screen
           and the page scrolled sideways. */
        --wl-gutter: calc(var(--u) * 1.4);
    }

    .wl-shell {
        height: auto;
        min-height: 100dvh;
        grid-template-rows: auto auto auto;
        padding: var(--wl-gutter) var(--wl-gutter) calc(var(--u) * 2);
    }

    .wl-main {
        grid-template-columns: minmax(0, 1fr);
        gap: calc(var(--u) * 2);
        align-items: start;
    }

    .wl-card { height: auto; max-height: none; }
    .wl-card-body { overflow: visible; }
    /* The nudge that pulls the CTA back under the headline is measured against
       the two-column hero geometry. Stacked, there is no second column to pull
       away from, and the offset just walks the button off the left edge (45px
       of it at 768px). */
    .wl-cta { min-height: 0; margin-top: calc(var(--u) * 1.4); transform: none; }
    .wl-error { max-width: none; }
    .wl-headline { font-size: calc(var(--u) * 2.9); }
    .wl-sub { max-width: none; }
    /* Same reason: the cap is 7.6 x the display size, which is a column width
       for a page that no longer has columns. */
    .wl-pitch { max-width: none; transform: none; }

    .wl-foot {
        grid-template-columns: minmax(0, 1fr);
        gap: calc(var(--u) * 0.8);
    }

    .wl-foot-list { grid-template-columns: minmax(0, 1fr); }
    .wl-foot-legal { white-space: normal; }
}

/* ── Phones ───────────────────────────────────────────────────────────────────
   The stacked layout above was written for a narrow WINDOW, and a phone is not
   a narrow window. Three things broke here and nowhere else:

     1. Every size on the page derives from --u, and --u derives from the
        viewport. On a 390px screen it bottomed out at its 13px floor, which put
        body copy at 12.4px and the micro lines at 9.4px. Type that small is not
        a smaller version of the design, it is an unreadable one, so the unit is
        pinned to a fixed value here and the page stops scaling with the screen.

     2. Two desktop nudges are measured in a column that no longer exists: the
        pitch is capped at 7.6 x the display size (213px on a phone, which broke
        the headline over four lines) and the CTA is pulled left to sit under
        that column (which dragged the button and both hint lines off the left
        edge of the screen). Both are undone rather than re-tuned.

     3. Everything you tap was sized for a cursor. The touch targets are set to
        44px here, and the email field to 16px, below which iOS zooms the whole
        page on focus and never zooms back. */

/* The second query is a phone on its side: 844x390 is not a small desktop
   window, it wants the same treatment as 390x844. */
@media (max-width: 640px), (max-width: 926px) and (max-height: 480px) {
    :root {
        --u: 16px;
        --fs-display: clamp(30px, 9.4vw, 42px);
        --fs-lead: 15px;
        --wl-gutter: 20px;
    }

    /* Let the document be the scroller. html/body at height:100% with their own
       overflow made the BODY the scroll container, which on iOS costs you the
       collapsing URL bar and the overscroll behaviour every other page has. */
    html, body { height: auto; overflow: visible; }

    .wl-shell {
        min-height: 0;
        gap: calc(var(--u) * 1.4);
        padding: 0 var(--wl-gutter) calc(var(--u) * 1.75);
    }

    .wl-top { height: 60px; }
    .wl-logo img { height: 26px; }
    /* One line or it doubles the height of the bar. It fits at 375px; the
       tracking comes in a little to make sure. */
    .wl-tag {
        font-size: 10px;
        letter-spacing: 0.06em;
        padding: 5px 10px;
        white-space: nowrap;
    }

    /* The pitch and the card are two blocks, not one column of text: with the
       reserved space above gone, this gap is the only thing left saying so. */
    .wl-main { gap: calc(var(--u) * 1.75); }

    .wl-headline { font-size: var(--fs-display); line-height: 1.06; }
    .wl-sub { margin-top: calc(var(--u) * 0.7); }

    /* Left, not centred: at this width the column IS the screen, so a centred
       block under a left-aligned headline reads as a mistake. (The cap and the
       nudge that caused the real damage are undone in the stacked block above.) */
    .wl-cta {
        text-align: left;
        margin-top: calc(var(--u) * 1.5);
    }

    /* Desktop stacks the three CTA states in one grid cell so the block is as
       tall as the tallest of them and nothing below it can move. On a phone that
       reservation is 25px of empty space under the idle state, sitting in the
       one screen everybody sees, to save a shift that happens while the visitor
       is away in the system file picker. The block follows its visible state
       here and the card comes up to meet it. */
    .wl-cta { display: block; }

    /* justify-self is a leftover from the grid stack, and it is NOT inert once
       the parent stops being a grid: a block-level box with justify-self:center
       is sized to its content and centred, which shrank the ready state to the
       width of the file chip while the idle state stayed full width. */
    .wl-cta-idle,
    .wl-cta-ready,
    .wl-cta-reading { display: none; justify-self: stretch; }

    .wl-cta[data-state="idle"] .wl-cta-idle { display: block; }
    .wl-cta[data-state="ready"] .wl-cta-ready,
    .wl-cta[data-state="busy"] .wl-cta-ready { display: flex; }
    .wl-cta[data-state="reading"] .wl-cta-reading { display: flex; }

    .wl-cta-ready { align-items: stretch; gap: 12px; }
    .wl-cta-reading { justify-content: flex-start; padding-top: 14px; }

    /* The one thing the page asks for, sized like it. Full width also settles
       the alignment question: there is nothing left to centre against. */
    .wl-btn-hero {
        width: 100%;
        font-size: 17px;
        min-height: 54px;
        padding: 0 20px;
    }

    /* "or drop it anywhere on this page" is desktop instruction. There is
       nothing to drag on a phone, so the line is not just filler, it is wrong. */
    .wl-cta-hint { display: none; }
    .wl-cta-micro { margin-top: 14px; font-size: 13px; }

    .wl-file { width: 100%; padding: 10px; }
    .wl-file-name { font-size: 14px; }
    .wl-file-meta { font-size: 12px; }

    /* 44px, the floor for anything a thumb has to hit. */
    .wl-file-change { min-height: 44px; padding: 0 14px; font-size: 14px; }

    /* Same trade, and the bigger half of it: the empty slot held 26px open
       between the button and the card at all times so a message could appear
       without moving anything. The page scrolls here, so the message pushes
       instead, and the space goes back to the layout. */
    .wl-error {
        font-size: 13.5px;
        min-height: 0;
        margin-top: 8px;
    }

    .wl-error:empty { display: none; }

    /* ── Card ── */

    .wl-eyebrow { font-size: 11px; margin-bottom: 10px; }
    .wl-card-title { font-size: 17px; }
    .wl-readout { gap: calc(var(--u) * 1.1); }
    .wl-dim-label, .wl-dim-value { font-size: 15px; }
    .wl-readout-detail { font-size: 17px; }

    /* A fix title is a sentence, and on a phone it does not fit on one line.
       Ellipsising it hid the finding; wrapping it, with the section dropping
       underneath, shows the whole thing. */
    .wl-fix { flex-wrap: wrap; align-items: flex-start; row-gap: 2px; }
    .wl-fix-dot { margin-top: 7px; }
    .wl-fix-title { flex: 1 1 auto; white-space: normal; font-size: 14.5px; line-height: 1.35; }
    .wl-fix-section { flex: 1 0 100%; padding-left: calc(var(--u) * 1.02); font-size: 12.5px; }
    .wl-fix-more { font-size: 13px; }

    .wl-offer { margin: 0 calc(var(--u) * 0.9) calc(var(--u) * 0.9); padding: 14px; }
    .wl-offer-title, .wl-offer-empty .wl-offer-title { font-size: 16px; }
    .wl-offer-copy, .wl-offer-empty .wl-offer-copy { font-size: 13.5px; }

    /* Desktop holds all three offer states in one grid cell so the card cannot
       change height as the visitor moves through it. That guarantee costs a
       fixed box as tall as the email form, and on a phone the pre-upload state
       paid for it with 90px of empty box in the first screen anyone scrolls
       past. The card is content-sized and the page scrolls here, so the box
       follows whichever state is showing instead. */
    .wl-offer { display: block; }
    .wl-offer-empty,
    .wl-offer-form,
    .wl-offer-done { display: none; }
    .wl-card[data-state="empty"] .wl-offer-empty,
    .wl-card[data-state="scoring"] .wl-offer-empty { display: flex; }
    .wl-card[data-state="scored"] .wl-offer-form { display: block; }
    .wl-card[data-state="joined"] .wl-offer-done { display: flex; }

    /* The note was pinned to the floor of the reserved box. There is no reserved
       box now, so it sits under the copy it belongs to. */
    .wl-offer-empty { padding-top: 0; }
    .wl-offer-note { margin-top: 0; padding-top: 12px; }

    /* 16px is not a style choice: any smaller and iOS zooms into the field on
       focus and leaves the visitor pinched in on a half-visible page. */
    .wl-field { flex-direction: column; gap: 10px; margin-top: 12px; }
    .wl-field input {
        font-size: 16px;
        min-height: 48px;
        padding: 0 14px;
    }
    .wl-field .wl-btn { width: 100%; min-height: 48px; font-size: 16px; }

    .wl-consent { font-size: 12px; margin-top: 10px; }
    /* The privacy link sits inside a paragraph, so it cannot be 44px tall
       without breaking the line box. Padding widens the hit area as far as the
       text flow allows. */
    .wl-consent a { padding: 2px 0; }

    .wl-readout { flex-direction: column; text-align: center; }
    .wl-offer-done-body span { font-size: 13px; }

    /* ── Footer ── */

    .wl-foot { padding-top: calc(var(--u) * 1.2); gap: calc(var(--u) * 0.9); }
    .wl-foot-label { font-size: 11px; }
    /* The break is there to hold the label to two lines in a narrow desktop
       column. Stacked, it has the whole width and the break just orphans two
       words. */
    .wl-foot-label br { display: none; }
    .wl-foot-list { gap: 10px; }
    .wl-foot-list li { padding: 12px 14px; }
    .wl-foot-list strong { font-size: 14px; }
    .wl-foot-list span { font-size: 12.5px; }
    .wl-foot-legal { font-size: 12px; line-height: 2; }
    .wl-foot-legal a { padding: 6px 0; }
}

/* Small phones (360px Androids, the old SE). The top bar is the only row on the
   page with two things that both refuse to shrink: the wordmark and the tag.
   Holding the tag to one line is right at 375px and wrong here, where the pair
   of them force the whole document 19px wider than the screen and the page
   scrolls sideways. The tag goes back to wrapping and the wordmark comes down a
   size, which is enough for both to fit at 320px. */
@media (max-width: 380px) {
    .wl-logo img { height: 22px; }

    .wl-tag {
        white-space: normal;
        text-align: center;
        font-size: 9.5px;
        letter-spacing: 0.05em;
        padding: 4px 8px;
        line-height: 1.3;
    }
}


@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
    }
}

/* ── Upload button glyph ──────────────────────────────────────────────────── */

.wl-btn-upload { gap: 10px; }

/* Tracks the button label so the glyph scales with it. */
.wl-upload-glyph {
    width: 1.05em;
    height: 1.05em;
    overflow: visible;
}

/* Only the arrow moves; the tray stays put, so it reads as lifting out of it. */
.wl-upload-arrow {
    transition: transform 0.16s var(--ease-out);
}

.wl-btn-upload:hover:not(:disabled) .wl-upload-arrow {
    transform: translateY(-2.5px);
}

.wl-btn-upload:active:not(:disabled) .wl-upload-arrow {
    transform: translateY(0);
}

/* The action never shrinks; the chip absorbs the width instead. */
.wl-cta-ready .wl-btn-hero { flex: none; }
