/* ============================================================
   BENDING MACHINE — vector chrome (wave 15).
   The chrome is DRAWN HERE, in CSS + inline SVG, cel style:
   flat fills, dark line work, neon. Palette sampled from Sol's
   production paint. Sol keeps the illustration (cans, labels,
   capsules, stickers, coin, fx); everything structural is code,
   so chrome and hit-boxes share one coordinate system and can
   never drift apart (the 2026-08-04 radio lesson, generalized).
   ============================================================ */
/* The locked palette (Claude Design pass, 2026-08-06). Grouped by surface;
   a colour used outside its group is a bug. The station keeps the old blue
   wall palette on purpose — it is the room, not the machine. */
:root{
  /* body */
  --body-lite:#4c827b; --body:#3f6d68; --body-deep:#2d514d;
  --panel-lite:#4f8880; --panel:#457b74; --panel-deep:#33605a;
  /* devices */
  --dev-lite:#3d444c; --dev:#2f353b; --dev-deep:#242a2f;
  --dev-slot:#414954; --line:#171a1d; --scanner:#8f97ad;
  /* glass + rack */
  --interior:#1d2523; --interior-mid:#151b1a; --interior-deep:#0e1312;
  --rail-lite:#4d555c; --rail:#363c42; --rail-deep:#282d32;
  /* neon + keys */
  --glow:#ffc44d; --plate:#e0a83a;
  --kp:#ead9b6; --kp-edge:#b9a780;
  --kp-alt:#f2c14e; --kp-alt-edge:#c99a2e;
  /* signage + paper */
  --coral:#e8635a; --red:#d94f4f;
  --cream:#f4e9d0; --gold:#e0b95c;
  --paper:#fdf8ec; --paper-ink:#3a3d36;
  /* CRT + station */
  --crt-bg:#0d1f16; --crt-fg:#4dff9d;
  --wall:#101637; --wall-lite:#151c42; --wall-edge:#1b2350;
  --ol:2.5px;               /* the cel outline weight */
  /* One can size, four breakpoints. The stray anomaly can was sized by hand at
     52x78 against a real can's 64x97, so the one object on the machine that is
     supposed to be indistinguishable from stock was visibly smaller than it
     (Ben, 2026-08-06). A shared token is the only way two elements that must
     match cannot drift apart. */
  --can-w:64px; --can-h:97px;
  font-family:"Trebuchet MS","Segoe UI",system-ui,sans-serif;
}
*{ box-sizing:border-box; margin:0; padding:0; -webkit-tap-highlight-color:transparent; }
/* the machine gives its own feedback (keys travel, cans lift, flaps swing) —
   the OS's blue tap silhouette on top of that read as a glitch (Ben) */
html,body{ height:100%; }
body{ background:var(--wall); overflow:auto; }   /* browser zoom must be able to pan */
/* scale-fitted stage: the visual fits, so the oversize layout box must not scroll */
@media (min-width:561px){ body.fitted{ overflow:hidden; } }
button{ font:inherit; border:none; background:none; cursor:pointer; color:inherit; }

/* ============================================================
   THE STATION (wave 16) — drawn in SVG (index.html).
   Fixed to the viewport and slice-scaled so it fills any screen at
   any orientation. It deliberately does NOT share the machine's
   scale: it is the environment, nothing in it registers to the
   machine, so nothing can drift. Every width gets the scene.
   ============================================================ */
#station{ position:fixed; inset:0; width:100%; height:100%; z-index:0; }
#station .st-decor{ pointer-events:none; }
#station text{ font-family:"Trebuchet MS","Segoe UI",system-ui,sans-serif; }
.st-name{ fill:#1b2340; font-size:38px; font-weight:700; text-anchor:middle; letter-spacing:.18em; }
.st-nameEn{ fill:#4a5578; font-size:15px; font-weight:700; text-anchor:middle; letter-spacing:.28em; }
.st-exit{ fill:#bfe8cf; font-size:19px; font-weight:900; text-anchor:middle; letter-spacing:.14em; }
.st-exitJp{ fill:#8fc3a5; font-size:12px; font-weight:700; text-anchor:middle; letter-spacing:.3em; }
.st-door{ fill:#39456f; font-size:17px; font-weight:900; text-anchor:middle; letter-spacing:.3em; }
/* sized to the poster's 120-unit interior — it used to run out under the machine */
.st-poster{ fill:#8b98bd; font-size:10.5px; font-weight:900; text-anchor:middle; letter-spacing:.04em; }

/* ---------- anomalies: quiet scene, loud anomaly ----------
   Always in the DOM (the engine measures what is genuinely on screen
   before it arms anything), invisible and inert until armed. */
.anom{ visibility:hidden; pointer-events:none; }
.anom.armed{ visibility:visible; pointer-events:auto; cursor:pointer; }
.anom.armed:focus-visible{ outline:3px solid var(--cream); outline-offset:4px; }
/* the flickering tube: it asks to be clicked. The lit base state carries
   the read on its own, so reduced-motion still sees an anomaly. */
#an-light.armed{ animation:anflick 1.9s steps(1) infinite; }
@keyframes anflick{
  0%,42%,100%{ opacity:1; } 46%{ opacity:.16; } 52%{ opacity:.95; }
  56%{ opacity:.1; } 60%{ opacity:1; } 74%{ opacity:.28; } 78%{ opacity:1; }
}
#an-door.armed,#an-poster.armed{ animation:anfade 1.1s ease-out; }
@keyframes anfade{ from{ opacity:0; } to{ opacity:1; } }
/* the figure is dark on dark by nature, so it gets a rim and the smallest
   possible movement — enough that the eye catches it, not enough to be a
   jump scare. The room stays peaceful; it is just not empty. */
#an-figure.armed{ animation:anfade 2.4s ease-out, answay 7s ease-in-out 2.4s infinite;
  filter:drop-shadow(0 0 6px rgba(120,170,220,.34)); transform-box:fill-box;
  transform-origin:bottom center; }
@keyframes answay{ 0%,100%{ transform:rotate(0deg); } 50%{ transform:rotate(.7deg); } }
/* the door stays open for good once the trail is finished */
body.trail-done #an-door{ visibility:visible; }

/* the two anomalies that live ON the machine, so every viewport —
   phones included — has something findable */
/* The can nobody stocked still obeys gravity. bottom:16px was a hand-tune that
   left it hovering over the bay's rail (Ben, 2026-08-06) — and the RAIL check
   never caught it because that check only measures STOCK, and this can is
   pointedly not stock. bottom:0 puts its box on the rail; the 1.2% seat is the
   same transparent-footer correction every other sprite on the machine gets,
   since it is drawn on the same 116x176 canvas. bm-audit measures it now. */
/* Full can size, and it overhangs the expansion bay's dashed outline on
   purpose: this can is not stock, the bay is not a can slot, and somebody put
   one in the other anyway. A can shrunk to fit would be tidy and would say
   nothing. */
.anom-can{ position:absolute; left:50%; bottom:0; width:var(--can-w); height:var(--can-h);
  transform:translateX(-50%); z-index:4; }
.anom-can img{ display:block; width:100%; height:100%; object-fit:contain;
  transform:translateY(1.2%);
  filter:drop-shadow(0 4px 3px rgba(0,0,0,.4)); }
.anom-note{ position:absolute; right:10px; top:34%; z-index:6; width:78px;
  padding:8px 7px 10px; background:#f2e9c9; color:#5a4a12; border:none;
  transform:rotate(-5deg); box-shadow:3px 4px 0 rgba(0,0,0,.35);
  font-family:"Comic Sans MS","Segoe Print",cursive; font-size:9.5px;
  line-height:1.35; text-align:left; }
.anom-can,.anom-note{ visibility:hidden; pointer-events:none; }
.anom-can.armed,.anom-note.armed{ visibility:visible; pointer-events:auto;
  cursor:pointer; animation:anfade 1.1s ease-out; }

/* the note an anomaly leaves behind */
#found{ position:fixed; inset:0; z-index:45; display:flex; align-items:center;
  justify-content:center; background:rgba(10,13,26,.82); backdrop-filter:blur(3px); }
#found[hidden]{ display:none; }
/* the one component with NO outline — a hard offset shadow does the work,
   because paper has no drawn edge. A dated diary page, not a headed note. */
.found-card{ max-width:min(340px,86vw); padding:20px 22px; background:#f2e9c9; color:#413515;
  box-shadow:6px 8px 0 rgba(0,0,0,.45); transform:rotate(-1.2deg);
  font-family:"Comic Sans MS","Segoe Print",cursive;
  display:flex; flex-direction:column; gap:12px; }
#found-date{ opacity:.6; }
.found-card p{ font-size:15px; line-height:1.55; }
.found-sig{ font-size:12px; text-align:right; opacity:.7; }

/* ---------- stage: author at 860x1060, scale to fit ---------- */
/* margin:auto centering (not justify/align center): a flex-centered child
   that overflows gets its top clipped unreachably; auto margins stay scrollable */
#stage{ position:relative; width:100%; min-height:100%; display:flex; }
#alcove{
  position:relative; z-index:1; width:860px; height:1060px; flex:none; margin:auto;
  /* top-left origin: initScale centers via computed margins, so the visual
     never extends above/left of the scroll area (center-origin clipped the
     machine's bottom on short viewports once the stage became scrollable) */
  transform-origin:0 0;
  /* no wall of its own since wave 16 — the station behind it IS the wall */
}
#alcove::after{ /* the machine's own shadow on the platform, scaled with it */
  content:""; position:absolute; left:12%; right:12%; bottom:26px; height:22px;
  background:radial-gradient(ellipse at 50% 50%, rgba(0,0,0,.5), transparent 70%);
}

/* ---------- radio ---------- */
/* radio: drawn in SVG (index.html). One coordinate system for the window and
   the text inside it, so the display can never drift out of its own glass. */
/* top:6 puts the chassis bottom (viewBox y72) exactly on the machine's top
   edge (y78) — it rests ON the machine instead of hovering over it (Ben) */
#radio{ position:absolute; left:301px; top:6px; width:280px; height:72px; z-index:5;
  overflow:visible; }
#radio .rd-label{ font:700 7.2px "Trebuchet MS",system-ui,sans-serif; letter-spacing:1.15px;
  fill:#dfe4ef; }
#radio #radio-display{ font-family:"Consolas","DejaVu Sans Mono",monospace; font-size:11.5px;
  fill:#5ee0c8; white-space:pre; }
#radio.on #radio-display{ fill:#7dfadf; }
#radio.on .rd-lamp{ fill:#ff5a5a; filter:drop-shadow(0 0 4px rgba(255,90,90,.95)); }
#radio-knob,#radio-power{ cursor:pointer; }
/* the browser's outline draws a RECTANGLE around an SVG group's bbox, which
   is the white box Ben saw on the dial. Suppress it and draw a round one. */
#radio-knob:focus,#radio-power:focus{ outline:none; }
.rd-focus{ fill:none; stroke:var(--cream); stroke-width:2; opacity:0; }
#radio-knob:focus-visible .rd-focus,#radio-power:focus-visible .rd-focus{ opacity:.9; }
#radio-knob{ transform-box:fill-box; transform-origin:center; transition:transform .2s; }
#rd-scroll{ transform:translateX(0); }
#radio.scroll #rd-scroll{ animation:rloop 9s linear infinite; }
/* The crawl itself. radio.js doubles the station text and sets --rd-shift to
   exactly half the doubled length, so translating by that much lands copy two
   where copy one began: a seamless loop with no visible jump.
   This keyframe did not exist, which made the animation above a silent no-op —
   CSS drops an animation whose name it cannot resolve without warning, so long
   station names simply clipped and nothing ever moved (Ben, 2026-08-06).
   bm-verify now asserts every animation-name referenced in the sheet resolves. */
@keyframes rloop{ to{ transform:translateX(var(--rd-shift, 0px)); } }

/* ---------- zoom control ----------
   Fixed to the VIEWPORT, never to the stage: it drives the stage's transform,
   so living inside it would mean growing with the thing it grows.
   It borrows the keypad's caps — cream, dark outline, a real 4px travel — so
   it reads as part of the machine rather than as browser furniture bolted on
   top of a diorama. It is still UI, and it still sits in the corner where UI
   belongs; the styling only decides whether it looks like it was invited. */
#zoomctl{ position:fixed; right:14px; bottom:14px; z-index:30;
  display:flex; align-items:stretch; gap:4px; padding:5px;
  background:linear-gradient(180deg, var(--dev-lite), var(--dev) 55%, var(--dev-deep));
  border:2px solid var(--line); border-radius:8px;
  box-shadow:0 3px 0 rgba(0,0,0,.34); }
#zoomctl[hidden]{ display:none; }
#zoomctl button{ min-width:30px; height:28px; padding:0 6px;
  font-family:"Trebuchet MS","Segoe UI",system-ui,sans-serif;
  font-size:15px; font-weight:900; line-height:1; color:var(--line);
  background:linear-gradient(180deg, #fff8e6, var(--kp) 62%, #dfcea8);
  border:2px solid var(--line); border-radius:5px;
  box-shadow:0 3px 0 var(--kp-edge), 0 5px 5px rgba(0,0,0,.3); }
#zoomctl button:active:not(:disabled){ transform:translateY(2px);
  box-shadow:0 1px 0 var(--kp-edge), 0 2px 3px rgba(0,0,0,.3); }
#zoomctl button:focus-visible{ outline:3px solid var(--cream); outline-offset:3px; }
/* the readout doubles as reset, so it is a key too — just a wider, quieter one */
#zoom-level{ font-size:11px; letter-spacing:.04em; min-width:46px; }
#zoomctl button:disabled{ opacity:.42; cursor:default;
  box-shadow:0 3px 0 var(--kp-edge); }
/* the machine is already scaled to fit and pinch-zoom is native there, so on a
   phone this would be a redundant control sitting on top of the content */
@media (max-width:560px){ #zoomctl{ display:none; } }

/* ---------- machine shell ---------- */
#machine{
  /* centered via margin, not transform — shake() owns the transform channel */
  position:absolute; left:50%; top:78px; margin-left:-350px;
  width:700px; height:950px;
  background:linear-gradient(180deg, var(--body-lite) 0%, var(--body) 30%, var(--body-deep) 100%);
  border:calc(var(--ol) + 1px) solid var(--line); border-radius:16px;
  box-shadow:
    inset 0 0 0 6px var(--dev),
    inset 0 0 0 9px var(--gold),
    inset -20px 0 26px rgba(0,0,0,.1),
    inset 20px 0 26px rgba(255,255,255,.09),
    inset 0 22px 20px -12px rgba(255,255,255,.14),
    12px 14px 0 #23413d,
    15px 17px 0 rgba(23,26,29,.9),
    26px 34px 60px rgba(0,0,0,.4);
  display:flex; flex-direction:column;
}
/* the cel depth cheat (Ben, 2026-08-06): the machine keeps its front view,
   but the hard offset shadow is now an OPAQUE shaded side with its own cel
   outline (the second hard shadow is the linework), and two corner strokes
   connect the front face to the back — a drawn hint of the box behind the
   face, not a projection change. Strokes live entirely OUTSIDE the face,
   over the extrude, so a vend shake can never flash them across the body. */
#machine::before,#machine::after{ content:''; position:absolute; width:20px; height:3px;
  background:rgba(23,26,29,.9); border-radius:2px; transform-origin:left center;
  transform:rotate(49deg); pointer-events:none; }
#machine::before{ right:-9px; top:3px; }
#machine::after{ left:5px; bottom:-11px; }
.m-feet{ position:absolute; left:0; right:0; bottom:-14px; display:flex; justify-content:space-between; padding:0 60px; }
.m-feet i{ width:70px; height:14px; background:var(--dev); border:var(--ol) solid var(--line);
  border-radius:0 0 6px 6px; }

/* marquee */
#marquee{ position:relative; flex:none; height:64px; margin:16px 18px 6px;
  display:flex; align-items:center; justify-content:center; }
.mq-plate{ background:linear-gradient(180deg, #ef7168, var(--coral) 55%, #d5544c);
  border:var(--ol) solid var(--line); border-radius:10px;
  padding:6px 34px; text-align:center;
  box-shadow:0 3px 0 rgba(0,0,0,.25), inset 0 0 0 3px rgba(255,255,255,.25),
    0 0 22px rgba(232,99,90,.4); }
.mq-en{ display:block; color:var(--cream); font-weight:900; font-size:23px; letter-spacing:.12em;
  white-space:nowrap; text-shadow:0 0 12px rgba(255,244,214,.5), 0 2px 0 rgba(0,0,0,.22); }
.mq-jp{ display:block; color:var(--cream); font-size:11px; letter-spacing:.4em; opacity:.9; }
/* flankers are marquee BULBS, not stars (design pass 2026-08-06) — the
   right one runs a half-cycle behind so the pair never reads as a pulse */
.mq-bulb{ position:absolute; top:50%; margin-top:-6px; width:12px; height:12px; border-radius:50%;
  background:radial-gradient(60% 60% at 38% 32%, #fff4d6, var(--gold) 62%, #b8913c);
  box-shadow:0 0 11px 3px rgba(224,185,92,.8), inset 0 -2px 0 rgba(0,0,0,.22);
  animation:twinkle 2.6s steps(2) infinite; }
.mq-bulb.l{ left:26px; } .mq-bulb.r{ right:26px; animation-delay:1.3s; }
@keyframes twinkle{ 50%{ opacity:.45; } }

/* deck: glass left, notch right. Grid, not flex — #deck and #bottomrow
   share the same column template so takeout aligns exactly under the
   glass (flex floors each item's share at its padding+border and skews
   the split). The notch hugs its content — no dead panel below. */
#deck{ flex:1; display:grid; grid-template-columns:minmax(0,1.55fr) minmax(0,1fr);
  gap:14px; padding:8px 18px; min-height:0; }
#glasswrap{ display:flex; flex-direction:column; min-width:0; }
#notch{ position:relative;   /* the maker's plate centres against this column */
  display:flex; flex-direction:row; flex-wrap:wrap; gap:10px; min-width:0; align-self:start;
  align-content:flex-start;
  background:
    linear-gradient(90deg, transparent 0 96%, rgba(255,255,255,.45) 97.5%, rgba(255,255,255,.1) 100%),
    linear-gradient(180deg, var(--panel-lite) 0%, var(--panel) 45%, var(--panel-deep) 100%);
  border:var(--ol) solid var(--line); border-radius:12px; padding:12px; }
#crt,#keypad,#payment,#printer{ flex-basis:100%; }
#code-toggle{ display:none; }

/* ---------- glass + shelves: Sol's deep-navy neon interior, drawn ---------- */
#glass{ position:relative; flex:1;
  background:
    radial-gradient(92% 46% at 50% 28%, rgba(255,196,77,.13), transparent 72%),
    linear-gradient(180deg, var(--interior) 0%, var(--interior-mid) 55%, var(--interior-deep) 100%);
  border:var(--ol) solid var(--line); border-radius:12px; overflow:hidden;
  box-shadow:inset 0 0 0 3px rgba(255,196,77,.45), inset 0 0 26px rgba(255,196,77,.2),
    inset 0 18px 26px rgba(0,0,0,.32), inset 0 -12px 22px rgba(0,0,0,.28);
  display:flex; flex-direction:column; padding:10px 10px 6px; gap:6px; }
.glass-sheen{ position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(115deg, rgba(255,255,255,.15) 0%, rgba(255,255,255,.05) 11%,
    transparent 22%, transparent 64%, rgba(255,255,255,.05) 76%, transparent 84%,
    rgba(255,255,255,.08) 100%); }
.shelf{ position:relative; flex:1; display:flex; flex-direction:column; justify-content:flex-end; }
.shelf.gacha{ flex:.62; }
.shelf.bays{ flex:.85; }
.shelf-glow{ display:none; }   /* wave 15: the light lives under the rail now */
.slots{ display:flex; align-items:flex-end; justify-content:space-around; padding:0 4px; }
/* the rack rail: metal front + the neon tube along its lower edge */
.shelf-lip{ position:relative; height:13px;
  background:linear-gradient(180deg, var(--rail-lite) 0%, var(--rail) 45%, var(--rail-deep) 100%);
  border:2px solid var(--line); border-radius:3px;
  box-shadow:inset 0 2px 0 rgba(255,232,180,.26), inset 0 -3px 0 rgba(0,0,0,.32); }
.shelf-lip::after{ content:''; position:absolute; left:4px; right:4px; bottom:-3px; height:3px;
  border-radius:2px; background:var(--glow);
  box-shadow:0 0 9px 2px rgba(255,196,77,.85), 0 2px 18px 6px rgba(255,196,77,.32); }

/* slot + can (flex, not fixed % — row 1 carries five since the Google split) */
.slot{ position:relative; display:flex; flex-direction:column; align-items:center; gap:4px; flex:1; min-width:0; }
/* pocket tabs, drawn PER SLOT — the dividers sit between the goods by
   construction, no registration to drift (the whole point of wave 15) */
.slot::before,.slot::after{ content:''; position:absolute; bottom:-2px; width:8px; height:18px;
  background:linear-gradient(180deg, #5b646f, #3b434d 80%);
  border:2px solid var(--line); border-bottom:none; border-radius:3px 3px 0 0;
  box-shadow:inset 0 2px 0 rgba(255,232,180,.24); }
.slot::before{ left:-5px; }
.slot::after{ right:-5px; display:none; }
.slot:last-child::after{ display:block; }
.shelf.bays .slot::before,.shelf.bays .slot::after{ display:none; }   /* bays row is open */
.slot .coil{ position:absolute; bottom:26px; left:50%; transform:translateX(-50%);
  width:8px; height:8px; opacity:0; }
.can-btn{ position:relative; display:block; transition:transform .15s; }
.can-btn:hover{ transform:translateY(-3px); }
.can-btn:focus-visible{ outline:3px solid var(--cream); outline-offset:3px; border-radius:8px; }
/* the tag is stuck ON the glass; the can sits BEHIND it — so from the
   front the tag naturally overlaps the goods and the rail (Ben's read,
   2026-08-06). Out of flow, which is also what finally seats every can
   directly on its rail instead of hovering a tag's height above it. */
.slot-tag{ position:absolute; left:50%; bottom:-13px; transform:translateX(-50%); z-index:3;
  background:var(--cream); color:var(--line); border:2px solid var(--line); border-radius:4px;
  font-size:10px; font-weight:900; padding:0 6px; letter-spacing:.08em; white-space:nowrap; }
.slot.soldout .slot-tag{ background:var(--red); color:var(--cream); }
.soldout-strip{ position:absolute; left:-6%; top:38%; width:112%; transform:rotate(-7deg);
  background:var(--red); color:var(--cream); border:2px solid var(--line);
  font-size:8.5px; font-weight:900; letter-spacing:.1em; text-align:center; padding:1px 0; border-radius:3px; }

/* row-5 expansion bays: stocked later, tagged now */
.bay{ width:58px; height:76px; border:2px dashed rgba(255,196,77,.5); border-radius:10px;
  background:rgba(4,12,26,.35); box-shadow:inset 0 4px 10px rgba(0,0,0,.3); }

/* ---------- gacha capsules: Sol's halves, top tinted via mask ---------- */
.gcap-btn{ display:block; transition:transform .15s; }
.gcap-btn:hover{ transform:translateY(-3px); }
.gcap-btn:focus-visible{ outline:3px solid var(--cream); outline-offset:3px; border-radius:50%; }
.gcapsule{ position:relative; width:46px; height:46px; }
.gc-top,.gc-bot{ position:absolute; inset:0;
  transition:transform .4s cubic-bezier(.4,0,.6,1), opacity .4s; }
.gc-top{ isolation:isolate; }   /* the color-blend tint may only see its own art,
  never the backdrop (it rendered as a navy blob while the art image loaded) */
.gc-top .gc-art,.gc-bot img{ display:block; width:100%; height:100%; object-fit:contain; }
.gcap-btn .gcapsule{ filter:drop-shadow(0 4px 3px rgba(0,0,0,.35)); }
.gc-top .gc-tint{ position:absolute; inset:0; mix-blend-mode:color;
  -webkit-mask:url("../assets/art/production/gacha/gacha-capsule-top.png") center/100% 100% no-repeat;
  mask:url("../assets/art/production/gacha/gacha-capsule-top.png") center/100% 100% no-repeat; }
#tray .gcapsule{ cursor:pointer; }
#tray .gcapsule.opened img{ width:100%; height:100%; object-fit:contain; }

/* the coin that pays the machine */
.coin{ touch-action:none; }
.coin.flying{ position:fixed; z-index:60;
  transition:top .42s cubic-bezier(.5,0,1,1), transform .42s, opacity .25s .22s; }
.coin.dragging{ position:fixed; z-index:70; cursor:grabbing; transition:none !important; }
#coincup .coin{ cursor:grab; }
#coinslot.drag-ok{ outline:3px dashed var(--glow); outline-offset:3px; }

/* the capsule viewer */
#gacha{ position:fixed; inset:0; z-index:40; display:flex; align-items:center; justify-content:center;
  background:rgba(10,13,26,.82); backdrop-filter:blur(3px); }
#gacha[hidden]{ display:none; }
.gacha-card{ display:flex; flex-direction:column; align-items:center; gap:16px; }
/* The 200px floor exists so the stage does not collapse mid-pop while the two
   capsule halves are flying apart. It has no business surviving the pop: once
   the capsule is gone the sticker alone should set the height, and holding 200
   left dead air above a short sticker's name (Ben, 2026-08-06). Scope it to
   the state that needs it rather than deleting it and reintroducing the jump. */
#gacha-stage{ position:relative; width:min(320px,80vw); height:auto;
  display:flex; align-items:center; justify-content:center; }
#gacha-stage:has(#gcap:not([hidden])){ min-height:200px; }
#gcap{ position:relative; width:200px; height:200px; }
#gcap.popped .gc-top{ transform:translate(-52px,-96px) rotate(-28deg); opacity:.25; }
#gcap.popped .gc-bot{ transform:translate(44px,80px) rotate(18deg); opacity:.25; }
#gsticker{ display:block; max-width:min(320px,80vw); max-height:min(320px,44vh);
  filter:drop-shadow(0 10px 24px rgba(0,0,0,.5)); }
#gsticker[hidden]{ display:none !important; }   /* hidden must actually hide */
#gacha-name{ color:var(--cream); font-weight:900; letter-spacing:.14em; font-size:13px; min-height:16px; }

/* the can: Sol's sprite in a fixed 58x88 frame (sprites share one canvas,
   so cup/short live inside the same frame) */
.can{ position:relative; width:var(--can-w); height:var(--can-h); }
.can-img{ display:block; width:100%; height:100%; object-fit:contain; pointer-events:none;
  filter:drop-shadow(0 4px 3px rgba(0,0,0,.35)); }
.can-strip{ position:absolute; inset:0; width:100%; height:100%; object-fit:contain; pointer-events:none; }
/* SEATING. A can rests on the rail when its INK touches it, not when its box
   does — and the box is what CSS lays out, so every sprite needs the distance
   between the two given back.
   MEASURED, not guessed (2026-08-06): every sprite is a 116x176 canvas, and
   every one bottoms out at row 173 — except the cup noodle, which stops at
   156. So there are two corrections, and they add:
     --seat-base  the 2 transparent rows every sprite carries (~1.2% of the
                  frame), which left every can on the machine hovering ~1px
                  over its rail;
     --seat       the extra air one sprite carries. 17 rows x the contain
                  scale = 9.4px at desktop size, which is why the first,
                  eyeballed 3px did not seat the noodle and Ben still saw it
                  floating.
   PERCENTAGES, not px: the frame shrinks to 38, 34 and 27px wide down the
   mobile ladder, and a fixed 9.4px would bury the can at every one of them. A
   translate percentage resolves against the element's own height, so one
   number seats the sprite at every size — which is the point of deriving a
   value instead of typing one.
   bm-audit's RAIL check measures the rendered ink against the rail, so a
   wrong number here fails the gate instead of shipping. */
/* The sold-out strip is a second sprite registered to the same 116x176 canvas,
   so it has to take the identical translate or it slides off the can it bands. */
.can-img,.can-strip{ --seat:0%; transform:translateY(calc(1.2% + var(--seat))); }
.can[data-art="43-cup-noodle-shoyu"] img{ --seat:9.6%; }
/* Apple and Assistant share the same row-173 baseline as every other can, so
   this 1% is Ben's eye, not geometry: both cans taper to a narrower foot and
   read as touching down later. Kept because he asked for it and looked at it;
   flagged here so nobody later "fixes" it back and reopens the argument. */
.can[data-art="15-apple"] img,
.can[data-art="13-google-assistant"] img{ --seat:1%; }
.can-body{ position:absolute; inset:0; border:var(--ol) solid var(--line); border-radius:10px 10px 8px 8px;
  overflow:hidden; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:1px;
  box-shadow:inset -8px 0 0 rgba(0,0,0,.12), inset 5px 0 0 rgba(255,255,255,.18); }
.can.cup .can-body{ border-radius:6px 6px 14px 14px;
  clip-path:polygon(6% 0, 94% 0, 86% 100%, 14% 100%); }
.can-lid{ position:absolute; left:6px; right:6px; top:-4px; height:8px;
  background:#c9cfdd; border:2px solid var(--line); border-radius:4px; }
.can.cup .can-lid{ background:var(--cream); }
.can-deco{ font-size:17px; line-height:1; }
.can-brand{ font-size:8.5px; font-weight:900; letter-spacing:.02em; text-align:center; line-height:1.1; padding:0 2px; }
.can-kind{ font-size:5.5px; font-weight:700; letter-spacing:.04em; text-align:center; opacity:.85; padding:0 2px; }
.can.clear .can-body{ backdrop-filter:blur(1px); background:rgba(215,235,240,.4) !important;
  box-shadow:inset -8px 0 0 rgba(255,255,255,.15), inset 5px 0 0 rgba(255,255,255,.3); }
.can.unlabeled .can-body{ background:#3a4157 !important; }
.can.unlabeled .can-deco,.can.unlabeled .can-brand,.can.unlabeled .can-kind{ visibility:hidden; }

/* falling can + dropzone */
#dropzone{ position:absolute; inset:0; pointer-events:none; z-index:3; }
.falling{ position:absolute; z-index:4; }
/* The drop, with weight (2026-08-06). It used to be one linear-ish slide that
   stopped in mid-air and cut to the tray, which is what read as unconvincing.
   Now: accelerate under gravity, meet the floor of the well, squash on impact,
   one small bounce, settle. transform-origin at the foot so the squash pushes
   DOWN into the floor instead of shrinking about the middle. */
.can.falling{ transform-origin:bottom center;
  animation:candrop .7s linear forwards; }
@keyframes candrop{
  0%{ transform:translateY(0) rotate(0deg);
      animation-timing-function:cubic-bezier(.45,.02,.85,1); }        /* gravity */
  58%{ transform:translateY(var(--drop)) rotate(var(--spin)) scale(1,1); }
  64%{ transform:translateY(var(--drop)) rotate(var(--spin)) scale(1.07,.9);
       animation-timing-function:cubic-bezier(.3,.7,.4,1); }          /* impact squash */
  77%{ transform:translateY(calc(var(--drop) - 15px)) rotate(calc(var(--spin) * 1.12)) scale(.97,1.04);
       animation-timing-function:cubic-bezier(.5,0,.9,1); }           /* the bounce */
  89%{ transform:translateY(var(--drop)) rotate(calc(var(--spin) * 1.04)) scale(1.03,.96); }
  100%{ transform:translateY(var(--drop)) rotate(calc(var(--spin) * 1.04)) scale(1,1); }
}
/* out through the floor into the chute, on the same beat as the flap */
.can.falling.chuted{ animation:none;
  transform:translateY(calc(var(--drop) + 46px)) rotate(var(--spin));
  opacity:0; transition:transform .22s cubic-bezier(.5,0,1,1), opacity .22s ease-in; }
.can.falling::before{ content:''; position:absolute; left:50%; top:-58px; width:30px; height:58px;
  transform:translateX(-50%); opacity:.75; pointer-events:none;
  background:url("../assets/art/production/fx/can-drop-smear-1.png") center/contain no-repeat;
  animation:smear .41s steps(1) both; }   /* the smear ends when the can lands */
@keyframes smear{
  33%{ background-image:url("../assets/art/production/fx/can-drop-smear-2.png"); }
  66%{ background-image:url("../assets/art/production/fx/can-drop-smear-3.png"); }
  100%{ opacity:0; }
}

/* ---------- bottom row: takeout left, open paper bay right ---------- */
/* height is FIXED (matches the drawn takeout, geometry freeze 2026-08-04):
   the deck above it is flex:1, so any growth here would resize the glass and
   lift every can off its drawn rail. Nothing in here may push. */
#bottomrow{ flex:none; height:182px; display:grid;
  grid-template-columns:minmax(0,1.55fr) minmax(0,1fr);
  gap:14px; padding:0 18px; margin-bottom:20px; }
#takeout{ min-width:0; position:relative;
  background:linear-gradient(180deg, var(--dev-lite), var(--dev) 45%, var(--dev-deep));
  border:var(--ol) solid var(--line); border-radius:12px; padding:10px 12px 12px; }
/* The bay the receipt hangs down into. It read as an unexplained black
   square (Ben, 2026-08-05), so it now says what it is: a shallow lit recess
   with a lip and a molded label, matching the takeout's language. */
#paperbay{ position:relative; min-width:0;
  background:linear-gradient(180deg, #1b211f 0%, #141917 42%, #0d1110 100%);
  border:var(--ol) solid var(--line); border-radius:12px;
  box-shadow:inset 0 10px 16px rgba(0,0,0,.45), inset 0 -3px 0 rgba(255,255,255,.05); }
#paperbay::before{ /* the lip you would reach over to take the paper */
  content:''; position:absolute; left:10px; right:10px; bottom:26px; height:9px;
  background:linear-gradient(180deg, var(--dev-lite), #252b2f);
  border:2px solid var(--line); border-radius:4px;
  box-shadow:0 -8px 14px rgba(0,0,0,.4); }
#paperbay::after{
  content:'TEAR HERE'; position:absolute; left:0; right:0; bottom:7px;
  text-align:center; font-size:9px; font-weight:900; letter-spacing:.26em;
  color:#4d5a86; text-shadow:0 -1px 0 rgba(0,0,0,.5), 0 1px 0 rgba(255,255,255,.05); }
/* filled, not outlined (design pass 2026-08-06): on a mid-tone body an
   outlined plate disappears — it has to be solid */
.to-plate{ text-align:center; background:var(--plate); color:var(--line); border:2px solid var(--line);
  border-radius:6px; font-size:12px; font-weight:900; letter-spacing:.18em; padding:3px 0; margin-bottom:8px; }
.to-arrow{ display:inline-block; animation:toblink 1.6s steps(2) infinite; }
@keyframes toblink{ 50%{ opacity:.25; } }
#flap{ height:52px; background:#0e1211; border:var(--ol) solid #14181a; border-radius:8px;
  overflow:hidden; perspective:300px; position:relative; }
.flap-face{ position:absolute; inset:4px; background:#2a3034; border:2px solid #14181a;
  border-radius:6px; transform-origin:top center; transition:transform .3s cubic-bezier(.6,0,.7,1.4);
  background-image:linear-gradient(180deg, rgba(255,255,255,.14), rgba(255,255,255,.04) 34%,
    transparent 46%, rgba(0,0,0,.18) 100%); }
#flap.open .flap-face{ transform:rotateX(-64deg); }
#flap::after{ content:''; position:absolute; left:12%; right:12%; bottom:3px; height:12px;
  background:radial-gradient(50% 100% at 50% 100%, rgba(0,0,0,.45), transparent 72%);
  opacity:0; transition:opacity .25s; pointer-events:none; }
#flap.open::after{ opacity:1; }
/* FIXED height, never content-driven: the tray used to grow when a can landed
   (cans are taller than the well), which shrank #deck and lifted every shelf
   off its drawn rail (Ben, 2026-08-04). Tray items scale to fit instead. */
#tray{ position:relative; display:flex; gap:8px; align-items:flex-end; height:70px; padding:6px;
  background:linear-gradient(180deg, #0f1312, #090c0b);
  border-radius:8px; box-shadow:inset 0 6px 12px rgba(0,0,0,.45); }
#tray::before{ /* the cavity's glass glare, Sol's wedge redrawn */
  content:''; position:absolute; left:8px; top:6px; width:128px; height:52px;
  background:linear-gradient(100deg, rgba(214,224,240,.30), rgba(214,224,240,.06) 66%, transparent);
  clip-path:polygon(0 0, 46% 0, 82% 100%, 0 100%); border-radius:4px; pointer-events:none; }
#tray .can,#tray .gcapsule{ flex:none; }   /* a spree never squishes the tray */
/* cans stay FULL SIZE and overflow upward out of the well — the tray's box
   just never changes, so nothing above it can move (Ben: not an ant farm) */
#tray .can{ cursor:grab; touch-action:none; }
#tray .can:active{ cursor:grabbing; }
/* The blue box Ben saw while dragging (2026-08-06). A tabbable div gets the
   UA's own focus ring on pointerdown and, because a drag is a pointer gesture
   across the page, the browser also paints a text selection over whatever it
   crosses. Kill both, then give keyboard users the house ring back — the same
   3px cream at 3px offset every other focusable object on the machine wears. */
#tray .can,#tray .gcapsule{ user-select:none; -webkit-user-select:none; }
#tray .can:focus,#tray .gcapsule:focus{ outline:none; }
#tray .can:focus-visible,#tray .gcapsule:focus-visible{
  outline:3px solid var(--cream); outline-offset:3px; border-radius:8px; }

/* ---------- notch devices ---------- */
/* device family look: navy plate, top light, mint rim (Sol's cluster language) */
/* CRT — the bezel carries a control chin under the screen, like a real set */
#crt{ position:relative; }
.crt-bezel{ position:relative;
  background:linear-gradient(180deg, var(--dev-lite), var(--dev) 55%, var(--dev-deep));
  border:var(--ol) solid var(--line); border-radius:12px; padding:10px 10px 26px;
  box-shadow:0 0 0 2.5px rgba(255,196,77,.3), 0 3px 0 rgba(0,0,0,.25); }
.crt-bezel::after{ /* molded maker's mark on the chin */
  content:'BENDING SYSTEMS'; position:absolute; left:15px; bottom:9px;
  font-size:8px; font-weight:900; letter-spacing:.22em; color:#57648f;
  text-shadow:0 -1px 0 rgba(0,0,0,.5), 0 1px 0 rgba(255,255,255,.06); }
.crt-screen{ position:relative; height:104px; border-radius:10px;
  background:radial-gradient(130% 130% at 50% 42%, #12281c 0%, var(--crt-bg) 62%, #081209 100%);
  border:2px solid #000; overflow:hidden; padding:10px 12px;
  box-shadow:inset 0 0 28px rgba(0,0,0,.6), inset 0 0 6px rgba(77,255,157,.12),
    0 0 22px rgba(77,255,157,.22); }   /* the phosphor bloom, drawn (was screen-glow.png) */
#screen-lines{ font-family:"Consolas","DejaVu Sans Mono",monospace; font-size:14.5px; line-height:1.5; color:var(--crt-fg);
  text-shadow:0 0 6px rgba(77,255,157,.55); white-space:pre-wrap;
  transition:opacity .13s linear; }
#screen-lines.swap{ opacity:.18; }   /* dim-out instead of blink-to-black */
#screen-lines .cursor{ display:inline-block; width:8px; height:14px; background:var(--crt-fg);
  animation:blink 1.1s steps(1) infinite; vertical-align:text-bottom; }
@keyframes blink{ 50%{ opacity:0; } }
.crt-scan{ position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(115deg, rgba(255,255,255,.055) 0%, transparent 26%),
    repeating-linear-gradient(180deg, rgba(0,0,0,.18) 0 1px, transparent 1px 3px); }
.crt-screen.glitch{ animation:glitch .5s steps(3) infinite; }
@keyframes glitch{
  0%{ transform:translate(0); filter:none; }
  33%{ transform:translate(-2px,1px) skewX(2deg); filter:hue-rotate(40deg); }
  66%{ transform:translate(2px,-1px) skewX(-1.5deg); filter:invert(.08); } }
/* the volume toggle: set INTO the bezel's chin — part of the machine, not a
   pill floating over the screen (Ben, 2026-08-04). The speaker is DRAWN,
   an icon not an acronym (design pass 2026-08-06): amber cone + two arcs
   when live, grey cone + a cross when muted. The pill is a FIXED 48x23 so
   muting can never resize the button. */
.snd{ position:absolute; right:11px; bottom:4px; z-index:2; width:48px; height:23px;
  background:#20262b; border:2px solid var(--line); border-radius:6px;
  box-shadow:inset 0 2px 4px rgba(0,0,0,.55), 0 1px 0 rgba(255,255,255,.06); }
.snd i{ position:absolute; top:50%; pointer-events:none; }
.snd-cone{ left:15px; margin-top:-5px; width:0; height:0; border-style:solid;
  border-color:transparent #6d757d transparent transparent; border-width:5px 6px 5px 0; }
.snd-w{ border:1.5px solid #6d757d; border-left:none; }
.snd-w1{ left:24px; margin-top:-3.5px; width:3px; height:7px; border-radius:0 5px 5px 0; }
.snd-w2{ left:27px; margin-top:-5.5px; width:5px; height:11px; border-radius:0 8px 8px 0; opacity:.6; }
.snd-x{ left:25px; margin-top:-4.5px; width:9px; height:9px; }
.snd-x::before,.snd-x::after{ content:''; position:absolute; left:-1px; right:-1px; top:50%;
  margin-top:-1px; height:1.5px; background:#6d757d; }
.snd-x::before{ transform:rotate(45deg); } .snd-x::after{ transform:rotate(-45deg); }
.snd.on .snd-cone{ border-right-color:var(--glow); }
.snd.on .snd-w{ border-color:var(--glow); border-left:none; }
.snd.on .snd-x{ display:none; }
.snd:not(.on) .snd-w{ display:none; }

/* keypad */
#keypad{ background:linear-gradient(180deg, var(--dev-lite), var(--dev) 55%, var(--dev-deep));
  border:var(--ol) solid var(--line); border-radius:12px; padding:10px 12px;
  box-shadow:0 0 0 2.5px rgba(255,196,77,.3), 0 3px 0 rgba(0,0,0,.25); }
.kp-row{ display:flex; gap:8px; justify-content:center; margin-bottom:8px; }
.kp{ width:52px; height:40px; color:var(--line);
  background:var(--kp) linear-gradient(180deg, rgba(255,255,255,.42) 0%, rgba(255,255,255,.12) 40%,
    transparent 52%, rgba(0,0,0,.06) 100%);
  border:var(--ol) solid var(--line); border-radius:9px; font-size:19px; font-weight:900;
  box-shadow:0 4px 0 var(--kp-edge), 0 6px 6px rgba(0,0,0,.3);
  transition:transform .05s, box-shadow .05s; }
.kp:active,.kp.pressed{ transform:translateY(3px);
  box-shadow:0 1px 0 var(--kp-edge), 0 2px 3px rgba(0,0,0,.3); }
.kp.alt{ background:var(--kp-alt) linear-gradient(180deg, rgba(255,255,255,.42) 0%, rgba(255,255,255,.12) 40%,
    transparent 52%, rgba(0,0,0,.06) 100%);
  box-shadow:0 4px 0 var(--kp-alt-edge), 0 6px 6px rgba(0,0,0,.3); }
.kp.alt:active,.kp.alt.pressed{ box-shadow:0 1px 0 var(--kp-alt-edge), 0 2px 3px rgba(0,0,0,.3); }
.kp-row.caps{ margin-bottom:0; }
.kp-cell{ display:flex; flex-direction:column; align-items:center; gap:6px; }
.kp-cap{ height:12px; font-size:10px; font-weight:900; letter-spacing:.16em; color:#8fa0c8; }

/* payment panel: a strict grid so the columns line up. Row logic is
   physical (Ben, 2026-08-04): scan card LEFT, COIN RETURN middle (two
   lines allowed), and the coin catch RIGHT — directly under the slot
   the coin goes into, because that is where a catch lives */
/* coin slot narrowed 16px -> 10px (design pass 2026-08-06): at 16 it took
   a candy bar */
#payment{ display:grid; grid-template-columns:1.15fr 1fr 46px 10px; grid-template-rows:auto auto;
  gap:8px 10px; align-items:stretch;
  background:linear-gradient(180deg, var(--dev-lite), var(--dev) 55%, var(--dev-deep));
  border:var(--ol) solid var(--line); border-radius:10px; padding:10px 12px;
  box-shadow:0 0 0 2.5px rgba(255,196,77,.3), 0 3px 0 rgba(0,0,0,.25); }
#billslot{ grid-column:1; grid-row:1; height:28px;
  background:linear-gradient(180deg, #565f6a, var(--dev-slot) 70%, #39414b); border:2px solid var(--line);
  border-radius:4px; display:flex; align-items:center; justify-content:center; }
#billslot i{ display:block; width:72%; height:6px; background:#000; border-radius:2px;
  box-shadow:inset 0 2px 0 rgba(255,255,255,.1); }
#cardreader{ grid-column:2 / 4; grid-row:1; position:relative; height:28px;
  background:linear-gradient(180deg, #565f6a, var(--dev-slot) 70%, #39414b); border:2px solid var(--line);
  border-radius:4px; }
.cr-slot{ position:absolute; left:5px; right:5px; top:5px; height:5px; background:#000; border-radius:2px; }
.cr-led{ position:absolute; right:4px; bottom:4px; width:5px; height:5px; border-radius:50%;
  background:#2ea84f; box-shadow:0 0 5px rgba(46,168,79,.9); }
#cardreader.declined .cr-led{ background:#ff3b30; box-shadow:0 0 6px rgba(255,59,48,.95); }
#coinslot{ grid-column:4; grid-row:1; background:#000; border:2px solid #cdd4e2;
  border-radius:3px; box-shadow:inset 0 3px 5px rgba(0,0,0,.9), inset 0 0 0 1px rgba(255,255,255,.07);
  position:relative; overflow:hidden; }
/* the paid coin: a brief glint INSIDE the slot, never outside the machine.
   It is drawn at the FULL 26px of a cup coin and the 10px slot clips it to a
   central band — same object, seen through a narrow mouth. Drawn at 12px to
   fit the slot it read as a different, tinier coin than the one you just
   dragged in, which is the thing Ben caught (2026-08-06). Sizing it to the
   opening was solving the slot's problem, not the coin's. */
.coin.in-slot{ position:absolute; width:26px; height:26px; left:50%; margin-left:-13px;
  top:-28px; transition:top .34s cubic-bezier(.5,0,1,1); }
#coinreturn{ grid-column:2; grid-row:2;
  background:linear-gradient(180deg, #565f6a, var(--dev-slot) 70%, #39414b); color:#c6cfe6;
  border:2px solid var(--line); border-radius:6px; font-size:10px; font-weight:900;
  letter-spacing:.08em; padding:6px; line-height:1.3;
  box-shadow:0 2px 0 rgba(0,0,0,.35); }
#coinreturn:active{ transform:translateY(2px); box-shadow:0 0 0 rgba(0,0,0,.35); }
#coincup{ grid-column:3 / 5; grid-row:2; position:relative; min-height:30px; background:#12172b;
  border:2px solid var(--line); border-radius:0 0 8px 8px; overflow:visible;
  box-shadow:inset 0 5px 9px rgba(0,0,0,.55); }
.coin{ position:absolute; width:26px; height:26px; border-radius:50%;
  background:url("../assets/art/production/fx/coin.png") center/cover no-repeat; }

/* printer */
#printer{ background:linear-gradient(180deg, var(--dev-lite), var(--dev) 55%, var(--dev-deep));
  border:var(--ol) solid var(--line); border-radius:12px; padding:10px 12px;
  box-shadow:0 0 0 2.5px rgba(255,196,77,.3), 0 3px 0 rgba(0,0,0,.25); }
.pr-label{ color:#aab3cc; font-size:10px; font-weight:900; letter-spacing:.22em; margin-bottom:6px; }
.pr-slot{ position:relative; height:14px; background:#000; border:2px solid var(--line); border-radius:4px;
  box-shadow:inset 0 3px 6px rgba(0,0,0,.85); }
#paper{ position:absolute; left:6px; right:6px; top:10px; max-height:0;
  overflow-y:auto; overflow-x:hidden; scrollbar-width:thin; overscroll-behavior:contain;
  background:var(--paper) url("../assets/art/production/fx/receipt-paper-texture.png") repeat-y;
  background-size:100% auto;
  border:2px solid var(--line); border-top:none; border-radius:0 0 4px 4px;
  box-shadow:0 6px 14px rgba(0,0,0,.4); z-index:8;
  font-family:"Consolas","DejaVu Sans Mono",monospace; font-size:10.5px; color:var(--paper-ink); line-height:1.45;
  transition:max-height .18s linear; }
#paper .p-inner{ padding:10px 10px 14px; }
#paper .p-head{ text-align:center; font-weight:700; letter-spacing:.08em; }
#paper .p-rule{ border-top:1.5px dashed #b9b09a; margin:6px 0; }
#paper .p-small{ font-size:9px; color:#7a755f; }
#paper .p-void{ position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  font-size:34px; font-weight:900; color:rgba(217,79,79,.8); transform:rotate(-14deg);
  letter-spacing:.2em; pointer-events:none; }
/* long receipts scroll; the buttons ride the paper's bottom edge so SAVE
   is always reachable (they printed off the 560px cap before) */
#paper .p-actions{ display:flex; gap:8px; justify-content:center;
  position:sticky; bottom:0; background:var(--paper);
  border-top:1.5px dashed #b9b09a; margin:0 -10px; padding:8px 10px 10px; }
#paper.tearing{ animation:tear .45s ease-in forwards; }
@keyframes tear{ to{ transform:translateY(30px) rotate(3deg); opacity:0; } }

/* scanner: the one grey device in the navy family (Sol's cluster) —
   the scan card leads row 2 on the left, label below the window */
#scanner{ grid-column:1; grid-row:2; display:flex; flex-direction:column;
  justify-content:center; gap:2px; padding:3px 6px;
  background:linear-gradient(180deg, #aab1c4, #8f97ad 70%, #7e879e);
  border:2px solid var(--line); border-radius:5px; }
.sc-window{ position:relative; width:100%; height:16px; background:#150a0d; border:2px solid var(--line); border-radius:4px; overflow:hidden; }
.sc-beam{ position:absolute; left:6%; right:6%; top:50%; height:2px; background:#ff3b30;
  box-shadow:0 0 10px 2px rgba(255,59,48,.8); border-radius:2px; }
#scanner.scanning .sc-beam{ animation:beam .5s ease-in-out 3; }
@keyframes beam{ 0%,100%{ top:16%; } 50%{ top:72%; } }
#scanner.drag-ok{ outline:3px dashed var(--glow); outline-offset:3px; }
.sc-label{ text-align:center; color:#c93a33; font-size:10px; font-weight:900; letter-spacing:.12em; }
.sc-long{ display:none; }
.sc-short{ display:inline; }

/* maker's plate — etched metal. THREE lines now: who made it, which machine
   this is, and how to reach the person who wrote it (Ben, 2026-08-06). The
   plate is the one surface a visitor already knows to read for "who do I
   call", so putting the contact hint anywhere else was working against a
   habit people arrive with. It replaces the second sticky note that decision 2
   called for: a machine wears one maker's plate and, if you are lucky, one
   note from whoever keeps it — two notes is clutter pretending to be lore.
   The only pale metal on the body besides the scanner, and that rhyme is
   deliberate.
   The third line COSTS ROOM: the band it centres in is a fixed 63 machine px
   and the two-line plate already used 51 of it. Vertical padding drops 10->6
   and the row gap 3->2 to buy the line back; the horizontal padding does not
   move, because the plate's proportions are what make it read as metal. */
/* centred BY CONSTRUCTION on the control column, not by a hand-tuned offset
   from the machine's edge: left/right 0 inside #notch plus auto margins, so
   the plaque tracks the column at every viewport and cannot drift (it sat
   3.7px left of every device above it when the offset was hand-picked —
   Ben caught it, 2026-08-06). Same lesson as the falling can: derive the
   position from the thing it aligns to. */
/* Vertically: OPTICALLY centred in the band between the receipt card and the
   paper bay (Ben, 2026-08-06). Optically, not geometrically — the plate's
   `0 3px 0` drop shadow is visible mass, so centring the BOX left 8.5px of
   air above and 4.5 below and read as lopsided at a glance. The centred
   thing has to be the ink, shadow included. The band is a stable 64px on
   desktop (#machine is a fixed 950px and every row but #deck is fixed, so
   the notch's leftover never moves); plate 48 + 3 shadow = 51, leaving 6.5
   each side. The margin carries a further +2.5 because top:100% resolves to
   the notch's PADDING edge, inside its border.
   Re-derived 2026-08-06 after Ben asked for "about 2px up": the band measured
   7 machine px above the plate and 5 below, so the difference he read WAS 2
   and the move is half of it. margin 9 -> 8, gaps 6 and 6.
   The number is coupled to the notch's height, so anything that grows the CRT,
   keypad or printer moves this plate off centre again — bm-audit's OPTICAL
   check is the guard, and its tolerance is now tight enough (0.75 screen px)
   to fail on a single machine pixel of drift rather than shrug at it. */
/* 192, up from 178. "Need assistance? Select item 25." measures 155px at the
   help line's size and the old plate offered 150px of inner width, so it wrapped
   onto a second line and pushed the plate out of its band. Widened rather than
   shrunk: the help line is the most important thing on the plate now and should
   not be the smallest type on it. Still well inside the 224.6px control column,
   so it keeps reading as a plate screwed onto the body. */
#makers{ position:absolute; left:0; right:0; top:100%; width:192px;
  margin:6px auto 0; padding:4px 12px;
  /* Monotonic light-to-dark. The old paint had a lighter stop at 62% to read
     as a sheen on brushed metal, which worked while the plate was two lines
     and the 62% mark fell in dead space. With a third line it lands directly
     behind the text and reads as a grey highlight somebody forgot to remove
     (Ben, 2026-08-06). A gradient stop is fine on empty metal and is noise
     under type; the plate keeps its inset top-edge glint for the metal read. */
  background:linear-gradient(158deg, #bcc3cb, #a8b0b9 30%, #949ca5 62%, #7d858e);
  border:2px solid var(--line); border-radius:3px;
  box-shadow:0 3px 0 rgba(0,0,0,.34), inset 0 1px 0 rgba(255,255,255,.4);
  display:flex; flex-direction:column; gap:2px;
  font-family:"Consolas","DejaVu Sans Mono",monospace; color:#20262b; }
.mk-name{ font-size:10px; font-weight:700; letter-spacing:.16em; }
.mk-model{ font-size:8px; letter-spacing:.1em; }
/* The actionable line, set apart from the two that are pure manufacture. Same
   engraved mono so it belongs to the plate, sentence case because it is an
   instruction to a person and not a part number, and a hairline rule above it
   the way a real plate separates its fields. */
.mk-help{ font-size:8.5px; font-weight:700; letter-spacing:.02em; color:#161b1f;
  margin-top:1px; padding-top:3px; border-top:1px solid rgba(23,26,29,.28); }
#makers i{ position:absolute; width:4px; height:4px; border-radius:50%; background:#6e767f;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.5); }
#makers i:nth-of-type(1){ left:4px; top:4px; }
#makers i:nth-of-type(2){ right:4px; top:4px; }
#makers i:nth-of-type(3){ left:4px; bottom:4px; }
#makers i:nth-of-type(4){ right:4px; bottom:4px; }

/* sticky note */
#sticky{ position:absolute; right:28px; top:270px; z-index:6; width:86px; padding:10px 8px 12px;
  background:#f7d968; border:none; box-shadow:3px 4px 0 rgba(0,0,0,.3);
  transform:rotate(4deg); transition:transform .2s;
  font-family:"Comic Sans MS","Segoe Print",cursive; color:#5a4a12; text-align:left; }
#sticky:hover{ transform:rotate(1deg) scale(1.06); }
#sticky::before{ content:""; position:absolute; right:-6px; bottom:-6px; width:22px; height:22px;
  background:linear-gradient(135deg, transparent 50%, var(--wall) 50%); }
.st-line{ display:block; font-size:10px; }
.st-code{ display:block; font-size:15px; font-weight:700; letter-spacing:.12em; }
.st-sig{ display:block; font-size:9px; text-align:right; margin-top:4px; opacity:.7; }

/* ---------- can zoom overlay ---------- */
#zoom,#leave{ position:fixed; inset:0; z-index:40; display:flex; align-items:center; justify-content:center;
  background:rgba(10,13,26,.82); backdrop-filter:blur(3px); }
#zoom[hidden],#leave[hidden]{ display:none; }
.zoom-card{ display:flex; flex-direction:column; align-items:center; gap:18px; }
/* the can never smooshes: one min() picks the tightest constraint and
   aspect-ratio derives the height (landscape phones squashed it before) */
#zoom-can{ position:relative; width:min(300px, 80vw, 50.9vh); aspect-ratio:150 / 230;
  height:auto; perspective:1200px; }
.bigcan{ position:absolute; inset:0; transform-style:preserve-3d; transition:transform .5s cubic-bezier(.5,0,.3,1.2); }
.bigcan.flipped{ transform:rotateY(180deg); }
.bigface{ position:absolute; inset:0; backface-visibility:hidden; border:4px solid var(--line);
  border-radius:26px; overflow:hidden; display:flex; flex-direction:column; align-items:center;
  box-shadow:inset -30px 0 40px rgba(0,0,0,.18), inset 20px 0 30px rgba(255,255,255,.14), 0 20px 50px rgba(0,0,0,.5); }
/* the back face is a MACRO SHOT, sharpened by the design pass (2026-08-06):
   the can is CROPPED, not shrunk. The wall runs off all four frame edges so
   both walls leave frame and nothing is left to measure the label against —
   the label is inset on the can and near full-bleed in frame at once. The
   cylinder gradient sits ABOVE the label (a label on a curved surface is
   lit by that curve), the highlight tapers at the shoulder, and there is NO
   lid: at reading distance you are looking at a wall. */
.bigface.back{ transform:rotateY(180deg); background:var(--cbody, var(--cream));
  padding-bottom:10%; }
.mac{ position:absolute; pointer-events:none; }
.mac-wall{ left:-32%; right:-32%; top:-9%; bottom:-5%; background:var(--cbody, #e6eff0); }
.mac-shoulder{ left:-32%; right:-32%; top:0; height:42%;   /* cans neck inward at the top */
  background:radial-gradient(150% 104% at 50% 100%, transparent 50%, rgba(8,12,32,.66) 100%); }
.mac-spec{ left:23%; top:0; bottom:0; width:54%;   /* the highlight bends along the shoulder */
  background:linear-gradient(90deg, transparent 0, rgba(255,255,255,.24) 55%, transparent 100%);
  clip-path:polygon(34% 0, 60% 0, 74% 100%, 44% 100%); }
.mac-cyl{ inset:0;
  background:linear-gradient(90deg, rgba(0,0,0,.32) 0%, rgba(255,255,255,.10) 14%,
    rgba(255,255,255,.22) 26%, transparent 44%, transparent 62%,
    rgba(0,0,0,.12) 80%, rgba(0,0,0,.38) 100%); }
.bigface.artface{ background:transparent; border:none; box-shadow:none; }
.label-img{ position:absolute; inset:0; width:100%; height:100%; object-fit:contain;
  filter:drop-shadow(0 18px 40px rgba(0,0,0,.55)); }
.big-lid{ width:70%; height:16px; background:#c9cfdd; border:3px solid var(--line); border-radius:8px; margin:10px 0 4px; }
.big-deco{ font-size:64px; margin-top:26px; }
.big-brand{ font-size:30px; font-weight:900; text-align:center; letter-spacing:.04em; padding:0 14px; }
.big-kind{ font-size:13px; font-weight:700; letter-spacing:.14em; opacity:.9; margin-top:4px; text-align:center; }
.big-slogan{ margin-top:auto; margin-bottom:20px; font-size:13px; font-style:italic; text-align:center;
  padding:0 22px; opacity:.95; }
/* dates chip removed from the zoom front (Ben, 2026-08-04) — the dates
   live on in the nutrition serving line and every receipt */
/* nutrition back — the FDA label, played straight (that's the joke) */
.nut{ position:relative; width:86%; margin-top:auto; background:#fff; border:2.5px solid #111; border-radius:2px;
  padding:8px 10px 9px; color:#111;
  font-family:"Helvetica Neue","Arial Narrow",Arial,sans-serif; }
.nut h3{ font-size:24px; font-weight:900; letter-spacing:-.02em; line-height:1.05;
  border-bottom:1px solid #111; padding-bottom:2px; }
.nut .serving{ font-size:10px; font-weight:700; padding:3px 0 4px; }
.nut .nut-bar{ height:8px; background:#111; margin:0 0 3px; }
.nut .nut-amount{ display:flex; justify-content:space-between; font-size:8.5px; font-weight:900;
  letter-spacing:.02em; border-bottom:4px solid #111; padding-bottom:2px; }
.nut table{ width:100%; border-collapse:collapse; font-size:10px; }
.nut td{ padding:3px 0; border-bottom:1px solid #111; }
.nut td:first-child{ font-weight:700; padding-right:6px; }
.nut td:nth-child(2){ text-align:right; color:#222; text-wrap:balance; }
.nut td:last-child{ text-align:right; font-weight:900; padding-left:8px; white-space:nowrap; }
.nut tr:last-child td{ border-bottom:4px solid #111; }
.nut .foot{ font-size:8px; line-height:1.35; padding-top:4px; color:#111; }
/* position:relative is load-bearing: the macro wall is a positioned layer,
   and a static barcode would paint underneath it (caught 2026-08-06) */
.big-barcode{ position:relative; margin-top:14px; margin-bottom:0; width:38%; height:44px; background:#fff;
  border:3px solid var(--line); border-radius:4px; display:flex; align-items:center; justify-content:center; }
.big-barcode i{ display:block; height:70%;
  background:repeating-linear-gradient(90deg,#111 0 3px, #fff 3px 5px, #111 5px 6px, #fff 6px 10px);
  width:86%; }
.zoom-actions{ display:flex; gap:12px; flex-wrap:wrap; justify-content:center; }
.zbtn{ background:var(--dev); color:var(--cream); border:3px solid var(--line); border-radius:8px;
  font-weight:900; letter-spacing:.1em; font-size:13px; padding:10px 18px;
  box-shadow:0 4px 0 rgba(0,0,0,.35); }
.zbtn:active{ transform:translateY(3px); box-shadow:0 1px 0 rgba(0,0,0,.35); }
.zbtn.primary{ background:var(--coral); }
.leave-card{ background:var(--dev); border:4px solid var(--line); border-radius:14px;
  color:var(--cream); max-width:min(340px,86vw); padding:20px 22px; display:flex; flex-direction:column; gap:14px; }
.leave-head{ font-weight:900; letter-spacing:.16em; color:var(--coral); font-size:13px; }
.leave-card p{ font-size:13.5px; line-height:1.5; }

/* ---------- the overlay shell (design pass 2026-08-06, LOCKED) ----------
   One shell, four overlays. The card SNAPS in over three stepped frames
   rather than easing — a cel-shaded object has no motion blur. Opacity
   only: a stepped SCALE jumped the card's edges sideways frame by frame
   (Ben saw the can shift left-right on open). Reduced motion gets the end
   state via the global rule. Failure never gets an overlay: wrong codes
   and declined cards belong to the CRT. */
@keyframes ovin{ from{ opacity:0; } }
#zoom:not([hidden]) .zoom-card, #gacha:not([hidden]) .gacha-card,
#leave:not([hidden]) .leave-card{ animation:ovin .12s steps(3); }
#found:not([hidden]) .found-card{ animation:ovin .12s steps(3); }

/* (The raster DRESSED MODE block lived here until wave 15. The chrome is
   now drawn per-element above — there is nothing left to register.) */

/* ============================================================
   MOBILE — SIDE CONSOLE (≤560px, design pass 2026-08-06, LOCKED).
   Not a miniature and not a tall stack: the same machine stood
   upright. Glass left, control column right — CRT, keypad, payment
   top to bottom, the order you use them — then the printer FULL
   WIDTH below (the receipt's 39-char monospace lines are what set
   that width), takeout at the bottom. One screen, tray included.
   No transform scale at this width: scaling to fit would shrink
   every key below the touch floor. initScale() skips the transform;
   the !important is the belt to that suspender.
   ============================================================ */
@media (max-width:560px){
  body{ overflow-x:hidden; overflow-y:auto; }
  #stage{ height:auto; display:block; }
  #alcove{ width:100%; height:auto; transform:none !important; padding-bottom:26px; }
  #alcove::after{ display:none; }

  /* radio joins the flow — no overlap math with the marquee */
  #radio{ position:static; display:block; width:250px; height:64px;
    margin:22px auto 0; }

  #machine{ position:relative; left:auto; top:auto;
    width:auto; height:auto; margin:12px 4px 14px; display:flex; flex-direction:column; }
  #machine .m-feet{ display:none; }
  #makers{ display:none; }

  #marquee{ margin:10px 12px 2px; height:52px; }
  .mq-plate{ padding:6px 10px; }
  /* fluid: THE fits at 375, and a 320 SE can't push the title under the sticky */
  .mq-en{ font-size:clamp(12.5px, 4vw, 15.5px); letter-spacing:.08em; }
  .mq-jp{ font-size:9px; letter-spacing:.24em; }

  /* the console grid. #notch dissolves (display:contents) so its devices
     become grid items; the glass spans the column's three rows, so the two
     sides are equal height by construction. */
  /* the column's 136px floor is where a three-across key holds exactly 40px —
     on the narrowest phones the GLASS gives way, never the touch targets */
  /* 10px side padding keeps every card inside the shell's gold moulding —
     at 6px the devices sat on top of the ring and read as overflowing */
  #deck{ display:grid; grid-template-columns:minmax(0,1.7fr) minmax(140px,1fr);
    grid-template-rows:auto auto auto auto; gap:7px; padding:10px; }
  #notch{ display:contents; }
  #glasswrap{ grid-column:1; grid-row:1 / span 3; }
  #glass{ padding:8px 6px 6px; }
  /* the goods sit ON the rail here too — no stray bottom padding lifting them */
  .slots{ padding:0 2px; }
  #crt{ grid-column:2; grid-row:1; }
  #keypad{ grid-column:2; grid-row:2; }
  #payment{ grid-column:2; grid-row:3; }
  #printer{ grid-column:1 / -1; grid-row:4; }
  #code-toggle{ display:none; }

  /* goods sized to the 63% glass — the shelves stay 5·5·5·4·4 */
  :root{ --can-w:38px; --can-h:58px; }
  .gcapsule{ width:34px; height:34px; }
  /* the tap target keeps its 40px floor, but the capsule sits at the BOTTOM
     of it so it rests on the rail like everything else */
  .gcap-btn{ min-width:40px; min-height:40px; display:flex; align-items:flex-end; justify-content:center; }
  .gcap-btn .gcapsule{ width:34px; height:34px; }
  .bay{ width:34px; height:48px; }
  .slot-tag{ font-size:8px; padding:0 4px; letter-spacing:.04em; }
  .soldout-strip{ font-size:6.5px; }
  .slot::before,.slot::after{ width:6px; height:14px; }
  /* NO bottom padding. A second .slots rule further down this same media
     block used to re-add 2px and silently beat the one above that had already
     removed it, so every can on mobile hovered 2px over its rail — invisible
     to the old box-based RAIL check and to me, for two waves. Duplicated
     selectors in one block are how a fix gets undone without leaving a mark. */

  /* the control column: keys flex to the column and keep the touch floor.
     flex-basis:0 is scoped to ROW children only — inside a .kp-cell the
     cell's column flex would read it as a HEIGHT and squash the key
     (caught by the sweep: 21px-tall ENTER, 2026-08-06) */
  #keypad{ display:block; padding:4px; }
  .kp{ min-width:0; width:auto; height:30px; font-size:14px; padding:0; }
  .kp-row > .kp{ flex:1 1 0; }
  .kp-row{ gap:3px; margin-bottom:3px; }
  .kp-cell{ flex:1 1 0; min-width:0; gap:4px; }
  .kp-cell .kp{ flex:none; width:100%; }
  .kp-cap{ font-size:8px; height:10px; letter-spacing:.08em; }

  .crt-bezel{ padding:8px 8px 24px; }
  .crt-bezel::after{ font-size:6.5px; letter-spacing:.14em; left:10px; bottom:8px; }
  .crt-screen{ height:96px; padding:6px 8px; }   /* narrow screen wraps more */
  #screen-lines{ font-size:11px; }
  .snd{ transform:scale(.8); transform-origin:right bottom; }

  /* payment goes VERTICAL down the column — bill, card, coin row, then the
     scanner SHARES the last row with the coin bowl (Ben, 2026-08-06). The
     bowl keeps the right side, under the slot the coin goes into. */
  #payment{ margin:0; padding:6px; display:grid; grid-template-columns:1fr 1fr 14px;
    align-content:start; gap:5px; }
  #billslot,#cardreader{ height:18px; grid-column:1 / 4; }
  #billslot{ grid-row:1; }
  #cardreader{ grid-row:2; }
  #coinslot{ grid-column:3; grid-row:3; width:14px; height:auto; align-self:stretch; }
  #coinreturn{ grid-column:1 / 3; grid-row:3; font-size:8px; padding:8px 0; letter-spacing:.08em; }
  #scanner{ grid-column:1; grid-row:4; }
  #coincup{ grid-column:2 / 4; grid-row:4; min-height:26px; }
  .coin{ width:22px; height:22px; }
  /* the in-slot coin follows the cup coin down, so the two stay one object */
  .coin.in-slot{ width:22px; height:22px; margin-left:-11px; top:-24px; }
  .sc-window{ height:12px; }
  .sc-label{ font-size:8px; }
  .pr-label{ font-size:8px; }

  #bottomrow{ display:block; padding:0 10px; margin-bottom:14px; }
  #paperbay{ display:none; }
  #printer{ padding:8px 10px; }
  #paper{ left:9px; right:9px; }
  #flap{ height:40px; }
  #tray{ min-height:56px; padding:6px 6px 0; }

  /* sticky note rides the marquee corner — high enough that it can never
     cover the CRT, which now lives right below it in the side console */
  #sticky{ top:70px; right:4px; transform:rotate(4deg) scale(.88); }
  #sticky:hover{ transform:rotate(1deg) scale(.94); }

  /* nutrition label gets the whole can back on a phone */
  .nut{ width:92%; padding:8px 9px; }
}

/* small phones: the column floor has taken glass width, so the goods shrink */
@media (max-width:380px){
  :root{ --can-w:34px; --can-h:52px; }
  .bay{ width:30px; height:44px; }
}
/* an SE-width screen forces the choice the kit named: narrower glass */
@media (max-width:330px){
  :root{ --can-w:27px; --can-h:41px; }
  .gcapsule,.gcap-btn .gcapsule{ width:26px; height:26px; }
  .bay{ width:24px; height:36px; }
  .slot-tag{ font-size:7px; padding:0 3px; }
}

/* reduced motion */
@media (prefers-reduced-motion:reduce){
  *{ animation-duration:.001s !important; transition-duration:.001s !important; }
}
