/* ============================================================================
   FEEDSTOCK — THE POCKET SKIN
   The handset shell, the 256×224 stage, and the panel/HUD anatomy.

   Visual spec: docs/pocket-bench.html, direction C (16-BIT) plus direction D
   (THE HANDSET). System of record: POCKET.md. Build contract: POCKET-BUILD.md.

   EVERY selector in this file starts with #pk. Nothing here may reach the
   Win98 desktop skin, which shares the document. The two skins are siblings,
   not layers: one is hidden while the other plays.

   THE DIVISION OF TYPE, which is the rule most likely to be broken by
   accident: the CASE is silkscreen — smooth italic print, the way a moulded
   plastic logo actually looks. The SCREEN is pixels — an 8×8 bitmap glyph set
   drawn as SVG <use> runs by arcade_font.py. There is no webfont in the skin
   and no CSS text on the stage. If you find yourself setting font-family on
   something inside #pk-stage, you are drawing the wrong thing.

   HOW THE STAGE IS SIZED. The stage is authored at true resolution — 256×224
   CSS pixels, one virtual pixel each — and scaled by transform. core.js's
   ARKP.scale() picks the largest whole number of DEVICE pixels per virtual
   pixel that still fits and writes the result to --pk-scale on #pk, so the
   grid stays crisp where it renders rather than where it is authored. The
   screen wrap is sized off that same number, in the same expression, which is
   what makes the bezel symmetric: the wrap can never be a fraction of a pixel
   wider than what it holds.

   The bench had a right-hand rail of dead plum because a fixed case width and
   a content-box wrap disagreed by a few pixels. Nothing here carries a fixed
   width. The wrap is sized from --pk-scale; the case hugs the wrap; the case
   may never exceed the room it was given.

   ── WHAT core.js READS OUT OF THIS FILE ───────────────────────────────────
   --pk-inset    what the page gutter, the rim and the bezel cost horizontally
   --pk-reserve  what the silkscreen and the face cost vertically
   Both move with the media queries at the foot of this file. scale() must
   read them, never hard-code them; the two numbers are the whole reason a
   short landscape phone gets a smaller screen instead of a clipped one.

   ── THE MARKUP THIS FILE DRESSES ──────────────────────────────────────────
   templates/feedstock.html builds it; input.js drives it. Two vocabularies are
   supported throughout — the template's (.pk-hand, .pk-arm--up, .pk-btn--a)
   and the shorter one in the build notes (.pk-case, .pk-arm-up, .pk-btn-a) —
   because both are already in the fleet's files and a rename costs someone
   else a merge. Neither is deprecated; they are the same rules.

     #pk[hidden]                       core.js unhides, sets --pk-scale
       .pk-ssr    > .pk-band, .pk-fallback   server-rendered, then clipped
       .pk-hand   (= .pk-case)
         .pk-logo > .pk-logo-m, .pk-logo-tm  (= .pk-silk > b, sup)
         .pk-screenwrap (= .pk-screen) > #pk-stage
         .pk-face
           .pk-dpad[data-pk-pad][data-dir]   ONE touch zone, not four
             .pk-dpad-v .pk-dpad-h           the cross, flat fill
             button.pk-arm.pk-arm--up …      the four ends, bevelled
             .pk-dpad-c                      the pivot
           .pk-ab > button.pk-btn--b, button.pk-btn--a, span.pk-btn-l--a/--b
           .pk-pills > .pk-pillbox > button.pk-pill + span.pk-pill-l
           .pk-spk > i ×6

   Input state, all three set by input.js or core.js and all three honoured:
     .pk-arm.pressed          the arm under the thumb
     .pk-btn.active           a face button on contact (also .pressed)
     #pk[data-face="off"] · .pk-face.pk-face-hidden · #pk.pk-noface
                              the face folds; the screen takes the room

   Stage classes owned here, for the screen modules:
     .pk-box .pk-dialog .pk-card .pk-card-rule .pk-hud .pk-lbl .pk-hud-bar
     .pk-hud-barfill .pk-hud-item .pk-rangebar (+ > b, .pk-mk) .pk-cursor
     .pk-scrim .pk-layer .pk-tile .pk-sprite .pk-txt .pk-ground .pk-scenery
     .pk-actor .pk-wipe .pk-bump .pk-blink .pk-flow
     .pk-dear .pk-cheap .pk-gold .pk-dim .pk-ink .pk-magenta
     (bare .dear/.cheap/.gold/.dim are aliased — core.js's glyphText passes
     'dim' unprefixed and the bench does the same)
   ========================================================================= */

#pk {
  /* Screen geometry. --pk-scale is written by core.js; 1 is the floor. */
  --pk-scale: 1;
  --pk-w: 256;                 /* virtual pixels, wide  — unitless on purpose */
  --pk-h: 224;                 /* virtual pixels, tall  — calc multiplies 1px */
  --pk-bezel: 3px;             /* the rim around the glass, all four sides */
  --pk-rim: 4px;               /* case padding around the wrap — thin, so the
                                  screen takes the phone (Greg, 2026-08-07) */
  --pk-face-h: 214px;          /* the plastic under the screen */

  /* The budget core.js spends. Horizontal: 12px page gutter ×2, 7px rim ×2,
     1px border ×2, 4px bezel ×2 = 48. Vertical: the face, its 12px margin,
     the silkscreen line (~21), the rim and the 14px chin = face + 62. */
  --pk-inset: 48px;
  --pk-reserve: calc(var(--pk-face-h) + 62px);

  /* The case. Plum, because the desktop's dithered plum is where this family
     of machines lives on this site. */
  --pk-case:      #352d3e;
  --pk-case-edge: #171320;
  --pk-case-hi:   #4a4156;
  --pk-glass:     #141019;
  --pk-bezel-c:   #241e2c;
  --pk-silk-ink:  #8a7f99;

  /* Buttons: moulded dark plastic, and the one magenta pair. */
  --pk-key:       #191521;
  --pk-key-hi:    #2d2738;
  --pk-key-lo:    #0b0910;
  --pk-key-down:  #12101a;
  --pk-mag:       #f21f92;   /* the mark's own magenta — THE LENS */
  --pk-mag-lo:    #b0136b;
  --pk-mag-face:  #d3197f;

  /* Screen tokens — the system block of the bench, verbatim. */
  --pk-ground:  #86bc6a;
  --pk-panel:   #101018;
  --pk-ink:     #f0f0f0;
  --pk-dim:     #8890a8;
  --pk-lbl:     #90a0c0;
  --pk-gold:    #e8c860;
  --pk-dear:    #f08878;
  --pk-cheap:   #88a8f0;
  --pk-stand:   #40c848;
  --pk-item:    #d8b040;
  --pk-well:    #303040;

  --pk-silk: Futura, "Avenir Next", "Century Gothic", "Trebuchet MS", sans-serif;

  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100svh;
  padding: 12px;
  box-sizing: border-box;
  background: #0b0a0e;
  /* The page is the machine. Nothing here scrolls, bounces, selects, or
     flashes a tap highlight while a thumb is on the pad.

     -webkit-touch-callout is the one that matters on iOS: without it a long
     press anywhere on the screen — a held direction is a long press — raises
     Copy / Look Up / Search Google over the game. Stated on the root so it
     inherits into the stage, the panels and the SVG glyph runs; a handheld
     has no text to select. */
  overscroll-behavior: contain;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  user-select: none;
  -webkit-user-select: none;
}

/* Belt and braces: Safari has historically ignored an inherited callout rule
   on generated and SVG content, so the two layers a thumb actually rests on
   say it themselves. */
#pk *,
#pk svg,
#pk svg * {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-user-drag: none;
}

/* [hidden] must win. Every other rule in this file is more specific than a
   bare attribute selector, so it is stated once, loudly, and never again. */
#pk[hidden] { display: none !important; }

#pk * { -webkit-tap-highlight-color: transparent; }

/* ── The server-rendered layer ──────────────────────────────────────────────
   The hazard band's three denials and the no-script paragraph are rendered
   into #pk by the template, before a byte of JavaScript arrives, because
   verify.py reads them out of the served HTML. The boot screen says them again
   in pixels, so the SSR copy steps out of the picture without leaving the
   document. Clipped, not display:none — a crawler and a screen reader both
   still have it, and #pk is hidden outright until the skin boots anyway.

   Note .pk-band here is the template's hazard band. The stage's local-range
   bar is .pk-rangebar. Two different things, and they no longer share a name. */
#pk .pk-ssr {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE CASE
   ══════════════════════════════════════════════════════════════════════════ */

/* border-box, and max-width 100%, on purpose. The case hugs its widest child,
   which is normally the screen wrap; if a narrow phone forces the wrap below
   the face's own minimum the face wins and the wrap centres inside it. Either
   way the rim stays equal on both sides, and either way the case can never be
   wider than the room it was given — which is exactly the failure the bench
   shipped with, a dead plum rail down one side. */
#pk .pk-hand,
#pk .pk-case {
  position: relative;
  box-sizing: border-box;
  width: fit-content;
  max-width: 100%;
  padding: var(--pk-rim) var(--pk-rim) 10px;
  background: var(--pk-case);
  border: 1px solid var(--pk-case-edge);
  border-radius: 12px 12px 22px 22px;
  box-shadow: inset 0 2px 0 var(--pk-case-hi), 0 14px 34px rgb(0 0 0 / .5);
  user-select: none;
  -webkit-user-select: none;
}

/* The silkscreen. Print type, italic, the way a moulded logo reads — and the
   only place in the skin where a system font is allowed to speak. PORTABLE is
   the one magenta on the case, which is the same ration the screen keeps. */
#pk .pk-logo,
#pk .pk-silk {
  display: block;
  margin: 2px 0 6px 3px;
  font-family: var(--pk-silk);
  font-style: italic;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: .04em;
  color: var(--pk-silk-ink);
  white-space: nowrap;
}
#pk .pk-logo-m,
#pk .pk-silk-m,
#pk .pk-silk b {
  font-weight: 800;
  letter-spacing: .02em;
  color: var(--pk-mag);
}
#pk .pk-logo-tm,
#pk .pk-silk-tm,
#pk .pk-silk sup {
  font-size: 8px;
  font-style: normal;
  vertical-align: super;
}

/* ── The bezel ──────────────────────────────────────────────────────────────
   content-box is stated, not inherited, and the content is the exact scaled
   size of the stage. Four equal borders, so the rim is symmetric by
   construction rather than by arithmetic somebody has to keep true. */
#pk .pk-screenwrap,
#pk .pk-screen {
  box-sizing: content-box;
  width:  calc(var(--pk-w) * var(--pk-scale) * 1px);
  height: calc(var(--pk-h) * var(--pk-scale) * 1px);
  margin: 0 auto;
  border: var(--pk-bezel) solid var(--pk-bezel-c);
  background: var(--pk-glass);
  overflow: hidden;
  /* Inset only. An outer highlight under the glass draws a bright hairline on
     one side of the wrap and nowhere else, which is a rail by another name. */
  box-shadow: inset 0 0 0 1px #0a0810;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE STAGE — 256×224 virtual pixels
   ══════════════════════════════════════════════════════════════════════════ */

#pk #pk-stage {
  position: relative;
  box-sizing: border-box;
  width:  calc(var(--pk-w) * 1px);
  height: calc(var(--pk-h) * 1px);
  transform: scale(var(--pk-scale));
  transform-origin: 0 0;
  overflow: hidden;
  background: var(--pk-ground);
  color: var(--pk-ink);
  image-rendering: pixelated;
  touch-action: none;
}

/* Everything on the stage is placed, not flowed. This is the bench's own rule
   and every screen module codes against it: give an element left/top/width/
   height in virtual pixels and it lands on the grid. .pk-flow is the escape
   hatch for the rare box that genuinely wants to stack its children. */
#pk #pk-stage * {
  position: absolute;
  box-sizing: border-box;
}
#pk #pk-stage .pk-flow,
#pk #pk-stage .pk-flow * { position: static; }

#pk #pk-stage svg {
  shape-rendering: crispEdges;
  image-rendering: pixelated;
  overflow: visible;
}
#pk #pk-stage img { image-rendering: pixelated; }

/* Standard art sizes. Contract 3 fixes these: tiles 8×8, sprites 16×16. */
#pk #pk-stage .pk-tile   { width: 8px;  height: 8px; }
#pk #pk-stage .pk-sprite { width: 16px; height: 16px; }
/* A bitmap-font run: one <use href="#pkg-C"> per character, 8px advance.
   fill: currentColor is the hinge that makes .pk-dear and friends work on
   glyphs — arcade_font.py must not bake white into the symbols. */
#pk #pk-stage .pk-txt    { height: 8px; fill: currentColor; }

/* The z ladder. Stated once here so no module invents its own number.
     1 ground · 2 scenery · 3 actors · 4 scrim · 5 HUD · 6 panel
     7 dialog · 8 cursor · 9 wipe                                            */
#pk #pk-stage .pk-ground  { z-index: 1; }
#pk #pk-stage .pk-scenery { z-index: 2; }
#pk #pk-stage .pk-actor   { z-index: 3; }
#pk #pk-stage .pk-scrim   { z-index: 4; }
#pk #pk-stage .pk-hud     { z-index: 5; }
#pk #pk-stage .pk-box     { z-index: 6; }
#pk #pk-stage .pk-dialog  { z-index: 7; }
#pk #pk-stage .pk-cursor  { z-index: 8; }
#pk #pk-stage .pk-wipe    { z-index: 9; }

/* A full-stage layer: the tilemap svg, the run card ground, the scrim. */
#pk #pk-stage .pk-layer {
  left: 0; top: 0;
  width:  calc(var(--pk-w) * 1px);
  height: calc(var(--pk-h) * 1px);
}

/* The menu dims the world but never leaves it. */
#pk #pk-stage .pk-scrim {
  left: 0; top: 0;
  width:  calc(var(--pk-w) * 1px);
  height: calc(var(--pk-h) * 1px);
  background: rgb(10 10 18 / .45);
}

/* ══════════════════════════════════════════════════════════════════════════
   PANEL ANATOMY — one component, everywhere
   #101018 ground, 2px white border, a 2px dark ring outside it so the box
   reads against grass as well as against floorboards.

   core.js writes the same colours inline when it builds a panel, so these are
   the defaults rather than the law. That is deliberate: a screen module that
   forgets a style still gets a box that looks like every other box.
   ══════════════════════════════════════════════════════════════════════════ */

#pk #pk-stage .pk-box {
  background: var(--pk-panel);
  border: 2px solid var(--pk-ink);
  box-shadow: 0 0 0 2px var(--pk-panel);
  color: var(--pk-ink);
}

/* The bottom speech box is a panel that knows where it lives. Modules that
   want exact geometry set left/top/width/height themselves. */
#pk #pk-stage .pk-dialog {
  background: var(--pk-panel);
  border: 2px solid var(--pk-ink);
  box-shadow: 0 0 0 2px var(--pk-panel);
  color: var(--pk-ink);
}

/* The run card and the boot screen are the two screens that own the frame:
   panel ground edge to edge, with an inset rule instead of a border. */
#pk #pk-stage .pk-card {
  left: 0; top: 0;
  width:  calc(var(--pk-w) * 1px);
  height: calc(var(--pk-h) * 1px);
  background: var(--pk-panel);
  color: var(--pk-ink);
}
#pk #pk-stage .pk-card-rule {
  left: 8px; top: 8px; right: 8px; bottom: 8px;
  width: auto; height: auto;
  border: 2px solid var(--pk-well);
}

/* The harbor's reading cursor: a white box with a dark keyline, so it is
   legible over sea, over coast, and over a port light. */
#pk #pk-stage .pk-cursor {
  border: 2px solid #f8f8f8;
  outline: 1px solid var(--pk-panel);
}

/* THE LOCAL-RANGE BAR — the one idea the DMG bench earned and 16-bit kept.
   A filled bar with quartile ticks: the price's position in localRange(),
   drawn before the player commits. Fill width and tick offsets are set by
   panels.js in virtual pixels. */
#pk #pk-stage .pk-rangebar {
  border: 2px solid var(--pk-ink);
  background: var(--pk-well);
}
#pk #pk-stage .pk-rangebar > b {
  left: 2px; top: 2px; bottom: 2px;
  height: auto;
  background: var(--pk-dear);
}
#pk #pk-stage .pk-rangebar .pk-mk {
  top: -4px;
  width: 2px; height: 4px;
  background: var(--pk-dim);
}

/* ══════════════════════════════════════════════════════════════════════════
   THE HUD — the same anatomy with the border traded for a bottom rule.
   It never blinks. Debt compounds while you walk and the clock stays up.
   ══════════════════════════════════════════════════════════════════════════ */

#pk #pk-stage .pk-hud {
  left: 0; top: 0;
  width: calc(var(--pk-w) * 1px);
  height: 24px;
  background: var(--pk-panel);
  border-bottom: 2px solid #000;
  color: var(--pk-ink);
}
#pk #pk-stage .pk-hud .pk-lbl { color: var(--pk-lbl); fill: var(--pk-lbl); }

/* Standing, as a vertical bar filling from the bottom. core.js sizes the fill
   and places both; these carry the colour so one file owns the palette. */
#pk #pk-stage .pk-hud-bar,
#pk #pk-stage .pk-bar {
  border: 1px solid #000;
  background: var(--pk-well);
}
#pk #pk-stage .pk-hud-barfill { background: var(--pk-stand); }
#pk #pk-stage .pk-bar > b {
  left: 1px; right: 1px; bottom: 1px;
  width: auto;
  background: var(--pk-stand);
}

/* The SATTRACK licence, as an item box. Gold keyline; dimmed until bought. */
#pk #pk-stage .pk-hud-item,
#pk #pk-stage .pk-item {
  border: 2px solid var(--pk-item);
  background: #202838;
}
#pk #pk-stage .pk-item--empty { border-color: var(--pk-dim); }

/* ══════════════════════════════════════════════════════════════════════════
   SEMANTIC UTILITIES
   Both `color` and `fill` are set: a run of bitmap glyphs is SVG, so the
   colour has to reach `fill`.

   Bare .dear/.cheap/.gold/.dim are aliased under #pk because core.js passes
   'dim' unprefixed to glyphText and the bench uses the short names too. They
   cannot leak: the #pk ancestor is the whole gate.
   ══════════════════════════════════════════════════════════════════════════ */

#pk .pk-dear,  #pk .dear  { color: var(--pk-dear);  fill: var(--pk-dear); }
#pk .pk-cheap, #pk .cheap { color: var(--pk-cheap); fill: var(--pk-cheap); }
#pk .pk-gold,  #pk .gold  { color: var(--pk-gold);  fill: var(--pk-gold); }
#pk .pk-dim,   #pk .dim   { color: var(--pk-dim);   fill: var(--pk-dim); }
#pk .pk-ink               { color: var(--pk-ink);   fill: var(--pk-ink); }
/* Rationed to one thing per screen: the player's cap, ship blips, a rack
   telltale. If two things on a screen carry this class, one of them is wrong. */
#pk .pk-magenta           { color: var(--pk-mag);   fill: var(--pk-mag); }

#pk .pk-none { display: none !important; }

/* ══════════════════════════════════════════════════════════════════════════
   THE FACE

   Laid out at a 280px working width and centred under the screen. The pad is
   anchored left and the buttons are anchored RIGHT, so the pair spreads with
   the case instead of drifting away from it. 280px is the width at which the
   clearance law is exactly met; every wider case is slack:

     · d-pad art  x 12–120,  hit box x 0–132   (+22%, one zone, slide-to-steer)
     · B art      x 156–214, hit box x 148–222 (+8 all round)
     · A art      x 214–272, hit box x 206–280 (+8 all round)
     · pad art → B art: 36px of case, the floor. Pad hit → B hit: 16px.

   No width is stated. Nothing here is allowed to reach 100% of a parent whose
   own width is still being worked out — a percentage inside a fit-content box
   resolves against the room available, not the room taken, and that is how a
   handset ends up wider than the phone holding it. The face is a plain block:
   it fills the case because blocks do.
   ══════════════════════════════════════════════════════════════════════════ */

#pk .pk-face {
  position: relative;
  box-sizing: border-box;
  min-width: 280px;
  max-width: 420px;
  height: var(--pk-face-h);
  margin: 12px auto 0;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}
#pk .pk-face * { position: absolute; box-sizing: border-box; }

/* No hover states anywhere on the face. A thumb has no hover, and a machine
   that lights up under a mouse it does not have is a machine that is lying. */
#pk .pk-face button {
  appearance: none;
  -webkit-appearance: none;
  margin: 0; padding: 0;
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
  cursor: pointer;
  touch-action: none;
}
#pk .pk-face button:focus-visible {
  outline: 2px solid var(--pk-mag);
  outline-offset: 2px;
}

/* ── The d-pad ──────────────────────────────────────────────────────────────
   One touch zone, not four: the hit box is 132px against 108px of art, and
   direction is the vector from the centre to the thumb (input.js's job), so
   the player presses once and slides around the cross without lifting.

   The cross is drawn in two flat bars and bevelled at the four ends. Doing it
   the other way round — a bevel per rectangle — draws rims across the joins,
   and the cross reads as a bar laid over two stubs. The bars carry the fill,
   the arms carry the outline, and nothing is lit on an interior edge. */
#pk .pk-dpad {
  left: 0;
  top: 16px;
  width: 132px;
  height: 132px;
  --bev-t: inset 0 2px 0 var(--pk-key-hi);
  --bev-b: inset 0 -3px 0 var(--pk-key-lo);
  --bev-l: inset 2px 0 0 var(--pk-key-hi);
  --bev-r: inset -2px 0 0 var(--pk-key-lo);
}
#pk .pk-dpad-v { left: 48px; top: 12px; width: 36px;  height: 108px; background: var(--pk-key); }
#pk .pk-dpad-h { left: 12px; top: 48px; width: 108px; height: 36px;  background: var(--pk-key); }

/* The arms are <button>s in the shipped markup, so the face's button reset
   wins the background and they sit transparent over the bars. That is the
   intent, not an accident: the bars are the fill and the arms are the outline.
   The fill here is only for markup that makes them <i> instead. */
#pk .pk-arm,
#pk .pk-hub { background: var(--pk-key); width: 36px; height: 36px; }

#pk .pk-arm--up,    #pk .pk-arm-up    { left: 48px; top: 12px; box-shadow: var(--bev-t), var(--bev-l), var(--bev-r); }
#pk .pk-arm--down,  #pk .pk-arm-down  { left: 48px; top: 84px; box-shadow: var(--bev-b), var(--bev-l), var(--bev-r); }
#pk .pk-arm--left,  #pk .pk-arm-left  { left: 12px; top: 48px; box-shadow: var(--bev-l), var(--bev-t), var(--bev-b); }
#pk .pk-arm--right, #pk .pk-arm-right { left: 84px; top: 48px; box-shadow: var(--bev-r), var(--bev-t), var(--bev-b); }
#pk .pk-hub { left: 48px; top: 48px; }

/* The pivot. Drawn last, over the join, which is where a real one sits. */
#pk .pk-dpad-c,
#pk .pk-hub-dot {
  left: 56px; top: 56px;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: var(--pk-key-down);
}

/* Held. The bevel flips — light off the top, dark under it. One frame, no
   transition: a switch is a switch. */
#pk .pk-arm.pressed,
#pk .pk-arm.active,
#pk .pk-arm.pk-pressed {
  background: var(--pk-key-down);
  box-shadow: inset 0 3px 0 var(--pk-key-lo), inset 0 -1px 0 var(--pk-key-hi);
}

/* ── A and B ────────────────────────────────────────────────────────────────
   Staggered the way the real thing staggered them: B low-left, A high-right,
   so the thumb rolls between them instead of reaching. The button element is
   the hit area — 74px, eight past the 58px circle on every side — and the
   circle is drawn by ::before, centred inside it. */
#pk .pk-ab {
  right: 0; left: auto;
  top: 8px;
  width: 132px;
  height: 140px;
}
#pk .pk-btn {
  width: 74px;
  height: 74px;
  background: none;
}
#pk .pk-btn::before {
  content: "";
  position: absolute;
  left: 8px; top: 8px;
  width: 58px; height: 58px;
  border-radius: 50%;
  background: var(--pk-mag-face);
  box-shadow:
    inset 0 3px 0 var(--pk-mag),
    inset 0 -5px 0 var(--pk-mag-lo),
    0 3px 0 var(--pk-case-edge);
}
/* Inside .pk-ab (template markup) the offsets are relative to that block,
   whose right edge is the face's. Without it (short-name markup) they are
   relative to the face. Same pixels either way. */
#pk .pk-btn--a, #pk .pk-btn-a { right: 0;    left: auto; top: 0; }
#pk .pk-btn--b, #pk .pk-btn-b { right: 58px; left: auto; top: 52px; }
#pk .pk-face > .pk-btn-a { top: 8px; }
#pk .pk-face > .pk-btn-b { top: 60px; }

/* Depressed: the cap sinks onto the case, the drop shadow goes, the highlight
   moves to the bottom of the well. .active is what input.js sets on contact. */
#pk .pk-btn:active::before,
#pk .pk-btn.active::before,
#pk .pk-btn.pressed::before,
#pk .pk-btn.pk-pressed::before {
  transform: translateY(3px);
  box-shadow:
    inset 0 4px 0 var(--pk-mag-lo),
    inset 0 -2px 0 var(--pk-mag);
}

/* The silkscreen letters. The template ships them as .pk-btn-l spans; the
   short-name markup has none, so CSS draws them from the class instead. Either
   way they are print type on the case, never pixel type. */
#pk .pk-btn-l,
#pk .pk-btn-a::after,
#pk .pk-btn-b::after {
  position: absolute;
  font-family: var(--pk-silk);
  font-style: italic;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  color: var(--pk-silk-ink);
  line-height: 1;
  pointer-events: none;
}
#pk .pk-btn-l--a { right: 10px; top: 74px; }
#pk .pk-btn-l--b { right: 68px; top: 126px; }
#pk .pk-btn-a::after { content: "A"; right: 12px; bottom: -4px; }
#pk .pk-btn-b::after { content: "B"; right: 12px; bottom: -4px; }

/* ── SELECT and START ───────────────────────────────────────────────────────
   Rotated pills at the chin, −18°, the angle the moulding actually used. The
   button is a 64×46 target; the pill is 46×14 of it. .pk-pillbox flows inside
   a flex row (the one place on the face that is not absolutely placed); the
   short-name markup puts the two buttons in directly and they place
   themselves, which the flex box ignores because they are out of flow. */
#pk .pk-pills {
  left: 50%;
  bottom: 8px;
  transform: translateX(-50%);
  width: 158px;
  height: 46px;
  display: flex;
  gap: 30px;
  align-items: flex-start;
}
#pk .pk-pillbox {
  position: relative;
  display: block;
  width: 64px;
  height: 46px;
  flex: 0 0 64px;
}
#pk .pk-pill {
  width: 64px;
  height: 46px;
  left: 0; top: 0;
}
#pk .pk-pill--select { left: 0; }
#pk .pk-pill--start  { right: 0; left: auto; }
#pk .pk-pill::before {
  content: "";
  position: absolute;
  left: 9px; top: 4px;
  width: 46px; height: 14px;
  border-radius: 7px;
  background: var(--pk-key);
  box-shadow: inset 0 2px 0 var(--pk-key-hi), inset 0 -2px 0 var(--pk-key-lo);
  transform: rotate(-18deg);
}
#pk .pk-pill-l,
#pk .pk-pill--select::after,
#pk .pk-pill--start::after {
  position: absolute;
  left: 0; right: 0; top: 26px;
  text-align: center;
  font-family: var(--pk-silk);
  font-style: italic;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .1em;
  color: var(--pk-silk-ink);
  line-height: 1;
  pointer-events: none;
}
#pk .pk-pill--select::after { content: "SELECT"; }
#pk .pk-pill--start::after  { content: "START"; }
#pk .pk-pill:active::before,
#pk .pk-pill.active::before,
#pk .pk-pill.pressed::before,
#pk .pk-pill.pk-pressed::before {
  background: var(--pk-key-down);
  box-shadow: inset 0 3px 0 var(--pk-key-lo);
}

/* ── The speaker ────────────────────────────────────────────────────────────
   Six slots, raked −24°. Decoration, and the one piece of the case that is
   allowed to be purely that. */
#pk .pk-spk {
  right: 8px;
  bottom: 10px;
  width: 50px;
  height: 34px;
  transform: rotate(-24deg);
  pointer-events: none;
}
#pk .pk-spk i {
  top: 0;
  width: 4px;
  height: 26px;
  border-radius: 2px;
  background: var(--pk-bezel-c);
}
#pk .pk-spk i:nth-child(1) { left: 0; }
#pk .pk-spk i:nth-child(2) { left: 9px; }
#pk .pk-spk i:nth-child(3) { left: 18px; }
#pk .pk-spk i:nth-child(4) { left: 27px; }
#pk .pk-spk i:nth-child(5) { left: 36px; }
#pk .pk-spk i:nth-child(6) { left: 45px; }

/* ══════════════════════════════════════════════════════════════════════════
   THE FACE FOLDS AWAY
   No face, no plastic. A screen at the largest whole scale on a dark surround,
   because a keyboard and a Gamepad are the real controls there and a picture
   of a d-pad nobody can press is furniture.

   Three ways in, all landing in the same place, because three different files
   reach for it: core.js sets #pk.pk-noface, input.js sets #pk[data-face="off"]
   and .pk-face-hidden, and a wide pointer-fine viewport does it unaided.
   ══════════════════════════════════════════════════════════════════════════ */

#pk.pk-noface,
#pk[data-face="off"] {
  --pk-bezel: 6px;
  --pk-bezel-c: #0b0a0e;
  --pk-face-h: 0px;
  --pk-reserve: 56px;
}
#pk.pk-noface .pk-face,
#pk[data-face="off"] .pk-face,
#pk .pk-face.pk-face-hidden { display: none; }

#pk.pk-noface .pk-hand,   #pk.pk-noface .pk-case,
#pk[data-face="off"] .pk-hand, #pk[data-face="off"] .pk-case {
  padding: var(--pk-rim);
  background: #0d0b12;
  border-color: #000;
  border-radius: 3px;
  box-shadow: 0 0 0 1px #2b2735, 0 18px 40px rgb(0 0 0 / .55);
}
#pk.pk-noface .pk-logo,   #pk.pk-noface .pk-silk,
#pk[data-face="off"] .pk-logo, #pk[data-face="off"] .pk-silk {
  color: #6b637a;
  margin-bottom: 7px;
}
#pk.pk-noface .pk-logo-m,   #pk.pk-noface .pk-silk-m,
#pk[data-face="off"] .pk-logo-m, #pk[data-face="off"] .pk-silk-m { color: #a0186a; }

@media (pointer: fine) and (min-width: 900px) {
  #pk {
    --pk-bezel: 6px;
    --pk-bezel-c: #0b0a0e;
    --pk-face-h: 0px;
    --pk-reserve: 56px;
  }
  #pk .pk-face { display: none; }
  #pk .pk-hand, #pk .pk-case {
    padding: var(--pk-rim);
    background: #0d0b12;
    border-color: #000;
    border-radius: 3px;
    box-shadow: 0 0 0 1px #2b2735, 0 18px 40px rgb(0 0 0 / .55);
  }
  #pk .pk-logo, #pk .pk-silk { color: #6b637a; margin-bottom: 7px; }
  #pk .pk-logo-m, #pk .pk-silk-m { color: #a0186a; }
}

/* Narrow phones: give the glass every pixel the case can spare. */
@media (max-width: 340px) {
  #pk { padding: 6px; --pk-inset: 32px; }
  #pk .pk-hand, #pk .pk-case { padding: 4px 4px 10px; }
  #pk .pk-face { margin-top: 8px; }
}

/* Short viewports — a landscape phone, or a small window — cannot afford
   214px of plastic under the screen. The controls stay; the chin does not.
   Deliberately not gated on pointer:coarse: a 700×400 window on a laptop has
   the same problem and the face is still on screen at that width.

   The pad's hit box loses 12px so the chin can rise without the pills stealing
   a tap from it. The cross keeps all 108px of its art. */
@media (max-height: 620px) {
  #pk { --pk-face-h: 172px; min-height: 0; padding: 6px; }
  #pk .pk-logo, #pk .pk-silk { display: none; }
  #pk .pk-face { margin-top: 8px; }
  #pk .pk-dpad { top: 0; width: 120px; height: 120px; }
  #pk .pk-dpad-v { left: 42px; top: 6px; }
  #pk .pk-dpad-h { left: 6px;  top: 42px; }
  #pk .pk-arm--up,    #pk .pk-arm-up    { left: 42px; top: 6px; }
  #pk .pk-arm--down,  #pk .pk-arm-down  { left: 42px; top: 78px; }
  #pk .pk-arm--left,  #pk .pk-arm-left  { left: 6px;  top: 42px; }
  #pk .pk-arm--right, #pk .pk-arm-right { left: 78px; top: 42px; }
  #pk .pk-hub { left: 42px; top: 42px; }
  #pk .pk-dpad-c, #pk .pk-hub-dot { left: 50px; top: 50px; }
  #pk .pk-ab { top: 0; height: 132px; }
  #pk .pk-face > .pk-btn-a { top: 0; }
  #pk .pk-face > .pk-btn-b { top: 52px; }
  #pk .pk-btn-l--a { top: 66px; }
  #pk .pk-btn-l--b { top: 118px; }
  #pk .pk-pills { bottom: 0; }
  #pk .pk-spk   { display: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   MOTION
   Sound and motion belong to the shell. The numbers do not move, do not
   flash, and are not coloured except by the pocket's semantic pair.
   ══════════════════════════════════════════════════════════════════════════ */

/* The ▼ gate at the end of a page of dialog, and PRESS START. Nothing else in
   the skin blinks — the HUD least of all. */
/* A prompt that goes fully dark half the time reads as the game flickering
   rather than as an invitation — the era blinked the CURSOR, not the words.
   Dim to a third and hold the on state longer (audit P8). */
#pk .pk-blink { animation: pk-blink 1.1s steps(1) infinite; }
@keyframes pk-blink { 0%, 64% { opacity: 1; } 65%, 100% { opacity: .35; } }

/* The screen wipe between rooms. A hard-edged bar, not a fade: this machine
   has no alpha channel and should not pretend to. */
#pk #pk-stage .pk-wipe {
  left: 0; top: 0;
  width:  calc(var(--pk-w) * 1px);
  height: calc(var(--pk-h) * 1px);
  background: #000;
  animation: pk-wipe .18s steps(7) forwards;
}
@keyframes pk-wipe {
  from { clip-path: inset(0 0 100% 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* One frame of shove when a walk step is blocked. */
#pk #pk-stage .pk-bump { animation: pk-bump .12s steps(2) 1; }
@keyframes pk-bump {
  50% { transform: translateX(1px); }
}

/* ── Reduced motion ─────────────────────────────────────────────────────────
   Kills every animation and transition in the skin. Letter-by-letter text
   prints instantly under the same preference — that is text.write()'s job,
   in core.js, and this rule is the half of the promise CSS can keep. */
@media (prefers-reduced-motion: reduce) {
  #pk *,
  #pk *::before,
  #pk *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-duration: .001ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
  #pk .pk-blink { animation: none !important; opacity: 1 !important; }
  #pk #pk-stage .pk-wipe { display: none !important; }
}


/* ═══ CONTRACT 4b — FULL-BLEED ON PHONES ═══════════════════════════════════
   On a touch viewport the Portable is not an object on a page: the page IS
   the machine. html.pk-on is stamped by core.js the moment the skin
   activates; everything here hangs off it so the desktop skin never feels
   any of this. The case color is the page — no black matte anywhere. */
html.pk-on, html.pk-on body { overflow: hidden; height: 100%; }
/* Full-bleed is for a TOUCH device — the machine in a hand. A narrow desktop
   WINDOW is not that: it has a keyboard, the face folds away, and pinning the
   case to the viewport leaves a screen at the top and a field of black under
   it. Gate on the pointer, not the width. */
@media (pointer: coarse) {
  html.pk-on #pk {
    position: fixed;
    inset: 0;
    z-index: 999;
    height: 100dvh;
    min-height: 0;
    background: var(--pk-case);
    /* NO horizontal padding on a phone. Every pixel of case beside the glass
       is a pixel the game cannot have, and the scale ladder is unforgiving:
       at 390pt and 3x, 26px of chrome holds the screen at 4/3 (341px) while
       6px of chrome reaches 3/2 (384px) — the same phone, a rung higher, for
       nothing but restraint. The rim and bezel go to 1 and 2. */
    padding: calc(2px + env(safe-area-inset-top))
             env(safe-area-inset-right)
             calc(6px + env(safe-area-inset-bottom))
             env(safe-area-inset-left);
    justify-content: flex-start;
    --pk-rim: 1px;
    --pk-bezel: 2px;
  }
  /* The case fills the frame; the face spends every pixel the screen leaves.
     Without this the hand keeps its fit-content height and the plastic
     stops halfway down a tall phone. */
  html.pk-on #pk .pk-hand,
  html.pk-on #pk .pk-case {
    width: 100%;
    max-width: none;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding: 0;
  }
  /* The face keeps its own coordinate space — every control inside it is
     absolutely placed — so it does NOT stretch. It floats in the middle of
     the plastic the screen left over, and scales up into it, which is how a
     tall phone buys bigger targets instead of more empty case. */
  html.pk-on #pk .pk-face {
    flex: 1 1 auto;              /* take the room, then place inside it */
    height: auto;
    min-height: var(--pk-face-h);
    position: relative;
    transform: none;
  }
  /* THE CLUSTER IS NOT A HUDDLE. Floating the face as one block centred what
     was drawn for a 214px slab and left the pad and the buttons bunched in
     the middle of a tall phone's plastic. Place them against the case
     instead: the pad out to the left edge and the buttons out to the right,
     both at a thumb's natural height, and START/SELECT down on the chin
     where the other thumb already rests. */
  /* A ROW, NOT TWO GUESSES. Placing the pad and the buttons by percentage
     let them drift into each other as the case width changed — at 98% screen
     width the B button was sitting on the pad's right arm. A flex row with
     space-between cannot overlap: the browser divides the case, and the two
     clusters keep whatever is left between them. The pills stay absolute on
     the chin because they belong to the case, not to the row. */
  html.pk-on #pk .pk-face {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 5%;
    gap: 24px;
  }
  html.pk-on #pk .pk-face > .pk-dpad,
  html.pk-on #pk .pk-face > .pk-ab {
    position: relative;
    left: auto; right: auto; top: auto; bottom: auto;
    flex: 0 0 auto;
    /* TRANSFORM DOES NOT MOVE THE LAYOUT. Flex divides the case using the
       UNSCALED boxes, so a generous scale let the two clusters grow into
       each other in the middle while the browser still believed they fit —
       measured at 55px of overlap. The row gets its own, smaller scale, and
       the arithmetic is written down so the next person can check it:
       (108 + 122) x 1.15 = 265, plus a 24px gap and 10% padding, inside 393. */
    /* NO TRANSFORM. Every scale here grew outside the layout box and ate the
       gap the flex row had just reserved — 55px of overlap at 1.3, still 15px
       at 1.15. The clusters ship at their drawn size, which is already a
       generous thumb target (58px buttons), and if they should be bigger the
       honest fix is bigger art, not a transform the layout cannot see. */
    transform: none;
  }
  html.pk-on #pk .pk-face > .pk-dpad { transform-origin: left center; }
  html.pk-on #pk .pk-face > .pk-ab   { transform-origin: right center; }
  html.pk-on #pk .pk-face > .pk-pills,
  html.pk-on #pk .pk-face > .pk-pillrow {
    position: absolute;
    left: 50%;
    bottom: 4%;
    top: auto;
    transform: translateX(-50%) scale(var(--pk-face-s, 1.05));
  }
  /* The speaker is decoration and must never take room from a thumb. */
  html.pk-on #pk .pk-face > .pk-spk { position: absolute; right: 4%; bottom: 6%; }
}
@media (pointer: coarse) and (min-height: 720px) {
  html.pk-on #pk { --pk-face-s: 1.18; }
}
@media (pointer: coarse) and (min-height: 820px) {
  html.pk-on #pk { --pk-face-s: 1.3; }
}


/* A FACE-LESS CASE DOES NOT STRETCH. When the controls fold away — a keyboard
   spoke, or a fine pointer — the case has nothing under the glass to spend
   height on, so it shrink-wraps the screen and centres in the frame. Without
   this the full-bleed rules leave the game pinned to the top of a black
   column, which reads as the game vanishing. */
html.pk-on #pk.pk-noface,
html.pk-on #pk[data-face="off"] {
  justify-content: center;
}
html.pk-on #pk.pk-noface .pk-hand,
html.pk-on #pk.pk-noface .pk-case,
html.pk-on #pk[data-face="off"] .pk-hand,
html.pk-on #pk[data-face="off"] .pk-case {
  flex: 0 0 auto;
  width: fit-content;
  max-width: 100%;
  margin: auto;
}


/* If the sheets never arrived, the stage is a lie — a flat ground colour with
   nothing on it. Show the case dark and let the message on the glass read. */
#pk[data-sheets="missing"] .pk-stage,
#pk[data-sheets="missing"] #pk-stage { background: #07070a; }
