/* ============================================================================
   THE DEVICE SYSTEM — a faithful web re-implementation of Byte's actual UI.
   ----------------------------------------------------------------------------
   This file is deliberately self-contained and app-shaped: class names, sizes,
   radii, gradients and states are transcribed from the React Native source so
   the phone on the marketing site shows the app as it really is.

   SOURCE MAP (app file -> block in this file)
     components/Background.tsx      -> .scr background wash + dot grid
     components/TopBar.tsx          -> .topbar
     components/CurrentModuleCard   -> .cmc
     components/ModulePath.tsx      -> .path, .prow, .node, .plabel
     components/BottomNav.tsx       -> .bnav
     components/question/QuestionUI -> .codeblock, .opt
     components/tour/TourChrome     -> .tour*, .segments, .say, .viz
     components/QuestionSheet.tsx   -> .q-head, .explain

   The phone shows three screens: Home, the lesson tour, and a question. The
   Profile and level-up screens are described in the page copy instead of being
   rendered, so there is less surface here that can drift out of date.

   RULES
     - No colour that is not in tokens.css (i.e. not in theme/colors.ts).
     - No UI element the app does not render.
     - Type is Funnel Display for UI and JetBrains Mono for code / numerals,
       matching App.tsx's font loading exactly.
   ========================================================================== */

/* Two overrides of the page's global `img` rule, both load-bearing.
   ----------------------------------------------------------------------------
   height: auto  - every <img> carries width and height ATTRIBUTES so the
   browser can reserve the right box before the file arrives. Those attributes
   are also presentational hints, so an element whose width we set in CSS while
   leaving height alone renders at the attribute's height. the koala came out
   110 x 384.

   max-width: none - site.css sets `img { max-width: 100% }`, which is right for
   page content and wrong here. the koala is deliberately WIDER than the 76px node
   cell he is centred in (110px, from ModulePath's mascotSize), and the global
   rule clamped him to 76px. That made him both the wrong size (as wide as the
   node instead of 1.5x it) and, because the -55px centring margin still
   assumed 110px, 17px left of the node. Inside the device, widths are the
   app's own values and must not be clamped by their containers. */
.scr img { height: auto; max-width: none; }

/* ---------------------------------------------------------------- device -- */
/* The screen is a REAL iPhone logical viewport: 390 x 844 points, the size of
   the device the reference captures came from. That is load-bearing, not
   decorative. Every measurement in this file is copied from the app in POINTS,
   so the screen box has to be 390 points wide or all of them come out at the
   wrong scale relative to it. The screen was 344px for a while and everything
   inside rendered 13% oversized: a 60pt node took 17.4% of the width instead
   of the 15.4% it takes on a real 390pt device. Do not change 390 x 844
   without rescaling every value inside.

   To fit a narrow viewport the whole device scales as a single unit.
   .device-frame reserves the SCALED box (transform alone would not shrink the
   layout box and would leave a hole under the phone). Change --s and both the
   phone and its reserved space follow. */

.device-frame {
  --s: 1;
  position: relative;
  flex: 0 0 auto;
  width: calc(410px * var(--s));
  height: calc(864px * var(--s));
}

.device {
  position: absolute;
  top: 0;
  left: 0;
  width: 410px;
  height: 864px;
  transform: scale(var(--s));
  transform-origin: top left;
  border-radius: 44px;
  padding: 10px;
  background: linear-gradient(180deg, var(--bg3), var(--bg0));
  box-shadow:
    0 50px 90px -34px rgba(0, 0, 0, 0.85),
    0 0 0 1px var(--stroke-soft);
  z-index: 2;
}

/* The screen. Background mirrors components/Background.tsx: a bg0 base, a
   periwinkle wash from the top, a fainter one rising from the bottom, and a
   22px dot grid at 0.4 opacity over all of it. */
.scr {
  position: relative;
  width: 390px;
  height: 844px;
  border-radius: 35px;
  overflow: hidden;
  background:
    linear-gradient(180deg, rgba(91, 124, 230, 0.18) 0%, rgba(91, 124, 230, 0) 60%),
    linear-gradient(180deg, rgba(91, 124, 230, 0) 60%, rgba(91, 124, 230, 0.10) 100%),
    var(--bg0);
  display: flex;
  flex-direction: column;
  font-family: var(--font-ui);
  color: var(--text-hi);
  isolation: isolate;
}

.scr::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: var(--dot-grid);
  background-size: var(--dot-grid-size);
  opacity: 0.4;
  pointer-events: none;
  z-index: 0;
}

/* Every screen's content sits above the backdrop. */
.scr > * { position: relative; z-index: 1; }

/* The top safe area (47pt on this device). The app pads its content by
   insets.top, so the screen has to reserve it or the top bar sits too high. */
.statusbar {
  flex: 0 0 47px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 32px;
  font-weight: 600;
  font-size: 15px;
  letter-spacing: -0.2px;
  color: var(--text-hi);
}

.statusbar .bars { display: flex; align-items: flex-end; gap: 2px; height: 11px; }
.statusbar .bars i { width: 3px; border-radius: 1px; background: var(--text-hi); }
.statusbar .bars i:nth-child(1) { height: 4px; }
.statusbar .bars i:nth-child(2) { height: 6px; }
.statusbar .bars i:nth-child(3) { height: 9px; }
.statusbar .bars i:nth-child(4) { height: 11px; }

.statusbar .batt {
  width: 24px;
  height: 12px;
  border-radius: 3px;
  border: 1px solid var(--text-mid);
  padding: 1.5px;
}

.statusbar .batt i { display: block; width: 70%; height: 100%; border-radius: 1px; background: var(--text-hi); }

/* The screens are stacked in one box so they can CROSS-FADE. Only one is
   visible; the rest are hidden from the accessibility tree and from hit
   testing, not merely transparent. */
.scr-body { position: relative; flex: 1; min-height: 0; }

.scr-view {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 420ms ease, visibility 0s linear 420ms;
}

.scr-view[data-active] {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 420ms ease;
}

/* ---------------------------------------------------------------- topbar -- */
/* TopBar.tsx: wordmark + XP pill on the LEFT, avatar (with the streak flame
   badge hanging off it) on the RIGHT. */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 20px 10px;
  flex: 0 0 auto;
}

.topbar-left { display: flex; align-items: center; gap: 10px; }

.wordmark {
  font-weight: 700;
  font-size: 20px;
  line-height: 22px;
  letter-spacing: 6px;
  color: var(--accent-bright);
  text-shadow: 0 0 18px var(--accent-glow);
}

.xp-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 10px 5px 8px;
  border-radius: 999px;
  background: var(--bg2);
  border: 1px solid var(--stroke-soft);
  font-weight: 600;
  font-size: 11px;
  letter-spacing: 0.3px;
  color: var(--accent-bright);
  white-space: nowrap;
}

.avatar {
  position: relative;
  width: 42px;
  height: 42px;
  border-radius: 21px;
  background: var(--bg2);
  border: 1px solid var(--stroke);
  display: grid;
  place-items: center;
}

/* The streak badge only renders when the streak is > 0, and dims to --text-low
   when the streak is alive but today has not been practised yet. */
.streak-badge {
  position: absolute;
  bottom: -3px;
  right: -4px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: var(--accent);
  border: 2px solid var(--bg0);
  display: flex;
  align-items: center;
  gap: 2px;
  font-weight: 700;
  font-size: 10px;
  letter-spacing: 0.2px;
  color: var(--bg0);
}

/* ------------------------------------------------ current module card ----- */
/* CurrentModuleCard.tsx */

.cmc {
  position: relative;
  margin: 6px 18px 22px;
  border-radius: 18px;
  border: 1px solid var(--stroke-soft);
  overflow: hidden;
  background: linear-gradient(160deg, var(--bg2), var(--bg1));
  padding: 16px 18px 18px;
  flex: 0 0 auto;
}

/* the accent glow bleeding in from the top-right corner */
.cmc::after {
  content: '';
  position: absolute;
  top: -50px;
  right: -30px;
  width: 180px;
  height: 180px;
  background: radial-gradient(circle at 50% 50%, rgba(143, 168, 255, 0.45) 0%, rgba(143, 168, 255, 0) 70%);
  pointer-events: none;
}

.cmc-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}

.cmc-eyebrow {
  font-weight: 600;
  font-size: 10.5px;
  letter-spacing: 1.6px;
  color: var(--text-low);
}

.cmc-pct { font-weight: 600; font-size: 13px; color: var(--text-mid); }

.cmc-title {
  margin-top: 4px;
  font-weight: 600;
  font-size: 26px;
  line-height: 30px;
  letter-spacing: -0.4px;
  color: var(--text-hi);
}

.cmc-bar {
  margin-top: 14px;
  height: 5px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.06);
  overflow: hidden;
}

.cmc-bar > i {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--accent-deep), var(--accent-bright));
  box-shadow: 0 0 5px var(--accent);
}

.cmc-foot {
  margin-top: 10px;
  display: flex;
  justify-content: space-between;
  font-weight: 400;
  font-size: 11px;
  color: var(--text-low);
}

/* ------------------------------------------------------------------ path -- */
/* ModulePath.tsx. States are done / current / locked. There is no "review"
   node and no "rank" node: the app has neither. */

.path-scroll {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  position: relative;
  padding-bottom: 8px;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 26px, #000 calc(100% - 112px), transparent calc(100% - 78px));
          mask-image: linear-gradient(180deg, transparent 0, #000 26px, #000 calc(100% - 112px), transparent calc(100% - 78px));
}

/* paddingTop mirrors ModulePath's mascotClearance, which is DERIVED from the
   mascot size so the koala's head can never be clipped at scroll 0:
     round(110 * 0.78 - (110 - 60) / 2) + 14 = 75
   The resting scroll position is set by js/device.js, using the same formula
   HomeScreen.recenter uses, so the current module lands where the app puts it. */
.path { position: relative; padding-top: 75px; padding-bottom: 120px; }

/* the spine: bright at the top where you have been, fading into --stroke */
.path::before {
  content: '';
  position: absolute;
  left: 50%;
  margin-left: -1px;
  top: 0;
  bottom: 0;
  width: 2px;
  opacity: 0.85;
  background: linear-gradient(
    180deg,
    transparent 0%,
    var(--accent) 4%,
    var(--accent) 48%,
    var(--accent-deep) 55%,
    var(--stroke) 65%,
    var(--stroke) 96%,
    transparent 100%
  );
}

/* Row is a 3-column grid; all three children are forced onto grid-row 1 so
   auto-placement can never push the label onto a second line. */
.prow {
  display: grid;
  grid-template-columns: 1fr 76px 1fr;
  align-items: center;
  min-height: 110px;
  position: relative;
}

.prow > * { grid-row: 1; }

.plabel { grid-column: 1; justify-self: end; text-align: right; padding-right: 18px; }
.prow.alt .plabel { grid-column: 3; justify-self: start; text-align: left; padding-right: 0; padding-left: 18px; }

.plabel b {
  display: block;
  font-weight: 600;
  font-size: 10.5px;
  letter-spacing: 1.6px;
  color: var(--text-mid);
}

.plabel span {
  display: block;
  margin-top: 3px;
  font-weight: 500;
  font-size: 17px;
  line-height: 21px;
  letter-spacing: -0.2px;
  color: var(--text-hi);
}

.plabel.off b { color: var(--text-low); }
.plabel.off span { color: var(--text-low); }

/* the current module's label is bigger and heavier */
.plabel.cur span { font-weight: 600; font-size: 22px; line-height: 26px; }

.node-cell {
  grid-column: 2;
  justify-self: center;
  position: relative;
  width: 76px;
  display: grid;
  place-items: center;
}

.node {
  position: relative;
  width: 60px;
  height: 60px;
  border-radius: 18px;
  display: grid;
  place-items: center;
  z-index: 2;
}

.node.done {
  background: linear-gradient(145deg, var(--accent-bright), var(--accent));
  box-shadow: 0 4px 14px rgba(143, 168, 255, 0.35);
}

.node.cur {
  background: linear-gradient(145deg, var(--bg2), var(--bg1));
  border: 1.5px solid var(--accent);
  box-shadow: 0 6px 22px rgba(143, 168, 255, 0.6);
}

.node.lock {
  background: linear-gradient(145deg, var(--bg1), var(--bg0));
  border: 1px solid var(--stroke);
}

/* the current node's 78px outline ring and its pulsing halo */
.node-cell.cur::before {
  content: '';
  position: absolute;
  width: 78px;
  height: 78px;
  border-radius: 22px;
  border: 1px solid var(--accent);
  opacity: 0.5;
  z-index: 1;
}

.node-cell.cur::after {
  content: '';
  position: absolute;
  width: 130px;
  height: 130px;
  border-radius: 65px;
  background: var(--accent-glow);
  opacity: 0.3;
  z-index: 0;
  animation: halo 3.6s ease-in-out infinite;
}

@keyframes halo {
  0%, 100% { transform: scale(1); opacity: 0.28; }
  50%      { transform: scale(1.08); opacity: 0.42; }
}

/* "+N": questions added to a module AFTER it was completed. Additive nudge —
   it never re-locks the module. */
.node .plus {
  position: absolute;
  top: -7px;
  right: -4px;
  min-width: 22px;
  height: 20px;
  padding: 0 6px;
  border-radius: 10px;
  background: var(--accent-bright);
  border: 1.5px solid var(--bg0);
  display: grid;
  place-items: center;
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: 11px;
  color: var(--bg0);
  z-index: 4;
}

/* The koala perches ABOVE the current node. Geometry taken straight from
   ModulePath.tsx rather than measured off a screenshot:

     mascotSize                 110          (ModulePath default)
     KoalaMascot viewBox        360 x 380    -> rendered 110 x 116.1
     MascotPerch top            -size * 0.78 = -85.8, from the node cell's top
     node box                   60

   So the image spans -85.8 to +30.3 relative to the node's top edge, i.e. its
   bottom sits 30px down the node and the koala covers the node's upper half. In
   bottom-anchored CSS that is 60 - 30.3 = 30px. */
.perch {
  position: absolute;
  bottom: 30px;
  left: 50%;
  width: 110px;
  margin-left: -55px;
  z-index: 3;
  filter: drop-shadow(0 14px 22px rgba(15, 23, 51, 0.7));
  animation: bob 2.2s ease-in-out infinite;
  pointer-events: none;
}

@keyframes bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-3px); }
}

/* -------------------------------------------------------------- bottom nav */
/* BottomNav.tsx: two tabs, a sliding pill with an accent halo behind the
   active icon. */

/* Height of the switcher: 1 border + 14 pad + 32 icon + 4 gap + 13 label
   + 34 bottom safe area. Screens inset themselves by it. */
:root { --navh: 98px; --dwell: 6000ms; }

.bnav {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  /* The bar IS --navh tall, exactly. The lesson footer and the explanation
     panel inset themselves by --navh, so if the bar's real height drifted from
     the variable (it did: an inherited 1.6 line-height on the labels), content
     tucked under the bar and looked cut off. Pinning the height and the label
     line-height makes the arithmetic true by construction. */
  height: var(--navh);
  box-sizing: border-box;
  border-top: 1px solid var(--stroke-soft);
  border-left: 1px solid var(--stroke-soft);
  border-right: 1px solid var(--stroke-soft);
  border-top-left-radius: 24px;
  border-top-right-radius: 24px;
  background: var(--bg0);
  padding: 14px 16px 34px;
  z-index: 6;
}

.bnav-row {
  display: flex;
  justify-content: space-evenly;
  align-items: flex-end;
  position: relative;
}

.bnav-tab[disabled] { cursor: default; }

.bnav-tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  position: relative;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font: inherit;
}

.bnav-icon {
  position: relative;
  width: 44px;
  height: 32px;
  display: grid;
  place-items: center;
}

.bnav-tab[data-on] .bnav-icon::before {
  content: '';
  position: absolute;
  top: -4px;
  width: 44px;
  height: 44px;
  border-radius: 22px;
  background: var(--accent-glow);
  opacity: 0.55;
}

.bnav-tab[data-on] .bnav-icon::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 10px;
  background: var(--bg2);
  border: 1px solid var(--stroke-soft);
}

.bnav-icon svg { position: relative; z-index: 1; }

.bnav-tab span {
  font-weight: 600;
  font-size: 9.5px;
  line-height: 1;
  letter-spacing: 0.4px;
  color: var(--text-low);
}

.bnav-tab[data-on] span { color: var(--accent-bright); }

/* The dwell timer. Sits on the bar's top hairline and fills left to right, so
   the screens changing by themselves reads as intentional rather than as a
   glitch. Removed from the DOM's meaning entirely once the user takes over. */
.bnav-timer {
  position: absolute;
  top: -1px;
  left: 0;
  height: 2px;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left center;
  background: linear-gradient(90deg, var(--accent-deep), var(--accent-bright));
  opacity: 0.55;
  pointer-events: none;
}

.bnav[data-auto] .bnav-timer { animation: dwell var(--dwell, 6000ms) linear forwards; }

@keyframes dwell { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* --------------------------------------------------------------- question -- */
/* QuestionSheet + question/QuestionUI.tsx */

/* TopChrome: 32px close button, an 8px module-progress bar, and the running XP
   total behind a gold bolt. */
.q-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px 12px;
  flex: 0 0 auto;
}

.q-progress {
  flex: 1;
  height: 8px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.06);
  overflow: hidden;
}

.q-progress > i {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: linear-gradient(90deg, var(--accent-deep), var(--accent-bright));
  box-shadow: 0 0 5px var(--accent);
}

.q-xp {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-weight: 600;
  font-size: 15px;
  letter-spacing: -0.3px;
  color: var(--text-hi);
}

/* Scrollable, like the app's question ScrollView. The bottom padding clears
   the docked explanation panel so the last option can still be scrolled into
   view rather than being stranded underneath it. */
.q-scroll {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-bottom: calc(376px + var(--navh));
}

.q-prompt {
  margin: 4px 22px 0;
  font-weight: 600;
  font-size: 24px;
  line-height: 28px;
  letter-spacing: -0.5px;
  color: var(--text-hi);
}

/* code block — main.py, three traffic dots, line numbers */
.codeblock {
  margin: 14px 18px 0;
  border-radius: 16px;
  border: 1px solid var(--stroke-soft);
  overflow: hidden;
  background: linear-gradient(160deg, var(--bg2), var(--bg1));
}

.code-tabs {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 14px;
  border-bottom: 1px solid var(--stroke-soft);
}

.code-dots { display: flex; gap: 5px; }
.code-dots i { width: 8px; height: 8px; border-radius: 4px; opacity: 0.7; }
.code-dots i:nth-child(1) { background: var(--bad); }
.code-dots i:nth-child(2) { background: var(--code-builtin); }
.code-dots i:nth-child(3) { background: var(--good); }

.code-file {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 10px;
  letter-spacing: 1.3px;
  color: var(--text-low);
}

.code-body { padding: 12px 14px; overflow-x: auto; }

.code-line { display: flex; gap: 12px; margin-bottom: 2px; }

.code-line > .ln {
  flex: 0 0 16px;
  text-align: right;
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: 13.5px;
  line-height: 22px;
  color: var(--text-dim);
  user-select: none;
}

.code-line > code {
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: 13.5px;
  line-height: 22px;
  white-space: pre;
  color: var(--text-hi);
}

/* the app's Python tokenizer colours */
.t-kw  { color: var(--code-keyword); }
.t-bi  { color: var(--code-builtin); }
.t-str { color: var(--good); }
.t-num { color: var(--bad); }
.t-cm  { color: var(--text-low); }
.t-pn  { color: var(--text-mid); }

/* options */
.opts { margin: 14px 18px 0; display: flex; flex-direction: column; gap: 10px; }

.opt {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px;
  border-radius: 16px;
  border: 1.5px solid var(--stroke-soft);
  background: linear-gradient(150deg, var(--bg2), var(--bg1));
  text-align: left;
}

.opt-badge {
  flex: 0 0 32px;
  width: 32px;
  height: 32px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  background: linear-gradient(150deg, var(--bg3), var(--bg2));
  border: 1px solid var(--stroke-soft);
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: 14px;
  color: var(--accent-bright);
}

.opt-text {
  flex: 1;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 15px;
  line-height: 21px;
  color: var(--text-hi);
}

.opt.correct {
  background: linear-gradient(150deg, rgba(159, 232, 194, 0.20), rgba(159, 232, 194, 0.06));
  border-color: var(--good);
  box-shadow: 0 0 12px rgba(159, 232, 194, 0.6);
}

.opt.correct .opt-badge {
  background: linear-gradient(150deg, var(--good), var(--good-deep));
  border: 0;
  color: var(--bg0);
}

.opt.incorrect {
  background: linear-gradient(150deg, rgba(255, 107, 122, 0.20), rgba(255, 107, 122, 0.06));
  border-color: var(--bad);
  box-shadow: 0 0 12px rgba(255, 107, 122, 0.6);
}

.opt.incorrect .opt-badge {
  background: linear-gradient(150deg, var(--bad), var(--bad-deep));
  border: 0;
  color: var(--bg0);
}

.opt.dimmed {
  background: linear-gradient(150deg, var(--bg1), var(--bg0));
  border-color: var(--stroke);
  opacity: 0.55;
}

.opt.dimmed .opt-badge { background: var(--bg2); color: var(--text-low); }
.opt.dimmed .opt-text { color: var(--text-low); }

/* Docked explanation panel (ExplanationPopup, docked variant). Anchored to the
   bottom of the screen with a fixed height, so a wordier module can never push
   the koala off the top or the button off the bottom: the text scrolls inside a
   fixed frame instead. The height is fixed rather than content-driven because
   .q-scroll above measures against it. */
.explain {
  position: absolute;
  left: 0;
  right: 0;
  z-index: 10;
  bottom: var(--navh);
  height: 360px;
  display: flex;
  flex-direction: column;
  border-top-left-radius: 28px;
  border-top-right-radius: 28px;
  border-top: 1px solid var(--stroke-bright);
  border-left: 1px solid var(--stroke-bright);
  border-right: 1px solid var(--stroke-bright);
  background: var(--bg2);
  box-shadow: 0 -16px 40px rgba(0, 0, 0, 0.5);
}

/* Pinned: the koala "says" Here's why in a little bubble, and it stays put while a
   long explanation scrolls beneath it. */
.explain-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 16px 22px 4px;
  flex: 0 0 auto;
}

.explain-head img { width: 56px; flex: 0 0 auto; }

.explain-head .say {
  margin-top: 0;
  align-self: flex-start;
  flex: 0 1 auto;
  border-radius: 14px;
  border-top-left-radius: 14px;
  border-color: var(--stroke-soft);
  background: var(--bg3);
  padding: 9px 14px;
  font-weight: 600;
  font-size: 18px;
  letter-spacing: -0.3px;
}

.explain-head .say::before {
  background: var(--bg3);
  border-color: var(--stroke-soft);
  top: 14px;
  left: -6px;
  width: 12px;
  height: 12px;
}

.explain-body { flex-shrink: 1; overflow-y: auto; padding: 10px 22px 8px; }

.explain-body p {
  font-weight: 400;
  font-size: 13.5px;
  line-height: 21px;
  color: var(--text-mid);
}

/* Pinned too: GOT IT is always reachable, never scrolled away. */
.explain-foot { padding: 12px 22px 20px; flex: 0 0 auto; }

/* ------------------------------------------------------------------ tour -- */
/* components/tour/* — the "Before <Topic>" lesson tour. */

/* The small square chrome button the app uses for close, back and settings. */
.icon-btn {
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  border-radius: 12px;
  background: var(--bg2);
  border: 1px solid var(--stroke-soft);
  display: grid;
  place-items: center;
}

/* TourPlayer's top row: the close button sits inline with the chapter
   segments, and there is no title. */
.tour-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px 6px;
  flex: 0 0 auto;
}

.tour-bar .icon-btn { width: 38px; height: 38px; border-radius: 13px; }

/* The segments live inline in the bar, so they take the remaining width. */
.tour-bar .segments { flex: 1; margin: 0; }

/* Everything between the bar and the footer scrolls, so a longer narration can
   never push the Next button off the bottom. */
.tour-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-bottom: 8px;
}

/* The lesson's code panel has no tab bar and no line numbers: it is the beat's
   code, not a file. */
.tour-code {
  margin: 12px 16px 0;
  border-radius: 18px;
  border: 1px solid var(--stroke-soft);
  background: linear-gradient(160deg, var(--bg2), var(--bg1));
  padding: 14px 16px;
}

.tour-code .code-line { margin-bottom: 0; }

.tour-code code {
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 24px;
  white-space: pre;
  color: var(--text-hi);
}

/* The window strip: cells the beat slides over, with start / end markers
   under the two edges of the current window. */
.win { margin: 16px 16px 0; }

.win-cells, .win-marks { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }

.wcell {
  aspect-ratio: 1;
  border-radius: 14px;
  border: 1px solid var(--stroke-soft);
  background: var(--bg1);
  display: grid;
  place-items: center;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 17px;
  color: var(--text-dim);
}

/* Inside the window: the chapter's mint tone. */
.wcell.in {
  border: 1.5px solid var(--good);
  background: rgba(159, 232, 194, 0.06);
  color: var(--text-hi);
}

.win-marks { margin-top: 5px; }

.mark {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  font-size: 9px;
  color: var(--good);
}

.mark b { font-family: var(--font-mono); font-weight: 500; font-size: 11px; color: var(--good); }

/* The value the beat carries along with the window. */
.carry {
  margin: 16px auto 0;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  align-self: center;
  padding: 10px 18px;
  border-radius: 13px;
  border: 1px solid rgba(159, 232, 194, 0.45);
  background: rgba(159, 232, 194, 0.06);
}

.carry b { font-weight: 700; font-size: 12px; letter-spacing: 1.4px; color: var(--good); }
.carry span { font-family: var(--font-mono); font-weight: 500; font-size: 17px; color: var(--text-hi); }

/* The step button, in its finished state. */
.step-btn {
  margin: 14px auto 0;
  align-self: center;
  padding: 12px 26px;
  border-radius: 13px;
  border: 1px solid rgba(159, 232, 194, 0.30);
  font-weight: 600;
  font-size: 15px;
  color: var(--good);
}

.step-btn.done { color: var(--text-dim); border-color: rgba(159, 232, 194, 0.22); }

/* The footer: a back button and the primary Next button, over a hairline. */
.tour-foot {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px calc(18px + var(--navh));
  border-top: 1px solid var(--stroke-soft);
}

.tour-foot .back { width: 52px; height: 52px; border-radius: 16px; }
.tour-foot .btn { flex: 1; }

/* One track per chapter, each filling in that chapter's own tone. */
.segments { display: flex; gap: 5px; margin: 4px 16px 0; }

.seg {
  flex: 1;
  height: 6px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.07);
  overflow: hidden;
}

.seg > i { display: block; height: 100%; border-radius: 3px; background: var(--tone, var(--accent)); }

/* the koala + speech bubble. The tail and the bubble border take the current
   chapter's tone, and the bubble's top-left corner is squared off toward the koala. */
.byte-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 8px 16px 2px;
}

/* the koala sits inside a soft dark disc; the bubble takes the chapter tone. */
.byte-wrap {
  position: relative;
  flex: 0 0 auto;
  width: 88px;
  height: 88px;
  display: grid;
  place-items: center;
}

.byte-wrap::before {
  content: '';
  position: absolute;
  width: 88px;
  height: 88px;
  border-radius: 44px;
  background: rgba(10, 16, 38, 0.55);
}

.byte-wrap img { position: relative; width: 78px; }

.say {
  position: relative;
  flex: 1;
  margin-top: 8px;
  border-radius: 16px;
  border-top-left-radius: 6px;
  border: 1px solid color-mix(in srgb, var(--tone, var(--accent)) 45%, transparent);
  background: var(--bg2);
  padding: 12px 14px;
  font-weight: 400;
  font-size: 14px;
  line-height: 21px;
  color: var(--text-hi);
}

.say::before {
  content: '';
  position: absolute;
  left: -7px;
  top: 20px;
  width: 14px;
  height: 14px;
  background: var(--bg2);
  border-left: 1px solid color-mix(in srgb, var(--tone, var(--accent)) 45%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--tone, var(--accent)) 45%, transparent);
  transform: rotate(45deg);
}

/* the little status pill under an interaction (DoChip) */
.dochip {
  display: inline-flex;
  align-items: center;
  align-self: center;
  gap: 7px;
  margin-top: 14px;
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--tone, var(--accent)) 40%, transparent);
  background: color-mix(in srgb, var(--tone, var(--accent)) 10%, var(--bg1));
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.6px;
  color: var(--tone, var(--accent));
}

/* The app's primary action button: the periwinkle gradient with dark ink. */
.btn-app {
  width: 100%;
  border-radius: 16px;
  padding: 14px 18px;
  background: linear-gradient(150deg, var(--accent-bright), var(--accent) 60%, var(--accent-deep));
  color: var(--bg0);
  box-shadow: 0 8px 18px rgba(143, 168, 255, 0.35);
}

.btn-app-sm { border-radius: 14px; padding: 12px 18px; font-size: 14px; letter-spacing: 0.3px; }

/* ----------------------------------------------------------- scrollbars -- */
/* Thin and low contrast: enough to signal that a region scrolls without
   putting a desktop chrome scrollbar inside a phone. */
.path-scroll,
.q-scroll,
.tour-body,
.explain-body,
.code-body {
  scrollbar-width: thin;
  scrollbar-color: var(--stroke-bright) transparent;
}

.path-scroll::-webkit-scrollbar,
.q-scroll::-webkit-scrollbar,
.tour-body::-webkit-scrollbar,
.explain-body::-webkit-scrollbar,
.code-body::-webkit-scrollbar { width: 6px; height: 6px; }

.path-scroll::-webkit-scrollbar-thumb,
.q-scroll::-webkit-scrollbar-thumb,
.tour-body::-webkit-scrollbar-thumb,
.explain-body::-webkit-scrollbar-thumb,
.code-body::-webkit-scrollbar-thumb {
  background: var(--stroke-bright);
  border-radius: 3px;
}

.path-scroll::-webkit-scrollbar-track,
.q-scroll::-webkit-scrollbar-track,
.tour-body::-webkit-scrollbar-track,
.explain-body::-webkit-scrollbar-track,
.code-body::-webkit-scrollbar-track { background: transparent; }

/* ------------------------------------------------------------------ touch -- */
/* On touch devices the phone's inner scroll areas must CHAIN to the page once
   they hit an edge. `contain` is right for desktop (it stops a mouse wheel over
   the phone from yanking the page around) but on a phone-sized viewport the
   device fills the screen, and contain turns it into a scroll trap: swipes move
   the module list, the list ends, and the page refuses to continue. */
@media (pointer: coarse) {
  .path-scroll,
  .q-scroll,
  .tour-body { overscroll-behavior-y: auto; }
}

/* ------------------------------------------------------------ reduced motion */

@media (prefers-reduced-motion: reduce) {
  .perch,
  .node-cell.cur::after,
  .bnav-timer { animation: none; }
  .scr-view { transition: none; }
}

/* ---------------------------------------------------------- device scaling */
/* Stepped rather than fluid: CSS cannot divide a length by a length, so a
   truly fluid scale would need JavaScript. The device needs 410px plus the
   page gutter, so each step is the largest scale that still fits.

     viewport   gutter   available   scale   used
     462px      2x26     410         1.00    410
     445px      2x26     393         0.96    394
     425px      2x18     389         0.92    377
     405px      2x18     369         0.88    361
     388px      2x18     352         0.84    344
     368px      2x16     336         0.80    328
     348px      2x14     320         0.76    312
     320px      2x14     292         0.71    291 */

@media (max-width: 462px) { .device-frame { --s: 0.96; } }
@media (max-width: 445px) { .device-frame { --s: 0.92; } }
@media (max-width: 425px) { .device-frame { --s: 0.88; } }
@media (max-width: 405px) { .device-frame { --s: 0.84; } }
@media (max-width: 388px) { .device-frame { --s: 0.80; } }
@media (max-width: 368px) { .device-frame { --s: 0.76; } }
@media (max-width: 348px) { .device-frame { --s: 0.71; } }
