/* ============================================================================
   Pingo — screen 1a, DEVICES (live list, mid-scan).
   Owns screens/devices.html and nothing else. Every selector below is scoped
   under .pgs-devices, and every keyframe name starts with "pgsdevices", so
   this file cannot reach styles.css, mock.css, or any sibling screen in the
   gallery. Two screens declaring `pgSweep` differently would fight; this is
   the reason for both prefixes.

   Rebuilt verbatim from the LOCKED design canvas
     C:/Dev/pingo/design/Pingo Screens.dc.html   <div class="opt" id="1a">
     canvas lines 68-174, with shared screen chrome at lines 32-45 and the
     shared keyframes at lines 46-56.
   Token, component and layout rules from
     C:/Dev/pingo/design/PINGO-STEEL-HANDOVER.md sections 2, 3, 4 and 5.

   ---------------------------------------------------------------------------
   THE CARDINAL RULE, from C:/Dev/pingo/MASTER-CONTEXT.md:39
       "Motion is a liveness cue: nothing hatched, withheld or stale may
        animate."
   It outranks the canvas and it outranks making this screen look livelier.

   WHAT THIS SCREEN'S MOMENT ACTUALLY JUSTIFIES — read this before adding
   anything. 1a is captured with a sweep IN FLIGHT, and exactly one thing on
   the screen is happening right now: that sweep. So the whole motion budget
   is TWO instances of ONE animation, the 1s indeterminate quadrant ring
   (header + footer). That is the complete inventory. Nothing else moves.

   Why it is deliberately this thin:
     * Every other number here already landed. "3 ms", "-52", "-67", "4 ms"
       are settled readings and "FIRST SEEN 2 MIN AGO" is two minutes old. An
       entrance, a pulse or a tint-breathe on a settled row would say "this is
       arriving now", which is the one claim the rule forbids.
     * The screens in this gallery are each captured at a NAMED MOMENT and
       carry only the motion that belongs to it. 1d Tools is almost perfectly
       still; 1b's dial is drawing. That contrast is what makes the moving
       screens read as live. A shared ambient loop across all six would
       destroy the signal and would also animate 1b's hatched pending phases
       and this screen's hatched row, both of which are forbidden outright.
     * Only the ACTIVE screen animates (see .is-offstage, section 12). Six
       mocks each running backdrop-filter: blur(20px) with live animations
       would drop frames on a mid-range phone, and it would misrepresent the
       app, whose offstage IndexedStack children genuinely stop ticking
       (lib/app.dart:103-110, TickerMode enabled only for the active tab).

   ---------------------------------------------------------------------------
   DELIBERATE DIVERGENCES FROM THE CANVAS. Four, all reasoned, none accidents.

   1. THE HATCHED "Identifying…" ROW DOES NOT ANIMATE.
      Canvas line 143 gives its label `animation:pgPulse 1.8s ease-in-out
      infinite`. Removed. That row is the Home hatch pattern, and hatch is
      this system's word for "no measurement here" — the app states it as
      "while a run is in flight with no honest fraction the track is hatched,
      which is the app's word for 'no measurement here'"
      (lib/widgets/phase_strip.dart:403-405). A pulsing label on a hatched row
      is a liveness claim about a device Pingo cannot yet name, made in the
      one place on the screen whose entire purpose is to admit it does not
      know. The turning ring nine rows up already says the scan is alive, and
      it says it about the scan, which is true. So this row holds perfectly
      still, in every state, including when motion is explicitly opted in.
      This is also the difference that keeps 1a consistent with 1b, where the
      hatched pending phases must not animate beside the running one.

   2. THE HEADER RING CARRIES NO PERCENTAGE.
      Canvas line 81 prints "68%" beside the ring. Removed and replaced with
      the word SCANNING. The app's own source carries the reason verbatim at
      lib/widgets/device_widgets.dart:463-466: "the canvas draws '68%' beside
      it, but the device sweep exposes no total, so a fraction would be
      invented. This ring signals ACTIVITY only and never carries a
      percentage." A word says the same true thing the ring says and claims
      nothing the ring cannot. The chip's own geometry (radius 16, padding
      8/12, gap 7, fill, border) is the canvas's, unchanged; only the glyphs
      inside it changed, from Sora 11.5/w700 (a number) to mono 10/w700/ls 1.2
      (a state word), which is the handover's StatusChip treatment — "pulsing
      dot + short mono label", section 3 — and stays above the 9px mono floor.

   3. WORK-HQ -> WORK-HQ (canvas line 79).
      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 at mock.html:76 and index.html:217, per
      RESUME-WEBSITE.md section 4.

   4. THE FOURTH NAV LABEL READS "Insights", NOT "Ask" (canvas line 159).
      The settled decision merges Ask Pingo and Guided Diagnose into one
      surface named Diagnose, with the AI parked and off by default, so the
      site must not advertise it. The screen behind that tab (1f) ships as
      Diagnose, and a gallery whose 1a nav says "Ask" while 1f is titled
      "Diagnose" would contradict itself. The ICON is untouched: the nav is
      shared chrome across all six screens, so only the string the decision
      renamed moved. If the gallery ever standardises the icon, that belongs
      in one place, not here.

   ---------------------------------------------------------------------------
   ILLUSTRATIVE VALUES. Every string and number on this screen is the canvas's
   own (canvas lines 79-151), so nothing had to be invented: 14 on WORK-HQ /
   1 new · All 14 · New 1 · Wired 4 · Cameras · Dream Machine 192.168.1.1
   UBIQUITI 3 ms WIRED GATEWAY · MacBook Pro 192.168.1.31 APPLE -52 DBM ·
   Galaxy S24 192.168.1.71 FIRST SEEN 2 MIN AGO NEW · Front door camera
   192.168.1.58 HIKVISION -67 DBM · brother-hl2350 192.168.1.140 BROTHER 4 ms
   WIRED · Identifying… 192.168.1.203 VENDOR LOOKUP · SWEEPING 192.168.1.0/24.
   Two strings are not the canvas's, both listed as divergences above: WORK-HQ
   (was WORK-HQ) and SCANNING (was 68%). They are illustrative, not measured,
   and the host page must say so — see data-pgs-note on the root.

   The 8px unit captions (WIRED / DBM) are the canvas's authored sizes, kept
   verbatim rather than re-specced, which is the same call mock.css made
   (mock.css:430, :658, :721). The handover's 9px floor is protected by
   building at true size and never scaling below --gal-scale .88.

   ---------------------------------------------------------------------------
   PUBLIC API — the integrator should not need to open this file.
     .pgs-devices                 root + outer box. Occupies
                                  calc(375px * scale) x calc(812px * scale).
     .pgs-devices--bezel          optional phone bezel (padding + border sit
                                  OUTSIDE the box, so layout is unaffected)
     .pgs-devices.is-offstage     stop all motion on honest final frames.
                                  Toggle this from the gallery shell so only
                                  the visible screen animates. Required.
     --gal-scale                  set on any ancestor. Default .88, which is
                                  the FLOOR. 1 = authored pixel sizes.
     [data-pgs-screen="1a"]       hook for the shell
     [data-pgs-title]             "Devices"
     [data-pgs-moment]            "live list, mid-scan"
     [data-pgs-note]              the illustrative-values sentence to print
   There is no tier switcher and no JS API on this screen: 1a has one state.
   ============================================================================ */


/* ---------------------------------------------------------------------------
   1. Outer box, tokens, defensive reset
       content-box so the optional bezel's padding and border sit OUTSIDE the
       375px, exactly as mock.css does it (mock.css:97-101).
   --------------------------------------------------------------------------- */

.pgs-devices {
  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;

  /* The webfonts (Sora 400/600/700/800, JetBrains Mono 500/700) are the host
     page's job; index.html already loads both. Every negative letter-spacing
     below assumes them. These five resets exist because the site's body sets
     line-height 1.6 and its own colour, which would break the whole stack. */
  font-family: var(--pgsd-font);
  font-size: 16px;
  font-weight: 400;
  line-height: normal;
  letter-spacing: normal;
  text-align: left;
  color: var(--pgsd-ink);
  -webkit-font-smoothing: antialiased;

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

  /* Handover section 2, colour table. Same values as mock.css's --pgm-*, named
     apart so the two files can never be confused for one another. */
  --pgsd-ink: #F2F6FC;          /* primary text/numbers */
  --pgsd-ink2: #D7E0EC;         /* secondary */
  --pgsd-faint: #94A2B6;        /* mono labels/units */
  --pgsd-faint2: #7F8EA4;       /* tertiary captions */
  --pgsd-untested: #A3B1C3;     /* the hatched row's label */
  --pgsd-on-light: #0B121C;     /* text on white actions */
  --pgsd-nav-idle: #7C8CA1;

  --pgsd-glass: rgba(9, 14, 21, .52);
  --pgsd-line: rgba(255, 255, 255, .11);
  --pgsd-line-strong: rgba(255, 255, 255, .14);
  --pgsd-line-soft: rgba(255, 255, 255, .13);
  --pgsd-surface-soft: rgba(255, 255, 255, .07);

  /* mint = pass · amber = caution · red = fail, and nothing else. There is no
     brand accent hue in this system (handover section 1). Red is unused on
     this screen: nothing here has failed. */
  --pgsd-pass: #35D6A0;
  --pgsd-pass-text: #6FE0B6;
  --pgsd-pass-tint: rgba(53, 214, 160, .12);
  --pgsd-pass-border: rgba(53, 214, 160, .3);
  --pgsd-warn: #FFB454;
  --pgsd-warn-text: #E7C08A;

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

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

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

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

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

/* Canvas type helpers .mono / .num (canvas lines 39-40) */
.pgs-devices .pgsd-mono { font-family: var(--pgsd-mono); font-variant-numeric: tabular-nums; }
.pgs-devices .pgsd-num { font-variant-numeric: tabular-nums; letter-spacing: -1px; }


/* ---------------------------------------------------------------------------
   2. Screen root + the two bloom layers (canvas lines 33-35).
       Built at the canvas's TRUE authored size and scaled as a whole, so the
       8-9px mono floor survives intact. This is the approach the 6a Home mock
       already proved (mock.css:184-197).
   --------------------------------------------------------------------------- */

.pgs-devices .pgsd-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-devices .pgsd-bloom { position: absolute; top: 0; left: 0; right: 0; }
.pgs-devices .pgsd-bloom--sheet {
  height: 46%;
  background: linear-gradient(180deg,
    rgba(163, 187, 214, .2), rgba(163, 187, 214, .06) 46%, transparent 100%);
}
.pgs-devices .pgsd-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 for the rule, line 74 for the content)
   --------------------------------------------------------------------------- */

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


/* ---------------------------------------------------------------------------
   4. Content column — 18px screen padding, and it does not scroll
      (canvas line 37). Every child is flex:none: the stack is tuned to fit
      812px, and per the LAYOUT CONTRACT an overflow is fixed by removing a
      block, never by nudging padding.
   --------------------------------------------------------------------------- */

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


/* ---------------------------------------------------------------------------
   5. Header — title + count, and the INDETERMINATE ACTIVITY RING
      (canvas lines 76-82)
   --------------------------------------------------------------------------- */

.pgs-devices .pgsd-head {
  display: flex;
  align-items: flex-end;      /* the chip sits on the count's baseline band */
  gap: 12px;
  padding: 2px 0 12px;
  flex: none;
}
.pgs-devices .pgsd-head-txt { flex: 1; min-width: 0; }

.pgs-devices .pgsd-title {
  font-size: 23px;
  font-weight: 800;
  letter-spacing: -.6px;
  color: var(--pgsd-ink);
}
.pgs-devices .pgsd-count {
  font-size: 9.5px;
  color: var(--pgsd-faint);
  letter-spacing: 1.2px;
  margin-top: 4px;
  white-space: nowrap;
}

/* The activity chip. Geometry is the canvas's verbatim; see divergence 2 for
   why the label is a word and not a fraction. */
.pgs-devices .pgsd-activity {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border-radius: 16px;
  padding: 8px 12px;
  background: var(--pgsd-surface-soft);
  border: 1px solid var(--pgsd-line-strong);
  font-size: 11.5px;
  font-weight: 700;
  color: var(--pgsd-ink);
  flex: none;
}
.pgs-devices .pgsd-activity-lbl {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  color: var(--pgsd-ink);
  white-space: nowrap;
}

/* THE ONLY ANIMATION ON THIS SCREEN.
   An 11px indeterminate ring: a pale full-circle track with ONE bright
   quadrant turning once a second. The canvas builds it out of borders
   (canvas lines 81 and 150), and that happens to land on the app's real
   geometry almost exactly — lib/widgets/device_widgets.dart:536 draws a
   track at rgba(232,238,247,0.30) plus a 90-degree arc at #F2F6FC. Kept as
   borders, verbatim: the quadrant's corners are mitred rather than round-
   capped, which is the canvas's authored rendering.
   1s is the handover's timing for a rotating quadrant, and the handover is
   explicit that it is "for INDETERMINATE activity only, never a percentage".
   box-sizing:border-box so the 2px border stays inside the 11px box. */
.pgs-devices .pgsd-spin {
  width: 11px;
  height: 11px;
  flex: none;
  border-radius: 50%;
  border: 2px solid rgba(232, 238, 247, .3);
  border-top-color: var(--pgsd-ink);
  animation: pgsdevicesSweep 1s linear infinite;
}

/* The footer instance. Dimmer, per canvas line 150, and it deliberately
   shares the header's exact 1s clock with NO delay or offset: the two rings
   report ONE sweep, so two independent-looking clocks would imply two
   activities. Lockstep is the honest rendering here. */
.pgs-devices .pgsd-spin--sweep {
  border-color: rgba(232, 238, 247, .25);
  border-top-color: var(--pgsd-ink2);
}


/* ---------------------------------------------------------------------------
   6. Glass card + search field (canvas lines 38, 83-86)
   --------------------------------------------------------------------------- */

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

.pgs-devices .pgsd-search {
  border-radius: 16px;
  padding: 11px 13px;
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
}
.pgs-devices .pgsd-search-ico { color: var(--pgsd-faint); }
.pgs-devices .pgsd-search-ph { font-size: 12.5px; color: var(--pgsd-faint2); }


/* ---------------------------------------------------------------------------
   7. Filter chips (canvas lines 87-92).
      The active chip is a WHITE action, not a coloured one. Handover section 1:
      actions are light, and mint/amber/red are reserved for pass/caution/fail.
   --------------------------------------------------------------------------- */

.pgs-devices .pgsd-filters {
  display: flex;
  gap: 7px;
  margin-top: 11px;
  flex: none;
  overflow: hidden;           /* the chip row clips rather than wraps */
}
.pgs-devices .pgsd-chip {
  flex: none;
  border-radius: 14px;
  padding: 7px 13px;
  background: var(--pgsd-surface-soft);
  border: 1px solid var(--pgsd-line-soft);
  color: var(--pgsd-ink2);
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
}
.pgs-devices .pgsd-chip--on {
  background: var(--pgsd-ink);
  border-color: transparent;
  color: var(--pgsd-on-light);
  font-weight: 700;
}


/* ---------------------------------------------------------------------------
   8. The device list (canvas lines 93-148).
      DeviceRow, handover section 3: 46px type-icon tile + name + mono meta +
      right value, and the WHOLE card is tinted by state. No photos at row
      scale — a 46px photo cannot read, and device photos belong on 1e.
   --------------------------------------------------------------------------- */

.pgs-devices .pgsd-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 12px;
  flex: none;
}

.pgs-devices .pgsd-row {
  border-radius: 18px;
  padding: 10px;
  display: flex;
  align-items: center;
  gap: 11px;
}

/* 46px icon tile. Neutral by default; tinted variants below. */
.pgs-devices .pgsd-tile {
  width: 46px;
  height: 46px;
  flex: none;
  border-radius: 13px;
  background: var(--pgsd-surface-soft);
  border: 1px solid var(--pgsd-line-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--pgsd-ink2);
}
.pgs-devices .pgsd-tile--pass {
  background: var(--pgsd-pass-tint);
  border-color: var(--pgsd-pass-border);
  color: var(--pgsd-pass-text);
}
.pgs-devices .pgsd-tile--warn {
  background: rgba(255, 180, 84, .14);
  border-color: rgba(255, 180, 84, .32);
  color: var(--pgsd-warn);
}
/* The hatch tile carries NO fill of its own: the 45deg stripes sit straight
   on the glass, so "nothing measured" reads as absence rather than as another
   surface (canvas line 141). */
.pgs-devices .pgsd-tile--hatch {
  background: var(--pgsd-hatch);
  border: 1px dashed var(--pgsd-hatch-line);
}

.pgs-devices .pgsd-row-txt { flex: 1; min-width: 0; }
.pgs-devices .pgsd-row-head { display: flex; align-items: center; gap: 7px; }
.pgs-devices .pgsd-row-name {
  font-size: 13px;
  font-weight: 700;
  color: var(--pgsd-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pgs-devices .pgsd-row-meta {
  font-size: 9.5px;
  color: var(--pgsd-faint);
  margin-top: 3px;
  white-space: nowrap;
}
.pgs-devices .pgsd-row-meta--warn { color: var(--pgsd-warn-text); }

/* Small mono state tag beside the name (canvas lines 98, 116) */
.pgs-devices .pgsd-tag {
  font-size: 7.5px;
  font-weight: 700;
  letter-spacing: 1px;
  padding: 2px 5px;
  border-radius: 4px;
  flex: none;
}
.pgs-devices .pgsd-tag--pass { background: rgba(53, 214, 160, .14); color: var(--pgsd-pass-text); }
.pgs-devices .pgsd-tag--warn { background: rgba(255, 180, 84, .2); color: var(--pgsd-warn); }

/* Right-hand value column. Mint only where a check genuinely passed (the
   gateway and the printer answered); a dBm reading is a reading, not a
   verdict, so it stays neutral ink (canvas lines 101, 110, 128, 137). */
.pgs-devices .pgsd-row-val { text-align: right; flex: none; }
.pgs-devices .pgsd-val { font-size: 12px; font-weight: 700; color: var(--pgsd-ink); }
.pgs-devices .pgsd-val--pass { color: var(--pgsd-pass-text); }
.pgs-devices .pgsd-unit {
  font-size: 8px;             /* the canvas's authored size, kept verbatim */
  color: var(--pgsd-faint2);
  margin-top: 2px;
}

/* 8a. The new device: the WHOLE card tints amber (canvas line 113). Amber is
       caution, which is the honest reading of "something joined and you have
       not seen it before". It shows a chevron instead of a number because the
       useful next step is to open it, not to read a figure.
       It does NOT animate. "FIRST SEEN 2 MIN AGO" is two minutes old, so an
       entrance or a pulse would claim it is landing right now. The tint is the
       cue, exactly as the canvas note says: "One glance finds the thing that
       changed" (canvas line 168). */
.pgs-devices .pgsd-row--new {
  border-color: rgba(255, 180, 84, .34);
  background: rgba(255, 180, 84, .07);
}
.pgs-devices .pgsd-row-chev { color: var(--pgsd-warn); }

/* 8b. THE HATCHED ROW (canvas lines 140-146). The honesty centrepiece of 1a.
       Dashed border + 45deg stripes = "no measurement here", so no invented
       name while the vendor lookup is still running.
       There is no animation declared on this row or on any of its children,
       and nothing in sections 12 or 13 needs to switch one off, because there
       has never been one to switch off. See divergence 1. Do not add one. */
.pgs-devices .pgsd-row--hatch {
  border-style: dashed;
  border-color: var(--pgsd-hatch-line);
}
.pgs-devices .pgsd-row-name--hatch {
  font-size: 12.5px;
  color: var(--pgsd-untested);
}
.pgs-devices .pgsd-row-meta--hatch { color: var(--pgsd-faint2); }


/* ---------------------------------------------------------------------------
   9. Footer sweep status (canvas lines 149-152)
   --------------------------------------------------------------------------- */

.pgs-devices .pgsd-sweep {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 12px 0 0;
  flex: none;
}
.pgs-devices .pgsd-sweep-lbl {
  font-size: 9.5px;
  letter-spacing: 1.2px;
  color: var(--pgsd-faint);
  white-space: nowrap;
}


/* ---------------------------------------------------------------------------
   10. Nav reserve (canvas line 153).
       104px, and it is structure rather than padding: the LAYOUT CONTRACT
       (handover section 5) requires >= 104px reserved below any content column
       because the floating nav overlays content and its shadow reaches ~20px
       above its own top edge. Measured against this stack the last real
       control (the sweep line) ends ~55px above the nav's top edge, which
       satisfies both halves of the contract: >= 20px of clearance, and no
       more than 60px of dead band.
   --------------------------------------------------------------------------- */

.pgs-devices .pgsd-navreserve { height: 104px; flex: none; }


/* ---------------------------------------------------------------------------
   11. Bottom nav (canvas lines 42-44 for the rules, 155-161 for content).
       72px glass pill, 5 items, Tools centre as a white gradient tile, active
       item in `ink`. The fourth label reads "Diagnose" — see divergence 4.
   --------------------------------------------------------------------------- */

.pgs-devices .pgsd-nav {
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: 14px;
  height: 72px;
  border-radius: 26px;
  background: rgba(9, 13, 20, .88);
  border: 1px solid var(--pgsd-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-devices .pgsd-tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  color: var(--pgsd-nav-idle);
  min-width: 0;
}
.pgs-devices .pgsd-tab-lbl { font-size: 9.5px; font-weight: 600; white-space: nowrap; }
.pgs-devices .pgsd-tab--on { color: var(--pgsd-ink); }
.pgs-devices .pgsd-tab--on .pgsd-tab-lbl { font-weight: 700; }
.pgs-devices .pgsd-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(--pgsd-on-light);
  box-shadow: 0 8px 18px rgba(0, 0, 0, .45);
}


/* ---------------------------------------------------------------------------
   12. Keyframes. One, because this screen's moment justifies one.
       Prefixed "pgsdevices" so it cannot collide with mock.css's pgm* names,
       with styles.css, or with a sibling screen declaring its own sweep at a
       different duration.
         pgsdevicesSweep = the canvas's pgSweep (canvas line 46)
   --------------------------------------------------------------------------- */

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


/* ---------------------------------------------------------------------------
   13. HONEST FINAL FRAMES — block A: offstage.
       The gallery shell toggles .is-offstage on .pgs-devices when this screen
       is not the active tab. This is the web's TickerMode, and it is required:
       the app's offstage IndexedStack children genuinely stop ticking.

       What is the honest resting pose of an INDETERMINATE ring? It has no
       final frame by definition, so we use the app's own answer: park the
       controller at 0 and hold a readable pose. rotate(0deg) is pgSweep's
       authored 0% frame and it is where the app's own spinner parks
       (lib/widgets/phase_strip.dart:372 draws the bright quadrant at 12
       o'clock inside a RotationTransition whose controller stops at turns 0).
       That is a real authored pose, not an arbitrary frozen mid-frame.
       Everything else on this screen is already static, so there is nothing
       else to stop — the hatched row in particular needs no entry here
       because it never animated.
       ***  KEEP THIS BLOCK AND BLOCK B (section 14) IDENTICAL.  ***
   --------------------------------------------------------------------------- */

.pgs-devices.is-offstage .pgsd-spin { animation: none; transform: rotate(0deg); }


/* ---------------------------------------------------------------------------
   14. HONEST FINAL FRAMES — block B: prefers-reduced-motion.
       Not animation-play-state:paused, and not a frozen mid-frame: the same
       authored resting pose as block A.

       Every selector is gated on html:not(.motion-opt-in), exactly as
       styles.css and mock.css already do it (mock.css:901-917). That class is
       set ONLY when the visitor explicitly asks for motion via the site's
       "Play the motion" control, which itself only renders when the
       preference is detected. Default behaviour, and the behaviour with JS
       off, is unchanged: the OS preference wins and this resting pose applies.
       Block A (.is-offstage) is deliberately NOT gated, because being on
       another tab is a fact, not a preference.
       ***  IDENTICAL TO BLOCK A ABOVE.  ***
   --------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html:not(.motion-opt-in) .pgs-devices .pgsd-spin { animation: none; transform: rotate(0deg); }
}
