/* ============================================================================
   Pingo — screen 1f, DIAGNOSE.  Owns screens/diagnose.html and nothing else.
   Every selector is scoped under .pgs-diagnose and every @keyframes name is
   prefixed pgsdiagnose, so nothing can leak into the site or fight another
   screen in the gallery. (Two screens declaring `pgSweep` differently would.)

   Rebuilt from THE canvas, C:/Dev/pingo/design/Pingo Screens.dc.html,
   <div class="opt" id="1f"> at lines 501-573 — verbatim px, hex, rgba and
   gradients. Tokens cross-checked against PINGO-STEEL-HANDOVER.md section 2,
   components against section 3, the 1f spec against line 111, and the layout
   contract against section 5 (line 116-117).

   ---------------------------------------------------------------------------
   THE CARDINAL RULE, from C:/Dev/pingo/MASTER-CONTEXT.md:
       "Motion is a liveness cue: nothing hatched, withheld or stale may
        animate."

   THIS SCREEN'S NAMED MOMENT decides its motion, and it is narrow: a diagnosis
   is IN FLIGHT on check 3 of 4. So exactly TWO things animate, both of them
   authored in the canvas, both genuinely live:

     1. .pgs-logo-sweep  — pgsdiagnoseSweep 2.8s linear infinite.
        The 26px radar mark. Canvas line 511. App chrome that is live on every
        screen; the handover's `sweep 2.8s (logo)` entry.
     2. .pgs-spin        — pgsdiagnoseSweep 1s linear infinite.
        The rotating quadrant on the ONE running check. Canvas line 527. The
        handover's 1s indeterminate quadrant, and it is indeterminate on
        purpose: the engine has no percentage and no ETA for this step, so the
        indicator states neither.

   Nothing else on this screen is live, and the restraint is the point:
     * The two landed ticks are SETTLED FACTS. A finished check is not a
       liveness cue, and drawing it in on appear would re-claim "this just
       landed" every time the gallery switched back to this tab. That is the
       exact failure mode recorded as fix #1 in RESUME-WEBSITE.md, where a
       changing computed animation-name re-fired the score's entrance every
       22.5s. Ticks are static, in every state.
     * The hatched 4th check HAS NOT RUN. It carries no animation at all, so
       there is literally nothing for the stop blocks below to switch off. This
       is the single most important line in the file: a hatched row sitting
       still one row under a turning spinner is what makes the spinner mean
       something.
     * The answer, the evidence chips, the "do this" and the confidence bar are
       conclusions already reached. Stale by definition. Still.
     * No expanding ring, no breathe, no flowX, no travel, no marquee, no bar
       ladder, no arc draw: this screen emits nothing, links nothing, traces
       nothing and plots nothing. Borrowing another screen's ambient loop to
       look livelier is the thing the cardinal rule forbids.
     * No numeric count-up anywhere. "3 OF 4" never ticks.

   HONEST FINAL FRAMES (sections 13 and 14). Never
   animation-play-state: paused, and never a frozen mid-frame:
     * the logo sweep parks at rotate(50.4deg), the app's own reduced pose
       (RadarMark.blipBearing = 0.14 turns), identical to mock.css line 870 so
       the gallery and the hero mock rest in the same place.
     * the spinner's quadrant COLLAPSES INTO A UNIFORM RING. An indeterminate
       spinner has no completion state, so there is no angle it could honestly
       freeze at — parking a bright arc at some arbitrary bearing would invent a
       reading. One `border-color` declaration makes all four sides equal: still
       clearly the active row, claiming no position and no progress. The row's
       words ("Isolating Wi-Fi from internet…") carry the state in the absence
       of motion, which is also the accessible reading.

   ---------------------------------------------------------------------------
   ILLUSTRATIVE, NOT MEASURED. Unlike Home's 6a plate, canvas 1f contains no
   {{template}} holes: the question, all three landed/running check names, the
   fault domain, the answer sentence, all three evidence chips, the action and
   MEDIUM confidence are the canvas's own strings. ONE value is invented and it
   is named here so nobody mistakes it for canvas: check 4, "Neighbour channel
   overlap". The canvas header says "3 OF 4" but lists only three rows, so the
   fourth had to be authored to make the counter true. That name was chosen
   because it is the check a "buffers at night" complaint actually needs (the
   neighbours are streaming too), and because leaving it unrun is what honestly
   caps confidence at MEDIUM with the 4th segment dashed.
   The host page must still say the numbers are illustrative.

   DELIBERATE DIVERGENCES from the canvas — five, all documented, none accidents:
     1. .pgs-thread gets justify-content: flex-end. The canvas thread is
        flex-start, which was invisible there because its three-row check list
        filled the column exactly. Adding the required hatched 4th row and the
        hatch legend costs ~26px, and the stack now finishes ~89px short, which
        at flex-start would open a dead band between the follow-up chips and the
        composer — and the layout contract forbids leaving >60px of empty band
        (handover line 117). Bottom-anchoring is also how a transcript actually
        reads: the newest turn sits against the composer and the slack lands
        above the first bubble, where it reads as history scrolled out of view.
     2. The hatched 4th check row, and the "HATCHED = NOT RUN YET" legend. The
        row is required by the brief and made true by the canvas's own "3 OF 4";
        the hatch values are lifted verbatim from 1b's pending phase chips
        (canvas line 215) and the legend from canvas line 225. The legend exists
        because the hatch is a semantic state, not a texture — flatten it or
        leave it unexplained and the honesty rule is broken (spec fidelity note
        4c).
     3. Em dashes used as connectors were rewritten, house style. Two strings:
        the running check ("Wi-Fi vs internet — isolating…" -> "Isolating Wi-Fi
        from internet…") and the answer ("…line is busy — the internet
        connection itself is fine." -> two sentences). The dash GLYPH survives
        as the withheld-value placeholder in the hatched row, which is design
        vocabulary, not prose.
     4. Composer placeholder "Ask about your network…" -> "Describe what is
        going wrong…". Under the rename the AI is parked and off by default, and
        the shipping path is a deterministic engine that matches a described
        symptom. "Ask about your network" advertises a free-text assistant that
        is switched off; symptom-entry language is accurate for what the
        composer does today and still true when the assistant is unparked.
     5. --gal-scale is floored at .88 in CSS with max(), rather than trusting the
        host. Below .88 the authored 8 and 8.5px mono captions drop under the
        handover's 9px minimum (line 68). styles.css already floors --mock-scale
        the same way for the Home mock; doing it inside this file means the
        gallery shell cannot break the floor by accident.

   Kept verbatim on purpose, even though it is arguable: the nav's 4th icon
   (speech bubble + 4-point spark). All six gallery screens share one nav and a
   per-screen icon change would make the nav flicker between tabs. Flagged in
   diagnose.html section 8 for whoever owns the icon set.

   ---------------------------------------------------------------------------
   PUBLIC API
     .pgs-diagnose               outer box; occupies calc(375px * scale)
                                 x calc(812px * scale) of layout space
     .pgs-diagnose--bezel        optional device bezel (padding + border OUTSIDE
                                 the box, so it does not change the 375px)
     .pgs-diagnose.is-offstage   stop both animations on honest final frames.
                                 Toggle it for "not the active tab" AND for
                                 "scrolled out of view". Required.
     --gal-scale                 set on any ancestor; default .88, floor .88,
                                 1 = authored size
     [data-pgs-screen="diagnose"] stable hook for the gallery shell
     html.motion-opt-in          set by the site's existing "Play the motion"
                                 control; releases the reduced-motion block only
   No JS, no images, no network requests. Needs the host page's two webfonts
   (Sora 400-800, JetBrains Mono 500;700); it degrades to system-ui, at which
   point the negative letter-spacing and the nowrap labels will look wrong.
   ============================================================================ */


/* ---------------------------------------------------------------------------
   1. Outer box, tokens, defensive reset
   --------------------------------------------------------------------------- */

.pgs-diagnose {
  /* --pgs-s is the resolved scale. max() enforces the .88 floor mechanically:
     see divergence 5. Both the box size and the transform read this one token,
     so they can never disagree. */
  --pgs-s: max(.88, var(--gal-scale, .88));

  /* content-box so .pgs-diagnose--bezel's padding and border sit OUTSIDE the
     375px, exactly as the canvas's .phone wraps its .screen. */
  box-sizing: content-box;
  width: calc(375px * var(--pgs-s));
  height: calc(812px * var(--pgs-s));
  flex: none;
  position: relative;
  user-select: none;
  -webkit-user-select: none;

  /* The host site's body sets line-height 1.6 and its own family; both would
     break this stack, so the subtree is re-based here. Every negative
     letter-spacing below assumes the two webfonts are present. */
  font-family: var(--pgs-font);
  font-size: 16px;
  font-weight: 400;
  line-height: normal;
  letter-spacing: normal;
  text-align: left;
  color: var(--pgs-ink);
  -webkit-font-smoothing: antialiased;

  --pgs-font: 'Sora', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --pgs-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Consolas, monospace;

  /* Handover section 2 colour table. Values that differ between the handover
     and the canvas markup follow the MARKUP, per spec fidelity note 8. */
  --pgs-ink: #F2F6FC;          /* primary text and numbers */
  --pgs-ink2: #D7E0EC;         /* secondary */
  --pgs-mut: #B7C4D6;          /* body copy */
  --pgs-faint: #94A2B6;        /* mono labels and units */
  --pgs-faint2: #7F8EA4;       /* tertiary captions, e.g. the composer hint */
  --pgs-untested: #A3B1C3;     /* not measured / not run yet — never green, never red */
  --pgs-on-light: #0B121C;     /* text on the white actions */

  --pgs-glass: rgba(9, 14, 21, .52);
  --pgs-line: rgba(255, 255, 255, .11);
  --pgs-line-strong: rgba(255, 255, 255, .14);
  --pgs-line-soft: rgba(255, 255, 255, .09);

  /* Mint / amber / red mean pass / caution / fail and nothing else. There is no
     brand accent hue in this system: actions are light, not coloured. */
  --pgs-pass-text: #6FE0B6;
  --pgs-warn: #FFB454;

  /* "not run yet" is a state, not a texture. Handover token `hatch`. */
  --pgs-hatch: repeating-linear-gradient(45deg, transparent 0 4px, rgba(255, 255, 255, .06) 4px 8px);
  --pgs-hatch-line: rgba(255, 255, 255, .2);
}

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

.pgs-diagnose div,
.pgs-diagnose span { margin: 0; padding: 0; }

.pgs-diagnose svg { display: block; flex: none; overflow: visible; }

/* Optional device bezel. Canvas .phone, line 504. Purely canvas chrome. */
.pgs-diagnose.pgs-diagnose--bezel {
  background: #060B16;
  padding: calc(9px * var(--pgs-s));
  border: calc(9px * var(--pgs-s)) solid #03060c;
  border-radius: calc(46px * var(--pgs-s));
  box-shadow: 0 0 0 2px #16223a, 0 30px 70px rgba(0, 0, 0, .6);
}

/* Type helpers, the canvas's .mono / .lbl (lines 39 and 41). */
.pgs-diagnose .pgs-mono { font-family: var(--pgs-mono); font-variant-numeric: tabular-nums; }
.pgs-diagnose .pgs-lbl { font: 700 9.5px var(--pgs-mono); letter-spacing: 1.6px; color: var(--pgs-faint); }
.pgs-diagnose .pgs-lbl--8 { font-size: 8px; }         /* the canvas overrides to 8px in 1f */
.pgs-diagnose .pgs-lbl--warn { color: var(--pgs-warn); }
.pgs-diagnose .pgs-lbl--col { width: 52px; flex: none; }
.pgs-diagnose .pgs-grow { flex: 1 1 auto; min-width: 0; }


/* ---------------------------------------------------------------------------
   2. Screen root + the two bloom layers. Canvas lines 33-35 and 505-506.
   --------------------------------------------------------------------------- */

.pgs-diagnose .pgs-screen {
  position: relative;
  width: 375px;
  height: 812px;
  flex: none;
  border-radius: 38px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg,
    #22303F 0%, #1A2634 13%, #111A25 36%, #0A0F16 66%, #04070B 100%);
  /* Built at the true authored size, then scaled as a whole. Nothing was
     re-specced smaller, so the 8-8.5px mono floor survives intact. */
  transform: scale(var(--pgs-s));
  transform-origin: top left;
}

.pgs-diagnose .pgs-bloom { position: absolute; top: 0; left: 0; right: 0; }
.pgs-diagnose .pgs-bloom--sheet {
  height: 46%;
  background: linear-gradient(180deg,
    rgba(163, 187, 214, .2), rgba(163, 187, 214, .06) 46%, transparent 100%);
}
.pgs-diagnose .pgs-bloom--core {
  height: 22%;
  background: radial-gradient(120% 100% at 50% 0%, rgba(190, 210, 232, .16), transparent 72%);
}


/* ---------------------------------------------------------------------------
   3. Status bar. Canvas line 36 / 507.
   --------------------------------------------------------------------------- */

.pgs-diagnose .pgs-sb {
  height: 44px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 26px;
  font: 700 12px var(--pgs-font);
  color: #D7E0EC;
  position: relative;
  z-index: 3;
}
.pgs-diagnose .pgs-sb-icons { display: flex; gap: 6px; align-items: center; }


/* ---------------------------------------------------------------------------
   4. Content column. 18px screen padding, and it does not scroll: the stack is
      tuned to 812px and overflow would be clipped, not scrolled. Canvas 37.
   --------------------------------------------------------------------------- */

.pgs-diagnose .pgs-col {
  flex: 1;
  min-height: 0;
  position: relative;
  z-index: 2;
  padding: 2px 18px 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}


/* ---------------------------------------------------------------------------
   5. Header. Canvas lines 509-518.
   --------------------------------------------------------------------------- */

.pgs-diagnose .pgs-head {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 2px 0 12px;
  flex: none;
}

/* The 26px radar mark is the ONLY radar Pingo ships — RadarField with device
   blips exists in lib/widgets/ui.dart:5139 but is mounted on no screen and
   test/screens/home_steel_test.dart:155 asserts it stays absent. Keep 26px: a
   conic-gradient inside a circular mask aliases badly if scaled up (handover
   section 3, ScoreDial note). 1f's mark carries no blip and no expanding ring —
   that is the canvas, not an omission. */
.pgs-diagnose .pgs-logo { position: relative; width: 26px; height: 26px; flex: none; }
.pgs-diagnose .pgs-logo-clip { position: absolute; inset: 1.6px; border-radius: 50%; overflow: hidden; }
.pgs-diagnose .pgs-logo-sweep {
  position: absolute;
  inset: 0;
  background: conic-gradient(from 0deg, transparent 0deg 290deg,
    rgba(232, 238, 247, .1) 336deg, rgba(232, 238, 247, .6) 360deg);
  animation: pgsdiagnoseSweep 2.8s linear infinite;
}
.pgs-diagnose .pgs-logo-outer { position: absolute; inset: 0; border-radius: 50%; border: 1.7px solid #E8EEF7; }
.pgs-diagnose .pgs-logo-inner { position: absolute; inset: 6px; border-radius: 50%; border: 1.5px solid rgba(232, 238, 247, .72); }
.pgs-diagnose .pgs-logo-dot {
  position: absolute; left: 50%; top: 50%;
  width: 4px; height: 4px; margin: -2px 0 0 -2px;
  border-radius: 50%; background: #E8EEF7;
}

.pgs-diagnose .pgs-head-text { flex: 1 1 auto; min-width: 0; }
.pgs-diagnose .pgs-title { font-size: 17px; font-weight: 800; letter-spacing: -.4px; color: var(--pgs-ink); }
.pgs-diagnose .pgs-sub { font-size: 8.5px; letter-spacing: 1.1px; color: var(--pgs-faint); margin-top: 2px; }

/* New-thread affordance. Secondary action = surSoft + line, never coloured. */
.pgs-diagnose .pgs-new {
  width: 32px; height: 32px; flex: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, .07);
  border: 1px solid var(--pgs-line-strong);
  display: flex; align-items: center; justify-content: center;
  color: var(--pgs-ink2);
}


/* ---------------------------------------------------------------------------
   6. The thread. Canvas line 519, plus divergence 1 (flex-end).
   --------------------------------------------------------------------------- */

.pgs-diagnose .pgs-thread {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;    /* divergence 1: newest turn sits on the composer */
  gap: 10px;
  overflow: hidden;
}

/* The question, as a white bubble. The one asymmetric corner (6px, bottom
   right) is what makes it read as "from you" without a colour. Canvas 520. */
.pgs-diagnose .pgs-ask {
  align-self: flex-end;
  max-width: 74%;
  border-radius: 18px 18px 6px 18px;
  padding: 11px 14px;
  background: linear-gradient(150deg, #FFFFFF, #D8E2EF);
  color: var(--pgs-on-light);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.4;
}

/* One answer turn: checking list + answer card + follow-ups. Canvas 521. */
.pgs-diagnose .pgs-turn { display: flex; flex-direction: column; gap: 8px; }


/* ---------------------------------------------------------------------------
   7. Glass card. Canvas line 38.
      backdrop-filter is load-bearing, not decoration: without it the
      rgba(9,14,21,.52) surfaces read as flat grey plates over the gradient
      (spec fidelity note 4a). This screen has four blurred surfaces (checking,
      answer, composer, nav) — which is exactly why only the active gallery tab
      may animate.
   --------------------------------------------------------------------------- */

.pgs-diagnose .pgs-card {
  background: var(--pgs-glass);
  border: 1px solid var(--pgs-line);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
}

/* Shared head row for both cards: label · spacer · mono value. Canvas 523/531. */
.pgs-diagnose .pgs-card-head { display: flex; align-items: center; gap: 8px; }


/* ---------------------------------------------------------------------------
   8. CHECKING LIST. Canvas lines 522-529.
      Named checks, ticks as they land, a spinner on the live one, and NO
      thinking prose. That is the whole design idea: the app never narrates what
      it might be doing, it names what it is doing.
   --------------------------------------------------------------------------- */

.pgs-diagnose .pgs-card--checking { border-radius: 18px; padding: 12px 14px; }

.pgs-diagnose .pgs-count { font-size: 8.5px; color: var(--pgs-faint); }

.pgs-diagnose .pgs-checks {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin-top: 9px;
}

.pgs-diagnose .pgs-check { display: flex; align-items: center; gap: 8px; }

.pgs-diagnose .pgs-check-name {
  font-size: 11.5px;
  color: var(--pgs-mut);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Landed. Mint = pass, and here it is a real pass: the check completed.
   No entrance animation — a finished check is a settled fact. */
.pgs-diagnose .pgs-tick { width: 13px; height: 13px; }

/* RUNNING. The 1s rotating quadrant, the only indeterminate cue in the system.
   It says "working", never "this much done". Canvas line 527. */
.pgs-diagnose .pgs-check--live .pgs-check-name { color: var(--pgs-ink); }
.pgs-diagnose .pgs-spin {
  width: 13px; height: 13px; flex: none;
  border-radius: 50%;
  border: 2px solid rgba(232, 238, 247, .28);
  border-top-color: var(--pgs-ink);
  animation: pgsdiagnoseSweep 1s linear infinite;
}

/* NOT RUN YET. Hatch fill + dashed border + the dash glyph, lifted verbatim
   from 1b's pending phase chips (canvas line 215) and the HatchCell spec
   (handover section 3). It carries no animation in any state.
   The negative side margin bleeds the cell out to the card's inner edges so the
   13px marker still lines up with the ticks above it:
       -9px margin + 1px border + 8px padding = 0px net shift. */
.pgs-diagnose .pgs-check--pending {
  margin: 0 -9px;
  padding: 4px 8px;
  border-radius: 10px;
  background: var(--pgs-hatch);
  border: 1px dashed var(--pgs-hatch-line);
}
.pgs-diagnose .pgs-check--pending .pgs-check-name { color: var(--pgs-untested); }
.pgs-diagnose .pgs-pending-mark {
  width: 13px; flex: none;
  text-align: center;
  font-size: 13px;
  font-weight: 700;
  line-height: .9;
  color: var(--pgs-untested);
}

/* Naming the hatch is what stops it reading as decoration. Canvas 1b line 225
   uses the same legend wording for phases. */
.pgs-diagnose .pgs-hatch-legend {
  font-size: 8.5px;
  color: var(--pgs-faint);
  margin-top: 8px;
  text-align: right;
}


/* ---------------------------------------------------------------------------
   9. ANSWER CARD. Canvas lines 530-541.
      The handover's fix for "four sentences in a paragraph": fault domain ·
      plain answer · evidence chips · do this · capped confidence.
   --------------------------------------------------------------------------- */

.pgs-diagnose .pgs-card--answer {
  border-radius: 18px;
  padding: 13px 14px;
  border-color: rgba(255, 180, 84, .3);   /* amber border = caution, authored */
}

.pgs-diagnose .pgs-fault {
  font-size: 8.5px;
  font-weight: 700;
  color: var(--pgs-warn);
}

.pgs-diagnose .pgs-answer {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.5;
  color: var(--pgs-ink);
  margin-top: 9px;
}

/* EvidenceChip (handover section 3): the raw measured value in a bordered
   chip, so every claim above is traceable. Neutral, never tinted: the chip
   reports a number, it does not deliver a verdict. */
.pgs-diagnose .pgs-evidence { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.pgs-diagnose .pgs-chip {
  font-size: 8.5px;
  font-weight: 700;
  padding: 5px 8px;
  border-radius: 8px;
  background: rgba(255, 255, 255, .08);
  border: 1px solid rgba(255, 255, 255, .16);
  color: var(--pgs-ink2);
}

.pgs-diagnose .pgs-rule { height: 1px; background: var(--pgs-line-soft); margin: 12px 0; }

.pgs-diagnose .pgs-do { display: flex; gap: 9px; align-items: flex-start; }
.pgs-diagnose .pgs-do .pgs-lbl--col { padding-top: 2px; }   /* optical baseline on a wrapping value */
.pgs-diagnose .pgs-do-text { font-size: 12px; line-height: 1.45; color: var(--pgs-mut); }

/* ConfidenceBar (handover section 3): 4 segments, and the FOURTH IS ALWAYS
   DASHED AND NEVER FILLED. It is not a 4th value waiting to arrive; it is the
   component's permanent statement that certainty is not on offer. Do not add a
   filled state for .pgs-seg--capped, and do not let a "high confidence" variant
   fill it later. */
.pgs-diagnose .pgs-conf { display: flex; gap: 9px; align-items: center; margin-top: 9px; }
.pgs-diagnose .pgs-conf-bar { display: flex; gap: 3px; flex: none; }
.pgs-diagnose .pgs-seg { width: 16px; height: 7px; border-radius: 2px; }
.pgs-diagnose .pgs-seg--on { background: var(--pgs-pass-text); }
.pgs-diagnose .pgs-seg--off { background: rgba(255, 255, 255, .14); }
.pgs-diagnose .pgs-seg--capped { border: 1px dashed rgba(255, 255, 255, .28); }
.pgs-diagnose .pgs-conf-word { font-size: 9px; font-weight: 700; color: var(--pgs-pass-text); }


/* ---------------------------------------------------------------------------
   10. Follow-up chips. Canvas lines 542-545.
   --------------------------------------------------------------------------- */

.pgs-diagnose .pgs-followups { display: flex; gap: 7px; }
.pgs-diagnose .pgs-follow {
  flex: none;
  border-radius: 14px;
  padding: 8px 12px;
  background: rgba(255, 255, 255, .07);
  border: 1px solid var(--pgs-line-strong);
  font-size: 11.5px;
  font-weight: 600;
  color: var(--pgs-ink2);
  white-space: nowrap;
}


/* ---------------------------------------------------------------------------
   11. Composer with a white send, and the nav reserve.
       Canvas lines 548-552. LAYOUT CONTRACT, handover line 117: reserve >=104px
       and end the last control >=20px above the nav's top edge. 114px puts the
       composer's bottom at y=698 against a nav top edge of y=726, so 28px of
       clearance for the nav's ~20px upward shadow.
   --------------------------------------------------------------------------- */

.pgs-diagnose .pgs-composer {
  border-radius: 20px;
  padding: 9px 10px 9px 14px;
  margin-top: 11px;
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
}
.pgs-diagnose .pgs-composer-hint {
  font-size: 12.5px;
  color: var(--pgs-faint2);
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Primary action = light, not coloured (handover section 2). */
.pgs-diagnose .pgs-send {
  width: 36px; height: 36px; flex: none;
  border-radius: 13px;
  background: linear-gradient(150deg, #FFFFFF, #CFDBE9);
  display: flex; align-items: center; justify-content: center;
  color: var(--pgs-on-light);
}

.pgs-diagnose .pgs-navreserve { height: 114px; flex: none; }


/* ---------------------------------------------------------------------------
   11b. GUIDED DIAGNOSE — the symptom picker.

   WHY THIS EXISTS. The screen this file was first written for showed a
   free-text AI assistant: a typed question in a white bubble and a composer
   reading "Describe what is going wrong…". Ask Pingo is PARKED for v1
   (lib/features.dart:128-129, askPingo: false), so no such interface ships and
   that mock advertised a feature a downloader would not find. What v1 actually
   ships is lib/screens/diagnose/diagnose_screen.dart: a FIXED list of symptoms,
   one of which you pick, after which Pingo runs only the checks that fit that
   symptom and grades each PASS or WARN.

   The rules from section 2 still hold and are the reason for every colour
   below: mint means pass and nothing else, so it marks the PICKED row and never
   decorates; the action is light, not coloured; nothing on this screen is live,
   so nothing here animates except the header's radar mark.

   The composer, thread, answer-card and check-list rules above are LEFT INTACT
   deliberately. They remain the correct spec for the run screen — the second
   half of the same feature — and screens/diagnose.html is a standalone fragment
   served from this host that may render either composition.
   --------------------------------------------------------------------------- */

/* The app's own framing paragraph, verbatim. It is the honesty statement for
   the whole feature — "runs only the checks that make sense", then "tells you
   what it could prove, and what it could not" — so it is copy, not chrome. */
.pgs-diagnose .pgs-intro {
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--pgs-mut);
  margin-top: 11px;
  flex: none;
}

/* Section rule, matching the app's "WHAT'S WRONG?" divider. */
.pgs-diagnose .pgs-seclbl {
  display: flex; align-items: center; gap: 10px;
  margin-top: 13px; flex: none;
}
.pgs-diagnose .pgs-seclbl-txt {
  font: 700 8.5px var(--pgs-mono);
  letter-spacing: 1.6px;
  color: var(--pgs-pass-text);
  flex: none;
}
.pgs-diagnose .pgs-seclbl-rule { height: 1px; background: var(--pgs-line-soft); flex: 1 1 auto; }

.pgs-diagnose .pgs-card--symptoms {
  border-radius: 18px;
  margin-top: 8px;
  margin-bottom: 12px;    /* the MINIMUM gap to RUN DIAGNOSE; see .pgs-run */
  overflow: hidden;
  flex: none;
}

/* 9px, not 10px, and the difference is the LAYOUT CONTRACT (handover line 117),
   not taste. At 10px the seven rows push the column 8px past its own height, so
   .pgs-col's overflow:hidden silently eats into the 114px nav reserve and the
   RUN DIAGNOSE button clears the floating nav by only ~20px instead of the 28px
   the reserve is sized for. Measured on the live page, not guessed. If a row
   ever gains a third line of copy, take it out of the copy or the reserve — do
   not shrink this again. */
.pgs-diagnose .pgs-sym {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 9px 12px;
  border-top: 1px solid var(--pgs-line-soft);
}
.pgs-diagnose .pgs-sym:first-child { border-top: 0; }

.pgs-diagnose .pgs-sym-ico {
  width: 26px; height: 26px; flex: none;
  border-radius: 9px;
  background: rgba(255, 255, 255, .05);
  border: 1px solid var(--pgs-line-soft);
  display: flex; align-items: center; justify-content: center;
  color: var(--pgs-faint);
  margin-top: 1px;
}
.pgs-diagnose .pgs-sym-txt { flex: 1 1 auto; min-width: 0; }

/* display:block on BOTH, and it is load-bearing. These are <span>s, so without
   it the name and its sub-line reflow as one run of text ("Internet is slow or
   keeps dropping Work out whether anything is actually slow…") instead of
   stacking. The app draws them as two stacked lines and the whole point of the
   sub-line is that it reads as a separate, quieter statement about what that
   choice would check. */
.pgs-diagnose .pgs-sym-name {
  display: block;
  font-size: 12px; font-weight: 700; letter-spacing: -.1px;
  color: var(--pgs-ink); line-height: 1.25;
}
.pgs-diagnose .pgs-sym-sub {
  display: block;
  font-size: 10px; line-height: 1.4;
  color: var(--pgs-faint);
  margin-top: 2px;
}

/* Unpicked: an empty ring. Not greyed out and not disabled — every row here is
   equally available, which is the whole point of a fixed list. */
.pgs-diagnose .pgs-sym-pick {
  width: 16px; height: 16px; flex: none; margin-top: 4px;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, .22);
}

/* PICKED. Mint, because this is the one state on the screen that is settled. */
.pgs-diagnose .pgs-sym--on {
  background: rgba(111, 224, 182, .09);
  box-shadow: inset 3px 0 0 var(--pgs-pass-text);
}
.pgs-diagnose .pgs-sym--on .pgs-sym-ico {
  background: rgba(111, 224, 182, .13);
  border-color: rgba(111, 224, 182, .34);
  color: var(--pgs-pass-text);
}
.pgs-diagnose .pgs-sym--on .pgs-sym-sub { color: var(--pgs-mut); }
.pgs-diagnose .pgs-sym--on .pgs-sym-pick {
  border: 0;
  background: var(--pgs-pass-text);
  color: var(--pgs-on-light);
  display: flex; align-items: center; justify-content: center;
}

/* RUN DIAGNOSE. Primary action = light, not coloured (handover section 2), and
   the same white gradient the composer's send button carried.

   margin-top:auto PINS IT to the foot of the column, directly above the 114px
   nav reserve, exactly as the app draws it. That is not a cosmetic choice: the
   seven rows above are text whose wrapped height depends on the font actually
   resolving, and a fixed top margin would leave the button floating at whatever
   height the copy happened to reach. The auto margin eats the slack instead, so
   the button lands in the same place whether a sub-line wraps to one line or
   two, and it degrades to the card's 12px margin-bottom if the list ever grows
   tall enough to consume the space. */
.pgs-diagnose .pgs-run {
  margin-top: auto; flex: none;
  height: 40px; border-radius: 20px;
  background: linear-gradient(150deg, #FFFFFF, #CFDBE9);
  color: var(--pgs-on-light);
  display: flex; align-items: center; justify-content: center;
  font: 700 11px var(--pgs-mono);
  letter-spacing: 1.7px;
}


/* ---------------------------------------------------------------------------
   12. Bottom nav. Canvas lines 42-44 and 554-560. 72px glass pill, 5 items,
       centre Tools = 34px white gradient tile, active item = ink.
   --------------------------------------------------------------------------- */

.pgs-diagnose .pgs-nav {
  position: absolute;
  left: 16px; right: 16px; bottom: 14px;
  height: 72px;
  border-radius: 26px;
  background: rgba(9, 13, 20, .88);
  border: 1px solid var(--pgs-line);
  backdrop-filter: blur(22px);
  -webkit-backdrop-filter: blur(22px);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 6px;
  z-index: 5;
  box-shadow: 0 14px 34px rgba(0, 0, 0, .5);
}
.pgs-diagnose .pgs-nvi {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  color: #7C8CA1;
}
/* nowrap is defensive, not canvas: "Diagnose" measures ~42px in a 65.8px slot,
   so it fits, but a fallback font must not be allowed to break it over two
   lines inside a fixed 72px pill. */
.pgs-diagnose .pgs-nvl { font-size: 9.5px; font-weight: 600; white-space: nowrap; }

.pgs-diagnose .pgs-nvi--active { color: var(--pgs-ink); }
.pgs-diagnose .pgs-nvi--active .pgs-nvl { font-weight: 700; }

.pgs-diagnose .pgs-nvi-tile {
  width: 34px; height: 34px; flex: none;
  border-radius: 12px;
  background: linear-gradient(150deg, #FFFFFF, #C9D6E6);
  display: flex; align-items: center; justify-content: center;
  color: var(--pgs-on-light);
  box-shadow: 0 8px 18px rgba(0, 0, 0, .45);
}


/* ---------------------------------------------------------------------------
   13. Keyframes. ONE, because this screen has one kind of live motion: rotation
       at two speeds. The name is the canvas's pgSweep, namespaced pgsdiagnose*
       so it cannot collide with the site or with another gallery screen.
       The canvas reuses pgSweep for both the logo (2.8s) and the check spinner
       (1s) too — see lines 511 and 527 — so this is its structure, not a merge.
   --------------------------------------------------------------------------- */

@keyframes pgsdiagnoseSweep { to { transform: rotate(360deg); } }


/* ---------------------------------------------------------------------------
   14. HONEST FINAL FRAMES — block A: offstage.
       The gallery shell toggles .is-offstage on .pgs-diagnose for "not the
       active tab" and for "scrolled out of view". This is the web's TickerMode,
       matching the app's IndexedStack, where offstage tabs genuinely stop
       ticking. CSS cannot see either condition, so this cannot be done here.
       ***  KEEP THIS BLOCK AND BLOCK B (section 15) IDENTICAL.  ***
   --------------------------------------------------------------------------- */

/* The sweep parks where the app parks it: RadarMark.blipBearing = 0.14 turns.
   Same pose as mock.css line 870, so the gallery and the hero mock agree. */
.pgs-diagnose.is-offstage .pgs-logo-sweep { animation: none; transform: rotate(50.4deg); }

/* An indeterminate spinner has no completion state, so there is no bearing it
   could honestly hold. Equalising the border collapses the quadrant into a
   uniform ring: still visibly the active row, claiming no position and no
   progress. The row's words carry the state without it. */
.pgs-diagnose.is-offstage .pgs-spin { animation: none; border-color: rgba(232, 238, 247, .55); }

/* NOTHING ELSE NEEDS A LINE HERE, and that is the honest result, not an
   oversight: the ticks, the hatched row, the evidence chips, the confidence bar
   and the whole answer card never animate in the first place. If a future edit
   adds an animation, it must be stopped here and in block B on the same commit. */


/* ---------------------------------------------------------------------------
   15. HONEST FINAL FRAMES — block B: prefers-reduced-motion.
       Not animation-play-state: paused, and not a frozen mid-frame: each
       animation's real end state.
       ***  IDENTICAL TO BLOCK A ABOVE.  ***

       Every selector is gated on html:not(.motion-opt-in). That class is set
       ONLY when the visitor explicitly asks for motion via the site's existing
       "Play the motion" control, which is itself only rendered when the
       preference is detected. Default behaviour, and behaviour with JS off, is
       unchanged: the OS preference wins and these final frames apply. Block A
       (.is-offstage) is deliberately NOT gated, because off-screen and
       off-tab are performance facts, not preferences. This mirrors styles.css
       and mock.css exactly.
   --------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html:not(.motion-opt-in) .pgs-diagnose .pgs-logo-sweep { animation: none; transform: rotate(50.4deg); }
  html:not(.motion-opt-in) .pgs-diagnose .pgs-spin { animation: none; border-color: rgba(232, 238, 247, .55); }
}
