/* ============================================================================
   Pingo — Steel screen 1e, DEVICE DETAIL.  Owns screens/device.html and nothing
   else.  Every selector below is scoped under .pgs-device and every @keyframes
   name starts with `pgsdevice`, so this file cannot collide with the site
   (styles.css), with the Home hero mock (mock.css), or with a sibling gallery
   screen that declares its own sweep/pulse/ring.

   SOURCE OF TRUTH FOR EVERY PIXEL
     C:/Dev/pingo/design/Pingo Screens.dc.html  ->  <div class="opt" id="1e">,
     lines 424-499. The shared canvas chrome (.phone/.screen/.bloom/.sb/.body/
     .card/.mono/.num/.lbl/.tool/.nav/.nvi/.nvl and the @keyframes block) comes
     from the same file, lines 32-53. Colours, radii, paddings, font sizes,
     gradients and the two animation durations below are copied verbatim from
     those lines; anywhere a value was CHOSEN rather than copied it is called out
     in the "ILLUSTRATIVE VALUES" or "DELIBERATE DIVERGENCES" list.
     Supporting spec: C:/Dev/pingo/design/PINGO-STEEL-HANDOVER.md sections 2-5
     (tokens, PhotoSlot 92x74 r16, TracePanel, the layout contract in section 5).

   ---------------------------------------------------------------------------
   THE LIVE MOMENT, AND WHY THIS SCREEN IS NEARLY STILL

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

   1e is captured at the moment "one live trace, everything else settled". The
   record is a record: an IP that was read once, a MAC Android refuses to give
   us, a first-seen timestamp, ports nobody has scanned. None of that is
   arriving right now, so none of it may move. The canvas agrees: screen 1e
   declares exactly TWO animations in 84 KB of markup, and this file implements
   exactly those two and nothing else.

     1. pgsdeviceMarq 9s linear infinite  on the response-time trace
        (canvas line 451: `animation:pgMarq 9s linear infinite`). This is the
        one genuinely streaming reading on the screen.
     2. pgsdevicePulse 2.2s ease-in-out infinite  on the LAST presence bucket
        (canvas line 474). Only the current bucket pulses, because only the
        current bucket is still being filled in. The eleven behind it are
        history and hold still.

   Deliberately NOT animated, and each for a stated reason:
     * The hatched MAC row. It is a withheld value. This is the single rule the
       whole design system exists to protect.
     * The OPEN PORTS row and its inline SCAN action. "Not scanned yet" is a
       not-yet-measured state; a spinner or a shimmer there would suggest a scan
       is under way when nothing is running.
     * The empty photo slot. Nothing is loading; the user simply has not set one.
     * The "ONLINE" and "NEW" tags. These are settled facts about the device,
       not readings landing now. A pulsing ONLINE tag would double-count the
       liveness the trace already asserts.
     * The "Watching" action tile, even though it is the ON state. The toggle
       being on is a fact about a setting, not a stream of data.
     * The eleven settled presence buckets, the grey "before Pingo watched"
       ones most of all: grey means we have no data for that hour, and animating
       absence is the same lie as animating a hatch.
     * Every record row, the back header, the nav. No ambient loop anywhere.
   There is no expanding ring, no breathe, no sweep, no dashed-wire flow, no
   packet dot, no arc draw, no signal ladder and no rotating quadrant on this
   screen, because 1e has nothing for any of them to describe. The stillness is
   the point: it is what makes 1a's streaming list and 1b's spinning phase read
   as genuinely live when the gallery switches to them.

   HONEST FINAL FRAMES (sections 13 and 14)
     * The trace: `animation: none` parks the strip at transform:translateX(0).
       Because the polyline is duplicated, translateX(0) and pgsdeviceMarq's own
       100% frame translateX(-50%) are pixel-identical, so the resting state IS
       the animation's final frame, and what the visitor sees is a real,
       complete waveform that has stopped scrolling. Never a mid-frame, and
       never animation-play-state:paused.
     * The live presence bucket: holds FULLY LIT (opacity 1), which is
       pgsdevicePulse's own 0%/100% frame. A dot or bar that means "live" must
       rest lit, never dimmed.
     * Nothing else has an animation or a transition to suppress, which is why
       both blocks are two rules long.

   ---------------------------------------------------------------------------
   ILLUSTRATIVE VALUES — the page must say numbers are illustrative, not measured
   Everything on this screen is the canvas's own published value (Galaxy S24 /
   SAMSUNG · ANDROID 15 / 192.168.1.71 / WI-FI 6 · 5 GHZ / -49 DBM · STRONG /
   TODAY 09:39 / ONLINE 1 H 58 M / 9 grey + 3 mint presence buckets), and the IP
   and first-seen agree with the same device's row in canvas 1a (line 117:
   "192.168.1.71 · FIRST SEEN 2 MIN AGO" at a 9:41 status bar), so the gallery
   stays internally consistent screen to screen.

   ONE value had to be chosen: the trace header reads `{{lan}} MS` in the canvas,
   an unfilled template hole that renders EMPTY in a real browser. Chosen: 6 MS.
   Why 6: it is a phone-to-phone round trip across Wi-Fi with power-save on, so
   it must sit above the 2 MS this project uses for the wired You->Gateway leg
   (see RESUME-WEBSITE.md, deferred fidelity item "wire 1 lost its ms chip") and
   well below the 18 MS internet leg in the Home mock. The canvas's own polyline
   wobbles from y=14 to y=32 around a y=28 baseline, i.e. heavy jitter on a low
   base, which is exactly what 6 MS with Wi-Fi power save looks like.

   ---------------------------------------------------------------------------
   DELIBERATE DIVERGENCES from the canvas (five, all recorded, none accidental)

   1. NO "Block?" ACTION, and the action grid is 2-up instead of 3-up.
      Canvas line 469 ships a third tile, "Block?", with a shield icon.
      MASTER-CONTEXT.md section 5 lists "Device block | DECLINED permanently, a
      phone cannot block another device", and its defect list flags "`Block?`
      reinstated despite being declined" as a bug to fix. So the tile is gone
      rather than relabelled: a control that cannot do the thing it names does
      not belong on a screen whose whole argument is that it only states what it
      knows. Dropping a column is layout-neutral (the grid keeps its height, the
      two tiles just get wider), so the 812px stack still fits with the same
      ~20px of slack and the layout contract in handover section 5 holds: 104px
      reserved for the nav, and well under the 60px of empty band it forbids.
      The rename affordance the third slot might otherwise carry is already on
      screen as the pencil button in the back header.

   2. THE PHOTO SLOT USES A SOLID BORDER, NOT A DASHED ONE.
      The canvas instantiates <image-slot ... placeholder="Drop a photo of this
      device">, a custom element that only exists inside the design canvas, so a
      real placeholder had to be built. It is the handover's PhotoSlot geometry
      exactly (92x74, radius 16) with surSoft fill + the 1px `line` border. The
      obvious drop-target treatment, a dashed border, is deliberately NOT used:
      `1px dashed rgba(255,255,255,.20)` is this system's HATCH vocabulary and
      means "not measured", and an empty photo slot is not a withheld
      measurement. Reusing the glyph would blunt the one marker the app relies
      on. The placeholder text is dropped too: at the 9px mono floor "Drop a
      photo of this device" cannot fit in 92px, so a camera glyph carries it.

   3. NAV LABEL "Ask" -> "Diagnose" (canvas line 484).
      The settled decision merges Ask and Guided Diagnose into one surface named
      Diagnose with the AI parked and off by default. The ICON is left exactly as
      the canvas draws it (speech bubble + spark) even though the spark reads as
      AI, because all six gallery screens share this nav and a unilateral glyph
      change here would make the bar jump as the visitor switches tabs. Label
      only; every sibling screen makes the same one-word change.

   4. `.pgsd-lbl` KEEPS THE CANVAS's #94A2B6, not mock.css's #61738E.
      mock.css darkened `.lbl` for the Home hero. This screen is built from
      Pingo Screens.dc.html line 41, where `.lbl` is `#94A2B6`, and every other
      gallery screen inherits from that same line, so the gallery is internally
      consistent and only the Home hero differs.

   5. `flex: none` ADDED TO THE TWO TRACE SVGS.
      The canvas relies on `width:50%` inside a `width:200%` flex row; a flex
      item's default `flex-shrink:1` can shrink it below that and the loop then
      shows a seam. mock.css made the same fix for the Home downlink trace.

   RECORDED, NOT ACTED ON — two content-honesty tensions for Fable to rule on.
   Both are reproduced verbatim because the brief for this screen names these
   exact rows and the canvas is authoritative for pixels; neither is a motion
   issue, and both are already owned by an app-side spec.
     a. CONNECTION and SIGNAL rows. design/DEVICE-PAGE-SPEC.md section B1 rules
        that another device's radio state is NEVER KNOWABLE from this phone and
        that those rows should collapse into one sentence about the limitation.
        The canvas predates that spec and prints "WI-FI 6 · 5 GHZ" and
        "-49 DBM · STRONG" (the latter in mint, i.e. as a pass).
     b. "SAMSUNG · ANDROID 15". SAMSUNG is honest (OUI lookup). An exact OS
        version is not derivable from a LAN scan; DEVICE-PAGE-SPEC section B2
        covers where identity may legitimately come from and requires stated
        provenance.
   Mono sizes below 9px (the 8px record-row labels, the 8px presence caption and
   the 7.5px tags) are the canvas's authored values and are reproduced as
   authored, which is why --gal-scale is floored at .88: any smaller and the
   handover's "minimum size 9px for any mono unit/caption" breaks even further.

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

     <link rel="stylesheet" href="screens/device.css">   load AFTER styles.css
     Paste screens/device.html where the screen should sit. It is a fragment.

     .pgs-device            outer box. Occupies real layout space of
                            calc(375px * scale) x calc(812px * scale).
     [data-pgs-device]      attribute hook on the same element, for the gallery's
                            IntersectionObserver / tab switcher to find it.
     .pgs-device.is-offstage
                            REQUIRED when the screen is not the active tab.
                            Stops both animations on their honest final frames.
                            This is the web's TickerMode; CSS cannot see the
                            viewport, so toggling it is the shell's job:
                              el.classList.toggle('is-offstage', !isActive);
     .pgs-device--bezel     optional device bezel. Adds padding + border OUTSIDE
                            the 375x812 box (the box is content-box for this).
     --gal-scale            set on .pgs-device or any ancestor. Default .88.
                            .88 IS THE FLOOR (9px mono rule). 1 = authored size.
     [data-pgs-screen]      the inner .pgsd-screen element.

     No JS is required for the screen itself: there is no tier switcher, no
     state classes and no text substitution. The only hook the shell needs is
     .is-offstage. Do NOT add the site's .motion-keep escape hatch to this
     subtree: under prefers-reduced-motion this screen must rest on final
     frames, always.
   ============================================================================ */


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

   The site's body sets a 1.6 line-height, a letter-spacing and a colour that
   would all break a 375px-wide stack tuned at the canvas's own metrics, so the
   root resets them rather than inheriting.
   --------------------------------------------------------------------------- */

.pgs-device {
  /* content-box so the optional bezel's padding/border 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;

  /* Sora 400/700/800 + JetBrains Mono 500/700 are the host page's job;
     index.html already loads both. Every negative letter-spacing assumes them. */
  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;

  /* Canvas lines 41, 48-53 of PINGO-STEEL-HANDOVER.md section 2 */
  --pgsd-ink: #F2F6FC;          /* primary text and numbers */
  --pgsd-ink2: #D7E0EC;         /* secondary */
  --pgsd-faint: #94A2B6;        /* mono labels and units */
  --pgsd-faint2: #7F8EA4;       /* tertiary captions */
  --pgsd-untested: #A3B1C3;     /* a value we do not have. Never mint, never red */
  --pgsd-on-light: #0B121C;     /* text on the white Tools tile */

  --pgsd-glass: rgba(9, 14, 21, .52);    /* canvas .card */
  --pgsd-solid: rgba(9, 14, 21, .72);    /* record rows: no blur, cheaper */
  --pgsd-soft: rgba(255, 255, 255, .06); /* inset cell / action tile */
  --pgsd-line: rgba(255, 255, 255, .11); /* 1px card border, load-bearing */
  --pgsd-line-soft: rgba(255, 255, 255, .09);
  --pgsd-line-strong: rgba(255, 255, 255, .14);

  --pgsd-pass-bright: #4BEDBA;
  --pgsd-pass-text: #6FE0B6;
  --pgsd-pass-tint: rgba(53, 214, 160, .12);
  --pgsd-pass-border: rgba(53, 214, 160, .34);
  --pgsd-warn: #FFB454;

  /* "hidden by Android" — a state, not a texture. Canvas line 459 uses .05
     here; tokens.dart's hatch token says .06. The canvas wins for pixels. */
  --pgsd-hatch: repeating-linear-gradient(45deg, transparent 0 4px, rgba(255, 255, 255, .05) 4px 8px);
}

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

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

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

/* Optional device bezel. Canvas .phone, line 32. Purely canvas chrome. */
.pgs-device.pgs-device--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);
}

/* Type helpers = the canvas's .mono / .num / .lbl, lines 39-41 */
.pgs-device .pgsd-mono { font-family: var(--pgsd-mono); font-variant-numeric: tabular-nums; }
.pgs-device .pgsd-num { font-variant-numeric: tabular-nums; letter-spacing: -1px; }
.pgs-device .pgsd-lbl { font: 700 9.5px var(--pgsd-mono); letter-spacing: 1.6px; color: var(--pgsd-faint); }
.pgs-device .pgsd-grow { flex: 1 1 auto; min-width: 0; }


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

   Scaling follows the 6a mock's proven approach: the screen is built at a true
   375x812 with the authored mono sizes intact and the WHOLE thing is scaled,
   so nothing was re-specced smaller. The outer box in section 1 uses the same
   calc() so it occupies the right amount of layout space.
   --------------------------------------------------------------------------- */

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

.pgs-device .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: #D7E0EC;
  position: relative;
  z-index: 3;
}
.pgs-device .pgsd-sb-icons { display: flex; gap: 6px; align-items: center; }


/* ---------------------------------------------------------------------------
   4. Content column.  Canvas line 37: 18px screen padding, and it does not
      scroll. The stack below is tuned to fit 812px exactly once.
   --------------------------------------------------------------------------- */

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


/* ---------------------------------------------------------------------------
   5. Back header — chevron, "Devices", rename pencil.  Canvas lines 432-436.
   --------------------------------------------------------------------------- */

.pgs-device .pgsd-back {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 2px 0 12px;
  flex: none;
}
.pgs-device .pgsd-back-title { font-size: 14px; font-weight: 700; color: var(--pgsd-ink); }
/* The rename affordance. It is also why dropping the declined "Block?" tile
   leaves no gap in the action set (divergence 1). */
.pgs-device .pgsd-pencil {
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, .07);
  border: 1px solid var(--pgsd-line-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--pgsd-ink2);
}


/* ---------------------------------------------------------------------------
   6. Identity card — photo slot + name + tags.  Canvas lines 437-447.
   --------------------------------------------------------------------------- */

.pgs-device .pgsd-id {
  flex: none;
  display: flex;
  gap: 14px;
  align-items: center;
  border-radius: 24px;
  padding: 14px;
  background: var(--pgsd-glass);
  border: 1px solid var(--pgsd-line);
  /* Load-bearing: without the blur these surfaces read as flat grey plates. */
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
}

/* PhotoSlot, handover section 3: device-detail only, 92x74, radius 16.
   Solid border, not dashed — see divergence 2. It never animates: nothing is
   loading here, the user simply has not set a photo. */
.pgs-device .pgsd-photo {
  position: relative;
  width: 92px;
  height: 74px;
  flex: none;
  border-radius: 16px;
  background: var(--pgsd-soft);
  border: 1px solid var(--pgsd-line);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--pgsd-faint2);
}

.pgs-device .pgsd-id-body { flex: 1; min-width: 0; }
.pgs-device .pgsd-name {
  font-size: 18px;
  font-weight: 800;
  letter-spacing: -.4px;
  color: var(--pgsd-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pgs-device .pgsd-vendor {
  font-size: 9.5px;
  color: var(--pgsd-faint);
  margin-top: 4px;
  white-space: nowrap;
}
.pgs-device .pgsd-tags { display: flex; gap: 6px; margin-top: 8px; }
/* Settled facts about the device, so they hold still. See the header note. */
.pgs-device .pgsd-tag {
  font-size: 7.5px;
  font-weight: 700;
  letter-spacing: 1px;
  padding: 3px 6px;
  border-radius: 5px;
}
.pgs-device .pgsd-tag--new { background: rgba(255, 180, 84, .18); color: var(--pgsd-warn); }
.pgs-device .pgsd-tag--online { background: rgba(53, 214, 160, .14); color: var(--pgsd-pass-text); }


/* ---------------------------------------------------------------------------
   7. Live response TracePanel — the ONE streaming reading on this screen.
      Canvas lines 448-456. Same chart idiom as Home's downlink trace.
   --------------------------------------------------------------------------- */

.pgs-device .pgsd-trace-card {
  flex: none;
  margin-top: 11px;
  border-radius: 20px;
  padding: 12px 14px;
  background: var(--pgsd-glass);
  border: 1px solid var(--pgsd-line);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
}
.pgs-device .pgsd-trace-head { display: flex; align-items: center; gap: 9px; }
.pgs-device .pgsd-trace-head .pgsd-lbl { font-size: 8.5px; }
/* 6 MS is the one chosen value on this screen — see ILLUSTRATIVE VALUES. */
.pgs-device .pgsd-trace-val {
  font-size: 9.5px;
  font-weight: 700;
  color: var(--pgsd-pass-text);
}

/* Gridline background, canvas line 450. Static: the gridlines are a ruler. */
.pgs-device .pgsd-trace {
  height: 34px;
  overflow: hidden;
  border-radius: 6px;
  margin-top: 8px;
  background: repeating-linear-gradient(90deg, rgba(255, 255, 255, .035) 0 1px, transparent 1px 24px);
}
/* The duplicated polyline is mandatory: with one copy the trace runs out mid
   loop. It also makes the resting frame honest, because translateX(0) and
   translateX(-50%) are pixel-identical here (see HONEST FINAL FRAMES above). */
.pgs-device .pgsd-trace-run {
  display: flex;
  width: 200%;
  height: 100%;
  animation: pgsdeviceMarq 9s linear infinite;
}
/* flex:none is divergence 5 — without it the two halves shrink and seam. */
.pgs-device .pgsd-trace-svg { width: 50%; height: 100%; flex: none; }
.pgs-device .pgsd-trace-line { stroke: var(--pgsd-pass-text); stroke-width: 1.7; fill: none; }


/* ---------------------------------------------------------------------------
   8. The record.  Canvas lines 457-464.

   One rounded group, `gap: 1px` over a light wrapper background so the gaps
   read as hairline separators. Rows use surSolid with NO blur: six blurred
   surfaces stacked would cost frames for nothing, and tokens.dart says so.
   --------------------------------------------------------------------------- */

.pgs-device .pgsd-record {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin-top: 11px;
  border-radius: 20px;
  overflow: hidden;
  background: var(--pgsd-line-soft);
  border: 1px solid var(--pgsd-line);
}
.pgs-device .pgsd-row {
  background: var(--pgsd-solid);
  padding: 10px 14px;
  display: flex;
  align-items: center;
}
/* 82px keeps every value on the same left edge, which is what makes the group
   scannable. 8px is the canvas's authored size (see the header note on the
   9px floor). */
.pgs-device .pgsd-row .pgsd-lbl {
  font-size: 8px;
  width: 82px;
  flex: none;
}
.pgs-device .pgsd-val {
  font-size: 10.5px;
  color: var(--pgsd-ink);
}
/* A value we do not have. Never mint, never red — absence has its own colour. */
.pgs-device .pgsd-val--untested { color: var(--pgsd-untested); }
.pgs-device .pgsd-val--pass { color: var(--pgsd-pass-text); }
.pgs-device .pgsd-copy { color: var(--pgsd-faint2); }

/* MAC. Android hides it, so the row is HATCHED: stated, not silently omitted.
   The stripes are absolutely positioned so the label and value sit on top of
   them (hence position:relative on both), matching canvas line 459.
   THIS ROW HAS NO ANIMATION AND MUST NEVER GET ONE. It is the withheld value
   the cardinal rule is written for. */
.pgs-device .pgsd-row--hatched { position: relative; }
.pgs-device .pgsd-hatch-fill {
  position: absolute;
  inset: 0;
  background: var(--pgsd-hatch);
}
.pgs-device .pgsd-row--hatched .pgsd-lbl,
.pgs-device .pgsd-row--hatched .pgsd-val { position: relative; }

/* Ports: measurable but not yet measured, so the row stays visible WITH its
   action rather than being hidden (design/DEVICE-PAGE-SPEC.md section B1). It
   must never read as "no open ports", and it must never look busy: no spinner,
   no shimmer, nothing is running. */
.pgs-device .pgsd-scan {
  font-size: 9px;
  font-weight: 700;
  color: var(--pgsd-ink);
}


/* ---------------------------------------------------------------------------
   9. ACTIONS.  Canvas lines 465-470, minus the declined "Block?" tile
      (divergence 1), so the grid is 2-up.
   --------------------------------------------------------------------------- */

.pgs-device .pgsd-divider {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 14px 2px 9px;
  flex: none;
}
.pgs-device .pgsd-divider-rule { flex: 1; height: 1px; background: var(--pgsd-line-soft); }

.pgs-device .pgsd-actions {
  display: grid;
  /* minmax(0,1fr) not 1fr: bare 1fr lets a long label widen its own column. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 9px;
  flex: none;
}
/* Canvas .tool (line 45) with 1e's overrides (line 467): centred, padding 12px 6px. */
.pgs-device .pgsd-act {
  border-radius: 16px;
  padding: 12px 6px;
  background: var(--pgsd-soft);
  border: 1px solid var(--pgsd-line);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 8px;
  color: var(--pgsd-ink2);
}
.pgs-device .pgsd-act-lbl { font-size: 11px; font-weight: 600; color: var(--pgsd-ink); }
/* "Watching" is ON. It is tinted, but it does not move: a setting being on is
   not a reading arriving now. */
.pgs-device .pgsd-act--on {
  background: var(--pgsd-pass-tint);
  border-color: var(--pgsd-pass-border);
  color: var(--pgsd-pass-text);
}
.pgs-device .pgsd-act--on .pgsd-act-lbl { color: var(--pgsd-pass-text); }


/* ---------------------------------------------------------------------------
   10. Presence today.  Canvas lines 471-477.

   Twelve buckets across 00:00 -> now. Grey means "before Pingo watched", i.e.
   we have no data for that span. Mint means observed online. ONLY the last
   bucket pulses, because only the last bucket is still being filled in.
   --------------------------------------------------------------------------- */

.pgs-device .pgsd-presence {
  flex: none;
  margin-top: 11px;
  border-radius: 20px;
  padding: 12px 14px;
  background: var(--pgsd-glass);
  border: 1px solid var(--pgsd-line);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
}
.pgs-device .pgsd-presence-head { display: flex; align-items: center; gap: 9px; }
.pgs-device .pgsd-presence-head .pgsd-lbl { font-size: 8.5px; }
.pgs-device .pgsd-presence-total { font-size: 8.5px; color: var(--pgsd-faint); }

.pgs-device .pgsd-strip { display: flex; gap: 2px; margin-top: 9px; }
.pgs-device .pgsd-pbar {
  flex: 1;
  height: 14px;
  border-radius: 2px;
}
/* Absence. It has no colour of its own and no motion of its own. Animating a
   span we have no data for is the same lie as animating a hatch. */
.pgs-device .pgsd-pbar--unknown { background: rgba(255, 255, 255, .07); }
/* Observed and settled. */
.pgs-device .pgsd-pbar--online { background: var(--pgsd-pass-bright); }
/* The bucket being filled in right now. The only pulse on this screen. */
.pgs-device .pgsd-pbar--now {
  background: var(--pgsd-pass-bright);
  animation: pgsdevicePulse 2.2s ease-in-out infinite;
}
.pgs-device .pgsd-presence-key {
  font-size: 8px;
  color: var(--pgsd-faint2);
  margin-top: 6px;
  text-align: center;
}


/* ---------------------------------------------------------------------------
   11. Nav reserve + floating tab bar.  Canvas lines 42-44, 478-486.

   The layout contract (handover section 5): the bar floats and its shadow
   reaches ~20px upward, so the column reserves a hard 104px and no control
   ever sits under it. This stack lands ~20px clear, which is inside the
   contract's other bound too (never leave >60px of empty band above the nav).
   --------------------------------------------------------------------------- */

.pgs-device .pgsd-reserve { height: 104px; flex: none; }

.pgs-device .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);
  -webkit-backdrop-filter: blur(22px);
  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-device .pgsd-tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  color: #7C8CA1;
}
.pgs-device .pgsd-tab-lbl { font-size: 9.5px; font-weight: 600; }
/* Devices is the active tab: this screen was pushed from that list. */
.pgs-device .pgsd-tab.is-active { color: var(--pgsd-ink); }
.pgs-device .pgsd-tab.is-active .pgsd-tab-lbl { font-weight: 700; }
.pgs-device .pgsd-tools-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);
}
.pgs-device .pgsd-tab--tools .pgsd-tab-lbl { color: var(--pgsd-ink); font-weight: 700; }


/* ---------------------------------------------------------------------------
   12. Keyframes. Two, because this screen's moment justifies two.

   Names carry the `pgsdevice` prefix so they cannot collide with the site
   (styles.css), the Home mock (mock.css `pgm*`) or a sibling gallery screen:
         pgsdeviceMarq  = the canvas's pgMarq   (Pingo Screens.dc.html line 50)
         pgsdevicePulse = the canvas's pgPulse  (line 51)
   The canvas's other nine keyframes (pgSweep, pgBreathe, pgRing, pgFlowX,
   pgBlink, pgBar, pgDraw, pgFill, pgFloat) are deliberately ABSENT: 1e has
   nothing for any of them to describe, and importing an ambient loop to make
   the screen feel livelier is exactly what the cardinal rule forbids.
   --------------------------------------------------------------------------- */

@keyframes pgsdeviceMarq { to { transform: translateX(-50%); } }
/* 0% and 100% sit FULLY LIT. A liveness cue must rest lit, never dimmed, or
   its stopped state understates a device that is genuinely online. */
@keyframes pgsdevicePulse { 0%, 100% { opacity: 1; } 50% { opacity: .28; } }


/* ---------------------------------------------------------------------------
   13. HONEST FINAL FRAMES — block A: offstage.

       The gallery shell toggles .is-offstage on .pgs-device when this screen is
       not the active tab. It is the web's equivalent of the app's IndexedStack +
       TickerMode, where offstage tabs genuinely stop ticking, and it is what
       keeps six blur(20px) mocks from animating at once on a mid-range phone.

       Neither rule pauses anything. `animation: none` on the trace parks it at
       translateX(0), which is pixel-identical to pgsdeviceMarq's 100% frame
       because the polyline is duplicated, so the visitor sees a real complete
       waveform that has stopped scrolling. The live bucket holds opacity 1,
       which is pgsdevicePulse's own 0%/100% frame.

       ***  KEEP THIS BLOCK AND BLOCK B (section 14) IDENTICAL.  ***
   --------------------------------------------------------------------------- */

.pgs-device.is-offstage .pgsd-trace-run { animation: none; }
.pgs-device.is-offstage .pgsd-pbar--now { animation: none; opacity: 1; }


/* ---------------------------------------------------------------------------
   14. HONEST FINAL FRAMES — block B: prefers-reduced-motion.

       ***  IDENTICAL TO BLOCK A ABOVE.  ***

       Every selector is gated on html:not(.motion-opt-in), exactly as
       styles.css and mock.css already do, so the site's existing "Play the
       motion" button keeps working: motion.js sets .motion-opt-in on <html>
       only when the visitor explicitly asks to see the motion despite their OS
       preference. With JS off the class can never be set, so the OS preference
       still wins. Block A is deliberately NOT gated: offstage is a performance
       and honesty fact about which tab is showing, not a preference.
   --------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html:not(.motion-opt-in) .pgs-device .pgsd-trace-run { animation: none; }
  html:not(.motion-opt-in) .pgs-device .pgsd-pbar--now { animation: none; opacity: 1; }
}
