@charset "UTF-8";
/* ############################################################################
   QLYNIC AUTH  ·  qlynic-auth.css
   THE THRESHOLD DESIGN SYSTEM   ("Qlynic Auth UI")
   ----------------------------------------------------------------------------
   THEME ........ THRESHOLD v1
                  The third Qlynic system, and the smallest. MERIDIAN sells the
                  product from the outside. NOCTURNE CORE runs it from the
                  inside. THRESHOLD is the doorway between them — the only
                  screen a person sees while they are neither visitor nor
                  operator. It is drawn as the facility seen from the doorway:
                  a lit volume, and one instrument panel hanging in it.

   FOR .......... /Account/* only — Login, Register, Forgot, ResetPassword,
                  Accept_Invite, Setup. Pages with NO master page, that must
                  stand alone and load fast.

   NOT FOR ...... marketing pages (qlynic-marketing.css) or the dashboard
                  (qlynic-dashboard.css). Never load two systems on one page.

   PREFIX ....... qa-  classes,  --qa-  tokens. Nothing unprefixed.

   LOAD ORDER ... qlynic-auth.css  ->  <page>.css
                  This file is the vocabulary and the shell. The page file
                  writes only what is true of that one page. Never the reverse.

   FONTS ........ Requires this Google Fonts request in the page <head>:
                  family=Geist:wght@400..700
                  family=Geist+Mono:wght@400..600
                  Deliberately NOT @import-ed here: an @import inside a
                  stylesheet serialises the font fetch behind the CSS fetch,
                  and this is the one page in the product where first paint is
                  the whole first impression. Degrades to SF Pro / Segoe UI
                  Variable / Inter with the same grotesque skeleton.

   ============================================================================
   WHY IT LOOKS LIKE THIS
   ============================================================================

   THE PAGE IS A VOLUME, NOT A BACKDROP.
     Every other login in this category is a card on wallpaper. This one has
     a floor and a ceiling: two lattices in true CSS perspective, above and
     below, receding into the dark. The card is not ON the page — it hangs
     IN the room, and it moves against the room when the pointer moves. That
     depth is the whole reason the screen reads as a facility instead of a
     form.

   THE LATTICE IS THE PRODUCT.
     The floor grid is not decoration: lanes are providers, ticks are hours,
     and it drifts because the day does. Sparks land on it the way slots fill.
     Qlynic sells time, so time is the material the room is built from.

   THE FORM IS AN INSTRUMENT, NOT A FORM.
     A field here is a CHANNEL STRIP, borrowed from the desks that run
     operating rooms and broadcast galleries: a micro-caps mono channel name,
     a lane rail that lights when the channel is live, the value set LARGE
     because the value is what matters, and a state lamp that arms when the
     channel is satisfied. Chrome shrinks, content grows — the exact inverse
     of the boxed-input convention.

   THE PANEL ANSWERS BACK.
     The header readout renames itself to whichever channel is live. Lamps
     arm. Caps Lock raises a real warning. The footer states the guarantees
     the SERVER actually enforces — lockout, rate limiting, audit — and never
     a guarantee it does not (this system will not print "end-to-end
     encrypted" over a TLS session).

   ============================================================================
   THE FOUR LAWS
   ============================================================================

   LAW 1 — DARK IS THE ROOM. LIGHT IS THE INSTRUMENT.
     Ground is deep blue-black, never #000. Every surface is defined by a 1px
     low-alpha edge and a top-edge highlight, not by shadow. Shadow below the
     console is ambient darkness, nothing more.

   LAW 2 — ONE GLOW, AND IT BELONGS TO THE LIVE CHANNEL.
     The focal glow is spent on whichever field has focus. The room's own
     light (aurora, horizon) is kept below the threshold where it reads as an
     object. If two things glow, the page has failed.

   LAW 3 — NEUTRAL SUBMIT. SPECTRUM MEANS STATE.
     Inherited unchanged from NOCTURNE CORE: the primary action is chrome on
     dark. Cyan marks what is LIVE — focus, armed channels, the pulse node.
     Azure->violet is the filament: brand structure, never a button fill.
     A coloured submit button does not exist in Qlynic.

   LAW 4 — SANS ASSERTS, MONO MEASURES.
     Inherited unchanged from MERIDIAN. Geist carries what the page SAYS.
     Geist Mono carries what is MEASURED — channel names, clock, status,
     keyboard hints, counts. If the reader sees mono, it is a fact.

   ============================================================================
   THE SIGNATURES  (two, and only two)
   ============================================================================
     1. THE CHANNEL STRIP — lane rail, mono channel name, large value, state
        lamp, and an underline that becomes the filament while live.
     2. THE THRESHOLD LINE — one vertical filament hairline dividing the
        narrative half from the instrument half, with a node that travels
        down it. It is the concept of this system rendered as one object:
        the signal crossing from outside to inside.
   If a third effect appears, something has gone wrong.

   ============================================================================
   CONTRAST — measured on the rendered page (#0C121B console), not asserted
   ============================================================================
     --qa-ink       #EAF0F8   15.8:1      --qa-cyan     #22D3EE   10.1:1
     --qa-ink-2     #AAB7CC   10.0:1      --qa-good     #34D399   10.0:1
     --qa-ink-3     #808EA6    5.7:1      --qa-warn     #FBBF24   11.2:1
     --qa-faint     #737F96    4.7:1      --qa-bad      #FF7089    6.5:1
     submit ink     #070B10 on the chrome fill          18.4:1
     kbd hint       rgba(7,11,16,.60) on the same        5.1:1

   NOTHING IN THIS SYSTEM SITS BELOW AA. NOCTURNE CORE keeps a sub-4.5 faint
   tone and a LAW that says "meta only" to police it; that rule is one
   careless label away from a real failure, and THRESHOLD found exactly that
   failure in its own first build (channel labels and the support line were
   3.5:1). The floor is raised instead of policed, so the mistake cannot be
   made here again.
   ############################################################################ */


/* ============================================================================
   01 · TOKENS
   ============================================================================ */
:root {
    color-scheme: dark;

    /* ── TYPE ─────────────────────────────────────────────────────────────── */
    --qa-font: "Geist", -apple-system, "SF Pro Text", "Segoe UI Variable Text", "Segoe UI", "Inter", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif;
    --qa-mono: "Geist Mono", "JetBrains Mono", ui-monospace, "SF Mono", "Cascadia Code", Consolas, monospace;

    /* Display sizes are fluid; the instrument's own sizes are FIXED.
       Equipment does not reflow its readouts — only prose does. */
    --qa-fs-display: clamp(2.125rem, 1.35rem + 2.55vw, 3.375rem); /* 34 -> 54 */
    --qa-fs-lead: clamp(1rem, 0.94rem + 0.24vw, 1.125rem); /* 16 -> 18 */
    --qa-fs-value: 1.1875rem; /* 19 — the field value. LARGE on purpose. */
    --qa-fs-value-m: 1.0625rem; /* 17 — mobile; never below 16 or iOS zooms. */
    --qa-fs-body: 0.9375rem; /* 15 */
    --qa-fs-sm: 0.8125rem; /* 13 */
    --qa-fs-chip: 0.6875rem; /* 11 */
    --qa-fs-channel: 0.625rem; /* 10 — the mono channel name */

    --qa-track-display: -0.038em;
    --qa-track-channel: 0.2em;
    --qa-track-chip: 0.13em;

    /* ── ROOM ─────────────────────────────────────────────────────────────── */
    --qa-void: #04060B; /* the room's ground              */
    --qa-deep: #070A11; /* behind the console            */
    --qa-console: #0C121B; /* the instrument face           */
    --qa-console-2: #111925; /* raised inside the console     */
    --qa-well: rgba(148, 180, 214, .035); /* a channel at rest  */
    --qa-well-live: rgba(148, 180, 214, .062); /* a channel while live */

    /* ── EDGES — depth is light (LAW 1) ───────────────────────────────────── */
    --qa-hair: rgba(148, 180, 214, .10);
    --qa-hair-2: rgba(148, 180, 214, .17);
    --qa-hair-3: rgba(148, 180, 214, .30);
    --qa-edge-light: rgba(214, 230, 255, .10);
    --qa-grid: rgba(140, 175, 220, .115); /* the lattice line   */
    --qa-grid-2: rgba(140, 175, 220, .065); /* the ceiling lattice */

    /* ── INK ──────────────────────────────────────────────────────────────── */
    --qa-ink: #EAF0F8; /* 15.8:1 on the console */
    --qa-ink-2: #AAB7CC; /* 10.0:1 */
    --qa-ink-3: #808EA6; /*  5.7:1 */
    /* THE FLOOR. Raised from #5F6C82, which measured 3.5:1 — the value this
       system's own LAW calls "meta only" was carrying channel labels and the
       support line. Nothing in THRESHOLD is allowed below AA any more, so
       the faint tone itself clears it and the question stops arising. */
    --qa-faint: #737F96; /*  4.7:1 */

    /* ── SPECTRUM — identical values to MERIDIAN and NOCTURNE CORE ────────── */
    --qa-cyan: #22D3EE; /* LIVE THINGS ONLY (LAW 3)      */
    --qa-cyan-ink: #67E3F5;
    --qa-azure: #4D8DFF;
    --qa-violet: #8A7CFF;
    --qa-filament: linear-gradient(90deg, #22D3EE 0%, #4D8DFF 52%, #8A7CFF 100%);
    --qa-filament-v: linear-gradient(180deg, #22D3EE 0%, #4D8DFF 52%, #8A7CFF 100%);
    --qa-cyan-soft: rgba(34, 211, 238, .12);
    --qa-cyan-line: rgba(34, 211, 238, .34);

    /* ── STATE ────────────────────────────────────────────────────────────── */
    --qa-good: #34D399;
    --qa-good-soft: rgba(52, 211, 153, .11);
    --qa-good-line: rgba(52, 211, 153, .30);
    --qa-warn: #FBBF24;
    --qa-warn-soft: rgba(251, 191, 36, .11);
    --qa-warn-line: rgba(251, 191, 36, .30);
    --qa-bad: #FF7089;
    --qa-bad-soft: rgba(255, 112, 137, .11);
    --qa-bad-line: rgba(255, 112, 137, .32);

    /* ── SUBMIT — chrome on dark (LAW 3) ──────────────────────────────────── */
    --qa-btn: linear-gradient(180deg, #FFFFFF 0%, #E4EBF4 100%);
    --qa-btn-hover: linear-gradient(180deg, #FFFFFF 0%, #F1F5FA 100%);
    --qa-btn-press: linear-gradient(180deg, #E9EFF6 0%, #D3DDEA 100%);
    --qa-on-btn: #070B10; /* 17.6:1 */

    /* ── ELEVATION ────────────────────────────────────────────────────────── */
    --qa-lift: 0 1px 0 var(--qa-edge-light) inset, 0 44px 100px -34px rgba(0, 0, 0, .96), 0 10px 34px -14px rgba(0, 0, 0, .74);
    --qa-lift-sm: 0 1px 0 var(--qa-edge-light) inset, 0 12px 30px -14px rgba(0, 0, 0, .7);
    --qa-ring: 0 0 0 2px var(--qa-console), 0 0 0 4px var(--qa-cyan);

    /* ── GEOMETRY ─────────────────────────────────────────────────────────── */
    --qa-r-ctl: 10px;
    --qa-r-ch: 12px;
    --qa-r-panel: 20px;
    --qa-r-pill: 999px;
    --qa-h-ctl: 50px; /* the submit. Substantial, not a slab. */
    --qa-h-lamp: 6px;
    --qa-gutter: clamp(1.25rem, 4vw, 2.75rem);
    --qa-console-w: 27.5rem; /* 440 */
    --qa-deck-w: 72rem; /* 1152 */

    /* ── SPACE ────────────────────────────────────────────────────────────── */
    --qa-sp-1: 4px;
    --qa-sp-2: 8px;
    --qa-sp-3: 12px;
    --qa-sp-4: 16px;
    --qa-sp-5: 20px;
    --qa-sp-6: 24px;
    --qa-sp-7: 32px;
    --qa-sp-8: 40px;
    --qa-sp-9: 56px;

    /* ── MOTION ───────────────────────────────────────────────────────────── */
    --qa-in: cubic-bezier(.16, 1, .30, 1); /* arrivals — expo out        */
    --qa-ease: cubic-bezier(.32, .88, .36, 1); /* everyday state change      */
    --qa-spring: cubic-bezier(.34, 1.46, .58, 1); /* lamps, knobs               */
    --qa-t: 160ms;
    --qa-t-slow: 420ms;

    /* Parallax, written by JS. -1..1 on each axis; 0 with no pointer. */
    --qa-px: 0;
    --qa-py: 0;
    /* How fast the room's day is running. JS raises it while authenticating. */
    --qa-run: 1;

    --qa-z-room: 0;
    --qa-z-deck: 10;
    --qa-z-busy: 90;
}


/* ============================================================================
   02 · RESET + PAGE FRAME
   ============================================================================ */
*, *::before, *::after {
    box-sizing: border-box;
}

html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

body.qa {
    margin: 0;
    min-height: 100vh;
    min-height: 100svh; /* iOS: the bar-independent height */
    background: var(--qa-void);
    color: var(--qa-ink);
    font-family: var(--qa-font);
    font-size: var(--qa-fs-body);
    line-height: 1.55;
    font-feature-settings: "cv11" 1, "ss01" 1;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    overflow-x: hidden;
    /* The room is drawn behind everything; nothing here scrolls it. */
    overscroll-behavior-y: none;
}

    body.qa > form {
        min-height: 100vh;
        min-height: 100svh;
        display: flex;
        flex-direction: column;
    }

/* ── THE ELEMENT RESET, AND WHY IT IS WRAPPED IN :where() ─────────────────
   Written as `.qa svg { display:block }` this block is specificity (0,1,1),
   which out-ranks EVERY (0,1,0) component class in the system. That is not
   theoretical: in this file it silently blockified the submit button's icon
   onto a second line, revealed every station check that `.qa-station__check
   { display:none }` was trying to hide, and repainted `.qa-link` in the
   anchor's cyan. Three different components, one cause.

   `:where()` has ZERO specificity, so the same rules still land on bare
   elements and lose to literally any class. The trap is closed by
   construction rather than by remembering it. New resets go in here; new
   components never have to out-rank them. */
:where(.qa) :where(img, svg) {
    display: block;
    max-width: 100%;
}

/* No `color` here either — every button in this system states its own, so a
   generic rule can never repaint a variant. (NOCTURNE CORE v5 unwound the
   same bug in `.qd .qd-btn:hover`.) */
:where(.qa) :where(button) {
    font: inherit;
}

:where(.qa) :where(a) {
    color: var(--qa-cyan-ink);
    text-decoration: none;
    transition: color var(--qa-t) var(--qa-ease);
}

    :where(.qa) :where(a):hover {
        color: #8FEBF9;
    }

/* One focus treatment for the whole system. */
.qa :focus {
    outline: none;
}

.qa :focus-visible {
    outline: 2px solid var(--qa-cyan);
    outline-offset: 3px;
    border-radius: 4px;
}

.qa .u-mono {
    font-family: var(--qa-mono);
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

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

.qa .u-sr {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}


/* ============================================================================
   03 · THE ROOM
   ----------------------------------------------------------------------------
   Six fixed layers, back to front:
     void -> aurora -> floor lattice -> ceiling lattice -> grain -> vignette
   All of them are `position: fixed`, pointer-events: none, and animate only
   transform / opacity / background-position. Nothing here can ever cause
   layout, so the room is free on every frame.
   ============================================================================ */
.qa-room {
    position: fixed;
    inset: 0;
    z-index: var(--qa-z-room);
    pointer-events: none;
    overflow: hidden;
    /* The perspective that makes the two lattices a ROOM and not two images.
       Origin sits above centre so the floor opens toward the reader. */
    perspective: 1400px;
    perspective-origin: 50% 44%;
    contain: strict;
}

/* ── the aurora: the room's own light. Kept under the glow threshold (LAW 2) ── */
.qa-room__aurora {
    position: absolute;
    inset: -25%;
    background:
        radial-gradient(46% 38% at 22% 18%, rgba(77, 141, 255, .13), transparent 68%), radial-gradient(40% 34% at 82% 26%, rgba(138, 124, 255, .10), transparent 66%), radial-gradient(60% 44% at 50% 104%, rgba(34, 211, 238, .07), transparent 72%);
    filter: blur(24px);
    animation: qa-aurora-drift 46s var(--qa-ease) infinite alternate;
}

/* NOTE — every room layer rests VISIBLE. Only `.qa-arm` (set inline in
   <head>, so only when scripting is on) hides them for the arrival. With
   JS off the room is simply there, already built, and the page is a
   finished screen rather than a flat form on black. */

@keyframes qa-aurora-drift {
    0% {
        transform: translate3d(-2.2%, -1.4%, 0) scale(1);
    }

    100% {
        transform: translate3d(2.6%, 1.8%, 0) scale(1.07);
    }
}

/* ── the lattices ─────────────────────────────────────────────────────────
   `--tilt` opens the plane, `--drop` pushes it away from the eye. The floor
   is the schedule: lanes are providers, ticks are hours, and the tick lines
   drift toward the reader because the day advances toward now.            */
/* The mask lives on the PLANE, not on its pseudo-elements, so the slot
   sparks fade with the lattice instead of surviving it as hard trapezoids
   floating in the void. It is also what stops this being a synthwave grid:
   only a soft patch of the floor is ever lit, and it dies to nothing well
   before any edge. */
.qa-room__plane {
    position: absolute;
    left: -60%;
    right: -60%;
    height: 190vh;
    will-change: transform, opacity;
    -webkit-mask-image: radial-gradient(ellipse 56% 44% at 50% 9%, #000 0%, rgba(0,0,0,.60) 38%, transparent 76%);
    mask-image: radial-gradient(ellipse 56% 44% at 50% 9%, #000 0%, rgba(0,0,0,.60) 38%, transparent 76%);
}

.qa-room__plane--floor {
    top: 42%;
    transform-origin: 50% 0%;
    transform: rotateX(var(--tilt, 74deg)) translateZ(calc(var(--qa-py) * -14px)) translateX(calc(var(--qa-px) * -18px));
}

.qa-room__plane--ceil {
    bottom: 58%;
    transform-origin: 50% 100%;
    transform: rotateX(calc(var(--tilt, 74deg) * -1)) translateZ(calc(var(--qa-py) * 10px)) translateX(calc(var(--qa-px) * -12px));
}

/* lanes — static. providers do not move during a shift. */
.qa-room__plane::before,
.qa-room__plane::after {
    content: "";
    position: absolute;
    inset: 0;
}

.qa-room__plane::before {
    background-image: repeating-linear-gradient(90deg, var(--line, var(--qa-grid)) 0 1px, transparent 1px 108px);
}

/* ticks — the hours, drifting. --qa-run scales the day's speed. */
.qa-room__plane::after {
    background-image: repeating-linear-gradient(0deg, var(--line, var(--qa-grid)) 0 1px, transparent 1px 108px);
    animation: qa-ticks calc(30s / var(--qa-run, 1)) linear infinite;
}

@keyframes qa-ticks {
    to {
        background-position: 0 108px;
    }
}

/* The ceiling is the same lattice, quieter, and lit from its own far edge —
   which is at the BOTTOM of its box once it is rotated the other way. */
.qa-room__plane--ceil {
    --line: var(--qa-grid-2);
    -webkit-mask-image: radial-gradient(ellipse 50% 38% at 50% 91%, #000 0%, rgba(0,0,0,.48) 36%, transparent 72%);
    mask-image: radial-gradient(ellipse 50% 38% at 50% 91%, #000 0%, rgba(0,0,0,.48) 36%, transparent 72%);
}

/* ── slot sparks: appointments landing on the floor plane ────────────────── */
.qa-room__slot {
    position: absolute;
    width: 106px;
    height: 106px;
    border-radius: 3px;
    background: linear-gradient(150deg, rgba(34,211,238,.22), rgba(77,141,255,.10));
    box-shadow: 0 0 0 1px rgba(34, 211, 238, .16) inset;
    opacity: 0;
    animation: qa-slot 13s var(--qa-ease) infinite;
}

@keyframes qa-slot {
    0%, 100% {
        opacity: 0;
        transform: scale(.86);
    }

    8% {
        opacity: .5;
        transform: scale(1);
    }

    26% {
        opacity: .16;
    }

    48% {
        opacity: 0;
        transform: scale(1);
    }
}

/* ── the horizon: where floor meets void. One hairline, no bloom object. ─── */
.qa-room__horizon {
    position: absolute;
    left: 50%;
    top: 42%;
    width: min(78rem, 128vw);
    height: 1px;
    transform: translateX(-50%) scaleX(1);
    transform-origin: 50% 50%;
    background: linear-gradient(90deg, transparent, rgba(120,170,235,.30) 26%, rgba(34,211,238,.42) 50%, rgba(120,170,235,.30) 74%, transparent);
}

/* ── grain: the difference between "premium" and "flat gradients" ───────── */
.qa-room__grain {
    position: absolute;
    inset: -50%;
    opacity: .16;
    mix-blend-mode: overlay;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.86' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

.qa-room__vignette {
    position: absolute;
    inset: 0;
    background: radial-gradient(120% 96% at 50% 44%, transparent 34%, rgba(4,6,11,.62) 78%, rgba(4,6,11,.94) 100%);
}


/* ============================================================================
   04 · THE THRESHOLD LINE   (signature 2)
   ----------------------------------------------------------------------------
   One vertical filament between the narrative half and the instrument half,
   with a node that travels down it. Outside becomes inside, once every 9s.
   ============================================================================ */
.qa-threshold {
    position: absolute;
    top: 4%;
    bottom: 4%;
    left: 0;
    width: 1px;
    pointer-events: none;
    background: var(--qa-filament-v);
    opacity: .5;
    -webkit-mask-image: linear-gradient(180deg, transparent, #000 18%, #000 82%, transparent);
    mask-image: linear-gradient(180deg, transparent, #000 18%, #000 82%, transparent);
}

    .qa-threshold::after {
        content: "";
        position: absolute;
        left: 50%;
        width: 3px;
        height: 3px;
        margin-left: -1.5px;
        border-radius: 50%;
        background: var(--qa-cyan);
        box-shadow: 0 0 10px 1px rgba(34, 211, 238, .8);
        animation: qa-node 9s var(--qa-ease) infinite;
    }

@keyframes qa-node {
    0% {
        top: 6%;
        opacity: 0;
    }

    12% {
        opacity: 1;
    }

    82% {
        opacity: 1;
    }

    100% {
        top: 94%;
        opacity: 0;
    }
}


/* ============================================================================
   05 · THE DECK   (layout)
   ============================================================================ */
.qa-deck {
    position: relative; /* the threshold line hangs off this */
    z-index: var(--qa-z-deck);
    flex: 1;
    width: 100%;
    max-width: var(--qa-deck-w);
    margin-inline: auto;
    padding: clamp(1.5rem, 4vh, 3.5rem) var(--qa-gutter);
    padding-bottom: max(clamp(1.5rem, 4vh, 3.5rem), env(safe-area-inset-bottom));
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--qa-console-w);
    align-items: center;
    gap: clamp(2rem, 6vw, 5.5rem);
    /* The volume the console hangs in. Children keep their own Z. */
    perspective: 1500px;
    perspective-origin: 50% 46%;
}


/* ============================================================================
   06 · THE MARK
   ============================================================================ */
.qa-mark {
    width: 30px;
    height: 30px;
    flex: none;
}

    .qa-mark polygon {
        transform-origin: 50% 50%;
    }

.qa-mark--live polygon {
    animation: qa-blade 5.6s var(--qa-ease) infinite;
}

    .qa-mark--live polygon:nth-child(2) {
        animation-delay: .14s;
    }

    .qa-mark--live polygon:nth-child(3) {
        animation-delay: .28s;
    }

    .qa-mark--live polygon:nth-child(4) {
        animation-delay: .42s;
    }

@keyframes qa-blade {
    0%, 70%, 100% {
        opacity: 1;
        transform: translateZ(0);
    }

    12% {
        opacity: .55;
    }

    24% {
        opacity: 1;
    }
}

.qa-wordmark {
    font-size: 1.0625rem;
    font-weight: 600;
    letter-spacing: -.022em;
    color: var(--qa-ink);
}


/* ============================================================================
   07 · THE RAIL   (the narrative half — outside the door)
   ============================================================================ */
.qa-rail {
    min-width: 0;
    /* Sits a little behind the console's plane, so the pointer parallax
       separates them the way real depth would. */
    transform: translate3d(calc(var(--qa-px) * 9px), calc(var(--qa-py) * 7px), 0);
    will-change: transform;
}

.qa-rail__brand {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: var(--qa-sp-8);
}

.qa-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    font-weight: 500;
    letter-spacing: var(--qa-track-chip);
    text-transform: uppercase;
    color: var(--qa-ink-3);
}

.qa-pulse {
    position: relative;
    width: var(--qa-h-lamp);
    height: var(--qa-h-lamp);
    border-radius: 50%;
    background: var(--qa-cyan);
    flex: none;
}

    .qa-pulse::after {
        content: "";
        position: absolute;
        inset: -3px;
        border-radius: 50%;
        border: 1px solid var(--qa-cyan);
        opacity: 0;
        animation: qa-ping 2.6s var(--qa-ease) infinite;
    }

@keyframes qa-ping {
    0% {
        transform: scale(.7);
        opacity: .85;
    }

    70%, 100% {
        transform: scale(2);
        opacity: 0;
    }
}

.qa-rail__title {
    margin: var(--qa-sp-5) 0 0;
    font-size: var(--qa-fs-display);
    font-weight: 700;
    line-height: 1.03;
    letter-spacing: var(--qa-track-display);
    color: var(--qa-ink);
}

    /* Line-level clip reveal. Each line needs its own block so the mask
       travels with the line and not with the paragraph. */
    .qa-rail__title .qa-line {
        display: block;
        overflow: hidden;
    }

        .qa-rail__title .qa-line > span {
            display: block;
        }

    .qa-rail__title .qa-grad {
        background: linear-gradient(96deg, #EAF0F8 0%, #A9C8FF 42%, #8A7CFF 92%);
        -webkit-background-clip: text;
        background-clip: text;
        color: transparent;
    }

.qa-rail__sub {
    margin: var(--qa-sp-5) 0 0;
    max-width: 34ch;
    font-size: var(--qa-fs-lead);
    line-height: 1.6;
    color: var(--qa-ink-2);
}

/* ── capability readouts: instrument rows, not a bullet list ─────────────── */
/* Dividers are BORDERS, not a 1px grid gap showing a background through.
   The gap trick paints each row --qa-void, which is darker than the
   aurora-lit ground behind it, so the list read as three black blocks
   pasted onto the room. Borders keep the rows transparent and the room
   visible straight through the rail. */
.qa-readouts {
    margin: var(--qa-sp-8) 0 0;
    padding: 0;
    list-style: none;
    border-top: 1px solid var(--qa-hair);
}

.qa-readout {
    display: grid;
    grid-template-columns: 2.25rem minmax(0, 1fr);
    align-items: baseline;
    gap: var(--qa-sp-4);
    padding: var(--qa-sp-4) 0;
    border-bottom: 1px solid var(--qa-hair);
    font-size: var(--qa-fs-body);
    color: var(--qa-ink-2);
}

    .qa-readout b {
        color: var(--qa-ink);
        font-weight: 600;
    }

.qa-readout__idx {
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    font-weight: 500;
    letter-spacing: .08em;
    color: var(--qa-faint);
}

/* ── the telemetry rule: MERIDIAN's punctuation, reused here on purpose ──── */
.qa-telemetry {
    display: flex;
    align-items: center;
    gap: var(--qa-sp-3);
    margin-top: var(--qa-sp-7);
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    letter-spacing: var(--qa-track-chip);
    text-transform: uppercase;
    color: var(--qa-faint);
}

    .qa-telemetry::before {
        content: "";
        flex: none;
        width: 2.5rem;
        height: 1px;
        background: var(--qa-hair-2);
    }


/* ============================================================================
   08 · THE CONSOLE   (the instrument half — inside the door)
   ============================================================================ */
.qa-consolewrap {
    min-width: 0;
    transform-style: preserve-3d;
}

.qa-console {
    position: relative;
    border-radius: var(--qa-r-panel);
    border: 1px solid var(--qa-hair-2);
    background:
        linear-gradient(180deg, rgba(20,29,42,.90) 0%, rgba(9,14,21,.94) 60%, rgba(7,11,17,.96) 100%), var(--qa-console);
    box-shadow: var(--qa-lift);
    padding: var(--qa-sp-6) var(--qa-sp-6) var(--qa-sp-5);
    /* Depth for the pointer parallax; the console leads the rail. */
    transform: translate3d(calc(var(--qa-px) * -14px), calc(var(--qa-py) * -10px), 0) rotateY(calc(var(--qa-px) * -1.6deg)) rotateX(calc(var(--qa-py) * 1.2deg));
    transform-style: preserve-3d;
    will-change: transform;
    /* VISIBLE, not hidden. The console carries a transform, which makes it a
       containing block for fixed positioning too — so `overflow: hidden` here
       clips a combobox popup no matter how the popup is positioned. The only
       thing that ever needed clipping is the authenticating sweep, so the
       clip is applied for exactly as long as the sweep runs. */
    overflow: visible;
}

    .qa-console.is-auth {
        overflow: hidden;
    }

@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
    .qa-console {
        -webkit-backdrop-filter: blur(20px) saturate(118%);
        backdrop-filter: blur(20px) saturate(118%);
        background:
            linear-gradient(180deg, rgba(20,29,42,.76) 0%, rgba(9,14,21,.84) 60%, rgba(7,11,17,.88) 100%);
    }
}

    /* the top filament — the console's one piece of jewellery */
    .qa-console::before {
        content: "";
        position: absolute;
        top: 0;
        left: 12%;
        right: 12%;
        height: 1px;
        background: var(--qa-filament);
        opacity: .62;
        -webkit-mask-image: linear-gradient(90deg, transparent, #000 30%, #000 70%, transparent);
        mask-image: linear-gradient(90deg, transparent, #000 30%, #000 70%, transparent);
    }

    /* THE SWEEP — one band that reads the panel while authenticating.
       Parked and invisible until .is-auth is set on the console. */
    .qa-console::after {
        content: "";
        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        height: 44%;
        pointer-events: none;
        opacity: 0;
        background: linear-gradient(180deg, transparent, rgba(34,211,238,.10) 46%, rgba(34,211,238,.20) 62%, transparent);
        border-bottom: 1px solid rgba(34, 211, 238, .34);
        transform: translateY(-110%);
    }

    .qa-console.is-auth::after {
        opacity: 1;
        animation: qa-sweep 1.15s var(--qa-ease) infinite;
    }

@keyframes qa-sweep {
    0% {
        transform: translateY(-110%);
        opacity: 0;
    }

    18% {
        opacity: 1;
    }

    82% {
        opacity: 1;
    }

    100% {
        transform: translateY(240%);
        opacity: 0;
    }
}

/* ── console header: the live readout rail ──────────────────────────────── */
.qa-console__head {
    display: flex;
    align-items: center;
    gap: var(--qa-sp-3);
    padding-bottom: var(--qa-sp-4);
    margin-bottom: var(--qa-sp-2);
    border-bottom: 1px solid var(--qa-hair);
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    font-weight: 500;
    letter-spacing: var(--qa-track-chip);
    text-transform: uppercase;
    color: var(--qa-faint);
}

.qa-console__state {
    color: var(--qa-ink-3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: color var(--qa-t) var(--qa-ease);
}

.qa-console.is-live .qa-console__state {
    color: var(--qa-cyan-ink);
}

.qa-console__rule {
    flex: 1;
    height: 1px;
    min-width: var(--qa-sp-4);
    background: var(--qa-hair-2);
}

.qa-console__clock {
    color: var(--qa-ink-3);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}


/* ============================================================================
   09 · ALERT
   ============================================================================ */
.qa-alert {
    display: flex;
    align-items: flex-start;
    gap: var(--qa-sp-3);
    margin: var(--qa-sp-4) 0 0;
    padding: var(--qa-sp-3) var(--qa-sp-4);
    border-radius: var(--qa-r-ctl);
    border: 1px solid var(--qa-info-line, var(--qa-hair-2));
    background: rgba(148, 180, 214, .06);
    font-size: var(--qa-fs-sm);
    line-height: 1.5;
    color: var(--qa-ink-2);
    /* Bottom margin too. With margin-top alone the banner butted straight
       into the step title underneath it — an error and a heading sharing an
       edge read as one block. */
    margin-bottom: var(--qa-sp-5);
}

/* The entrance is a separate class so a SECOND message re-plays it: the JS
   removes it, forces a reflow, adds it back. An animation declared on
   .qa-alert itself would fire once and then sit silent for every message
   after — the one a person most needs to notice. */
.qa-alert--in {
    animation: qa-alert-in 260ms var(--qa-in) both;
}

@keyframes qa-alert-in {
    from {
        opacity: 0;
        transform: translateY(-6px);
    }
}

.qa-alert__dot {
    flex: none;
    width: var(--qa-h-lamp);
    height: var(--qa-h-lamp);
    margin-top: .5em;
    border-radius: 50%;
    background: currentColor;
    opacity: .9;
}

.qa-alert.danger {
    border-color: var(--qa-bad-line);
    background: var(--qa-bad-soft);
    color: #FFC2CD;
}

.qa-alert.success {
    border-color: var(--qa-good-line);
    background: var(--qa-good-soft);
    color: #9FEBCD;
}

.qa-alert.info {
    border-color: var(--qa-cyan-line);
    background: var(--qa-cyan-soft);
    color: #A9E9F5;
}

.qa-alert.warn {
    border-color: var(--qa-warn-line);
    background: var(--qa-warn-soft);
    color: #FBDFA0;
}


/* ============================================================================
   10 · THE CHANNEL STRIP   (signature 1)
   ----------------------------------------------------------------------------
   Chrome shrinks, content grows. The label is a 10px mono channel name; the
   value is 19px. There is no box — a lane rail on the left, a recess behind,
   and an underline that becomes the filament while the channel is live.
   ============================================================================ */
.qa-channels {
    display: grid;
    gap: var(--qa-sp-3);
    margin-top: var(--qa-sp-5);
}

.qa-ch {
    position: relative;
    display: grid;
    grid-template-columns: 2px minmax(0, 1fr);
    align-items: stretch;
    border-radius: var(--qa-r-ch);
    background: var(--qa-well);
    box-shadow: inset 0 0 0 1px var(--qa-hair);
    transition: background var(--qa-t) var(--qa-ease), box-shadow var(--qa-t) var(--qa-ease);
    overflow: hidden;
}

    .qa-ch:hover {
        background: rgba(148, 180, 214, .05);
    }

/* the lane rail */
.qa-ch__rail {
    background: var(--qa-hair-2);
    transform: scaleY(.34);
    transform-origin: 50% 50%;
    transition: transform var(--qa-t-slow) var(--qa-in), background var(--qa-t) var(--qa-ease);
}

.qa-ch__pad {
    display: grid;
    gap: 2px;
    padding: 10px var(--qa-sp-4) 11px;
    min-width: 0;
}

.qa-ch__label {
    display: flex;
    align-items: center;
    gap: 7px;
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-channel);
    font-weight: 500;
    letter-spacing: var(--qa-track-channel);
    text-transform: uppercase;
    color: var(--qa-ink-3); /* a form label, not meta — 5.7:1 */
    cursor: pointer;
    transition: color var(--qa-t) var(--qa-ease);
    user-select: none;
}

.qa-ch__body {
    display: flex;
    align-items: center;
    gap: var(--qa-sp-3);
    min-width: 0;
}

/* The leading glyph. THIS BELONGS TO THE SYSTEM, not to a page — it was
   defined in login.css, so the first page to reuse the channel strip
   (Register) rendered every SVG at its intrinsic 300×150, which blew each
   strip to ~500px tall and squeezed its input to zero width. A component's
   parts all live with the component. */
.qa-ch__glyph {
    flex: none;
    width: 17px;
    height: 17px;
    color: var(--qa-faint);
    transition: color var(--qa-t) var(--qa-ease);
}

.qa-ch.is-live .qa-ch__glyph {
    color: var(--qa-cyan-ink);
}

.qa-ch.is-bad .qa-ch__glyph {
    color: var(--qa-bad);
}

/* THE VALUE — the biggest type in the console, because it is the content.
   ----------------------------------------------------------------------------
   The type is declared through four private custom properties instead of
   four literal values. That is not decoration: the autofill rule further
   down has to restate the type with !important, and if it restated LITERAL
   values it would silently pin the password channel to the e-mail channel's
   face, and both to the desktop size, the moment either changed. Variants
   set the variables; the base and the autofill override both consume them.
   One place to change the type, and the exception can never drift.
   (NOCTURNE CORE LAW 10, applied to type rather than colour.)

   SINGLE SELECTOR, ON PURPOSE. This rule used to read
   `.qa-ch__value, .qa input.qa-ch__value` — and that second half, at
   (0,2,1), quietly out-ranked `.qa-ch__value[type="password"]` at (0,2,0),
   so the passphrase channel never actually got its mono face. A UA sheet
   loses to ANY author rule, so the extra selector was never buying
   anything; it was only breaking the variant below it. */
.qa-ch__value {
    --_ff: var(--qa-font);
    --_fs: var(--qa-fs-value);
    --_fw: 500;
    --_ls: -.006em;
    flex: 1;
    min-width: 0;
    width: 100%;
    appearance: none;
    -webkit-appearance: none;
    background: transparent;
    border: 0;
    padding: 0;
    margin: 0;
    color: var(--qa-ink);
    font-family: var(--_ff);
    font-size: var(--_fs);
    font-weight: var(--_fw);
    letter-spacing: var(--_ls);
    line-height: 1.45;
}

    .qa-ch__value::placeholder {
        color: var(--qa-faint);
        font-weight: 400;
        opacity: 1;
    }

    .qa-ch__value:focus {
        outline: none;
    }

/* a password channel keeps its dots on the same rhythm as the text */
.qa-ch__value[type="password"] {
    --_ff: var(--qa-mono);
    --_fs: 1.0625rem;
    --_ls: .18em;
}

/* ── BROWSER AUTOFILL ────────────────────────────────────────────────────
   A field the browser fills is painted by the BROWSER, with UA rules the
   normal cascade cannot reach. Three separate things go wrong and all
   three have to be answered:

   1 · THE FILL. Chromium's `background-color` on an autofilled input is
       `!important`, so it cannot be overridden — only hidden.
       `background-clip: text` confines that fill to the glyphs, and
       -webkit-text-fill-color then paints over the glyphs, so nothing of
       it survives and the channel's own recess shows through untouched.
       The older remedy — a 100px inset box-shadow — does NOT do that: it
       lays an opaque slab the size of the INPUT inside the channel, which
       reads as a lighter rectangle with hard edges floating in the recess.
       That slab was the visible bug this replaces.

   2 · THE TYPE. An autofilled value was dropping to the UA's 13.3px
       default face — against a 19px hand-typed one in the same field.
       The whole premise of the channel strip is that the value is large,
       so the type is restated here, from the same variables, !important.

   3 · THE INK. -webkit-text-fill-color beats the UA `color`; caret-color
       keeps the cursor cyan.

   Two rules, not one selector list: `:autofill` is standard and
   `:-webkit-autofill` is not, and a plain selector list is invalidated
   ENTIRELY by one pseudo-class the browser doesn't know. Split, each
   engine keeps whichever it understands.

   The absurd transition delay is the standard defence against Chromium
   re-applying its fill a frame or two after first paint. */
.qa-ch__value:-webkit-autofill,
.qa-ch__value:-webkit-autofill:hover,
.qa-ch__value:-webkit-autofill:focus,
.qa-ch__value:-webkit-autofill:active {
    -webkit-background-clip: text !important;
    background-clip: text !important;
    -webkit-text-fill-color: var(--qa-ink) !important;
    caret-color: var(--qa-cyan);
    font-family: var(--_ff) !important;
    font-size: var(--_fs) !important;
    font-weight: var(--_fw) !important;
    letter-spacing: var(--_ls) !important;
    transition: background-color 100000s ease-out 0s, color 100000s ease-out 0s;
    /* the hook the script listens for — see "autofill detection" below */
    animation: qa-autofilled 1ms;
}

.qa-ch__value:autofill,
.qa-ch__value:autofill:hover,
.qa-ch__value:autofill:focus,
.qa-ch__value:autofill:active {
    background-clip: text !important;
    -webkit-text-fill-color: var(--qa-ink) !important;
    caret-color: var(--qa-cyan);
    font-family: var(--_ff) !important;
    font-size: var(--_fs) !important;
    font-weight: var(--_fw) !important;
    letter-spacing: var(--_ls) !important;
    transition: background-color 100000s ease-out 0s, color 100000s ease-out 0s;
    animation: qa-autofilled 1ms;
}

/* Autofill detection. Chromium fires `animationstart` the instant it fills
   a field — including long after DOMContentLoaded, which is exactly when a
   `load` handler has already given up. The script arms the channel lamps
   off this event, so a restored identity lights its lamp like a typed one.
   The keyframe deliberately changes nothing: it exists to be observed. */
@keyframes qa-autofilled {
    from {
        opacity: 1;
    }

    to {
        opacity: 1;
    }
}

/* ── BROWSER-INJECTED CONTROLS ───────────────────────────────────────────
   Edge draws its OWN reveal eye inside every password field, so the
   channel was showing two eyes side by side — one the system's, one the
   browser's, doing the same job with different art. Ours stays; it is the
   one that is styled, labelled, and reports aria-pressed.
   Safari's password-manager key is genuinely useful, so it is tinted to
   the system's ink rather than removed — left alone it renders near-black
   on a near-black field. */
.qa-ch__value::-ms-reveal,
.qa-ch__value::-ms-clear {
    display: none;
}

.qa-ch__value::-webkit-credentials-auto-fill-button,
.qa-ch__value::-webkit-contacts-auto-fill-button {
    background-color: var(--qa-ink-3);
    margin: 0 6px 0 2px;
}

/* the state lamp */
.qa-ch__lamp {
    flex: none;
    width: var(--qa-h-lamp);
    height: var(--qa-h-lamp);
    border-radius: 50%;
    background: var(--qa-faint);
    opacity: .5;
    transition: background var(--qa-t) var(--qa-ease), opacity var(--qa-t) var(--qa-ease), box-shadow var(--qa-t) var(--qa-ease), transform var(--qa-t) var(--qa-spring);
}

.qa-ch.is-armed .qa-ch__lamp {
    background: var(--qa-cyan);
    opacity: 1;
    transform: scale(1.12);
    box-shadow: 0 0 0 3px var(--qa-cyan-soft);
}

.qa-ch.is-bad .qa-ch__lamp {
    background: var(--qa-bad);
    opacity: 1;
    box-shadow: 0 0 0 3px var(--qa-bad-soft);
}

/* the underline — becomes the filament while live. THE ONE GLOW (LAW 2). */
.qa-ch__line {
    position: absolute;
    left: 2px;
    right: 0;
    bottom: 0;
    height: 1px;
    background: var(--qa-hair-2);
}

    .qa-ch__line::after {
        content: "";
        position: absolute;
        inset: 0;
        background: var(--qa-filament);
        transform: scaleX(0);
        transform-origin: 0 50%;
        transition: transform var(--qa-t-slow) var(--qa-in);
    }

/* ── LIVE ────────────────────────────────────────────────────────────────── */
/* The console clips its own overflow (the sweep needs it), so this glow is
   sized to die inside the console's 24px padding rather than being sheared
   off at the panel edge. */
.qa-ch.is-live {
    background: var(--qa-well-live);
    box-shadow: inset 0 0 0 1px var(--qa-hair-2), 0 0 26px -16px rgba(34, 211, 238, .55);
}

    .qa-ch.is-live .qa-ch__rail {
        transform: scaleY(1);
        background: var(--qa-filament-v);
    }

    .qa-ch.is-live .qa-ch__label {
        color: var(--qa-cyan-ink);
    }

    .qa-ch.is-live .qa-ch__line::after {
        transform: scaleX(1);
    }

/* ── BAD ─────────────────────────────────────────────────────────────────── */
.qa-ch.is-bad {
    box-shadow: inset 0 0 0 1px var(--qa-bad-line);
}

    .qa-ch.is-bad .qa-ch__rail {
        background: var(--qa-bad);
        transform: scaleY(1);
    }

    .qa-ch.is-bad .qa-ch__label {
        color: var(--qa-bad);
    }

/* ── the eye button ──────────────────────────────────────────────────────── */
.qa-eye {
    flex: none;
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    margin: -6px -8px -6px 0;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--qa-ink-3);
    cursor: pointer;
    transition: color var(--qa-t) var(--qa-ease), background var(--qa-t) var(--qa-ease);
}

    .qa-eye:hover {
        color: var(--qa-ink);
        background: rgba(148, 180, 214, .08);
    }

    .qa-eye svg {
        width: 17px;
        height: 17px;
    }

    .qa-eye .qa-eye__off {
        display: none;
    }

    .qa-eye.is-on .qa-eye__on {
        display: none;
    }

    .qa-eye.is-on .qa-eye__off {
        display: block;
    }

    .qa-eye.is-on {
        color: var(--qa-cyan-ink);
    }

/* ── caps lock warning: a real lamp on the channel label ─────────────────── */
.qa-caps {
    display: none;
    align-items: center;
    gap: 5px;
    margin-left: auto;
    padding: 1px 7px;
    border-radius: var(--qa-r-pill);
    border: 1px solid var(--qa-warn-line);
    background: var(--qa-warn-soft);
    color: var(--qa-warn);
    letter-spacing: .1em;
}

.qa-ch.is-caps .qa-caps {
    display: inline-flex;
    animation: qa-alert-in 200ms var(--qa-in) both;
}


/* ============================================================================
   11 · THE MID ROW   (switch + link)
   ============================================================================ */
.qa-mid {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--qa-sp-4);
    margin-top: var(--qa-sp-5);
}

/* position:relative because the real input inside is absolutely positioned.
   Without it the input anchors to whatever ancestor happens to be positioned,
   which is where stray focus rings and scroll-into-view jumps come from. */
.qa-switch {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    user-select: none;
}

    /* the real checkbox stays in the DOM for the server; it is never seen */
    .qa-switch input {
        position: absolute;
        opacity: 0;
        width: 1px;
        height: 1px;
        margin: -1px;
        pointer-events: none;
    }

.qa-switch__track {
    position: relative;
    flex: none;
    width: 36px;
    height: 20px;
    border-radius: var(--qa-r-pill);
    background: rgba(148, 180, 214, .10);
    box-shadow: inset 0 0 0 1px var(--qa-hair-2);
    transition: background var(--qa-t) var(--qa-ease), box-shadow var(--qa-t) var(--qa-ease);
}

    .qa-switch__track::after {
        content: "";
        position: absolute;
        top: 3px;
        left: 3px;
        width: 14px;
        height: 14px;
        border-radius: 50%;
        background: var(--qa-ink-3);
        transition: transform 220ms var(--qa-spring), background var(--qa-t) var(--qa-ease);
    }

.qa-switch__label {
    font-size: var(--qa-fs-sm);
    color: var(--qa-ink-2);
    cursor: pointer;
}

.qa-switch.is-on .qa-switch__track {
    background: var(--qa-cyan-soft);
    box-shadow: inset 0 0 0 1px var(--qa-cyan-line);
}

    .qa-switch.is-on .qa-switch__track::after {
        transform: translateX(16px);
        background: var(--qa-cyan);
    }

.qa-switch input:focus-visible + .qa-switch__track {
    outline: 2px solid var(--qa-cyan);
    outline-offset: 3px;
}

.qa-link {
    font-size: var(--qa-fs-sm);
    color: var(--qa-ink-2);
    white-space: nowrap;
}

    .qa-link:hover {
        color: var(--qa-ink);
    }


/* ============================================================================
   12 · BUTTONS
   ----------------------------------------------------------------------------
   ONE submit per screen, chrome on dark (LAW 3). Everything else is a link.
   Two stacked full-width slabs is what made the old page feel heavy — the
   second action is demoted to text and the weight halves.
   ============================================================================ */
.qa-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    width: 100%;
    height: var(--qa-h-ctl);
    padding-inline: var(--qa-sp-5);
    border: 0;
    border-radius: var(--qa-r-ctl);
    font-size: .9375rem;
    font-weight: 600;
    letter-spacing: -.008em;
    cursor: pointer;
    overflow: hidden;
    transition: transform 120ms var(--qa-ease), box-shadow var(--qa-t) var(--qa-ease), filter var(--qa-t) var(--qa-ease);
}

    .qa-btn svg {
        width: 17px;
        height: 17px;
        flex: none;
    }

    .qa-btn:active {
        transform: translateY(1px);
    }

    .qa-btn[disabled] {
        cursor: default;
        pointer-events: none;
    }

.qa-btn--primary {
    margin-top: var(--qa-sp-5);
    background: var(--qa-btn);
    color: var(--qa-on-btn);
    box-shadow: 0 1px 0 rgba(255, 255, 255, .55) inset, 0 10px 26px -14px rgba(120, 170, 255, .55), 0 2px 6px rgba(0, 0, 0, .4);
}

    .qa-btn--primary:hover {
        background: var(--qa-btn-hover);
        box-shadow: 0 1px 0 rgba(255, 255, 255, .7) inset, 0 14px 34px -14px rgba(120, 170, 255, .7), 0 2px 6px rgba(0, 0, 0, .4);
    }

    .qa-btn--primary:active {
        background: var(--qa-btn-press);
    }

    /* the filament rule under the label — the submit's signature */
    .qa-btn--primary::after {
        content: "";
        position: absolute;
        left: 14%;
        right: 14%;
        bottom: 0;
        height: 2px;
        background: var(--qa-filament);
        transform: scaleX(0);
        transform-origin: 50% 50%;
        transition: transform 320ms var(--qa-in);
    }

    .qa-btn--primary:hover::after,
    .qa-btn--primary:focus-visible::after {
        transform: scaleX(1);
    }

    /* A chrome button barely changes under a filter — it was still reading as
       "press me" while the phone gate was holding it. Held actions lose their
       lift, their glow and their contrast, so the eye goes looking for what is
       still wrong instead of clicking. */
    .qa-btn--primary[disabled] {
        background: linear-gradient(180deg, #9AA6B6 0%, #808D9E 100%);
        color: rgba(7, 11, 16, .55);
        box-shadow: none;
        transform: none;
    }

        .qa-btn--primary[disabled]::after {
            opacity: 0;
        }

        .qa-btn--primary[disabled] .qa-kbd {
            color: rgba(7, 11, 16, .38);
        }

/* the keyboard hint, mono, right-aligned inside the button */
.qa-kbd {
    position: absolute;
    right: 14px;
    font-family: var(--qa-mono);
    font-size: 10px;
    font-weight: 500;
    letter-spacing: .06em;
    color: rgba(7, 11, 16, .60); /* 5.1:1 on the chrome fill */
}

/* the running state: the label is replaced by a mono progress readout */
.qa-btn__run {
    display: none;
    font-family: var(--qa-mono);
    font-size: .8125rem;
    letter-spacing: .1em;
    text-transform: uppercase;
}

.qa-btn.is-running .qa-btn__idle {
    display: none;
}

.qa-btn.is-running .qa-btn__run {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

/* the shortcut is advice for a decision already taken */
.qa-btn.is-running .qa-kbd {
    display: none;
}

.qa-spin {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    border: 1.5px solid rgba(7, 11, 16, .25);
    border-top-color: rgba(7, 11, 16, .85);
    animation: qa-spin .7s linear infinite;
}

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

/* The label is its own flex row rather than an inline run with an inline
   SVG in it. `.qa svg { display:block }` is (0,1,1) and would blockify any
   inline icon here — the same specificity trap as the button colour, and it
   pushed "Sign in" onto a second line. Flex sidesteps it entirely: flex
   items are blockified anyway, so the reset and the layout agree. */
.qa-btn__idle {
    display: inline-flex;
    align-items: center;
    gap: 9px;
}

.qa-btn__ico {
    width: 17px;
    height: 17px;
    flex: none;
}

/* the secondary action — a line of text, not a second slab */
.qa-alt {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    margin: var(--qa-sp-4) 0 0;
    font-size: var(--qa-fs-sm);
    color: var(--qa-ink-3);
}

    .qa-alt a {
        color: var(--qa-ink);
        font-weight: 500;
        border-bottom: 1px solid var(--qa-hair-3);
        padding-bottom: 1px;
        transition: border-color var(--qa-t) var(--qa-ease), color var(--qa-t) var(--qa-ease);
    }

        .qa-alt a:hover {
            color: #fff;
            border-color: var(--qa-cyan);
        }


/* ============================================================================
   13 · STATUS FOOTER
   ----------------------------------------------------------------------------
   These three chips name guarantees the SERVER enforces on this exact
   request — account lockout, IP rate limiting, audit logging. Nothing here
   may claim a property the code does not implement.
   ============================================================================ */
/* ONE mono line, not three pills. Three badges wrapped to a ragged second
   row at 440px and read as decoration; a single measured sentence reads as
   a readout, which is what it is. */
.qa-status {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 7px;
    margin-top: var(--qa-sp-5);
    padding-top: var(--qa-sp-4);
    border-top: 1px solid var(--qa-hair);
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    font-weight: 500;
    letter-spacing: var(--qa-track-chip);
    text-transform: uppercase;
    color: var(--qa-ink-3); /* it states a security guarantee — read it */
}

    .qa-status svg {
        flex: none;
        width: 11px;
        height: 11px;
        color: var(--qa-good);
    }

.qa-status__sep {
    color: var(--qa-hair-3);
}

.qa-console__foot {
    margin-top: var(--qa-sp-4);
    font-size: var(--qa-fs-sm);
    color: var(--qa-ink-3);
    text-align: center;
}


/* ############################################################################
   THRESHOLD v1.2 — THE MULTI-STEP VOCABULARY
   ----------------------------------------------------------------------------
   Everything above is what a single-panel auth page needs (Login). Everything
   below is what a MULTI-STEP one needs (Register, Setup, Accept_Invite): a
   progress track, the field types a real form has, and a way for one panel to
   become the next without the page jumping.

   THE ONE IDEA THAT TIES IT TO LOGIN
     On Login, the threshold line is a static filament with a node crossing it
     — outside becoming inside, once. On a multi-step page that same filament
     is the PROGRESS TRACK: it fills as the steps complete, so crossing the
     threshold is literally what the reader is doing. Same object, same
     signature, now carrying the page's actual job. That is why there is no
     separate "stepper" component here.

   WHAT IS DELIBERATELY NOT HERE
     A per-step accent colour. The page this replaced rotated the card accent
     through the four mark colours (blue, green, red, indigo) as you advanced,
     which is what made it read as four unrelated screens — and it put green
     on a Qlynic surface, which no Qlynic system allows. Under LAW 3 the live
     colour is cyan and the structural colour is the filament, on every step.
     The MARK keeps its four colours, because the mark is the logo.
   ############################################################################ */


/* ============================================================================
   13B · THE INTAKE RAIL   (progress as an instrument track)
   ----------------------------------------------------------------------------
   A hairline track with node ticks carrying a mono index — MERIDIAN's OPS RAIL
   signature, doing duty as the step list. Three states per station:
     pending  faint index, hollow tick
     live     cyan index, filled tick with a halo, name in full ink
     done     filament tick with a check, name still readable
   ============================================================================ */
.qa-stations {
    position: relative;
    margin: var(--qa-sp-7) 0 0;
    padding: 0 0 0 var(--qa-sp-6);
    list-style: none;
    display: grid;
    gap: var(--qa-sp-5);
}

    /* the unlit track */
    .qa-stations::before {
        content: "";
        position: absolute;
        left: 5px;
        top: 6px;
        bottom: 6px;
        width: 1px;
        background: var(--qa-hair-2);
    }

/* the lit track — THE THRESHOLD LINE, now measuring progress */
.qa-stations__fill {
    position: absolute;
    left: 5px;
    top: 6px;
    width: 1px;
    height: 0;
    background: var(--qa-filament-v);
    box-shadow: 0 0 10px -2px rgba(34, 211, 238, .55);
    transition: height var(--qa-t-slow) var(--qa-in);
}

.qa-station {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
    gap: var(--qa-sp-3);
    padding-left: var(--qa-sp-5);
}

/* the tick */
.qa-station::before {
    content: "";
    position: absolute;
    left: calc(-1 * var(--qa-sp-6) + 2px);
    top: .45em;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--qa-void);
    box-shadow: 0 0 0 1px var(--qa-hair-3);
    transition: background var(--qa-t) var(--qa-ease), box-shadow var(--qa-t) var(--qa-ease), transform var(--qa-t) var(--qa-spring);
}

.qa-station__name {
    font-size: var(--qa-fs-body);
    font-weight: 500;
    color: var(--qa-ink-3);
    transition: color var(--qa-t) var(--qa-ease);
}

.qa-station__sub {
    display: block;
    margin-top: 1px;
    font-size: var(--qa-fs-sm);
    color: var(--qa-faint);
}

/* The index and the check occupy ONE grid cell and swap — as two siblings in
   a two-column grid the third child wrapped to a row of its own and every
   station grew a stray tick underneath it. */
.qa-station__state {
    display: grid;
    place-items: center end;
    min-width: 1.4rem;
}

.qa-station__idx {
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    font-weight: 500;
    letter-spacing: .08em;
    color: var(--qa-faint);
    transition: color var(--qa-t) var(--qa-ease);
}

/* ── LIVE ────────────────────────────────────────────────────────────────── */
.qa-station.is-live::before {
    background: var(--qa-cyan);
    box-shadow: 0 0 0 1px var(--qa-cyan), 0 0 0 4px var(--qa-cyan-soft);
    transform: scale(1.15);
}

.qa-station.is-live .qa-station__name {
    color: var(--qa-ink);
}

.qa-station.is-live .qa-station__idx {
    color: var(--qa-cyan-ink);
}

/* ── DONE ────────────────────────────────────────────────────────────────── */
.qa-station.is-done::before {
    background: var(--qa-cyan);
    box-shadow: 0 0 0 1px var(--qa-cyan-line);
}

.qa-station.is-done .qa-station__name {
    color: var(--qa-ink-2);
}

.qa-station.is-done .qa-station__idx {
    color: var(--qa-ink-3);
}

.qa-station__check {
    display: none;
    width: 11px;
    height: 11px;
    color: var(--qa-cyan-ink);
}

.qa-station.is-done .qa-station__check {
    display: block;
}

.qa-station.is-done .qa-station__idx {
    display: none;
}


/* ============================================================================
   13C · MORE CHANNEL TYPES
   ----------------------------------------------------------------------------
   Same strip, same lamp, same filament underline. Only the control inside
   changes — which is the whole point of having built the strip as a shell.
   ============================================================================ */

/* ── select ──────────────────────────────────────────────────────────────── */
.qa-ch__select {
    flex: 1;
    min-width: 0;
    width: 100%;
    appearance: none;
    -webkit-appearance: none;
    background: transparent;
    border: 0;
    padding: 0 22px 0 0; /* clears the chevron; see .qa-ch__chev */
    margin: 0;
    color: var(--qa-ink);
    font-family: var(--qa-font);
    font-size: var(--qa-fs-value);
    font-weight: 500;
    letter-spacing: -.006em;
    line-height: 1.45;
    cursor: pointer;
}

    .qa-ch__select:focus {
        outline: none;
    }

    /* The drop-down list itself is painted by the OS, not by CSS. `color-scheme`
       on :root is what makes it dark; these two keep the OPTIONS legible on
       the platforms that read them from the element instead. */
    .qa-ch__select option {
        background: var(--qa-console-2);
        color: var(--qa-ink);
    }

    .qa-ch__select:invalid,
    .qa-ch__select option[value=""] {
        color: var(--qa-faint);
    }

/* A select channel drops the leading glyph. Two affordances were competing
   for the same strip — a decorative glyph on the left and the chevron that
   actually means "this opens" on the right — and between them they squeezed
   the value to ~147px, which clipped "Select province…" mid-word on the very
   first render. The chevron stays because it carries meaning; the glyph goes
   because it did not. */
.qa-ch--select .qa-ch__glyph {
    display: none;
}

/* The chevron sits INSIDE the select's box but clear of the state lamp, which
   holds the far-right 6px of every channel. Right: 32px = lamp (6) + body gap
   (12) + channel padding (16) minus a hair of overlap, so the two never touch
   however long the selected option's text is. */
.qa-ch__chev {
    position: absolute;
    right: 32px;
    bottom: 17px;
    width: 15px;
    height: 15px;
    pointer-events: none;
    color: var(--qa-ink-3);
    transition: color var(--qa-t) var(--qa-ease), transform var(--qa-t) var(--qa-ease);
}

.qa-ch.is-live .qa-ch__chev {
    color: var(--qa-cyan-ink);
}

/* ── helper note under a channel ─────────────────────────────────────────── */
.qa-note {
    display: flex;
    align-items: flex-start;
    gap: 7px;
    margin: 7px 0 0 2px;
    font-size: var(--qa-fs-sm);
    line-height: 1.45;
    color: var(--qa-ink-3);
}

    .qa-note svg {
        flex: none;
        width: 14px;
        height: 14px;
        margin-top: 2px;
        color: var(--qa-faint);
    }

.qa-note.is-ok {
    color: var(--qa-good);
}

    .qa-note.is-ok svg {
        color: var(--qa-good);
    }

.qa-note.is-bad {
    color: var(--qa-bad);
}

    .qa-note.is-bad svg {
        color: var(--qa-bad);
    }

/* ── two-column field grid ───────────────────────────────────────────────── */
.qa-grid2 {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--qa-sp-3);
}

    .qa-grid2 > .qa-span2 {
        grid-column: 1 / -1;
    }

/* ── the honeypot. Off-screen, not display:none — a bot that reads computed
      style skips hidden fields, and skipping it is the whole tell. ───────── */
.qa-hp {
    position: absolute;
    left: -9999px;
    top: auto;
    width: 1px;
    height: 1px;
    overflow: hidden;
    opacity: 0;
    pointer-events: none;
}


/* ============================================================================
   13C-2 · THE COMBOBOX
   ----------------------------------------------------------------------------
   WHY THIS EXISTS AND A <select> DOES NOT
     A native select's POPUP is drawn by the operating system, outside the
     page. No stylesheet can reach it: on Windows it renders as a white
     system list in the system font, which is exactly the object that looked
     wrong sitting under a dark console. It is also unsearchable, and this
     page has ~250 countries in one of them.

     So the control is ours. The trigger is a normal channel strip — same
     rail, same lamp, same filament underline — and the list is a THRESHOLD
     panel. Typing filters. The keyboard works the way a listbox is supposed
     to. Both are things a native select cannot be made to do.

   ONE COMPONENT, THREE JOBS
     Country, Province and the address suggestions all use this. The address
     one differs only in where its rows come from (Google, debounced) and in
     carrying the attribution Google's Places policy requires when results
     are shown without a Google map.
   ============================================================================ */
.qa-combo {
    position: relative; /* the popup hangs off this, NOT off .qa-ch, which
                           clips its own overflow to keep the rail inside the
                           channel's rounded corner */
    min-width: 0;
}

.qa-combo__pop {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    right: 0;
    z-index: 40;
    max-height: 17rem;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 5px;
    border-radius: var(--qa-r-ch);
    border: 1px solid var(--qa-hair-2);
    background: linear-gradient(180deg, rgba(22,31,45,.98), rgba(11,16,24,.99));
    box-shadow: 0 1px 0 var(--qa-edge-light) inset, 0 26px 60px -22px rgba(0, 0, 0, .95);
    animation: qa-pop-in 160ms var(--qa-in) both;
    -webkit-overflow-scrolling: touch;
}

@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
    .qa-combo__pop {
        -webkit-backdrop-filter: blur(22px) saturate(120%);
        backdrop-filter: blur(22px) saturate(120%);
    }
}

@keyframes qa-pop-in {
    from {
        opacity: 0;
        transform: translateY(-6px) scale(.985);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

.qa-combo__pop[hidden] {
    display: none;
}

/* ── LIFTING THE PANE STACK SO A MENU IS ACTUALLY ON TOP ──────────────────────
   The popup's z-index is worth nothing on its own here. Every direct child of
   .qa-stagger carries a pinned transform (qa-rise runs with fill-mode `both`,
   which it must: .qa-arm holds those children at opacity 0, so dropping the
   fill would blank the console). A pinned transform is a stacking context, so
   .qa-panes, .qa-status and .qa-console__foot are three PEER contexts at the
   same level — and among peers, DOM order decides. The two footer rows come
   after the panes, so they painted straight over an open menu: on Setup step 1
   the "how did you hear about us" list had "STRIPE CHECKOUT · CANCEL ANYTIME"
   and "Need a hand?" lying across its options, which reads as a transparent
   menu but is really a menu underneath.

   Raising the panes only while a menu is open keeps the resting order intact
   (the footer legitimately sits above nothing) and costs one repaint. overflow
   is forced open in the same breath because .qa-panes clips by default for its
   height transition, and a clipped menu is the same bug wearing a hat. */
.qa-panes:has(.qa-combo__pop:not([hidden])),
.qa-pane:has(.qa-combo__pop:not([hidden])) {
    position: relative;
    z-index: 60;
    overflow: visible;
}

.qa-combo__opt {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--qa-sp-3);
    padding: 9px 11px;
    border-radius: 8px;
    font-size: var(--qa-fs-body);
    color: var(--qa-ink-2);
    cursor: pointer;
    scroll-margin: 6px;
}

    /* Hover and keyboard-active are ONE look. Two different highlights in one
       list is how a person loses track of what Enter will pick. */
    .qa-combo__opt:hover,
    .qa-combo__opt.is-active {
        background: var(--qa-cyan-soft);
        color: var(--qa-ink);
        box-shadow: inset 0 0 0 1px var(--qa-cyan-line);
    }

    .qa-combo__opt.is-picked {
        color: var(--qa-ink);
    }

.qa-combo__main {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.qa-combo__sub {
    display: block;
    margin-top: 1px;
    font-size: var(--qa-fs-sm);
    color: var(--qa-faint);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* the ISO code — a measured value, so mono (LAW 4) */
.qa-combo__code {
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    font-weight: 500;
    letter-spacing: .06em;
    color: var(--qa-faint);
}

.qa-combo__opt.is-active .qa-combo__code,
.qa-combo__opt:hover .qa-combo__code {
    color: var(--qa-cyan-ink);
}

/* A popup with nothing to pick is a MESSAGE, and a message must not be a
   click target: left as one it swallows the click meant for whatever field it
   happens to be covering, and the person is stuck poking at the word "No
   match" wondering why the next field will not focus. The panel itself opts
   out, not just the text inside it — the panel's own padding is a target too. */
.qa-combo__pop.is-empty,
.qa-combo__empty,
.qa-combo__attrib {
    pointer-events: none;
}

.qa-combo__empty {
    padding: 12px 11px;
    font-size: var(--qa-fs-sm);
    color: var(--qa-faint);
}

/* Google's Places policy requires this wherever results are shown without a
   Google map. It is not decoration and it is not optional. */
.qa-combo__attrib {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 5px;
    margin-top: 4px;
    padding: 6px 11px 2px;
    border-top: 1px solid var(--qa-hair);
    font-family: var(--qa-mono);
    font-size: 10px;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--qa-faint);
}

/* the channel spins while Google is being asked */
.qa-ch__spin {
    flex: none;
    width: 13px;
    height: 13px;
    border-radius: 50%;
    border: 1.5px solid var(--qa-hair-3);
    border-top-color: var(--qa-cyan);
    animation: qa-spin .7s linear infinite;
}

/* a combobox input must not look typed-into when it is only a trigger */
.qa-ch--combo .qa-ch__value {
    cursor: pointer;
}

    .qa-ch--combo .qa-ch__value:focus {
        cursor: text;
    }


/* ============================================================================
   13C-3 · THE VERIFY LAMP
   ----------------------------------------------------------------------------
   One control, four states, in the slot the channel lamp already occupies —
   so a verified field and an ordinary armed field sit on exactly the same
   axis and the eye can scan the column.
   ============================================================================ */
.qa-verify {
    flex: none;
    display: grid;
    place-items: center;
    width: 18px;
    height: 18px;
}

    .qa-verify > * {
        grid-area: 1 / 1;
    }

.qa-verify__dot {
    width: var(--qa-h-lamp);
    height: var(--qa-h-lamp);
    border-radius: 50%;
    background: var(--qa-faint);
    opacity: .5;
    transition: background var(--qa-t) var(--qa-ease), opacity var(--qa-t) var(--qa-ease);
}

.qa-verify__spin,
.qa-verify__ok,
.qa-verify__bad {
    display: none;
}

.qa-verify__ok {
    width: 16px;
    height: 16px;
    color: var(--qa-good);
}

.qa-verify__bad {
    width: 15px;
    height: 15px;
    color: var(--qa-bad);
}

.qa-verify__spin {
    width: 13px;
    height: 13px;
    border-radius: 50%;
    border: 1.5px solid var(--qa-hair-3);
    border-top-color: var(--qa-cyan);
    animation: qa-spin .7s linear infinite;
}

/* armed = something typed, nothing checked yet */
.qa-ch.is-armed .qa-verify__dot {
    background: var(--qa-cyan);
    opacity: 1;
    box-shadow: 0 0 0 3px var(--qa-cyan-soft);
}

.qa-ch[data-verify="checking"] .qa-verify__dot,
.qa-ch[data-verify="ok"] .qa-verify__dot,
.qa-ch[data-verify="bad"] .qa-verify__dot {
    display: none;
}

.qa-ch[data-verify="checking"] .qa-verify__spin {
    display: block;
}

.qa-ch[data-verify="ok"] .qa-verify__ok {
    display: block;
    animation: qa-tick 320ms var(--qa-spring) both;
}

.qa-ch[data-verify="bad"] .qa-verify__bad {
    display: block;
}

@keyframes qa-tick {
    from {
        opacity: 0;
        transform: scale(.4);
    }

    to {
        opacity: 1;
        transform: scale(1);
    }
}

/* a verified channel wears the green, not the cyan — cyan is LIVE, and this
   is a settled verdict rather than a thing currently happening */
.qa-ch[data-verify="ok"] {
    box-shadow: inset 0 0 0 1px var(--qa-good-line);
}

    .qa-ch[data-verify="ok"] .qa-ch__rail {
        background: var(--qa-good);
        transform: scaleY(1);
    }

.qa-ch[data-verify="bad"] {
    box-shadow: inset 0 0 0 1px var(--qa-bad-line);
}

    .qa-ch[data-verify="bad"] .qa-ch__rail {
        background: var(--qa-bad);
        transform: scaleY(1);
    }

    .qa-ch[data-verify="bad"] .qa-ch__label {
        color: var(--qa-bad);
    }


/* ============================================================================
   13D · CHECKBOX
   ============================================================================ */
.qa-check {
    position: relative; /* anchors the visually-hidden input — see .qa-switch */
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
    gap: var(--qa-sp-3);
    cursor: pointer;
    font-size: var(--qa-fs-sm);
    line-height: 1.5;
    color: var(--qa-ink-2);
}

    .qa-check input {
        position: absolute;
        opacity: 0;
        width: 1px;
        height: 1px;
        margin: -1px;
        pointer-events: none;
    }

.qa-check__box {
    position: relative;
    width: 18px;
    height: 18px;
    margin-top: 1px;
    border-radius: 5px;
    background: var(--qa-well);
    box-shadow: inset 0 0 0 1px var(--qa-hair-2);
    transition: background var(--qa-t) var(--qa-ease), box-shadow var(--qa-t) var(--qa-ease);
}

    .qa-check__box svg {
        position: absolute;
        inset: 2px;
        color: var(--qa-on-btn);
        opacity: 0;
        transform: scale(.5);
        transition: opacity var(--qa-t) var(--qa-ease), transform 200ms var(--qa-spring);
    }

.qa-check input:checked + .qa-check__box {
    background: var(--qa-cyan);
    box-shadow: inset 0 0 0 1px var(--qa-cyan), 0 0 0 3px var(--qa-cyan-soft);
}

    .qa-check input:checked + .qa-check__box svg {
        opacity: 1;
        transform: scale(1);
    }

.qa-check input:focus-visible + .qa-check__box {
    outline: 2px solid var(--qa-cyan);
    outline-offset: 3px;
}

.qa-check.is-bad .qa-check__box {
    box-shadow: inset 0 0 0 1px var(--qa-bad-line), 0 0 0 3px var(--qa-bad-soft);
}


/* ============================================================================
   13E · NOTICE
   ============================================================================ */
.qa-notice {
    display: none;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--qa-sp-3);
    margin-top: var(--qa-sp-4);
    padding: var(--qa-sp-3) var(--qa-sp-4);
    border-radius: var(--qa-r-ch);
    border: 1px solid var(--qa-warn-line);
    background: var(--qa-warn-soft);
    font-size: var(--qa-fs-sm);
    line-height: 1.5;
}

    .qa-notice.is-shown {
        display: grid;
        animation: qa-alert-in 260ms var(--qa-in) both;
    }

    .qa-notice svg {
        flex: none;
        width: 16px;
        height: 16px;
        margin-top: 2px;
        color: var(--qa-warn);
    }

.qa-notice__t {
    font-weight: 600;
    color: var(--qa-warn);
}

.qa-notice__b {
    margin-top: 3px;
    color: var(--qa-ink-2);
}


/* ============================================================================
   13F · THE STRENGTH METER
   ----------------------------------------------------------------------------
   Five segments and five requirement chips. The chips are the honest part:
   a bar alone tells you "weak" without telling you what to do about it.
   Colour here is semantic, not spectrum — this is a measurement, and LAW 3's
   cyan is reserved for what is LIVE.
   ============================================================================ */
.qa-meter {
    margin-top: var(--qa-sp-3);
}

.qa-meter__bar {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 4px;
}

    .qa-meter__bar span {
        height: 3px;
        border-radius: 2px;
        background: var(--qa-hair-2);
        transition: background 240ms var(--qa-ease);
    }

.qa-meter__label {
    margin-top: 7px;
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    font-weight: 500;
    letter-spacing: var(--qa-track-chip);
    text-transform: uppercase;
    color: var(--qa-ink-3);
}

/* s1..s5 — how many segments are lit, and in what colour */
.qa-meter.s1 .qa-meter__bar span:nth-child(-n+1),
.qa-meter.s2 .qa-meter__bar span:nth-child(-n+2) {
    background: var(--qa-bad);
}

.qa-meter.s3 .qa-meter__bar span:nth-child(-n+3) {
    background: var(--qa-warn);
}

.qa-meter.s4 .qa-meter__bar span:nth-child(-n+4) {
    background: #7DD3A8;
}

.qa-meter.s5 .qa-meter__bar span {
    background: var(--qa-good);
}

.qa-meter.s1 .qa-meter__label,
.qa-meter.s2 .qa-meter__label {
    color: var(--qa-bad);
}

.qa-meter.s3 .qa-meter__label {
    color: var(--qa-warn);
}

.qa-meter.s4 .qa-meter__label,
.qa-meter.s5 .qa-meter__label {
    color: var(--qa-good);
}

.qa-reqs {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
    margin: var(--qa-sp-3) 0 0;
    padding: 0;
    list-style: none;
}

    .qa-reqs li {
        display: inline-flex;
        align-items: center;
        gap: 5px;
        padding: 3px 9px 3px 7px;
        border-radius: var(--qa-r-pill);
        border: 1px solid var(--qa-hair);
        background: rgba(148, 180, 214, .03);
        font-family: var(--qa-mono);
        font-size: 10px;
        font-weight: 500;
        letter-spacing: .08em;
        text-transform: uppercase;
        color: var(--qa-faint);
        white-space: nowrap;
        transition: color var(--qa-t) var(--qa-ease), border-color var(--qa-t) var(--qa-ease), background var(--qa-t) var(--qa-ease);
    }

        .qa-reqs li svg {
            width: 10px;
            height: 10px;
            opacity: .4;
            transition: opacity var(--qa-t) var(--qa-ease);
        }

        .qa-reqs li.is-ok {
            color: var(--qa-good);
            border-color: var(--qa-good-line);
            background: var(--qa-good-soft);
        }

            .qa-reqs li.is-ok svg {
                opacity: 1;
            }


/* ============================================================================
   13G · THE CODE CELLS   (one-time code entry)
   ----------------------------------------------------------------------------
   Six lanes, not six boxes: the same language as the channel strip, at digit
   scale. A filled cell lights its underline; the live cell lights the whole
   filament. Reading a code off a phone is the most error-prone moment in the
   whole flow, so the type is the largest in the system.
   ============================================================================ */
.qa-otp {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: var(--qa-sp-2);
    margin: var(--qa-sp-6) 0 var(--qa-sp-5);
}

    .qa-otp input {
        width: 100%;
        height: 62px;
        padding: 0;
        text-align: center;
        appearance: none;
        -webkit-appearance: none;
        border: 0;
        border-radius: var(--qa-r-ch);
        background: var(--qa-well);
        box-shadow: inset 0 0 0 1px var(--qa-hair), inset 0 -1px 0 0 var(--qa-hair-2);
        color: var(--qa-ink);
        font-family: var(--qa-mono);
        font-size: 1.5rem;
        font-weight: 500;
        font-variant-numeric: tabular-nums;
        caret-color: var(--qa-cyan);
        transition: background var(--qa-t) var(--qa-ease), box-shadow var(--qa-t) var(--qa-ease), transform var(--qa-t) var(--qa-spring);
    }

        .qa-otp input::-webkit-outer-spin-button,
        .qa-otp input::-webkit-inner-spin-button {
            -webkit-appearance: none;
            margin: 0;
        }

        .qa-otp input.is-filled {
            background: var(--qa-well-live);
            box-shadow: inset 0 0 0 1px var(--qa-hair-2), inset 0 -2px 0 0 var(--qa-cyan);
        }

        .qa-otp input:focus {
            outline: none;
            background: var(--qa-well-live);
            box-shadow: inset 0 0 0 1px var(--qa-cyan-line), inset 0 -2px 0 0 var(--qa-cyan), 0 0 24px -8px rgba(34, 211, 238, .6);
            transform: translateY(-2px);
        }

.qa-otp.is-bad input {
    box-shadow: inset 0 0 0 1px var(--qa-bad-line), inset 0 -2px 0 0 var(--qa-bad);
}


/* ============================================================================
   13H · GHOST BUTTON + ACTION ROW
   ============================================================================ */
.qa-btn--ghost {
    width: auto;
    background: transparent;
    color: var(--qa-ink-2);
    box-shadow: inset 0 0 0 1px var(--qa-hair-2);
    font-weight: 500;
}

    .qa-btn--ghost:hover {
        color: var(--qa-ink);
        background: rgba(148, 180, 214, .06);
        box-shadow: inset 0 0 0 1px var(--qa-hair-3);
    }

.qa-btn--go {
    width: auto;
    min-width: 11rem;
    margin-top: 0;
}

.qa-btn--block {
    width: 100%;
}

.qa-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--qa-sp-3);
    margin-top: var(--qa-sp-6);
}

.qa-actions--center {
    justify-content: center;
    gap: var(--qa-sp-6);
    margin-top: var(--qa-sp-4);
}

/* a quiet text button — "resend", "change email" */
.qa-textbtn {
    border: 0;
    background: none;
    padding: 0;
    font-size: var(--qa-fs-sm);
    color: var(--qa-cyan-ink);
    cursor: pointer;
    transition: color var(--qa-t) var(--qa-ease);
}

    .qa-textbtn:hover {
        color: #8FEBF9;
    }

    .qa-textbtn[disabled] {
        color: var(--qa-faint);
        cursor: default;
        font-family: var(--qa-mono);
        font-size: var(--qa-fs-chip);
        letter-spacing: var(--qa-track-chip);
        text-transform: uppercase;
    }


/* ============================================================================
   13I · PANES   (one step becoming the next)
   ----------------------------------------------------------------------------
   The inactive panes are display:none, so only the live step is in the tab
   order and in the accessibility tree — a stepper that leaves hidden fields
   focusable is a stepper that traps keyboard users.

   The wrapper's height is animated by the script (measure, pin, transition,
   release). If that fails for any reason the height simply stays auto and
   the step still changes: the morph is polish, never load-bearing.
   ============================================================================ */
.qa-panes {
    position: relative;
    overflow: hidden;
    transition: height var(--qa-t-slow) var(--qa-in);
}

    .qa-panes.is-fluid {
        height: auto !important;
        overflow: visible;
    }

.qa-pane {
    display: none;
}

    .qa-pane.is-active {
        display: block;
        animation: qa-pane-in 480ms var(--qa-in) both;
    }

@keyframes qa-pane-in {
    from {
        opacity: 0;
        transform: translate3d(0, 18px, -70px);
        filter: blur(7px);
    }

    to {
        opacity: 1;
        transform: none;
        filter: blur(0);
    }
}

.qa-pane__title {
    margin: 0;
    font-size: 1.4375rem;
    font-weight: 650;
    letter-spacing: -.022em;
    line-height: 1.2;
    color: var(--qa-ink);
}

.qa-pane__desc {
    margin: 7px 0 0;
    font-size: var(--qa-fs-body);
    line-height: 1.55;
    color: var(--qa-ink-2);
}


/* ============================================================================
   13J · THE COMPLETION
   ============================================================================ */
.qa-done {
    text-align: center;
    padding: var(--qa-sp-4) 0 var(--qa-sp-2);
}

.qa-done__mark {
    width: 74px;
    height: 74px;
    margin: 0 auto var(--qa-sp-5);
}

.qa-done__badge {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 4px 12px 4px 9px;
    border-radius: var(--qa-r-pill);
    border: 1px solid var(--qa-good-line);
    background: var(--qa-good-soft);
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    font-weight: 500;
    letter-spacing: var(--qa-track-chip);
    text-transform: uppercase;
    color: var(--qa-good);
}

    .qa-done__badge svg {
        width: 12px;
        height: 12px;
    }

.qa-done__title {
    margin: var(--qa-sp-5) 0 0;
    font-size: 1.75rem;
    font-weight: 700;
    letter-spacing: -.03em;
    color: var(--qa-ink);
}

.qa-done__sub {
    margin: var(--qa-sp-3) auto 0;
    max-width: 30rem;
    font-size: var(--qa-fs-body);
    line-height: 1.6;
    color: var(--qa-ink-2);
}

.qa-done .qa-btn--primary {
    margin-top: var(--qa-sp-7);
}


/* ============================================================================
   13L · THE PROGRESS HAIRLINE
   ----------------------------------------------------------------------------
   Below 1024px the intake rail is gone with the rest of the narrative half,
   so progress needs somewhere to live. It is a 2px filament under the console
   header — NOT a second copy of the station list. A stepper rendered twice is
   two things to keep in sync and two things for a screen reader to read; the
   header already says STEP 02 / 04 in words, so this only has to show the
   shape of the journey.
   ============================================================================ */
.qa-progress {
    display: none;
    height: 2px;
    margin: 0 0 var(--qa-sp-2);
    border-radius: 2px;
    background: var(--qa-hair);
    overflow: hidden;
}

.qa-progress__fill {
    display: block;
    height: 100%;
    width: 0;
    border-radius: 2px;
    background: var(--qa-filament);
    transition: width var(--qa-t-slow) var(--qa-in);
}


/* ============================================================================
   13K · THE MARK BUILDER
   ----------------------------------------------------------------------------
   Four blades, one per step. This concept is inherited from the page this
   replaces and kept on purpose — it was never the problem. On the old light
   ground an unlit blade had to be a washed grey, so the mark read as a dead
   smudge until the very last step. On the void an unlit blade is a ghost at
   14%, and lighting one is an event.
   ============================================================================ */
/* An unlit blade is DRAWN, not faded out. At 14% it read as a smudge — the
   mark looked broken rather than unfinished, which is the single thing the
   old page got most wrong. Desaturated to clean slate geometry at 38% it
   reads as an outline waiting to be filled, and lighting one is a colour
   event against it. */
.qa-mark--build polygon {
    opacity: .38;
    filter: saturate(0) brightness(2.1);
    transform-origin: 50% 50%;
    transition: opacity 420ms var(--qa-in), filter 420ms var(--qa-in);
}

    .qa-mark--build polygon.is-lit {
        opacity: 1;
        filter: none;
    }

    .qa-mark--build polygon.is-pop {
        animation: qa-blade-pop 620ms var(--qa-spring);
    }

@keyframes qa-blade-pop {
    0% {
        transform: scale(1);
    }

    35% {
        transform: scale(1.16);
    }

    100% {
        transform: scale(1);
    }
}

/* the completed mark, assembling from scattered pieces */
.qa-done__mark polygon {
    opacity: 1;
    filter: none;
    transition: transform 760ms var(--qa-in), opacity 520ms var(--qa-ease);
}

.qa-done__mark.is-scattered polygon {
    opacity: 0;
}

.qa-done__mark.is-scattered .qa-b1 {
    transform: translate(-38px, -30px) rotate(-16deg);
}

.qa-done__mark.is-scattered .qa-b2 {
    transform: translate(38px, -30px) rotate(16deg);
}

.qa-done__mark.is-scattered .qa-b3 {
    transform: translate(-38px, 30px) rotate(16deg);
}

.qa-done__mark.is-scattered .qa-b4 {
    transform: translate(38px, 30px) rotate(-16deg);
}


/* ############################################################################
   THRESHOLD v1.5 — THE PLAN VOCABULARY
   ----------------------------------------------------------------------------
   Everything below serves Setup's third step, where a clinic picks seats and
   add-ons and watches a bill assemble itself.

   THE RULE THIS SECTION IS BUILT AROUND
     Not one number here is invented, derived or reformatted by CSS. Every
     figure on that step comes out of QlynicPricingService via the plans JSON
     and is computed by the same JavaScript that shipped before — the same
     cadence normalisation, the same single-discount rule that mirrors
     ChooseCheckoutCoupon, the same regional scaling. What is new is only how
     those numbers ARRIVE: a seat dial you can feel, allowances that visibly
     recount, a ledger whose rows slide in as you switch things on.

     "Live" is motion and responsiveness here, deliberately — never a new
     figure. A page that invents a saving, a projection or a conversion is a
     page that can disagree with the Stripe invoice on the next screen, and
     that is the one thing this step must never do.
   ############################################################################ */


/* ============================================================================
   13M · SEGMENTED CONTROL   (monthly / annual, practice size)
   ============================================================================ */
.qa-seg {
    display: inline-flex;
    padding: 3px;
    border-radius: var(--qa-r-ctl);
    background: var(--qa-well);
    box-shadow: inset 0 0 0 1px var(--qa-hair);
}

.qa-seg--full {
    display: flex;
    width: 100%;
}

.qa-seg__opt {
    flex: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    min-height: 34px;
    padding: 0 var(--qa-sp-4);
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--qa-ink-3);
    font-size: var(--qa-fs-sm);
    font-weight: 500;
    white-space: nowrap;
    cursor: pointer;
    transition: color var(--qa-t) var(--qa-ease), background var(--qa-t) var(--qa-ease), box-shadow var(--qa-t) var(--qa-ease);
}

    .qa-seg__opt:hover {
        color: var(--qa-ink);
    }

    .qa-seg__opt.is-sel {
        background: var(--qa-console-2);
        color: var(--qa-ink);
        box-shadow: 0 1px 0 var(--qa-edge-light) inset, 0 1px 3px rgba(0, 0, 0, .4);
    }

.qa-seg__save {
    padding: 2px 6px;
    border-radius: var(--qa-r-pill);
    background: var(--qa-good-soft);
    border: 1px solid var(--qa-good-line);
    font-family: var(--qa-mono);
    font-size: 9.5px;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--qa-good);
}


/* ============================================================================
   13N · CHOICE CHIPS   (practice type)
   ============================================================================ */
.qa-chips {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
    gap: var(--qa-sp-2);
}

.qa-chip-opt {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 11px 12px;
    border-radius: var(--qa-r-ch);
    background: var(--qa-well);
    box-shadow: inset 0 0 0 1px var(--qa-hair);
    font-size: var(--qa-fs-sm);
    font-weight: 500;
    color: var(--qa-ink-2);
    cursor: pointer;
    transition: background var(--qa-t) var(--qa-ease), box-shadow var(--qa-t) var(--qa-ease), color var(--qa-t) var(--qa-ease);
}

    .qa-chip-opt svg {
        flex: none;
        width: 17px;
        height: 17px;
        color: var(--qa-faint);
        transition: color var(--qa-t) var(--qa-ease);
    }

    .qa-chip-opt:hover {
        background: rgba(148, 180, 214, .06);
        color: var(--qa-ink);
    }

    .qa-chip-opt.is-sel {
        background: var(--qa-cyan-soft);
        box-shadow: inset 0 0 0 1px var(--qa-cyan-line);
        color: var(--qa-ink);
    }

        .qa-chip-opt.is-sel svg {
            color: var(--qa-cyan);
        }


/* ============================================================================
   13O · THE STEPPER   (± counters)
   ============================================================================ */
.qa-step {
    display: inline-flex;
    align-items: center;
    border-radius: var(--qa-r-ctl);
    background: var(--qa-well);
    box-shadow: inset 0 0 0 1px var(--qa-hair);
    overflow: hidden;
}

    .qa-step button {
        width: 36px;
        height: 36px;
        border: 0;
        background: transparent;
        color: var(--qa-ink-2);
        font-size: 1rem;
        line-height: 1;
        cursor: pointer;
        transition: background var(--qa-t) var(--qa-ease), color var(--qa-t) var(--qa-ease);
    }

        .qa-step button:hover {
            background: rgba(148, 180, 214, .09);
            color: var(--qa-ink);
        }

.qa-step__v {
    min-width: 2.5rem;
    text-align: center;
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-body);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--qa-ink);
}

.qa-step--sm button {
    width: 30px;
    height: 30px;
}

.qa-step--sm .qa-step__v {
    min-width: 2rem;
    font-size: var(--qa-fs-sm);
}


/* ============================================================================
   13P · THE SEAT DIAL
   ----------------------------------------------------------------------------
   The one control on this page a clinic actually drags, and every figure
   below it moves when they do. It stays a real <input type="range"> — the
   calculator binds to it, and a native range is the only version that a
   keyboard, a screen reader and a touch screen all already understand.
   Only the skin is ours.
   ============================================================================ */
.qa-dial {
    margin-top: var(--qa-sp-4);
}

.qa-dial__head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--qa-sp-4);
    margin-bottom: var(--qa-sp-4);
}

.qa-dial__label {
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-channel);
    font-weight: 500;
    letter-spacing: var(--qa-track-channel);
    text-transform: uppercase;
    color: var(--qa-ink-3);
}

.qa-dial__read {
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.qa-dial__n {
    font-family: var(--qa-mono);
    font-size: 2.125rem;
    font-weight: 600;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    letter-spacing: -.02em;
    color: var(--qa-ink);
}

.qa-dial__unit {
    font-size: var(--qa-fs-sm);
    color: var(--qa-ink-3);
}

.qa-range {
    -webkit-appearance: none;
    appearance: none;
    display: block;
    width: 100%;
    height: 26px;
    background: transparent;
    cursor: pointer;
}

    /* WebKit and Firefox will not accept a shared selector list for these —
       one unknown pseudo-element invalidates the whole rule — so the track
       and thumb are declared once per engine on purpose. */
    .qa-range::-webkit-slider-runnable-track {
        height: 4px;
        border-radius: 3px;
        background: linear-gradient(90deg, var(--qa-cyan) 0%, var(--qa-azure) var(--fill, 0%), var(--qa-hair-2) var(--fill, 0%));
    }

    .qa-range::-moz-range-track {
        height: 4px;
        border-radius: 3px;
        background: var(--qa-hair-2);
    }

    .qa-range::-moz-range-progress {
        height: 4px;
        border-radius: 3px;
        background: linear-gradient(90deg, var(--qa-cyan), var(--qa-azure));
    }

    .qa-range::-webkit-slider-thumb {
        -webkit-appearance: none;
        width: 22px;
        height: 22px;
        margin-top: -9px;
        border-radius: 50%;
        background: linear-gradient(180deg, #FFFFFF, #DCE6F2);
        border: 0;
        box-shadow: 0 0 0 1px rgba(0, 0, 0, .5), 0 2px 8px rgba(0, 0, 0, .55), 0 0 0 6px var(--qa-cyan-soft);
        cursor: grab;
        transition: box-shadow var(--qa-t) var(--qa-ease), transform var(--qa-t) var(--qa-spring);
    }

    .qa-range::-moz-range-thumb {
        width: 22px;
        height: 22px;
        border-radius: 50%;
        background: linear-gradient(180deg, #FFFFFF, #DCE6F2);
        border: 0;
        box-shadow: 0 0 0 1px rgba(0, 0, 0, .5), 0 2px 8px rgba(0, 0, 0, .55), 0 0 0 6px var(--qa-cyan-soft);
        cursor: grab;
    }

    .qa-range:hover::-webkit-slider-thumb {
        transform: scale(1.08);
        box-shadow: 0 0 0 1px rgba(0, 0, 0, .5), 0 2px 10px rgba(0, 0, 0, .6), 0 0 0 9px var(--qa-cyan-soft);
    }

    .qa-range:active::-webkit-slider-thumb {
        cursor: grabbing;
    }

    .qa-range:focus-visible::-webkit-slider-thumb {
        box-shadow: 0 0 0 1px rgba(0, 0, 0, .5), 0 0 0 4px var(--qa-cyan);
    }

    .qa-range:focus-visible::-moz-range-thumb {
        box-shadow: 0 0 0 1px rgba(0, 0, 0, .5), 0 0 0 4px var(--qa-cyan);
    }

.qa-dial__scale {
    display: flex;
    justify-content: space-between;
    margin-top: 2px;
    font-family: var(--qa-mono);
    font-size: 10px;
    letter-spacing: .06em;
    color: var(--qa-faint);
    font-variant-numeric: tabular-nums;
}


/* ============================================================================
   13Q · THE PLAN PANEL + ALLOWANCE READOUT
   ----------------------------------------------------------------------------
   Included credits are per-doctor and ride the dial. They are set through
   one function whose whole job is: if the text changed, re-run the tick. The
   tick is why the step feels live — five figures visibly recounting when a
   seat is added is the truth of what just happened.
   ============================================================================ */
.qa-plan {
    position: relative;
    margin-top: var(--qa-sp-5);
    padding: var(--qa-sp-5);
    border-radius: var(--qa-r-ch);
    border: 1px solid var(--qa-hair-2);
    background: linear-gradient(180deg, rgba(24,34,49,.55), rgba(11,17,26,.55));
    overflow: hidden;
}

    /* the filament again — the panel is the focal object of the step */
    .qa-plan::before {
        content: "";
        position: absolute;
        top: 0;
        left: 8%;
        right: 8%;
        height: 1px;
        background: var(--qa-filament);
        opacity: .6;
        -webkit-mask-image: linear-gradient(90deg, transparent, #000 30%, #000 70%, transparent);
        mask-image: linear-gradient(90deg, transparent, #000 30%, #000 70%, transparent);
    }

.qa-plan__tag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px 3px 7px;
    border-radius: var(--qa-r-pill);
    border: 1px solid var(--qa-cyan-line);
    background: var(--qa-cyan-soft);
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    font-weight: 500;
    letter-spacing: var(--qa-track-chip);
    text-transform: uppercase;
    color: var(--qa-cyan-ink);
}

    .qa-plan__tag svg {
        width: 11px;
        height: 11px;
    }

.qa-plan__price {
    display: flex;
    align-items: flex-start;
    gap: 3px;
    margin-top: var(--qa-sp-4);
    color: var(--qa-ink);
}

.qa-plan__cur {
    font-family: var(--qa-mono);
    font-size: 1.25rem;
    font-weight: 500;
    margin-top: .35rem;
}

.qa-plan__amt {
    font-family: var(--qa-mono);
    font-size: 2.75rem;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -.035em;
    font-variant-numeric: tabular-nums;
}

.qa-plan__suf {
    align-self: flex-end;
    margin-bottom: .3rem;
    margin-left: 5px;
    font-size: var(--qa-fs-sm);
    color: var(--qa-ink-3);
}

.qa-plan__note {
    margin-top: 7px;
    font-size: var(--qa-fs-sm);
    color: var(--qa-ink-3);
}

    .qa-plan__note b {
        color: var(--qa-ink);
        font-weight: 600;
    }

.qa-plan__note--region {
    color: var(--qa-good);
}

    .qa-plan__note--region b {
        color: var(--qa-good);
    }

.qa-allow__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--qa-sp-3);
    margin: var(--qa-sp-5) 0 var(--qa-sp-3);
    padding-top: var(--qa-sp-4);
    border-top: 1px solid var(--qa-hair);
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    font-weight: 500;
    letter-spacing: var(--qa-track-chip);
    text-transform: uppercase;
    color: var(--qa-ink-3);
}

.qa-pool {
    padding: 3px 9px;
    border-radius: var(--qa-r-pill);
    border: 1px solid var(--qa-hair-2);
    background: rgba(148, 180, 214, .05);
    color: var(--qa-ink-2);
    white-space: nowrap;
}

/* The allowance readout holds FIVE figures, and the grid's own background is
   what shows through any cell slot the five do not fill. auto-fit at three
   columns therefore left a sixth, empty, hairline-coloured rectangle sitting
   under "Telehealth min" — which reads as a sixth allowance that failed to
   load, exactly the wrong thing on the one panel that must never look like it
   is missing a number.

   Six tracks instead: the first three take two each, the last two take three
   each. Five figures, two rows, no hole, at every width down to the phone
   rule. The counts are hard-coded because the figure count is: these are the
   plan's five metered resources, not a list. */
.qa-allow {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 1px;
    background: var(--qa-hair);
    border: 1px solid var(--qa-hair);
    border-radius: var(--qa-r-ctl);
    overflow: hidden;
}

    .qa-allow__cell {
        grid-column: span 2;
    }

        .qa-allow__cell:nth-child(4),
        .qa-allow__cell:nth-child(5) {
            grid-column: span 3;
        }

.qa-allow__cell {
    display: grid;
    gap: 3px;
    padding: 11px 12px;
    background: var(--qa-console);
}

.qa-allow__k {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--qa-fs-sm);
    color: var(--qa-ink-3);
}

    .qa-allow__k svg {
        flex: none;
        width: 13px;
        height: 13px;
        color: var(--qa-faint);
    }

.qa-allow__v {
    font-family: var(--qa-mono);
    font-size: 1.0625rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    letter-spacing: -.01em;
    color: var(--qa-ink);
}

/* THE TICK — a figure that just changed lifts, brightens and settles. It is
   the whole reason moving the dial reads as the panel recalculating rather
   than as text being replaced. */
.qa-tick {
    animation: qa-tick-roll 420ms var(--qa-in);
}

@keyframes qa-tick-roll {
    0% {
        opacity: 0;
        transform: translateY(6px);
    }

    45% {
        color: var(--qa-cyan-ink);
    }

    100% {
        opacity: 1;
        transform: none;
    }
}


/* ============================================================================
   13R · ADD-ON CARDS
   ============================================================================ */
.qa-addons__h {
    margin: var(--qa-sp-6) 0 var(--qa-sp-3);
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    font-weight: 500;
    letter-spacing: var(--qa-track-chip);
    text-transform: uppercase;
    color: var(--qa-ink-3);
}

.qa-addon {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--qa-sp-3);
    padding: var(--qa-sp-4);
    margin-bottom: var(--qa-sp-2);
    border-radius: var(--qa-r-ch);
    background: var(--qa-well);
    box-shadow: inset 0 0 0 1px var(--qa-hair);
    cursor: pointer;
    transition: background var(--qa-t) var(--qa-ease), box-shadow var(--qa-t) var(--qa-ease);
}

    .qa-addon:hover {
        background: rgba(148, 180, 214, .055);
    }

    .qa-addon.is-on {
        background: var(--qa-cyan-soft);
        box-shadow: inset 0 0 0 1px var(--qa-cyan-line);
    }

.qa-addon__check {
    position: relative;
    width: 18px;
    height: 18px;
    margin-top: 2px;
    border-radius: 5px;
    background: rgba(148, 180, 214, .06);
    box-shadow: inset 0 0 0 1px var(--qa-hair-2);
    transition: background var(--qa-t) var(--qa-ease), box-shadow var(--qa-t) var(--qa-ease);
}

    .qa-addon__check svg {
        position: absolute;
        inset: 2px;
        color: var(--qa-on-btn);
        opacity: 0;
        transform: scale(.5);
        transition: opacity var(--qa-t) var(--qa-ease), transform 200ms var(--qa-spring);
    }

.qa-addon.is-on .qa-addon__check {
    background: var(--qa-cyan);
    box-shadow: inset 0 0 0 1px var(--qa-cyan);
}

    .qa-addon.is-on .qa-addon__check svg {
        opacity: 1;
        transform: scale(1);
    }

.qa-addon__top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--qa-sp-3);
}

.qa-addon__name {
    font-size: var(--qa-fs-body);
    font-weight: 600;
    color: var(--qa-ink);
}

.qa-addon__price {
    flex: none;
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-sm);
    font-weight: 500;
    color: var(--qa-ink-2);
    white-space: nowrap;
}

.qa-addon__desc {
    margin-top: 3px;
    font-size: var(--qa-fs-sm);
    line-height: 1.5;
    color: var(--qa-ink-3);
}

/* the per-unit row inside an add-on — clicks here must not toggle the card */
.qa-addon__units {
    display: none;
    align-items: center;
    justify-content: space-between;
    gap: var(--qa-sp-3);
    margin-top: var(--qa-sp-3);
    padding-top: var(--qa-sp-3);
    border-top: 1px solid var(--qa-hair-2);
    font-size: var(--qa-fs-sm);
    color: var(--qa-ink-2);
}

.qa-addon.is-on .qa-addon__units {
    display: flex;
}


/* ============================================================================
   13S · VOUCHER
   ============================================================================ */
.qa-voucher {
    display: flex;
    gap: var(--qa-sp-2);
    margin-top: var(--qa-sp-5);
}

    .qa-voucher input {
        flex: 1;
        min-width: 0;
        height: 42px;
        padding: 0 var(--qa-sp-4);
        border: 0;
        border-radius: var(--qa-r-ctl);
        background: var(--qa-well);
        box-shadow: inset 0 0 0 1px var(--qa-hair);
        color: var(--qa-ink);
        font-family: var(--qa-mono);
        font-size: var(--qa-fs-sm);
        letter-spacing: .08em;
        text-transform: uppercase;
    }

        .qa-voucher input::placeholder {
            font-family: var(--qa-font);
            letter-spacing: 0;
            text-transform: none;
            color: var(--qa-faint);
        }

        .qa-voucher input:focus {
            outline: none;
            box-shadow: inset 0 0 0 1px var(--qa-cyan-line), 0 0 22px -10px rgba(34, 211, 238, .6);
        }

    .qa-voucher .qa-btn {
        width: auto;
        height: 42px;
        flex: none;
        margin: 0;
    }

.qa-vmsg {
    margin-top: 7px;
    font-size: var(--qa-fs-sm);
    line-height: 1.45;
    color: var(--qa-ink-3);
}

.qa-vmsg.is-ok {
    color: var(--qa-good);
}

.qa-vmsg.is-bad {
    color: var(--qa-bad);
}


/* ============================================================================
   13T · THE LEDGER   (summary)
   ----------------------------------------------------------------------------
   A receipt, so it is set like one: labels in sans, every figure in mono and
   tabular so the column of numbers lines up on the decimal, and rows that
   appear as their add-on switches on rather than sitting there greyed.
   ============================================================================ */
.qa-ledger {
    margin-top: var(--qa-sp-6);
    padding: var(--qa-sp-5);
    border-radius: var(--qa-r-ch);
    border: 1px solid var(--qa-hair-2);
    background: linear-gradient(180deg, rgba(20,29,42,.62), rgba(9,14,21,.62));
}

.qa-ledger__row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--qa-sp-4);
    padding: 7px 0;
    font-size: var(--qa-fs-sm);
    color: var(--qa-ink-2);
}

    .qa-ledger__row b {
        font-family: var(--qa-mono);
        font-weight: 500;
        font-variant-numeric: tabular-nums;
        color: var(--qa-ink);
        white-space: nowrap;
    }

    .qa-ledger__row.is-shown {
        animation: qa-row-in 260ms var(--qa-in) both;
    }

@keyframes qa-row-in {
    from {
        opacity: 0;
        transform: translateY(-4px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

.qa-ledger__row--disc,
.qa-ledger__row--disc b {
    color: var(--qa-good);
}

.qa-ledger__div {
    height: 1px;
    margin: var(--qa-sp-3) 0;
    background: var(--qa-hair-2);
}

.qa-ledger__total {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--qa-sp-4);
}

.qa-ledger__tl {
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    font-weight: 500;
    letter-spacing: var(--qa-track-chip);
    text-transform: uppercase;
    color: var(--qa-ink-3);
}

.qa-ledger__tv {
    font-family: var(--qa-mono);
    font-size: 1.875rem;
    font-weight: 600;
    letter-spacing: -.03em;
    font-variant-numeric: tabular-nums;
    color: var(--qa-ink);
}

    .qa-ledger__tv small {
        margin-left: 3px;
        font-size: var(--qa-fs-sm);
        font-weight: 500;
        color: var(--qa-ink-3);
    }

.qa-ledger__annual {
    margin-top: 5px;
    text-align: right;
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-sm);
    color: var(--qa-ink-3);
}

.qa-ledger__refill {
    margin-top: var(--qa-sp-4);
    padding-top: var(--qa-sp-4);
    border-top: 1px solid var(--qa-hair);
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    letter-spacing: var(--qa-track-chip);
    text-transform: uppercase;
    color: var(--qa-faint);
}

    .qa-ledger__refill:empty {
        display: none;
    }

.qa-secure {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    margin-top: var(--qa-sp-3);
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    letter-spacing: var(--qa-track-chip);
    text-transform: uppercase;
    color: var(--qa-faint);
}

    .qa-secure svg {
        width: 12px;
        height: 12px;
    }


/* ============================================================================
   13U · EXPANDABLE PANEL   (the AHCIP form)
   ============================================================================ */
.qa-switchrow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--qa-sp-4);
    padding: var(--qa-sp-4);
    border-radius: var(--qa-r-ch);
    background: var(--qa-well);
    box-shadow: inset 0 0 0 1px var(--qa-hair);
    transition: background var(--qa-t) var(--qa-ease), box-shadow var(--qa-t) var(--qa-ease);
}

    .qa-switchrow.is-on {
        background: var(--qa-cyan-soft);
        box-shadow: inset 0 0 0 1px var(--qa-cyan-line);
    }

.qa-switchrow__t {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--qa-fs-body);
    font-weight: 600;
    color: var(--qa-ink);
}

    .qa-switchrow__t svg {
        width: 16px;
        height: 16px;
        color: var(--qa-cyan);
    }

.qa-switchrow__s {
    margin-top: 3px;
    font-size: var(--qa-fs-sm);
    color: var(--qa-ink-3);
}

/* grid-template-rows 0fr -> 1fr animates to CONTENT height with no measuring
   and no magic max-height that clips a long form on a narrow screen. */
.qa-exp {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--qa-t-slow) var(--qa-in);
}

    .qa-exp > * {
        overflow: hidden;
        min-height: 0;
    }

    .qa-exp.is-open {
        grid-template-rows: 1fr;
    }

.qa-fieldnote {
    display: none;
    margin-top: 7px;
    font-size: var(--qa-fs-sm);
    color: var(--qa-warn);
}

    .qa-fieldnote.is-shown {
        display: block;
    }

.qa-skipnote {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    margin-top: var(--qa-sp-4);
    padding: var(--qa-sp-3) var(--qa-sp-4);
    border-radius: var(--qa-r-ctl);
    background: rgba(148, 180, 214, .04);
    border: 1px solid var(--qa-hair);
    font-size: var(--qa-fs-sm);
    line-height: 1.5;
    color: var(--qa-ink-3);
}

    .qa-skipnote svg {
        flex: none;
        width: 15px;
        height: 15px;
        margin-top: 2px;
        color: var(--qa-faint);
    }

.qa-notice--info {
    border-color: var(--qa-cyan-line);
    background: var(--qa-cyan-soft);
}

    .qa-notice--info svg {
        color: var(--qa-cyan);
    }

.qa-notice--info .qa-notice__t {
    color: var(--qa-cyan-ink);
}

/* the clinic being set up — a fact, so mono */
.qa-clinicchip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-top: var(--qa-sp-5);
    padding: 5px 12px 5px 9px;
    border-radius: var(--qa-r-pill);
    border: 1px solid var(--qa-hair);
    background: rgba(148, 180, 214, .035);
    font-size: var(--qa-fs-sm);
    color: var(--qa-ink-3);
}

    .qa-clinicchip svg {
        flex: none;
        width: 13px;
        height: 13px;
        color: var(--qa-cyan);
    }

    .qa-clinicchip b {
        font-family: var(--qa-mono);
        font-size: var(--qa-fs-chip);
        font-weight: 500;
        letter-spacing: .04em;
        color: var(--qa-ink);
    }


/* ============================================================================
   14 · BUSY OVERLAY
   ----------------------------------------------------------------------------
   Contract preserved verbatim: #busyOverlay / .busy__text / .hidden, driven
   by window.QBusy. Only the skin is new.
   ============================================================================ */
.busy {
    position: fixed;
    inset: 0;
    z-index: var(--qa-z-busy);
    display: grid;
    place-items: center;
    background: rgba(4, 6, 11, .74);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    opacity: 1;
    transition: opacity 200ms var(--qa-ease);
}

    .busy.hidden {
        opacity: 0;
        pointer-events: none;
        visibility: hidden;
    }

.busy__box {
    display: grid;
    gap: var(--qa-sp-3);
    justify-items: center;
    padding: var(--qa-sp-6) var(--qa-sp-7);
    border-radius: var(--qa-r-ch);
    border: 1px solid var(--qa-hair-2);
    background: var(--qa-console);
    box-shadow: var(--qa-lift-sm);
    min-width: 15rem;
}

.busy__bar {
    position: relative;
    width: 100%;
    height: 2px;
    border-radius: 2px;
    background: var(--qa-hair);
    overflow: hidden;
}

    .busy__bar::after {
        content: "";
        position: absolute;
        inset: 0;
        width: 40%;
        border-radius: 2px;
        background: var(--qa-filament);
        animation: qa-bar 1.15s var(--qa-ease) infinite;
    }

@keyframes qa-bar {
    0% {
        transform: translateX(-105%);
    }

    100% {
        transform: translateX(255%);
    }
}

.busy__title {
    font-family: var(--qa-mono);
    font-size: var(--qa-fs-chip);
    letter-spacing: var(--qa-track-chip);
    text-transform: uppercase;
    color: var(--qa-faint);
}

.busy__text {
    font-size: var(--qa-fs-body);
    color: var(--qa-ink);
    text-align: center;
}


/* ============================================================================
   15 · THE ARRIVAL
   ----------------------------------------------------------------------------
   One choreography, ~1.4s, entirely transform + opacity. The console lands
   at ~1.15s and is typeable from frame one — nothing here gates input, and
   nothing here can cause layout.

   Everything starts hidden ONLY once `.qa-arm` is on <html>, which the page
   sets inline in <head>. A browser with JS off therefore renders the page
   fully visible and static rather than blank — the form still works.
   ============================================================================ */
.qa-arm .qa-anim {
    opacity: 0;
}

.qa-arm .qa-room__aurora,
.qa-arm .qa-room__plane,
.qa-arm .qa-threshold {
    opacity: 0;
}

.qa-arm .qa-room__horizon {
    transform: translateX(-50%) scaleX(0);
}

body.is-ready .qa-room__aurora {
    animation: qa-fade-in 900ms var(--qa-ease) 60ms both, qa-aurora-drift 46s var(--qa-ease) 960ms infinite alternate;
}

body.is-ready .qa-room__plane {
    animation: qa-plane-in 1500ms var(--qa-in) 90ms both;
}

/* The floor opens from nearly edge-on to its resting tilt: the room builds
   itself around the reader instead of fading in as a picture. */
@keyframes qa-plane-in {
    from {
        opacity: 0;
        --tilt: 82deg;
    }

    to {
        opacity: 1;
        --tilt: 68deg;
    }
}

/* @property lets --tilt interpolate. Without it the keyframe still applies
   the end value, so the room simply arrives already open — a graceful floor,
   not a broken one. */
@property --tilt {
    syntax: "<angle>";
    inherits: true;
    initial-value: 68deg;
}

@property --qa-px {
    syntax: "<number>";
    inherits: true;
    initial-value: 0;
}

@property --qa-py {
    syntax: "<number>";
    inherits: true;
    initial-value: 0;
}

body.is-ready .qa-room__horizon {
    animation: qa-horizon 1100ms var(--qa-in) 240ms both;
}

@keyframes qa-horizon {
    from {
        transform: translateX(-50%) scaleX(0);
        opacity: 0;
    }

    to {
        transform: translateX(-50%) scaleX(1);
        opacity: 1;
    }
}

body.is-ready .qa-threshold {
    animation: qa-thresh 900ms var(--qa-in) 320ms both;
}

@keyframes qa-thresh {
    from {
        opacity: 0;
        transform: scaleY(.2);
    }

    to {
        opacity: .5;
        transform: scaleY(1);
    }
}

/* ── the rail: line-level reveals ────────────────────────────────────────── */
body.is-ready .qa-anim {
    animation: qa-rise 720ms var(--qa-in) both;
    animation-delay: calc(var(--d, 0ms));
}

@keyframes qa-rise {
    from {
        opacity: 0;
        transform: translateY(14px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

body.is-ready .qa-line > span {
    animation: qa-lift 900ms var(--qa-in) both;
    animation-delay: calc(var(--d, 0ms));
}

@keyframes qa-lift {
    from {
        transform: translateY(105%);
    }

    to {
        transform: translateY(0);
    }
}

/* ── THE CONSOLE ARRIVAL — the 3D moment ─────────────────────────────────
   It comes out of the room's depth: 300px back, tipped 11deg, blurred, and
   settles onto the reader's plane. The blur is what sells the distance;
   without it a Z translate just reads as a scale.                        */
.qa-arm .qa-console {
    opacity: 0;
}

body.is-ready .qa-console {
    animation: qa-console-in 880ms var(--qa-in) 220ms both;
}

@keyframes qa-console-in {
    from {
        opacity: 0;
        transform: translate3d(0, 42px, -300px) rotateX(11deg) scale(.965);
        filter: blur(14px);
    }

    60% {
        opacity: 1;
    }

    to {
        opacity: 1;
        transform: translate3d(0, 0, 0) rotateX(0deg) scale(1);
        filter: blur(0);
    }
}

/* rows inside the console, staggered behind it */
.qa-arm .qa-stagger > * {
    opacity: 0;
}

body.is-ready .qa-stagger > * {
    animation: qa-rise 620ms var(--qa-in) both;
    animation-delay: calc(520ms + var(--i, 0) * 66ms);
}


/* ============================================================================
   16 · REACTIVE STATES
   ============================================================================ */

/* denial — three decaying strikes, not a cartoon wobble */
.qa-shake {
    animation: qa-shake 420ms var(--qa-ease) both;
}

@keyframes qa-shake {
    0% {
        transform: translateX(0);
    }

    18% {
        transform: translateX(-7px);
    }

    38% {
        transform: translateX(5px);
    }

    58% {
        transform: translateX(-3px);
    }

    78% {
        transform: translateX(2px);
    }

    100% {
        transform: translateX(0);
    }
}

/* THE HAND-OFF — on success the console recedes into the room it came from
   while the day accelerates. The reader passes through the threshold. */
body.is-handoff .qa-console {
    animation: qa-handoff 460ms cubic-bezier(.5, 0, .78, 0) both;
}

@keyframes qa-handoff {
    to {
        opacity: 0;
        transform: translate3d(0, -16px, 240px) scale(1.06);
        filter: blur(12px);
    }
}

body.is-handoff .qa-rail {
    animation: qa-fade-out 340ms var(--qa-ease) both;
}

@keyframes qa-fade-out {
    to {
        opacity: 0;
        transform: translateY(-10px);
    }
}

@keyframes qa-fade-in {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}


/* ============================================================================
   17 · RESPONSIVE
   ----------------------------------------------------------------------------
   Three real layouts, not one layout squeezed:
     >= 1024   the room  — rail beside console, full depth
     640-1023  the panel — console centred, rail condensed above it
     <  640    the device — console is the page, room reduced to a horizon
   ============================================================================ */

/* ── laptops with short viewports: tighten the vertical rhythm ───────────── */
@media (max-height: 780px) and (min-width: 1024px) {
    :root {
        --qa-fs-display: clamp(1.875rem, 1.2rem + 2vw, 2.75rem);
    }

    .qa-rail__brand {
        margin-bottom: var(--qa-sp-6);
    }

    .qa-readouts {
        margin-top: var(--qa-sp-6);
    }

    .qa-readout {
        padding: var(--qa-sp-3) 0;
    }

    .qa-telemetry {
        margin-top: var(--qa-sp-5);
    }
}

/* ── tablet / narrow laptop ──────────────────────────────────────────────── */
@media (max-width: 1023.98px) {
    .qa-deck {
        grid-template-columns: minmax(0, 1fr);
        justify-items: center;
        gap: var(--qa-sp-7);
        max-width: 34rem;
        align-content: center;
    }

    .qa-rail {
        text-align: center;
        transform: none;
        max-width: 30rem;
    }

    .qa-rail__brand {
        justify-content: center;
        margin-bottom: var(--qa-sp-5);
    }

    .qa-rail__title {
        margin-top: var(--qa-sp-4);
    }

    .qa-rail__sub {
        margin-inline: auto;
    }

    /* the readouts move BELOW the console on this layout — see the page file */
    .qa-rail .qa-readouts,
    .qa-rail .qa-telemetry {
        display: none;
    }

    .qa-consolewrap {
        width: 100%;
        max-width: var(--qa-console-w);
    }

    .qa-console {
        transform: none;
    }

    .qa-threshold {
        display: none;
    }

    .qa-room {
        perspective-origin: 50% 52%;
    }

    /* Raise the horizon out of the console's top edge. At 42% on a 3:4
       viewport the two landed within 3px of each other and the horizon
       read as a seam welded to the panel instead of a floor behind it. */
    .qa-room__plane--floor,
    .qa-room__horizon {
        top: 34%;
    }

    /* the rail's station list goes with the rail; the hairline takes over */
    .qa-rail .qa-stations {
        display: none;
    }

    .qa-progress {
        display: block;
    }
}

/* ── phones ──────────────────────────────────────────────────────────────── */
@media (max-width: 639.98px) {
    :root {
        --qa-fs-display: clamp(1.75rem, 1.35rem + 2vw, 2.125rem);
        --qa-console-w: 100%;
        --qa-r-panel: 18px;
        --qa-h-ctl: 52px; /* thumbs, not cursors */
    }

    .qa-deck {
        padding: max(var(--qa-sp-6), env(safe-area-inset-top)) var(--qa-sp-4) max(var(--qa-sp-6), env(safe-area-inset-bottom));
        gap: var(--qa-sp-6);
    }

    .qa-console {
        padding: var(--qa-sp-5) var(--qa-sp-5) var(--qa-sp-4);
    }

    /* 17px keeps the value large AND stays above the 16px line at which
       iOS Safari zooms the viewport on focus. Both matter.
       Set through the variable, so the autofill override picks the phone
       size up too and a restored identity is not 13px on a phone. */
    .qa-ch__value {
        --_fs: var(--qa-fs-value-m);
    }

    .qa-ch__value[type="password"] {
        --_fs: 1rem;
    }

    .qa-ch__pad {
        padding: 9px var(--qa-sp-4) 10px;
    }

    /* every tap target clears 44px without growing the visible chrome */
    .qa-eye {
        width: 44px;
        height: 44px;
        margin: -11px -12px -11px 0;
    }

    .qa-switch__track {
        width: 40px;
        height: 22px;
    }

        .qa-switch__track::after {
            width: 16px;
            height: 16px;
        }

    .qa-switch.is-on .qa-switch__track::after {
        transform: translateX(18px);
    }

    .qa-rail__sub {
        display: none; /* the headline carries it; screen space is dear */
    }

    .qa-rail__brand {
        margin-bottom: var(--qa-sp-4);
    }

    /* The header rule is decoration; the readout beside it is information.
       Keeping both truncated the readout to "NEW CLINIC · ACCO…" on every
       step. The rule yields its space and the readout fits whole. */
    /* The rule stops being DRAWN on a phone, but it must not stop existing:
       flex:1 is what holds the clock against the right edge. Removing it let
       the two collapse together, so the return page's header read as the
       single phrase "ACTIVE STRIPE" rather than a state and its source. */
    .qa-console__rule {
        background: transparent;
        min-width: var(--qa-sp-3);
    }

    /* The header line is name + rule + clock. On a phone there is no room for
       all three, and the ellipsis was truncating the one word that says where
       you are ("CLINIC SETUP · SUBSCR…"). The scope prefix goes; the step name
       and the counter stay. */
    .qa-console__pre {
        display: none;
    }

    /* the room reduces to a horizon so it never competes with the form */
    .qa-room__plane--ceil {
        display: none;
    }

    .qa-room__plane--floor {
        opacity: .55;
    }

    .qa-room__grain {
        opacity: .1;
    }

    /* ── multi-step, on a phone ──────────────────────────────────────────── */

    /* two columns of 150px is two columns of nothing; stack them */
    .qa-grid2 {
        grid-template-columns: minmax(0, 1fr);
    }

    /* six cells across 326px: 46px each, and the digit stays large because
       reading a code off a second device is where people mistype */
    .qa-otp {
        gap: 6px;
        margin: var(--qa-sp-5) 0 var(--qa-sp-4);
    }

        .qa-otp input {
            height: 54px;
            font-size: 1.25rem;
            border-radius: 10px;
        }

    /* Back shrinks to its glyph + word, Continue takes the rest of the row:
       the forward action is the one a thumb should find without aiming. */
    .qa-actions {
        margin-top: var(--qa-sp-5);
        gap: var(--qa-sp-2);
    }

    .qa-btn--ghost {
        flex: none;
        padding-inline: var(--qa-sp-4);
    }

    .qa-btn--go {
        flex: 1;
        min-width: 0;
    }

    .qa-pane__title {
        font-size: 1.3125rem;
    }

    .qa-done__mark {
        width: 62px;
        height: 62px;
    }

    .qa-done__title {
        font-size: 1.5rem;
    }

    .qa-reqs li {
        font-size: 9.5px;
        letter-spacing: .06em;
        padding: 3px 7px 3px 6px;
    }

    /* ── the plan step, on a phone ───────────────────────────────────────── */

    /* Two allowance cells per row: five across a 316px console is five columns
       of nothing, and these figures are the reason the panel exists. The fifth
       takes the whole last row rather than leaving a hole beside itself. */
    .qa-allow {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

        .qa-allow__cell,
        .qa-allow__cell:nth-child(4) {
            grid-column: span 1;
        }

            .qa-allow__cell:nth-child(5) {
                grid-column: span 2;
            }

    .qa-chips {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .qa-plan,
    .qa-ledger {
        padding: var(--qa-sp-4);
    }

    .qa-plan__amt {
        font-size: 2.25rem;
    }

    .qa-ledger__tv {
        font-size: 1.625rem;
    }

    .qa-dial__n {
        font-size: 1.875rem;
    }

    /* the whole point of a range on a touch screen is a thumb you can catch */
    .qa-range {
        height: 34px;
    }

        .qa-range::-webkit-slider-thumb {
            width: 26px;
            height: 26px;
            margin-top: -11px;
        }

        .qa-range::-moz-range-thumb {
            width: 26px;
            height: 26px;
        }

    .qa-addon__top {
        flex-wrap: wrap;
    }
}

@media (max-width: 400px) {
    /* one notch down keeps the guarantee line on ONE row at 390px, which is
       the whole point of it being a line rather than three badges */
    .qa-status {
        gap: 4px 5px;
        font-size: 10px;
        letter-spacing: .1em;
    }
}

/* ── touch: no hover affordances, and the parallax is meaningless ────────── */
@media (hover: none) {
    :root {
        --qa-px: 0;
        --qa-py: 0;
    }

    /* keyed to the INPUT, not the width — a 12" tablet has no ⌘↵ either */
    .qa-kbd {
        display: none;
    }

    .qa-console {
        transform: none;
    }

    .qa-rail {
        transform: none;
    }

    .qa-btn--primary::after {
        transform: scaleX(1); /* the filament is simply always lit */
    }
}


/* ============================================================================
   18 · ACCESS
   ============================================================================ */

/* Reduced motion is not "less animation" — it is NO motion. Every arrival
   collapses to a single opacity fade, every loop stops, the room stands
   still, and the parallax is switched off in the JS as well. Nothing about
   the layout or the information changes. */
@media (prefers-reduced-motion: reduce) {
    .qa *,
    .qa *::before,
    .qa *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }

    body.is-ready .qa-anim,
    body.is-ready .qa-console,
    body.is-ready .qa-stagger > *,
    body.is-ready .qa-room__aurora,
    body.is-ready .qa-room__plane,
    body.is-ready .qa-threshold {
        animation: qa-fade-in 220ms linear both !important;
    }

    body.is-ready .qa-line > span {
        animation: none !important;
        transform: none !important;
    }

    body.is-ready .qa-room__horizon {
        animation: none !important;
        transform: translateX(-50%) scaleX(1) !important;
        opacity: 1 !important;
    }

    .qa-console,
    .qa-rail {
        transform: none !important;
    }

    .qa-room__plane--floor {
        transform: rotateX(68deg) !important;
    }

    .qa-room__plane--ceil {
        transform: rotateX(-68deg) !important;
    }

    /* The height morph and the scatter are the two places where "no motion"
       has to mean "no state left behind", not just "instant". Both are
       neutralised at the source rather than sped up. */
    .qa-panes {
        transition: none !important;
        height: auto !important;
        overflow: visible;
    }

    .qa-done__mark.is-scattered polygon {
        opacity: 1;
        transform: none !important;
    }

    .qa-mark--build polygon.is-pop {
        animation: none !important;
    }
}

/* Windows high-contrast: the system repaints everything, so give it real
   borders to repaint instead of alpha films it will silently drop. */
@media (forced-colors: active) {
    .qa-ch,
    .qa-console,
    .qa-btn,
    .qa-otp input,
    .qa-check__box,
    .qa-notice,
    .qa-reqs li {
        border: 1px solid CanvasText;
    }

    .qa-station::before,
    .qa-check input:checked + .qa-check__box,
    .qa-meter__bar span,
    .qa-stations__fill,
    .qa-progress__fill {
        forced-color-adjust: none;
    }

    .qa-room,
    .qa-threshold {
        display: none;
    }

    .qa-ch__lamp,
    .qa-pulse {
        forced-color-adjust: none;
    }
}

@media print {
    .qa-room,
    .qa-threshold,
    .busy {
        display: none !important;
    }
}
