/* ══════════════════════════════════════════════════════════════════
   ATOMIC VISION · THE WARM-UP, AND THE PHONE
   © 2026 Apollo (Atomic Apollo). All rights reserved.

   Its own file because the PHONE loads it. The player has no
   business pulling in a console stylesheet it will never show.
   ══════════════════════════════════════════════════════════════════ */

/* ════════ THE AR WARM-UP ════════
   What the visitor sees on a phone while the Holo-Matic starts.
   Same family as the console's own warm-up: dark field, scanlines,
   chrome plate, phosphor type. The atom is the console's atom. */
/* ══════════════════════════════════════════════════════════════════
   THE SET WARMS UP
   Studied 2026-11-13, docs/study/the-set-warms-up.md.

   Apollo: "the init screen should look like an old advertisement, or
   something like a monitor turning on and flickering as it gets warm
   as it loads."

   The checklist on this screen has said "Warming the tubes" for days
   while the picture simply appeared, fully formed, which is the one
   thing a warming tube never does.

   ⚠️ AND IT WAS ALREADY BROKEN. This rule used to say
   `animation:crtOn 1.1s`, with `@keyframes crtOn` living in
   av-shell.css — the CONSOLE stylesheet, which play/index.html does
   not load. So the player named an animation that did not exist
   there and CSS did what it always does with a name it cannot
   resolve: nothing, silently. The power-on has never once played on
   Apollo's phone.

   Everything it needs is in THIS file now, so the screen cannot be
   separated from its own behaviour again.

   ── NOT QUAKEY'S CRT ──
   Quakey's init overlay is an arcade HUD: a 16px blue pixel grid, a
   bright narrow sweep every 2.4s, corner brackets, Press Start 2P.
   Its own header says that treatment IS the Quakey identity, so
   taking it would be taking the identity. It also never turns ON:
   it is ambience, constant from the first frame.

   This is a television set in 1957, switched on from cold. Every
   beat below is a thing the glass actually did.
   ══════════════════════════════════════════════════════════════════ */
.arWarm{position:fixed;inset:0;z-index:600;display:grid;place-items:center;
  background:radial-gradient(circle at 50% 42%,#13363d,#040c0e 70%);
  transition:opacity .7s,visibility .7s;-webkit-tap-highlight-color:transparent;
  overflow:hidden;
  /* THE WARM. Three seconds climbing out of dim and blue-grey into
     warm phosphor, because the tube is literally heating up.

     ⚠️ DELAYED, AND `forwards` RATHER THAN `both`. A filter on this
     element applies to everything under it, including the cathode
     strike, so opening at brightness(.35) multiplied a pure white
     hairline down to mid grey. Compensating with a counter-filter on
     the child did not win it back.

     The real answer is that these are two events in sequence, not
     one: the cathode strikes, and THEN the picture begins warming.
     `forwards` instead of `both` is what makes that true — `both`
     back-fills the 0% keyframe during the delay, which would dim the
     strike exactly as before. Caught at +170ms by screenshot. */
  animation:avTubeWarm 3.2s ease-out .34s forwards}

/* THE SWITCH-OFF. A tube does not fade, it collapses: the raster
   pinches to a hairline, then a dot, then dark. Quakey fades opacity
   to zero. This is the moment the piece appears, so the set should
   stop being the thing you are looking at. */
.arWarm.gone{animation:avTubeOff .5s cubic-bezier(.6,0,.9,.4) both;
  pointer-events:none}

/* the glass: scanlines, warm rather than neutral */
.arWarm::after{content:"";position:absolute;inset:0;pointer-events:none;z-index:3;
  background:repeating-linear-gradient(180deg,#0000 0 2px,#00000030 2px 3px)}

/* THE BOWED GLASS. A vignette that darkens hard into the corners,
   and one specular sheen across the top, the way a curved tube
   catches the room. No pixel grid: a lattice is a videogame, a
   television has scanlines and nothing else. */
.arWarm::before{content:"";position:absolute;inset:0;pointer-events:none;z-index:2;
  background:
    linear-gradient(176deg,#ffffff0e 0,#ffffff00 22%),
    radial-gradient(ellipse 120% 100% at 50% 45%,#0000 42%,#000000d0 100%)}

/* THE HUM BAR. Wide, faint, and slow: nine seconds to cross, which
   reads as a set that wants its vertical hold adjusting. Quakey's
   sweep is narrow, bright and crosses in 2.4s, which reads as a
   scanning instrument. Different object, different motion. */
.arWarm .arHum{position:absolute;left:0;right:0;height:38%;top:-38%;z-index:2;
  pointer-events:none;
  background:linear-gradient(180deg,#0000,#e9b4430c 38%,#e9b44316 50%,#e9b4430c 62%,#0000);
  animation:avHumBar 9s linear infinite}

.arWarmCore{display:flex;flex-direction:column;align-items:center;gap:13px;text-align:center;
  padding:24px;max-width:360px;position:relative;z-index:1;
  /* THE STRIKE AND THE BLOOM, then the unsteady first seconds.
     Two animations because they are two different machines: the
     raster opening, and the picture refusing to settle. */
  animation:avRasterOn 1.15s cubic-bezier(.2,.9,.25,1) both,
            avFlicker 2.4s steps(1,end) .25s 1 both}

/* ── the strike: the cathode lights before the yoke deflects, so
      there is a hairline across the middle far too bright, and only
      then does the raster open. It overshoots and settles. ── */
@keyframes avRasterOn{
  0%{transform:scaleY(.002) scaleX(.72);filter:brightness(6) saturate(0);opacity:0}
  8%{transform:scaleY(.002) scaleX(1.04);filter:brightness(6) saturate(0);opacity:1}
  34%{transform:scaleY(.004) scaleX(1);filter:brightness(5) saturate(.1)}
  58%{transform:scaleY(1.09) scaleX(1);filter:brightness(1.7) saturate(.6)}
  76%{transform:scaleY(.97);filter:brightness(1.15) saturate(.85)}
  100%{transform:none;filter:none;opacity:1}
}

/* ── the tubes coming up to temperature ── */
@keyframes avTubeWarm{
  0%{filter:brightness(.35) contrast(1.5) saturate(.15)}
  30%{filter:brightness(.72) contrast(1.22) saturate(.5)}
  100%{filter:none}
}

/* ── ⚠️ IRREGULAR ON PURPOSE. Evenly spaced flicker reads as a CSS
      animation; a set warming up is unsteady in a way you cannot
      predict. The gaps between these stops are all different, and
      steps(1,end) keeps each one a hard cut rather than a fade. ── */
@keyframes avFlicker{
  0%,6%{opacity:1}
  7%{opacity:.62}
  9%{opacity:1}
  22%{opacity:1}
  23%{opacity:.48}
  24.5%{opacity:1}
  30%{opacity:1}
  31%{opacity:.78}
  32%{opacity:1}
  52%{opacity:1}
  53%{opacity:.86}
  54%{opacity:1}
  100%{opacity:1}
}

@keyframes avHumBar{from{top:-38%}to{top:100%}}

/* ── THE STRIKE ──
   One searing hairline across the middle before there is any
   picture, then it is swallowed as the raster blooms open around it.
   Its own element on purpose: the first attempt used
   filter:brightness(6) on the panel content, and a multiplier over
   mostly-transparent pixels gives a grey smudge, not a tube firing.
   A screenshot at +70ms is the only reason that was ever noticed. */
.arStrike{position:absolute;left:0;right:0;top:50%;height:2px;z-index:4;
  pointer-events:none;transform-origin:50% 50%;
  background:linear-gradient(90deg,#fff0,#fff 12%,#fff 88%,#fff0);
  box-shadow:0 0 18px 4px #ffffffcc,0 0 48px 10px #e9b44366;
  animation:avStrike .66s cubic-bezier(.2,.9,.3,1) both}
/* ⚠️ THE STRIKE HAS TO FIGHT THE WARM-UP.
   `.arWarm` runs avTubeWarm, which opens at brightness(.35), and a
   filter on a parent applies to everything under it. So a pure white
   hairline was being multiplied down to mid grey: full width,
   correctly timed, and completely unconvincing. Caught at +170ms by
   screenshot, not by reading the CSS.

   A child filter composes with the parent's, so this multiplies back
   up: .35 x 3.6 is a little over 1, which is what a cathode striking
   should look like against a cold screen. */
@keyframes avStrike{
  0%{transform:scaleX(.42) scaleY(.5);opacity:0;filter:brightness(3.6)}
  7%{transform:scaleX(1.02) scaleY(1);opacity:1;filter:brightness(3.6)}
  30%{transform:scaleX(1) scaleY(1);opacity:1;filter:brightness(3.2)}
  /* as the raster opens, the line stops being a line */
  62%{transform:scaleX(1) scaleY(2.6);opacity:.45;filter:brightness(2.2)}
  100%{transform:scaleX(1) scaleY(9);opacity:0;filter:brightness(1.4)}
}

@keyframes avTubeOff{
  0%{transform:none;filter:none;opacity:1}
  42%{transform:scaleY(.016) scaleX(1.02);filter:brightness(3.2) saturate(.2);opacity:1}
  72%{transform:scaleY(.01) scaleX(.12);filter:brightness(5);opacity:1}
  100%{transform:scaleY(.008) scaleX(0);filter:brightness(7);opacity:0;visibility:hidden}
}

/* A warming tube is motion for its own sake, which is exactly what
   somebody who asked for less of it does not want. The screen still
   works: it simply arrives already warm. */
@media (prefers-reduced-motion:reduce){
  .arWarm,.arWarmCore{animation:none}
  .arWarm.gone{animation:none;opacity:0;visibility:hidden}
  .arWarm .arHum,.arWarm .arStrike{display:none}
}
.arAtomWrap{width:170px;height:170px;position:relative;
  /* flex:none, or the box has no content to hold it open and the
     atom collapses to nothing. It did exactly that. */
  flex:none}
/* explicit px, not 100%: a canvas has its own intrinsic size, and
   a percentage against it resolved to zero height here */
.arAtom{width:170px;height:170px;display:block}
.arWord{display:flex;align-items:baseline;gap:8px}
.aw1{font-family:var(--f-plate);font-size:30px;letter-spacing:3px;color:var(--cream)}
.aw2{font-family:var(--f-script,var(--f-plate));font-size:38px;color:var(--orange);
  text-shadow:0 0 18px #ee6a2f66}
.arPlate{font-family:var(--f-plate);font-size:11px;letter-spacing:2.5px;color:#1b2b30;
  background:var(--chromeBar);padding:4px 13px;border-radius:3px;box-shadow:0 3px 8px #0008}
.arPlate em{color:var(--orange);font-style:normal}

/* the stages: a lamp each, dark until it lands */
.arStages{list-style:none;margin:4px 0 0;padding:0;display:flex;flex-direction:column;gap:7px;width:100%}
.arStages li{display:flex;align-items:center;gap:10px;font-family:var(--f-mono);font-size:12px;
  letter-spacing:1.4px;color:#7e9aa0;transition:color .35s}
.arStages li i{width:11px;height:11px;border-radius:50%;flex:none;background:#0d2024;
  box-shadow:inset 0 1px 2px #000,0 0 0 1px #2c5a61;transition:background .35s,box-shadow .35s}
.arStages li.now{color:var(--cream)}
.arStages li.now i{background:radial-gradient(circle at 38% 32%,#ffe7a8,var(--mustard));
  box-shadow:0 0 10px var(--mustard),0 0 0 1px #6d5a1e;animation:arPulse 1s ease-in-out infinite}
.arStages li.done{color:var(--phosphor)}
.arStages li.done i{background:radial-gradient(circle at 38% 32%,#d9fff1,var(--phosphor));
  box-shadow:0 0 9px #7dffcfaa,0 0 0 1px #1d6358;animation:none}
@keyframes arPulse{0%,100%{opacity:.45}50%{opacity:1}}

.arMsg{font-family:var(--f-mono);color:var(--phosphor);letter-spacing:1.6px;font-size:13px;
  text-shadow:0 0 8px #7dffcf88;min-height:18px;line-height:1.5}
.arBar{width:100%;max-width:280px;height:8px;border-radius:4px;background:#0a1a1c;
  box-shadow:inset 0 1px 3px #000,0 0 0 1px #2c5a61;overflow:hidden}
.arBar b{display:block;height:100%;width:0;border-radius:4px;
  background:linear-gradient(90deg,var(--orange),var(--mustard));
  box-shadow:0 0 10px var(--orange);transition:width .45s cubic-bezier(.2,.9,.3,1)}
.arHint{font-size:11.5px;color:var(--mustard);letter-spacing:.4px;min-height:15px;line-height:1.45}

.arWarm.ready .arMsg{color:var(--mustard);text-shadow:0 0 12px #e9b44399;font-size:14px}
.arWarm.failed .arMsg{color:#ff8a7a;text-shadow:none}
.arWarm.failed .arHint{color:#ffb7ad}
.arWarm.failed .arBar b{background:linear-gradient(90deg,#cf3b3b,#ff8a6a);box-shadow:0 0 10px #cf3b3b}

@media (max-height:620px){
  .arAtomWrap{width:118px;height:118px;flex:none}
  .arAtom{width:118px;height:118px}
  .aw1{font-size:24px}.aw2{font-size:30px}
  .arWarmCore{gap:9px}
  .arStages li{font-size:11px}
}

/* ── THE START SWITCH, on the warm-up itself ──
   Quakey puts START AR on its init overlay rather than on a screen
   before it, so the machine is warming while the visitor decides.
   Same here, and the switch is the bakelite bat-handle from the
   console, not a web button. */
.arStart{position:relative;width:200px;height:60px;border:0;border-radius:30px;cursor:pointer;
  background:linear-gradient(180deg,#2b2520,#15110e);
  box-shadow:inset 0 2px 5px #000,0 0 0 2px #6d5a3a,0 6px 14px #000a;
  display:flex;align-items:center;justify-content:flex-end;padding:0 18px 0 0;margin-top:2px}
.arStart b{font-family:var(--f-plate);font-size:14px;letter-spacing:2.6px;color:var(--mustard);
  text-shadow:0 0 10px #e9b44366}
.asKnob{position:absolute;left:5px;top:5px;width:50px;height:50px;border-radius:50%;
  background:radial-gradient(circle at 38% 30%,#fff3c0,#ee6a2f 60%,#8c3410);
  box-shadow:0 0 16px #ee6a2faa,inset 0 -3px 6px #0006;transition:left .18s cubic-bezier(.3,1.4,.5,1)}
/* ══ ⚠️ THE THROW IS LATCHED, NOT HELD ══
   Studied 2026-11-13, docs/study/the-switch-must-latch.md.

   This used to be `:active` alone. `:active` lasts exactly as long
   as the finger is down, so the knob animated back to the left the
   instant it lifted and the switch un-threw itself. Apollo, from
   work:

     "The button should slide and STAY SLID, not retract back to its
      original position, cause it will look like a failed state until
      the atom starts its reaction."

   He is right, and it was two faults stacked: the knob sprang home,
   and then the whole switch was hidden, so the thrown state was
   never visible for a single frame. A real bat-handle stays where it
   is put. */
.arStart:active .asKnob{left:145px}
.arStart.thrown .asKnob{left:145px;
  background:radial-gradient(circle at 38% 30%,#dfffe9,#7dffcf 55%,#1f8f6a);
  box-shadow:0 0 22px #7dffcfaa,inset 0 -3px 6px #0006}
.arStart:active b,
.arStart.thrown b{color:var(--phosphor);text-shadow:0 0 10px #7dffcf88}

/* Thrown and done with: it stays on the panel as a record of what
   was done, the way a switch on a real console stays thrown. Dimmed
   and dead to the touch, never removed. */
/* ⚠️ THE LEGEND MOVES OUT FROM UNDER THE KNOB.
   The idle switch right-aligns its legend, because the knob is on
   the left. Thrown, the knob travels to 145px on a 200px switch and
   sits exactly on top of it: the first screenshot of the latch read
   "CONT" with the knob over the rest. The legend changes sides with
   the handle, the way the lit word on a real panel is the one the
   handle is NOT covering. */
.arStart.thrown{cursor:default;box-shadow:inset 0 2px 5px #000,0 0 0 2px #3f7a66,0 6px 14px #000a;
  justify-content:flex-start;padding:0 0 0 20px}
.arStart.thrown.spent{opacity:.45;transition:opacity .9s ease .6s}
/* while it waits, the atom idles rather than racing */
.arWarm.waiting .arMsg{color:var(--mustard);text-shadow:0 0 12px #e9b44399}

/* ══ KEEPING THE PIECE ══
   The print control, under the switch. See
   docs/study/cutting-a-print.md and play/sw.js.

   Deliberately NOT a second START AR. A start screen with two equal
   buttons on it asks a question the visitor did not come to answer.
   This one is a small engraved plate: legible, period, and plainly
   secondary to the switch above it. */
.arKeep{
  margin:18px auto 0; display:block; cursor:pointer;
  background:rgba(4,12,14,.55); color:var(--phosphor,#7dffcf);
  border:1px solid rgba(125,255,207,.38); border-radius:3px;
  font-family:var(--f-mono,monospace); font-size:10px; letter-spacing:2px;
  padding:9px 16px; text-shadow:0 0 8px #7dffcf55;
  transition:border-color .2s linear, color .2s linear, background .2s linear;
}
.arKeep[hidden]{display:none}
.arKeep:active{background:rgba(125,255,207,.14)}
/* while it is cutting: busy, and not pressable twice */
.arKeep.cutting{color:var(--mustard,#e9b443);border-color:rgba(233,180,67,.6);
  text-shadow:0 0 10px #e9b44366;pointer-events:none}
/* once it is kept, it stops being an invitation */
.arKeep.kept{color:var(--mustard,#e9b443);border-color:rgba(233,180,67,.75);
  pointer-events:none}

.arKeepSay{
  margin:8px auto 0; max-width:min(78vw,420px); text-align:center;
  font-family:var(--f-mono,monospace); font-size:9px; line-height:1.7;
  letter-spacing:1.2px; color:rgba(243,227,192,.72);
}
.arKeepSay[hidden]{display:none}
.arKeepSay.bad{color:rgba(233,180,67,.9)}
