/* ============================================================================
   Pingo — screen 1c, WI-FI CHECK.  Owns screens/wifi.html and nothing else.

   Every selector is scoped under .pgs-wifi and every @keyframes name is
   prefixed pgswifi, so nothing leaks into the site or into another screen in
   the gallery. Two screens declaring `pgSweep` differently would fight, and
   @keyframes are global no matter where they are declared.

   Reproduced from the LOCKED design canvas
     C:/Dev/pingo/design/Pingo Screens.dc.html  ->  <div class="opt" id="1c">
   lines 265-343, with the shared chrome from that file's <style> block
   (.screen line 33 · .bloom 34 · .bloom2 35 · .sb 36 · .body 37 · .card 38 ·
   .mono 39 · .num 40 · .lbl 41 · .nav 42 · .nvi 43 · .nvl 44 · keyframes 46-56).
   Token names and bands cross-checked against
   C:/Dev/pingo/design/PINGO-STEEL-HANDOVER.md sections 2-5.

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

   THE NAMED MOMENT this screen is captured at, which is what decides its
   motion: A MEASURED READING, LARGELY SETTLED. Concretely:

     LIVE, so it may animate (5 animation instances, 4 keyframes):
       * the LIVE chip's mint dot (pgswifiPulse 1.8s). The connection read
         really is on a 5s cadence while this screen is open — MASTER-CONTEXT
         section 6: "Cadences are now split: connection read 5s, neighbour
         scans 30s."
       * the two expanding rings behind the dial (pgswifiRing 4s, the second
         de-synced by a 2s delay) and the breathing glow (pgswifiBreathe 6s).
         These are the app's RadarHalo treatment, and its gate is
         `node.live && tone != null && tone != untested` (ui.dart _halo). The
         signal reading is live and its tone is pass, so the halo is earned.
       * the arc's one-shot draw on appear (pgswifiDraw 1.7s). An entrance, not
         a loop, and it can only ever land on the complete reading.

     SETTLED OR IMPOSSIBLE, so it holds perfectly still, in EVERY state:
       * The CHANNEL-CROWDING HISTOGRAM. All eight bars are static. They are a
         finished count from a neighbour scan on a 30s cadence, forced by
         Android's own scan throttle (~4 scans per 2 minutes per app,
         MASTER-CONTEXT section 6). A rippling histogram would say the counts
         are changing as you watch. They are not.
       * The NEARBY-NETWORK SIGNAL LADDER. The handover's `bar 2.2s` staggered
         ripple belongs to the SignalLadder, but the app gates it on a live RSSI
         stream younger than 6 seconds (ui.dart:3341-3345, maxSampleAge). A
         neighbour's strength comes from the 30s scan, not the 5s connection
         read, so there is no stream to ripple. And its unlit fourth rung could
         never animate anyway — ui.dart:3247-3249: "an unlit rung is the
         statement 'the signal is not this strong', and animating it would
         soften a measurement into a suggestion."
       * The HATCHED SNR CELL. See the divergence list below. Nothing about an
         impossible measurement may move, and it may not be mint either.

     NOT PRESENT AT ALL, on purpose: no logo sweep (this is a sub-screen with a
     back chevron, not the Home header), no flowX or travel (no link strip on
     this screen), no marquee (no response trace), no rotating quadrant (nothing
     indeterminate is running here — the reading has arrived), and no numeric
     count-up anywhere.

   HONEST FINAL FRAMES. Both stop states (sections 13 and 14) render each
   animation's real end state, never `animation-play-state: paused` and never a
   frozen mid-frame:
       pgswifiPulse   -> the dot holds FULLY LIT (its 100% frame, opacity 1).
       pgswifiRing    -> the rings VANISH. pgRing ends at opacity 0, so "no
                         ring" IS its final frame; display:none also removes
                         their compositor layers.
       pgswifiBreathe -> the glow parks at rest, which is its own 0% frame
                         (opacity .5, scale 1). Not opacity 1: that is brighter
                         than any frame the design authored.
       pgswifiDraw    -> the arc holds its COMPLETE value. A partial arc would
                         misstate the reading, which is the one thing this dial
                         must never do.

   ---------------------------------------------------------------------------
   ILLUSTRATIVE VALUES. Numbers are illustrative, not measured, and the gallery
   shell must say so in visible copy.

   Reused verbatim from the canvas (it authored all of these):
     quality word "Strong" · plain line "Good enough here for 4K and calls." ·
     WPA3 · WI-FI 6 · 5 GHZ · 44 · 80M · the eight crowding bar heights
     70/92/54/30/22/18/26/34% and their tones · YOURS IS QUIET ·
     2.4 GHZ · 11 APS · 5 GHZ · 6 APS · 17 SEEN · TelstraB4A2C1 · CH 1 ·
     the ladder's 60/80/100/100% rungs · the verdict sentence ·
     "Walk & map signal" · the arc's stroke-dashoffset 53 of 251.3 (78.9%).

   CHOSEN HERE (the canvas left them as template holes that render blank):
     -48  for {{dbm}}. It is the site-wide illustrative signal value already
          published in RESUME-WEBSITE.md line 162 and in the Home mock's
          aria-label ("-48 dBm at four of five bars"), so the gallery agrees
          with itself. It is also consistent with this dial: 78.9% of the arc is
          within a pixel of four fifths, and -48 dBm is genuinely strong.
   The canvas's internal arithmetic already checks out and was left alone:
     11 APs on 2.4 GHz + 6 on 5 GHz = the 17 SEEN in the nearby card; the mint
     YOU bar at 18% is the shortest of the three 5 GHz bars (18/26/34), which is
     what makes "channel 44 is the quietest 5 GHz option here" true; and the
     amber neighbour on CH 1 is the amber 70% bar at the left of the histogram.

   ---------------------------------------------------------------------------
   DELIBERATE DIVERGENCES from the canvas. Six, all documented, none accidental.

   1. **SNR IS HATCHED, NOT FILLED.** REQUIRED. The canvas (line 300) gives the
      fourth radio cell a mint tint, a mint border, a mint label and the value
      "34 DB". SNR is impossible on Android: MASTER-CONTEXT section 6 records
      that ScanResult's own docs say the supplicant tracks noise but never
      reports it to apps, and no public noise getter exists. So the cell becomes
      a HatchCell (handover section 3): 45-degree hatch fill, 1px dashed border,
      an em-dash marker, `untested` ink, and a stated reason. The mint is
      REMOVED, not dimmed — mint means pass, and a measurement that cannot exist
      has not passed. The cell carries no animation in any state.
   2. The HatchCell's reason line sits on the card's full width, not inside the
      cell. At the 9px mono floor a real explanation needs ~218px and the cell
      is 72px wide, so the alternative was jargon ("NO API") or a third line in
      all four cells, which would leave three of them with dead space. It also
      pays for itself against the LAYOUT CONTRACT (handover section 5): without
      it this column leaves ~70px of empty band above the nav, and the contract
      says fill that band with real payload, never spacer.
   3. WORK-HQ -> WORK-HQ. The canvas's placeholder network name named a third
      party unrelated to this app, so it is replaced everywhere it appears. The
      same substitution is already applied in mock.html:76 and index.html:217.
   4. The verdict's em dash (canvas line 326: "quietest 5 GHz option here — no
      change needed") becomes two sentences. House style forbids em dashes as
      connectors in user-facing strings. The dash GLYPH survives in the hatched
      SNR cell, where it is the withheld-value marker and therefore design
      vocabulary rather than punctuation.
   5. Tab 4 reads "Diagnose", not "Ask". Ask Pingo and Guided Diagnose are one
      surface now, named Diagnose, with the AI parked and off by default. The
      icon geometry is left exactly as the canvas draws it: the nav is shared
      chrome across all six gallery screens, so one screen must not unilaterally
      redraw it. (The speech-bubble-plus-spark does read as an AI sparkle, which
      is worth a platform-wide decision. Flagged in the report, not acted on.)
   6. The actions are spans, not <button>s, and the cell labels get
      `white-space: nowrap`. The first keeps focusable controls out of an
      aria-hidden subtree (mock.html made the same choice). The second stops
      "STANDARD" wrapping and growing the cell row if JetBrains Mono has not
      loaded and a wider fallback is substituted; nowrap spills harmlessly into
      the cell's 9px padding instead.

   Also noted and NOT changed: the canvas authors the four radio-cell labels and
   the YOU tag at 7.5px mono, which is below the handover's 8-9px band, so at the
   0.88 scale floor they render at 6.6px. Raising them to 8px puts "STANDARD" at
   51.2px inside 52px of usable cell width, i.e. one fallback-font glyph away
   from wrapping the row. Canvas fidelity and zero layout risk won. Flagged in
   the report so it can be settled for the whole gallery at once.

   ---------------------------------------------------------------------------
   PUBLIC API — the integrator should not need to open either file.

     .pgs-wifi                  outer box. Occupies
                                calc(375px * scale) x calc(812px * scale) of
                                real layout space. Put it in any flex/grid cell.
     .pgs-wifi.is-offstage      REQUIRED GATE. Stops every animation on its
                                honest final frame. Toggle it on every gallery
                                screen except the visible one. CSS cannot see
                                the viewport or know which tab is active.
     [data-pgs-screen="1c"]     stable hook for querySelector.
     --gal-scale                set on .pgs-wifi or ANY ancestor. Default .88.
                                1 = the canvas's authored type sizes.
                                **0.88 is the floor** — below it the 8-9px mono
                                captions break the handover's 9px minimum.
                                Scroll the rail instead of scaling smaller.
     Fonts                      the host page must load Sora 400/600/700/800 and
                                JetBrains Mono 500/700. index.html already does.
     Retuning the reading       edit stroke-dashoffset on .pgsw-dial-arc in
                                wifi.html (53 of 251.3 = 78.9%) and the -48 in
                                .pgsw-dbm. Nothing in CSS hard-codes either.
     Do NOT add                 the site's .motion-keep escape hatch, or any
                                animation to .pgsw-bar, .pgsw-rung or
                                .pgsw-cell--hatch. See the cardinal rule above.
   ============================================================================ */


/* ---------------------------------------------------------------------------
   1. Outer box, tokens, defensive reset.
      The box is sized with calc() so it takes correct layout space, and the
      375x812 screen inside it is scaled with a transform. This is the 6a mock's
      proven approach (mock.css section 1): it keeps every authored pixel and
      every 8-9px mono size intact and shrinks the whole plate at once, rather
      than re-speccing type smaller and breaking the floor.
   --------------------------------------------------------------------------- */

.pgs-wifi {
  /* content-box so an optional bezel's padding/border would sit OUTSIDE the 375px */
  box-sizing: content-box;
  width: calc(375px * var(--gal-scale, .88));
  height: calc(812px * var(--gal-scale, .88));
  flex: none;
  position: relative;
  user-select: none;
  -webkit-user-select: none;

  /* Type. Every negative letter-spacing below assumes these two webfonts; the
     host page loads them. Resetting line-height and letter-spacing matters:
     the site's body sets line-height 1.6, which would break the whole stack. */
  font-family: var(--pgsw-font);
  font-size: 16px;
  font-weight: 400;
  line-height: normal;
  letter-spacing: normal;
  text-align: left;
  color: var(--pgsw-ink);
  -webkit-font-smoothing: antialiased;

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

  /* Ink. Handover section 2. */
  --pgsw-ink: #F2F6FC;
  --pgsw-ink2: #D7E0EC;
  --pgsw-mut: #B7C4D6;
  --pgsw-faint: #94A2B6;
  --pgsw-faint2: #7F8EA4;
  --pgsw-nav-ink: #7C8CA1;
  --pgsw-on-light: #0B121C;
  /* Absence has its own colour in this system: never mint, never red. */
  --pgsw-untested: #A3B1C3;

  /* Surfaces. */
  --pgsw-glass: rgba(9, 14, 21, .52);
  --pgsw-soft: rgba(255, 255, 255, .06);
  --pgsw-line: rgba(255, 255, 255, .11);
  --pgsw-line-strong: rgba(255, 255, 255, .14);
  --pgsw-line-hair: rgba(255, 255, 255, .07);

  /* Semantic hues. mint = pass, amber = caution, red = fail. There is no brand
     accent hue in this system and these three mean nothing else. */
  --pgsw-pass: #35D6A0;
  --pgsw-pass-bright: #4BEDBA;
  --pgsw-pass-text: #6FE0B6;
  --pgsw-warn: #FFB454;
  --pgsw-fail: #FF5C6E;

  /* Ring/glow tint, canvas lines 279-281. */
  --pgsw-ring-line: rgba(53, 214, 160, .26);
  --pgsw-glow: rgba(53, 214, 160, .18);

  /* "cannot be measured" — a state, not a texture. Handover section 2, `hatch`. */
  --pgsw-hatch: repeating-linear-gradient(45deg, transparent 0 4px, rgba(255, 255, 255, .06) 4px 8px);
  --pgsw-hatch-line: rgba(255, 255, 255, .2);
}

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

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

.pgs-wifi b { font-weight: 700; }

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

/* Type helpers = the canvas's .mono / .num / .lbl (lines 39-41). Note .lbl keeps
   the canvas's own #94A2B6; the Home mock darkened its copy to #61738E, but the
   canvas is authoritative for this screen. */
.pgs-wifi .pgsw-mono { font-family: var(--pgsw-mono); font-variant-numeric: tabular-nums; }
.pgs-wifi .pgsw-num { font-variant-numeric: tabular-nums; letter-spacing: -1px; }
.pgs-wifi .pgsw-lbl {
  font: 700 9.5px var(--pgsw-mono);
  letter-spacing: 1.6px;
  color: var(--pgsw-faint);
}
.pgs-wifi .pgsw-grow { flex: 1 1 auto; min-width: 0; }


/* ---------------------------------------------------------------------------
   2. Screen root + the two bloom layers. Canvas lines 33-35, 269-270.
   --------------------------------------------------------------------------- */

.pgs-wifi .pgsw-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%);
  transform: scale(var(--gal-scale, .88));
  transform-origin: top left;
}

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


/* ---------------------------------------------------------------------------
   3. Status bar. Canvas lines 36, 271.
   --------------------------------------------------------------------------- */

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


/* ---------------------------------------------------------------------------
   4. Content column. Canvas lines 37, 272.
      overflow:hidden is load-bearing: the stack below is tuned to fit 812px
      exactly, and per the LAYOUT CONTRACT a screen that overflows loses a block
      or a shorter chart, never padding.
   --------------------------------------------------------------------------- */

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


/* ---------------------------------------------------------------------------
   5. Header row: back chevron, title, LIVE chip. Canvas lines 273-277.
   --------------------------------------------------------------------------- */

.pgs-wifi .pgsw-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 2px 0 10px;
  flex: none;
}
.pgs-wifi .pgsw-title {
  font-size: 19px;
  font-weight: 800;
  letter-spacing: -.5px;
  color: var(--pgsw-ink);
}
.pgs-wifi .pgsw-live {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: 14px;
  padding: 6px 11px;
  background: rgba(255, 255, 255, .07);
  border: 1px solid var(--pgsw-line-strong);
  font-size: 11px;
  font-weight: 700;
  color: var(--pgsw-ink);
  flex: none;
}
/* The screen's only pulse, and it is earned: the connection read runs every 5s
   while this screen is open. Nothing else here is on a sub-10s cadence. */
.pgs-wifi .pgsw-live-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--pgsw-pass);
  flex: none;
  animation: pgswifiPulse 1.8s ease-in-out infinite;
}


/* ---------------------------------------------------------------------------
   6. The dBm dial. Canvas lines 278-292.
      Same instrument as Home's ScoreDial and Speed's gauge (handover section 3):
      dotted tick ring at r47, track at r40, progress arc at r40 with a round
      cap, drawn from empty on appear. Never a conic-gradient plus circular
      mask — the handover records that it aliases badly.
   --------------------------------------------------------------------------- */

.pgs-wifi .pgsw-dialbox {
  height: 162px;
  flex: none;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Two rings, identical, de-synced by the second one's 2s delay so the pair
   reads as a repeating outward pulse rather than one ring stuttering. */
.pgs-wifi .pgsw-dial-ring {
  position: absolute;
  width: 172px;
  height: 172px;
  border-radius: 50%;
  border: 1px solid var(--pgsw-ring-line);
  animation: pgswifiRing 4s ease-out infinite;
}
.pgs-wifi .pgsw-dial-ring--b { animation-delay: 2s; }

.pgs-wifi .pgsw-dial-glow {
  position: absolute;
  width: 150px;
  height: 150px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--pgsw-glow), transparent 70%);
  animation: pgswifiBreathe 6s ease-in-out infinite;
}

/* rotate(-90deg) or the arc starts at 3 o'clock instead of 12. */
.pgs-wifi .pgsw-dial-svg { position: relative; display: block; transform: rotate(-90deg); }
.pgs-wifi .pgsw-dial-tick { stroke: rgba(232, 238, 247, .26); }
.pgs-wifi .pgsw-dial-track { stroke: rgba(255, 255, 255, .09); }
.pgs-wifi .pgsw-dial-arc {
  stroke: var(--pgsw-pass-bright);
  /* stroke-dashoffset deliberately lives on the ATTRIBUTE in wifi.html, not
     here. pgswifiDraw has no `to` block, so CSS interpolates up to whatever the
     element's own value is: the arc can only ever finish on the complete
     reading. Setting it here as well would work, but it would invite someone to
     animate it, and a sweeping arc restates the measurement mid-flight. */
  animation: pgswifiDraw 1.7s cubic-bezier(.22, .9, .24, 1) both;
}

.pgs-wifi .pgsw-dial-read {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
/* No count-up. The digits never tick: the reading either arrived or it did not. */
.pgs-wifi .pgsw-dbm {
  font-size: 38px;
  font-weight: 800;
  color: var(--pgsw-ink);
  line-height: 1;
}
.pgs-wifi .pgsw-dbm-unit {
  font-size: 9px;
  letter-spacing: 2px;
  color: var(--pgsw-faint);
  margin-top: 3px;
}
.pgs-wifi .pgsw-quality {
  font-size: 13px;
  font-weight: 800;
  color: var(--pgsw-pass-text);
  margin-top: 6px;
}


/* ---------------------------------------------------------------------------
   7. The plain-English line under the dial. Canvas line 293.
      Home's sentence layer: the dial reports the number, this says what it
      means for the person holding the phone.
   --------------------------------------------------------------------------- */

.pgs-wifi .pgsw-plain {
  text-align: center;
  font-size: 12.5px;
  color: var(--pgsw-mut);
  line-height: 1.4;
  padding: 0 14px;
  flex: none;
}


/* ---------------------------------------------------------------------------
   8. Glass card + its header row. Canvas lines 38, 294/303/317.
      The 1px border is load-bearing depth in this system, not decoration.
   --------------------------------------------------------------------------- */

.pgs-wifi .pgsw-card {
  background: var(--pgsw-glass);
  border: 1px solid var(--pgsw-line);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-radius: 20px;
  padding: 12px 14px;
  margin-top: 11px;
  flex: none;
}
/* The radio card is the canvas's one 12px gap (line 294); the other two are 11. */
.pgs-wifi .pgsw-col > .pgsw-card:first-of-type { margin-top: 12px; }

.pgs-wifi .pgsw-card-head { display: flex; align-items: center; gap: 9px; }
.pgs-wifi .pgsw-lbl--head { font-size: 8.5px; }
.pgs-wifi .pgsw-card-meta { font-size: 8.5px; color: var(--pgsw-faint); }
.pgs-wifi .pgsw-card-meta--pass { color: var(--pgsw-pass-text); }
.pgs-wifi .pgsw-card-meta--faint { color: var(--pgsw-faint2); }


/* ---------------------------------------------------------------------------
   9. Radio readout: standard, band, channel/width, SNR. Canvas lines 296-301.
      Mono and copyable in the app (canvas note line 349).
   --------------------------------------------------------------------------- */

.pgs-wifi .pgsw-cells { display: flex; gap: 7px; margin-top: 9px; }

.pgs-wifi .pgsw-cell {
  flex: 1 1 0;
  /* minmax-equivalent for flex: without min-width:0 a long nowrap value would
     push its own column wider and make the four cells unequal. */
  min-width: 0;
  border-radius: 12px;
  padding: 8px 9px;
  background: var(--pgsw-soft);
  border: 1px solid var(--pgsw-line);
}
.pgs-wifi .pgsw-cell-lbl {
  font-size: 7.5px;
  /* Divergence 6: the canvas has no nowrap here. Without it, a wider fallback
     font (JetBrains Mono not yet loaded) wraps "STANDARD" and grows the whole
     row, which cascades into the layout contract. Spilling into the 9px padding
     is the cheaper failure. */
  white-space: nowrap;
}
.pgs-wifi .pgsw-cell-val {
  font-size: 11.5px;
  font-weight: 700;
  color: var(--pgsw-ink);
  margin-top: 3px;
  white-space: nowrap;
}

/* DIVERGENCE 1, REQUIRED. The canvas fills this cell with a mint "34 DB". SNR
   cannot be measured on Android at all: MASTER-CONTEXT section 6 records that
   ScanResult's own docs say the supplicant tracks noise but never reports it to
   apps, and there is no public noise getter. So this is a HatchCell: hatch fill,
   dashed border, em-dash marker, `untested` ink. The mint is REMOVED rather than
   dimmed, because mint means pass and a measurement that cannot exist has not
   passed. It has no animation in any state, and must never be given one. */
.pgs-wifi .pgsw-cell--hatch {
  background: var(--pgsw-hatch);
  border: 1px dashed var(--pgsw-hatch-line);
}
.pgs-wifi .pgsw-cell--hatch .pgsw-cell-lbl,
.pgs-wifi .pgsw-cell--hatch .pgsw-cell-val { color: var(--pgsw-untested); }

/* DIVERGENCE 2. The HatchCell's reason, on the card's full width because it
   does not fit in 72px at the 9px mono floor. 9px, not the canvas's 8.5px: the
   handover allows 8px only for non-essential axis ticks, and the reason a value
   is missing is the opposite of non-essential. Tracking is eased to 1.2px so
   the 40 characters clear the 309px of card width (~218px at this size). */
.pgs-wifi .pgsw-hatch-why {
  font-size: 9px;
  letter-spacing: 1.2px;
  color: var(--pgsw-untested);
  margin-top: 8px;
  white-space: nowrap;
}


/* ---------------------------------------------------------------------------
   10. Channel crowding. Canvas lines 305-315.
       This is the payload of the screen (canvas note line 348): it explains the
       dBm number instead of just reporting it. Bars 1-5 are 2.4 GHz, bars 6-8
       are 5 GHz, and yours is bar 6.
       NOT ANIMATED. See the motion argument in this file's header: these are a
       finished count from a 30s-cadence neighbour scan that Android's own scan
       throttle forces, so movement would be a lie about the data's freshness.
   --------------------------------------------------------------------------- */

.pgs-wifi .pgsw-bars {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  height: 40px;
  margin-top: 9px;
}
.pgs-wifi .pgsw-bar {
  flex: 1 1 0;
  min-width: 0;
  border-radius: 3px 3px 0 0;
  /* Heights are inline in wifi.html, one per channel, so the data reads in the
     markup rather than being buried in eight CSS classes. */
}
/* Two amber alphas in the canvas (.6 and .5) — the taller bar is the more
   congested one, so it is also the more opaque. Kept verbatim. */
.pgs-wifi .pgsw-bar--warn { background: rgba(255, 180, 84, .6); }
.pgs-wifi .pgsw-bar--warn2 { background: rgba(255, 180, 84, .5); }
.pgs-wifi .pgsw-bar--fail { background: rgba(255, 92, 110, .65); }
.pgs-wifi .pgsw-bar--idle { background: rgba(232, 238, 247, .3); }
.pgs-wifi .pgsw-bar--you { background: var(--pgsw-pass-bright); position: relative; }

.pgs-wifi .pgsw-bar-tag {
  position: absolute;
  bottom: calc(100% + 4px);
  left: 50%;
  transform: translateX(-50%);
  font-size: 7.5px;
  font-weight: 700;
  color: var(--pgsw-pass-text);
  white-space: nowrap;
}

/* 8px is inside the handover's allowance: "8px only for non-essential axis
   ticks". These two are exactly that. */
.pgs-wifi .pgsw-axis {
  display: flex;
  justify-content: space-between;
  margin-top: 6px;
}
.pgs-wifi .pgsw-axis .pgsw-mono { font-size: 8px; color: var(--pgsw-faint2); }


/* ---------------------------------------------------------------------------
   11. Nearby networks + the verdict. Canvas lines 318-327.
       The ladder is NOT ANIMATED. Reasoning in this file's header: a neighbour's
       RSSI comes from the 30s scan, not the 5s connection read, so the app's own
       SignalLadder liveness gate (a sample younger than 6s) would be false here.
   --------------------------------------------------------------------------- */

.pgs-wifi .pgsw-net {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 9px;
}
.pgs-wifi .pgsw-net-name {
  font-size: 12px;
  font-weight: 700;
  color: var(--pgsw-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1 1 auto;
  min-width: 0;
}
.pgs-wifi .pgsw-net-ch { font-size: 9px; color: var(--pgsw-faint); flex: none; }

.pgs-wifi .pgsw-ladder {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 12px;
  flex: none;
}
/* Amber, not mint: a neighbour strong enough to be heard clearly is a caution,
   and it is the same amber as the CH 1 bar it corresponds to in the histogram
   above. Heights are inline in wifi.html. */
.pgs-wifi .pgsw-rung {
  width: 3px;
  background: var(--pgsw-warn);
  border-radius: 1px;
  flex: none;
}
/* An unlit rung is a statement, not an empty slot: "the signal is not this
   strong". ui.dart:3247-3249 forbids animating it for exactly that reason. */
.pgs-wifi .pgsw-rung--off { background: rgba(255, 255, 255, .16); }

.pgs-wifi .pgsw-verdict {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  padding-top: 9px;
  border-top: 1px solid var(--pgsw-line-hair);
}
.pgs-wifi .pgsw-verdict-txt {
  font-size: 11.5px;
  color: var(--pgsw-mut);
  line-height: 1.4;
}
.pgs-wifi .pgsw-verdict-txt b { color: var(--pgsw-ink); font-weight: 700; }


/* ---------------------------------------------------------------------------
   12. Actions + the nav reserve. Canvas lines 329-333.
       Actions are light, never coloured (handover section 2): the primary is a
       white gradient with dark ink, the secondary is soft fill plus the 1px
       line. The right-hand button is the geiger beeper (canvas note line 350).
   --------------------------------------------------------------------------- */

.pgs-wifi .pgsw-actions {
  display: flex;
  gap: 9px;
  margin-top: 11px;
  flex: none;
}
.pgs-wifi .pgsw-cta {
  flex: 1 1 auto;
  min-width: 0;
  height: 48px;
  border-radius: 18px;
  background: linear-gradient(150deg, #FFFFFF, #CFDBE9);
  font-family: var(--pgsw-font);
  font-size: 12.5px;
  font-weight: 700;
  color: var(--pgsw-on-light);
  box-shadow: 0 10px 24px rgba(0, 0, 0, .4);
  display: flex;
  align-items: center;
  justify-content: center;
  /* cursor:default, not pointer: this is a picture of a control, not a control. */
  cursor: default;
}
.pgs-wifi .pgsw-icon-btn {
  flex: none;
  width: 48px;
  height: 48px;
  border-radius: 18px;
  background: rgba(255, 255, 255, .07);
  border: 1px solid var(--pgsw-line-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--pgsw-ink2);
  cursor: default;
}

/* LAYOUT CONTRACT, handover section 5: the floating nav overlays the column, so
   reserve >= 104px and keep every last control >= 20px clear of the nav's top
   edge (its shadow reaches ~20px upward). Measured here: the action row ends
   ~50px above the nav. If you add a block and it overflows, remove a block or
   shorten the chart. Never nudge this number and never nudge padding. */
.pgs-wifi .pgsw-navgap { height: 104px; flex: none; }


/* ---------------------------------------------------------------------------
   13. Bottom nav. Canvas lines 42-44, 335-341.
        Tools is the active tab because Wi-Fi check is reached from Tools.
        Tab 4 reads "Diagnose" (divergence 5).
   --------------------------------------------------------------------------- */

.pgs-wifi .pgsw-nav {
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: 14px;
  height: 72px;
  border-radius: 26px;
  background: rgba(9, 13, 20, .88);
  border: 1px solid var(--pgsw-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-wifi .pgsw-tab {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  color: var(--pgsw-nav-ink);
}
.pgs-wifi .pgsw-tab-lbl { font-size: 9.5px; font-weight: 600; }

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


/* ---------------------------------------------------------------------------
   14. Keyframes. The canvas's own (lines 46-56), namespaced pgswifi* so they
       cannot collide with the site, the Home mock, or another gallery screen.
         pgswifiPulse   = pgPulse    (canvas line 51)
         pgswifiRing    = pgRing     (canvas line 48)
         pgswifiBreathe = pgBreathe  (canvas line 47)
         pgswifiDraw    = pgDraw     (canvas line 54)
       The canvas's other seven keyframes (pgSweep, pgFlowX, pgMarq, pgBlink,
       pgBar, pgFill, pgFloat) are deliberately absent: nothing on this screen
       is a logo, a dashed link, a scrolling trace, a live signal stream or an
       indeterminate task. Adding one would mean animating settled data.
   --------------------------------------------------------------------------- */

@keyframes pgswifiPulse { 0%, 100% { opacity: 1; } 50% { opacity: .28; } }

/* Ends at opacity 0, which is why "no ring" is its honest final frame. */
@keyframes pgswifiRing {
  0% { transform: scale(.6); opacity: .55; }
  100% { transform: scale(1.55); opacity: 0; }
}

/* 0%/100% sit at opacity .5 deliberately — this glow never rests at full. */
@keyframes pgswifiBreathe {
  0%, 100% { opacity: .5; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.04); }
}

/* No `to` block: CSS interpolates up to the element's own stroke-dashoffset,
   i.e. the reading. The arc must always finish on the complete value. */
@keyframes pgswifiDraw { from { stroke-dashoffset: 251.3px; } }


/* ---------------------------------------------------------------------------
   15. HONEST FINAL FRAMES — block A: offstage.
        Your tab switcher toggles .is-offstage on .pgs-wifi. This is the web's
        TickerMode: an offstage screen genuinely stops ticking, exactly as the
        app's IndexedStack children do (lib/app.dart:103-110). It is also a
        performance requirement — six backdrop-filter: blur(20px) plates
        animating at once drop frames on a mid-range phone.
        ***  KEEP THIS BLOCK AND BLOCK B (section 16) IDENTICAL.  ***
   --------------------------------------------------------------------------- */

.pgs-wifi.is-offstage .pgsw-live-dot { animation: none; opacity: 1; }
.pgs-wifi.is-offstage .pgsw-dial-ring { display: none; }
.pgs-wifi.is-offstage .pgsw-dial-glow { animation: none; opacity: .5; transform: scale(1); }
.pgs-wifi.is-offstage .pgsw-dial-arc { animation: none; }


/* ---------------------------------------------------------------------------
   16. 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. The dot holds fully lit, the rings vanish
        (they end at opacity 0), the glow parks at its own 0% frame, and the arc
        holds its complete value.
        ***  IDENTICAL TO BLOCK A ABOVE.  ***

        Every selector is gated on html:not(.motion-opt-in), exactly as
        styles.css and mock.css already do. That class is set only when the
        visitor explicitly asks for motion via the site's "Play the motion"
        control, which is itself only rendered when the preference is detected.
        Default behaviour, and the behaviour with JS off, is unchanged: the OS
        preference wins and these honest final frames apply. Block A is
        deliberately NOT gated, because offstage is a performance fact rather
        than a preference, and the opt-in must not defeat it.
   --------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html:not(.motion-opt-in) .pgs-wifi .pgsw-live-dot { animation: none; opacity: 1; }
  html:not(.motion-opt-in) .pgs-wifi .pgsw-dial-ring { display: none; }
  html:not(.motion-opt-in) .pgs-wifi .pgsw-dial-glow { animation: none; opacity: .5; transform: scale(1); }
  html:not(.motion-opt-in) .pgs-wifi .pgsw-dial-arc { animation: none; }
}
