/* ============================ THE HELPER SPRITE AND ITS DEMOS ============================
   The floating sprite, its bubble, and the interactive demo cards that render
   inside it — the visual half of helper.js and demos.js.

   ORDER MATTERS. This is file 7 of 10 and the cascade is the order
   pages/app.html links them in:
     base.css · layout.css · board.css · screens.css · home.css · editor.css · helper.css · panels.css · drill.css · class.css
   Later files override earlier ones on purpose. Moving a rule between files
   without keeping that order is how a Chinese override or a `won` state
   quietly stops applying. */

/* ============================ THE HELPER SPRITE ============================
   Bottom-right, always there, never in the way of the map. It idles with a
   slow bob so it reads as alive, waves when tapped, and rings when it has
   something new to offer. */
#helper{position:fixed;right:20px;bottom:18px;z-index:36;display:flex;flex-direction:column;
  align-items:flex-end;gap:8px;pointer-events:none}
#helper>*{pointer-events:auto}

#helpSprite{width:76px;height:76px;padding:0;position:relative;filter:drop-shadow(0 10px 16px rgba(90,60,45,.28));
  animation:scoot 14s cubic-bezier(.4,0,.35,1) infinite;transition:filter .15s}
#helpSprite:hover{filter:drop-shadow(0 12px 20px rgba(224,102,58,.4)) brightness(1.04)}
#helpSprite:hover .hant{animation-duration:.9s}

/* ---- the idle ----
   Four beats, not a metronome: it HOPS a few steps out (each hop is a real
   arc, with a lean into the direction of travel), STOPS and has a look round,
   hops back, then rests longer than it moved. The old version slid left and
   right at a constant rate, which read as a sticker on a rail. Long holds are
   what make the moving parts read as decisions.
   It always ends at translate(0,0) so the speech-bubble tail lines up, and it
   drops out of the animation entirely while talking. */
@keyframes scoot{
   0%,10%{transform:translate(0,0) rotate(-1deg)}
  13%{transform:translate(-9px,-13px) rotate(5deg)}
  16%{transform:translate(-19px,0) rotate(1deg)}
  19%{transform:translate(-28px,-13px) rotate(5deg)}
  22%,44%{transform:translate(-38px,0) rotate(-1deg)}   /* stop and look round */
  47%{transform:translate(-29px,-12px) rotate(-5deg)}
  50%{transform:translate(-19px,0) rotate(-1deg)}
  53%{transform:translate(-9px,-12px) rotate(-5deg)}
  56%{transform:translate(0,0) rotate(1deg)}
  59%{transform:translate(0,-5px) rotate(0deg)}          /* a small settling hop */
  62%,100%{transform:translate(0,0) rotate(-1deg)}}
#helper.talking #helpSprite{animation:none;transform:translate(0,0) rotate(-1deg)}

#helpSprite.wave svg{animation:wave .6s ease-in-out}
@keyframes wave{0%,100%{transform:rotate(0)}25%{transform:rotate(-11deg)}70%{transform:rotate(9deg)}}
#helpSprite svg{display:block;transform-origin:50% 85%}

/* the shell breathes, so it is alive even when it is standing still */
#helpSprite .hbod{transform-box:fill-box;transform-origin:50% 100%;
  animation:breathebot 2.6s ease-in-out infinite}

/* wheels jiggle out of phase — cheap, and it reads as a machine idling */
#helpSprite .hwh{transform-box:fill-box;transform-origin:center;
  animation:jig 1.15s ease-in-out infinite}
#helpSprite .hwh2{animation-delay:.15s}

#helpSprite .hant{transform-origin:23px 12.5px;animation:nod 3.2s ease-in-out infinite}
#helpSprite .hbulb{animation:blink 2.4s ease-in-out infinite}
/* one timeline for the eyes: two blinks and a glance each way. Two separate
   animations would both be writing `transform` and the last one would win. */
#helpSprite .heye{transform-box:fill-box;transform-origin:center;
  animation:eyelife 9s ease-in-out infinite}

/* hover: he perks up — a hop, and his eyes squint into a smile */
#helper #helpSprite:hover svg{animation:hperk .5s cubic-bezier(.34,1.6,.5,1)}
#helpSprite:hover .heye{animation:none;transform:scaleY(.45)}
#helpSprite:active svg{transform:scale(.94,1.04);transition:transform .08s}
/* something new to say: the bulb glows, and now and then he hops and leans,
   calling "over here" — then stays quiet for most of the loop */
#helper.news .hbulb{animation:hglow 1.4s ease-in-out infinite}
#helper.news:not(.talking) #helpSprite svg{animation:hcall 7s ease-in-out infinite}
@keyframes hcall{0%,80%,100%{transform:none}84%{transform:translateY(-8px) rotate(-6deg)}
  88%{transform:none}91%{transform:translateY(-4px) rotate(5deg)}94%{transform:none}}

@media(prefers-reduced-motion:reduce){
  #helpSprite,#helpSprite .hbod,#helpSprite .hwh,#helpSprite .hant,#helpSprite .heye,
  #helpSprite svg,#helper.news .hbulb,.hping.new{animation:none}}

/* the hover line: a small bubble to his LEFT, tail pointing at him, so it
   never sits where the hint bubble opens (above) */
#hsay{position:absolute;right:90px;bottom:26px;width:max-content;max-width:230px;padding:9px 13px;
  border-radius:14px 14px 4px 14px;background:var(--card);border:1.5px solid var(--line);
  box-shadow:0 12px 26px -12px rgba(90,60,45,.45);font:600 12.5px/1.4 var(--sans);color:var(--ink);
  pointer-events:none;opacity:0;transform:translateX(10px) scale(.92);transform-origin:right bottom;
  transition:opacity .16s,transform .22s cubic-bezier(.34,1.5,.5,1)}
#hsay b{color:var(--terra-deep)}
#hsay::after{content:"";position:absolute;right:-7px;bottom:10px;width:11px;height:11px;
  background:var(--card);border-right:1.5px solid var(--line);border-top:1.5px solid var(--line);
  transform:rotate(45deg);border-radius:0 3px 0 0}
#hsay.on{opacity:1;transform:none}
#helper.talking #hsay{display:none}
/* he stops wandering while you talk to him, so the line stays on him */
#helpSprite:hover,#helpSprite:focus-visible{animation-play-state:paused}

/* "I have something for you" — a small badge on the sprite. The old version
   was a pulsing orange halo, which read as an error state and never stopped. */
.hping{position:absolute;right:2px;top:2px;width:19px;height:19px;border-radius:50%;
  background:var(--terracotta);border:2.5px solid var(--paper,#f4f5f7);
  pointer-events:none;transition:.25s cubic-bezier(.34,1.5,.5,1)}
.hping::after{content:"?";position:absolute;inset:0;display:flex;align-items:center;
  justify-content:center;font:800 10px/1 var(--sans);color:#fff}
.hping.new{animation:hnudge 2.6s ease-in-out infinite}
@keyframes hnudge{0%,88%,100%{transform:scale(1)}94%{transform:scale(1.18)}}

#hbub{width:290px;transition:width .25s cubic-bezier(.34,1.3,.5,1);max-width:calc(100vw - 44px);position:relative;padding:14px 16px 12px;
  background:var(--card);border:1.5px solid var(--line);border-radius:16px 16px 6px 16px;
  box-shadow:0 22px 44px -20px rgba(90,60,45,.45);
  opacity:0;transform:translateY(8px) scale(.96);transform-origin:100% 100%;
  transition:.22s cubic-bezier(.34,1.3,.5,1);pointer-events:none}
#hbub.on{opacity:1;transform:none;pointer-events:auto}
#hbub.wide{width:296px}
#helpText[hidden]{display:none}

/* ---------- the in-bubble demo (was two full-screen briefings) ---------- */
#hbdemo[hidden]{display:none}
.dtitle{font:800 13px/1.3 var(--sans);color:var(--ink);margin:9px 0 10px}
.dstage{position:relative;margin:0 auto}
.dmove .dtrack{display:flex;gap:4px;position:absolute;inset:0}
.dmove .dtrack i{width:46px;height:46px;border-radius:9px;background:var(--regolith);flex:0 0 auto}
.dturn{width:92px;height:92px;background:var(--regolith);border-radius:14px;
  box-shadow:inset 0 -3px 0 rgba(0,0,0,.07)}
.dturn #dRover{position:absolute;left:23px;top:23px}
.dmove #dRover{position:absolute;left:0;top:0}
/* A SIZE, not just a position: with no box of its own #dRover was a zero-sized
   point, so rotate() turned about its top-left corner instead of the rover's
   centre and walked the sprite outside the track. */
#dRover{width:46px;height:46px;transition:transform .3s cubic-bezier(.34,1.4,.5,1)}
/* the rover art is built at 76px; the same machine at 46px is a width */
#dCap{margin:11px 0 0;font:400 12px/1.5 var(--sans);color:var(--ink2);min-height:54px;text-wrap:pretty}
#dCap code{font:600 11px var(--mono);background:var(--well);color:var(--terra-deep);
  padding:1px 5px;border-radius:5px;white-space:nowrap}
#dCap b{color:var(--ink)}
.hbtn.hnav[hidden]{display:none}
.hbtn.hnav[disabled]{opacity:.3;pointer-events:none}
/* the repeat demo's two-line program. The body line lights up once per turn
   round the loop while BOLTZ steps — that flashing is the whole lesson, and it
   is the one thing about a loop that cannot be written down. */
.dcode{margin:10px 0 0;padding:9px 11px;border-radius:10px;background:var(--well);
  font:600 11.5px/1.7 var(--mono);color:var(--ink2);text-align:left;white-space:pre}
.dcode b{color:#8a5cc4;font-weight:700}
/* lines are highlighted exactly as the editor's #runline: a square band, edge
   to edge, with the gutter number filled (editor.css #gutter b.on) */
.dcode > i{display:block;font-style:normal;transition:background .1s}
.dcode > i.on{background:var(--terra-tint);box-shadow:inset 3px 0 0 var(--terracotta);color:var(--ink)}
/* the debug card's failing line. Red says WHERE, never "you were wrong" — the
   card's caption carries that, and the mission's story says it twice more. */
.dcode > i.bad{background:rgba(214,92,62,.16);box-shadow:inset 3px 0 0 var(--terra-deep);color:var(--ink)}
/* the ask card: the line `if` skipped — struck through, but not its indent */
.dcode > i s{text-decoration:none}
/* the piece of a line that is written wrong: pointed at, never explained */
.dcode mark{background:#ffe08a;border-radius:3px;padding:0 2px;color:inherit}
/* a character that is NOT there (the bracket that never closes): an empty slot
   where it belongs, so the eye lands on the gap rather than on what is written */
.dcode mark.gap{display:inline-block;width:.72em;height:1.05em;vertical-align:-.2em;
  padding:0;background:#ffe08a;box-shadow:inset 0 0 0 1px #d9a400}
.dcode > i.skip{opacity:.35}
.dcode > i.skip s{text-decoration:line-through}
/* A card's line numbers are the EDITOR's gutter, at the card's size: one rule
   down the side (editor.css #gutter's border-right), plain numbers that go
   solid terracotta on the running line and pale red on the stopped one
   (#gutter b.on / b.err). Same look, so a card and the editor read as one
   thing. The debug and typo cards are about naming a line, so they need it. */
.dnum{padding:9px 0;overflow-x:auto;position:relative}
.dnum::before{content:"";position:absolute;top:0;bottom:0;left:33px;width:1px;background:var(--line)}
.dnum > i{padding:1px 8px 1px 0}
.dnum u{display:inline-block;width:24px;margin-right:15px;text-align:right;
  text-decoration:none;color:var(--ink3);font-weight:400;border-radius:3px}
.dnum > i.on u{background:var(--terracotta);color:#fff;font-weight:700}
.dnum > i.bad u{background:rgba(214,92,62,.18);color:var(--terra-deep);font-weight:700}
/* The lit band starts where the CODE starts, not at the page edge: in the
   editor #runline sits beside the gutter, so its 3px edge lands right of the
   numbers. Drawn as one gradient — transparent over the gutter, the edge, then
   the band — because the number and the band are one row here. */
.dnum > i.on{background:linear-gradient(90deg,transparent 0 34px,var(--terracotta) 34px 37px,var(--terra-tint) 37px);
  box-shadow:none}
.dnum > i.bad{background:linear-gradient(90deg,transparent 0 34px,var(--terra-deep) 34px 37px,rgba(214,92,62,.16) 37px);
  box-shadow:none}
/* The lines card's meter is the SAME meter as #linebar — the parts are shared
   above, and only the box differs, because the card is 250px wide and the real
   one lives in a column. The child should recognise it on sight. */
.dstage.dmeter{display:flex;align-items:center;gap:8px;width:100%;height:auto;
  padding:8px 10px;border-radius:10px;background:var(--well)}
.dmeter.full u{background:#c9502a}
.dmeter.full b{color:#c9502a}
/* the question and if cards. The square BOLTZ asks about is outlined, and the
   answer — a Python value, so never translated — is written on it. True wears
   the rock's colour. The same badge sits on the code line that asked. */
.dtrack i .qring{position:absolute;inset:3px;border:2px dashed var(--terracotta);
  border-radius:7px;z-index:2;pointer-events:none}
.qa{font:700 10px/1 var(--mono);font-style:normal;padding:4px 6px;border-radius:999px}
.qa:empty{display:none}
.qa.yes{color:#fff;background:#c9502a}
.qa.no{color:var(--ink3);background:#fff}
.dtrack i .qa{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);z-index:3}
#dQ{margin-left:8px;vertical-align:1px}
/* the if card's scene: the program and the two places it runs, in one well.
   The code sits on white with a border, like the real editor. */
.ifscene{margin-top:4px;padding:10px;border-radius:14px;background:var(--well)}
.ifscene .dcode{margin:0;background:#fff;border:1.5px solid var(--line)}
.dlanes{display:flex;flex-direction:column;gap:8px;margin-top:10px}
.dlanes .dstage{transition:opacity .25s}
.dlanes .dstage.dim{opacity:.35}
.dmove .drov{position:absolute;left:0;top:0;width:46px;height:46px;z-index:2}
/* the cable card: the board's cable (render.js) under a rover that glides with
   the board's own timing (board.css .rover) */
.dcable{margin-top:10px}
.dcable #dRover{z-index:3;transition:transform .42s cubic-bezier(.34,1.15,.5,1)}
/* ▶ RUN on a card is the real RUN, small: same green, same riser */
.dbtn.drun{flex:0 0 auto;margin:0 auto;padding:9px 26px;
  background:var(--go);border-color:var(--go);color:#fff;font:800 12px/1 var(--sans);
  letter-spacing:.04em;box-shadow:0 3px 0 var(--go-deep)}
.dbtn.drun .g{color:#fff}
.dbtn.drun:hover{background:#25b062;border-color:#25b062;color:#fff}
.dbtn.drun:active{transform:translateY(2px);box-shadow:0 1px 0 var(--go-deep)}
/* the charge card's gauge: the CHARGE meter's cells (layout.css), card-sized */
.dbatt{display:flex;gap:4px;width:124px}
.dbatt u{height:12px;border-radius:4px}

/* ---------- the rock demo ----------
   The same five-lane track with one lane solid. Nothing else on the stage
   moves: the rock is the only dark thing on the card, and the bump is the only
   thing that happens. */
.drock .dtrack i{position:relative}
/* ---------- the drill card ----------
   The card is a rehearsal of the board, so the rock is the SAME rock the child
   has been driving round since mission 3 -- ordinary basalt, no special
   material. That is the point of the mission: the rule about rock has not
   changed, what BOLTZ can do about it has.

   The rock cracks and breaks exactly as on the board: drill.css, driven by
   drillMoment() in drillbit.js. */
.drock #dRover{z-index:2}
/* the refusal: a short knock back off the rock, never a wobble that reads as
   "try again harder" */
#dRover.dbump{animation:dbump .4s cubic-bezier(.36,.07,.19,.97)}
@media (prefers-reduced-motion:reduce){ #dRover.dbump{animation:none} }

.dbtns{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.dbtn{flex:1 0 auto;padding:9px 6px;border-radius:10px;background:var(--card);border:1.5px solid var(--line);
  font:500 10.5px/1 var(--mono);color:var(--ink);display:flex;align-items:center;
  justify-content:center;gap:5px;transition:.12s}
.dbtn:hover{border-color:var(--terracotta);color:var(--terra-deep);background:var(--terra-tint)}
.dbtn:active{transform:translateY(2px)}
.dbtn .g{font-size:13px;color:var(--terra-deep)}
/* the little tail that ties the bubble to the sprite */
#hbub::after{content:"";position:absolute;right:24px;bottom:-9px;width:16px;height:16px;
  background:var(--card);border-right:1.5px solid var(--line);border-bottom:1.5px solid var(--line);
  transform:rotate(45deg);border-radius:0 0 4px 0}
.hbwho{font:700 11px/1 var(--sans);color:var(--terracotta)}
#helpText{margin:8px 0 0;font:400 12.5px/1.55 var(--sans);color:var(--ink2);text-wrap:pretty}
#helpText b{color:var(--ink)}
#helpText code{font:600 11.5px var(--mono);background:var(--well);color:var(--terra-deep);
  padding:1px 5px;border-radius:5px;white-space:nowrap}
.hbacts{display:flex;align-items:center;gap:7px;margin-top:12px}
.hbtn{display:grid;place-items:center;width:32px;height:32px;border-radius:50%;
  flex:0 0 auto;font-size:13px;line-height:1;
  border:1.5px solid var(--line);background:var(--card);color:var(--terra-deep);transition:.14s}
.hbtn svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
.hbtn:hover{border-color:var(--terracotta);background:var(--terra-tint);transform:translateY(-1px)}
.hbtn[hidden]{display:none}
.hbmeta{margin-left:auto;font:600 12px/1 var(--sans);color:var(--ink3)}
.hbx{position:absolute;right:8px;top:6px;width:22px;height:22px;border-radius:7px;
  font:400 15px/1 var(--sans);color:var(--ink3)}
.hbx:hover{background:var(--well);color:var(--ink)}
/* stacked, the dock (42% high, screens.css) holds RUN and STOP: wait above it, at the board's corner */
@media(max-width:860px){#helper{right:12px;bottom:calc(42% + 12px)}#helpSprite{width:58px;height:58px}
  #helpSprite svg{width:100%;height:100%}   /* the drawing's own width is 71: fit the smaller box */
  .hping{width:15px;height:15px;border-width:2px}.hping::after{font-size:8.5px}}   /* the badge keeps a quarter of him */

/* an upright tablet's dock is shorter (screens.css), so he stands a little lower */
@media(min-width:601px) and (max-width:860px){#helper{bottom:calc(36% + 12px)}}

/* ---------- phones: the helper joins the top bar ----------
   There is no free corner on a phone — the board fills the width and the dock
   holds the code — so BOLTZ sits in the top row beside the language and sound
   buttons, and his bubble opens downward from there. */
@media(max-width:600px){
  #helper{top:10px;bottom:auto;right:92px;flex-direction:column-reverse}
  #helpSprite{width:36px;height:36px}
  .hping{right:0;top:0;width:12px;height:12px;border-width:1.5px}.hping::after{font-size:7.5px}
  #hbub{width:auto;max-width:calc(100vw - 110px);border-radius:16px 6px 16px 16px;transform-origin:100% 0;transform:translateY(-8px) scale(.96)}
  #hbub.on{transform:none}
  #hbub::after{top:-9px;bottom:auto;right:9px;border:0;border-left:1.5px solid var(--line);border-top:1.5px solid var(--line);border-radius:4px 0 0 0}
}
@media(max-height:500px) and (orientation:landscape){#helper{top:auto;right:12px;bottom:12px;flex-direction:column}#helpSprite{width:44px;height:44px}}
