/* Course page: layout, panels and widgets.
 * Desktop (> 900px): story column left, control panel right, 3D stage in between.
 * Phone (<= 900px): 3D stage on top, a scrolling sheet below, a thumb dock at the bottom.
 * css/focus.css (callouts, system picker, part card extras) loads after this file; the few
 * panel rules here that refine focus.css defaults are scoped under .panel. */

:root {
  --bg: #0a0e17;
  --bg-2: #111827;
  --ink: #e9eef6;
  --ink-2: #aab4c3;
  --ink-3: #6f7b8d;
  --line: rgba(170, 190, 220, 0.14);
  --line-2: rgba(170, 190, 220, 0.26);
  --glass: rgba(13, 19, 31, 0.66);
  --glass-2: rgba(20, 28, 43, 0.9);
  --brand: #0ea9e2;
  --brand-ink: #7fd6f6;
  --brand-soft: rgba(14, 169, 226, 0.14);
  --hot: #ff7a45;
  --cold: #5cc8ff;
  --warn: #ffc24b;
  --radius: 16px;
  --radius-sm: 10px;
  --sans: "Noto Sans TC", "PingFang HK", "PingFang TC", "Microsoft JhengHei", system-ui, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --math: "Times New Roman", "Noto Serif", Georgia, serif;
  --edge: inset 0 1px 0 rgba(255, 255, 255, 0.05);
  --shadow: 0 18px 50px -18px rgba(0, 0, 0, 0.6);
  --blur: blur(18px) saturate(1.25);
  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);

  /* Desktop type scale (the note under :root): 1px and 0 everywhere but desktop. */
  --ui: 1px;
  --ui-floor: 0px;

  /* Layout. app.js measures the rendered boxes, so these can change freely. They grow with the
     desktop type scale, so a line holds the same words whatever the text size. */
  --gutter: calc(32 * var(--ui));
  --story-w: min(calc(380 * var(--ui)), calc(100vw - 64px));
  --panel-w: calc(300 * var(--ui));
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  color-scheme: dark;
}

/* The Japanese page (js/i18n.js also links the Noto Sans JP stylesheet): Japanese glyph forms. */
html[lang="ja"] { --sans: "Noto Sans JP", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic UI", Meiryo, system-ui, sans-serif; }

/* ------------------------------------------------------------------ */
/* Desktop type scale: ONE factor sizes all desktop text.              */
/* ------------------------------------------------------------------ */
/* --ui is one "desktop pixel", written as a length. Every text size on the course page is
   calc(N * var(--ui)), N being its size at scale 1, and so are --gutter, --story-w and --panel-w,
   so a column keeps the same words per line as its text grows. Labels smaller than 12 at scale 1
   use max(var(--ui-floor), calc(N * var(--ui))): never under 12px on desktop.
   - Phones and tablets (<= 900px): --ui is 1px and --ui-floor 0 (:root above), so every size there
     is exactly the number written.
   - Desktop (>= 901px): --ui grows with the window, by width or by height, whichever gives less, so
     a short, wide window doesn't overflow the story column:
       --ui = 1px + --ui-grow * min(width - 900px, 2.3 * (height - 630px)) / 1000, between 1px and 1.4px.
     With --ui-grow 0.265 the chapter text (14.5 at scale 1) is 15px at 1024x768, 16px at 1280x800,
     16.6px at 1440x900, 18.4px at 1920x1080 and 20.3px at 2560x1440.
   To make every desktop size bigger or smaller, change --ui-grow (0 gives the old sizes; the 1.4px
   cap stops it on very big screens). The 3D view follows by itself: app.js measures the story
   column and the panel for the free area. Kept at their own size: the 3D callout labels (focus.css
   .fx-label; engine/focus.js stacks them at a fixed pitch), the chart plot height (--lc-h) and the
   part photo. */
@media (min-width: 901px) {
  :root {
    --ui-grow: 0.265;
    --ui: clamp(1px, 1px + var(--ui-grow) * min(100vw - 900px, 2.3 * (100vh - 630px)) / 1000, 1.4px);
    --ui-floor: 12px;
  }
}

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }
html, body { margin: 0; height: 100%; }
body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: calc(15 * var(--ui));
  line-height: 1.6;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  /* No text selection or long-press callout: a drag that starts on the 3D view and slides over the
     panels must keep rotating, not select copy. Form fields opt back in below. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
input, textarea, [contenteditable] { -webkit-user-select: text; user-select: text; }
img { -webkit-user-drag: none; }
button { font: inherit; color: inherit; }
::selection { background: rgba(14, 169, 226, 0.35); color: #fff; }
:focus:not(:focus-visible) { outline: none; }
:focus-visible { outline: 2px solid var(--brand-ink); outline-offset: 2px; }
summary:focus-visible { border-radius: 8px; outline-offset: 0; }
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
kbd {
  font-family: var(--mono);
  font-size: max(var(--ui-floor), calc(11 * var(--ui)));
  padding: 1px 6px;
  border: 1px solid var(--line-2);
  border-bottom-width: 2px;
  border-radius: 5px;
  color: var(--ink-2);
  background: rgba(255, 255, 255, 0.03);
}

#scene {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  display: block;
  outline: none;
  touch-action: none;
}
.veil {
  position: fixed;
  inset: 0;
  background: var(--bg);
  pointer-events: none;
  opacity: 1;
  transition: opacity 0.6s ease;
  z-index: 5;
}
.veil.off { opacity: 0; }

.ui { position: fixed; inset: 0; pointer-events: none; z-index: 10; }
/* Desktop: a soft scrim behind the story column so the title and lede stay legible when a
   chapter camera brings the machine close. Static gradient, no blur, so it costs nothing. */
.ui::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  z-index: -1;
  width: calc(var(--gutter) + var(--story-w) + 140px);
  background: linear-gradient(90deg,
    rgba(10, 14, 23, 0.84) 0,
    rgba(10, 14, 23, 0.68) calc(100% - 150px),
    rgba(10, 14, 23, 0.24) calc(100% - 70px),
    rgba(10, 14, 23, 0) 100%);
  pointer-events: none;
}
.stage-btn { display: none; }
.ui > * { pointer-events: auto; }
.dock { display: none; }

/* Shared glass surface. */
.chapter,
.panel,
.metrics,
.switcher-track {
  background: var(--glass);
  border: 1px solid var(--line);
  box-shadow: var(--edge), var(--shadow);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
}

/* ================================================================== */
/* Story column (desktop: top-left)                                    */
/* ================================================================== */

.story {
  position: absolute;
  top: 28px;
  left: var(--gutter);
  width: var(--story-w);
  max-height: calc(100vh - 96px);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* A quiet scrollbar once 深入啲 makes the column taller than the screen; the gutter is
     reserved so the column (and the 3D view offset) never shifts when it appears. */
  scrollbar-width: thin;
  scrollbar-color: rgba(170, 190, 220, 0.2) transparent;
  scrollbar-gutter: stable;
  padding-bottom: 12px;
}
/* More below (app.js toggles .more while the column can still scroll down): a fade over the
   bottom edge, so a chapter cut off on a short screen reads as "scroll for more". */
.story::after {
  content: "";
  position: sticky;
  bottom: 0;
  display: block;
  height: 44px;
  margin-top: -44px;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(10, 14, 23, 0), rgba(10, 14, 23, 0.94) 88%);
  opacity: 0;
  transition: opacity 0.2s;
}
.story.more::after { opacity: 1; }

.brand { display: flex; align-items: center; gap: 12px; margin-bottom: 24px; min-width: 0; }
/* The logo stands alone at the top left, as on the quote page (Eddie, 2026-09-27): no lesson tag
   beside or under it. The lesson's name is the title and its number is in the lesson switcher;
   the eyebrow element stays for js/app.js but isn't shown. */
.brand .eyebrow { display: none; }
@media (min-width: 901px) {
  .brand { margin-bottom: calc(24 * var(--ui)); }
}
/* The company's white logo for dark pages (Bibi and the white Fixes wordmark,
   images/fixes-logo-white.png) sits straight on the page. images/fixes-wordmark.png, the dark
   wordmark, stays for light grounds. */
.logo {
  display: inline-flex;
  flex: none;
  border-radius: 8px;
  line-height: 0;
}
.logo img { height: calc(34 * var(--ui)); width: auto; }
/* Language switch: 中文 / English / 日本語, each name in its own language (index.html). The page's
   language is lit (aria-pressed); the others reload the page in theirs. */
.lang-seg {
  flex: none;
  display: flex;
  margin-left: auto;
  height: calc(28 * var(--ui));
  padding: 2px;
  border-radius: 9px;
  border: 1px solid var(--line-2);
  background: rgba(9, 13, 21, 0.62);
  transition: border-color 0.2s;
}
.lang-seg button {
  position: relative;
  margin: 0;
  padding: 0 calc(8 * var(--ui));
  border: 0;
  border-radius: 7px;
  background: none;
  color: var(--ink-3);
  font-family: var(--sans);
  font-size: max(var(--ui-floor), calc(11.5 * var(--ui)));
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.2s, background-color 0.2s;
}
.lang-seg button[aria-pressed="true"] { background: var(--brand-soft); color: var(--brand-ink); cursor: default; }
.lang-seg button::before { content: ""; position: absolute; inset: -8px 0; } /* 44px tall hit area */
.eyebrow {
  font-family: var(--mono);
  font-size: max(var(--ui-floor), calc(11 * var(--ui)));
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--brand-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
h1 {
  font-size: calc(32 * var(--ui));
  line-height: 1.14;
  margin: 0 0 14px;
  font-weight: 500;
  letter-spacing: -0.005em;
  text-wrap: balance;
}
h1 em {
  display: block;
  font-style: normal;
  font-weight: 900;
  background: linear-gradient(90deg, #fff 0%, var(--brand-ink) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.lede { color: var(--ink-2); margin: 0 0 18px; font-size: calc(14.5 * var(--ui)); line-height: 1.7; }
/* Desktop: the lesson intro folds away after chapter 1 (app.js .past-intro) in both languages, so
   the chapter card keeps the height the bigger desktop type needs (at 1280x800 every Chinese chapter
   ran past the column with it). Phones show it at the end of the sheet instead ("About this lesson"). */
@media (min-width: 901px) { .story.past-intro .lede { display: none; } }
/* Title and intro sit straight on the 3D: a soft halo keeps them legible over busy frames. */
h1, .lede, .eyebrow, .hint { text-shadow: 0 1px 14px rgba(10, 14, 23, 0.85), 0 0 2px rgba(10, 14, 23, 0.6); }
h1 em { text-shadow: none; } /* a shadow would paint over the clipped gradient */

/* Live readouts. One glass surface; cells are split by hairlines. */
.metrics {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  margin: 0 0 14px;
  border-radius: 12px;
  overflow: hidden;
}
.metric { padding: 9px 11px 8px; min-width: 0; }
.metric + .metric { border-left: 1px solid var(--line); }
.metric dt { font-size: max(var(--ui-floor), calc(11 * var(--ui))); color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.metric dd {
  margin: 2px 0 0;
  font-family: var(--mono);
  font-size: calc(17 * var(--ui));
  line-height: 1.3;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  transition: color 0.3s;
}
.metric dd small { font-size: max(var(--ui-floor), calc(11 * var(--ui))); color: var(--ink-3); font-weight: 400; margin-left: 2px; }
.metric dd.hot { color: var(--hot); }
.metric dd.cold { color: var(--cold); }
.metric dd.warn { color: var(--warn); }

/* ---------- chapter card ---------- */
.chapter {
  border-radius: var(--radius);
  padding: calc(14 * var(--ui)) calc(18 * var(--ui)) calc(18 * var(--ui));
}
.chapter-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 32px; }
.chapter-count { font-family: var(--mono); font-size: max(var(--ui-floor), calc(11 * var(--ui))); letter-spacing: 0.12em; color: var(--ink-3); }
.chapter-nav { display: flex; gap: 6px; }
.chapter-dots { display: flex; gap: 4px; margin: 10px 0 14px; }
.chapter-dots button {
  position: relative;
  flex: 1;
  height: 4px;
  border: 0;
  padding: 0;
  border-radius: 2px;
  background: var(--line-2);
  cursor: pointer;
  transition: background 0.25s;
}
/* A taller invisible hit area; the bar stays 4px. */
.chapter-dots button::before { content: ""; position: absolute; inset: -8px 0; }
.chapter-dots button.done { background: rgba(14, 169, 226, 0.45); }
.chapter-dots button.on { background: var(--brand); box-shadow: 0 0 10px rgba(14, 169, 226, 0.5); }
.chapter-dots button:focus-visible { outline-offset: 4px; }
.chapter h2 { font-size: calc(19 * var(--ui)); line-height: 1.35; margin: 0 0 8px; font-weight: 700; letter-spacing: 0.01em; text-wrap: balance; }
#chapterBody { margin: 0; color: var(--ink-2); font-size: calc(14.5 * var(--ui)); line-height: 1.75; }
#chapterBody b { color: var(--ink); font-weight: 600; }
.chapter.swap #chapterTitle, .chapter.swap #chapterBody { animation: swap 0.45s var(--ease); }
@keyframes swap { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* ---------- 深入啲 (engine/deep.js) ---------- */
.deep { margin-top: 14px; padding-top: 5px; border-top: 1px solid var(--line); }
.deep-sum {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  list-style: none;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
}
.deep-sum::-webkit-details-marker { display: none; }
.deep-title {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: calc(13.5 * var(--ui));
  font-weight: 700;
  color: var(--brand-ink);
  white-space: nowrap;
}
.deep-title::before {
  content: "";
  width: 18px;
  height: 18px;
  border-radius: 6px;
  background:
    linear-gradient(currentColor, currentColor) center / 8px 1.5px no-repeat,
    linear-gradient(currentColor, currentColor) center / 1.5px 8px no-repeat,
    var(--brand-soft);
  transition: transform 0.25s var(--ease), background-size 0.25s;
}
.deep-fold[open] .deep-title::before {
  background:
    linear-gradient(currentColor, currentColor) center / 8px 1.5px no-repeat,
    linear-gradient(currentColor, currentColor) center / 1.5px 0 no-repeat,
    var(--brand-soft);
}
.deep-sub { flex: 1; min-width: 0; font-size: max(var(--ui-floor), calc(11.5 * var(--ui))); color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.deep-caret {
  flex: none;
  width: 7px;
  height: 7px;
  margin-right: 4px;
  border-right: 1.5px solid var(--ink-3);
  border-bottom: 1.5px solid var(--ink-3);
  transform: translateY(-2px) rotate(45deg);
  transition: transform 0.25s var(--ease);
}
.deep-fold[open] .deep-caret { transform: translateY(1px) rotate(-135deg); }
.deep-inner { padding-top: 10px; animation: swap 0.35s var(--ease); }
.deep-body p { margin: 0 0 11px; font-size: calc(13.5 * var(--ui)); line-height: 1.8; color: var(--ink-2); }
.deep-body p:last-child { margin-bottom: 0; }
.deep-body b { color: var(--ink); font-weight: 600; }
.deep-body i { font-family: var(--math); font-style: italic; font-size: 1.06em; color: var(--ink); letter-spacing: 0.01em; }
.deep-body sub, .deep-body sup { font-size: 0.72em; line-height: 0; }
/* ---------- The lesson's link to our repair site (#siteLink, under the chapter card) ---------- */
.site-link {
  display: grid;
  grid-template-columns: minmax(0, auto) auto;
  justify-content: start;
  align-items: center;
  column-gap: 8px;
  min-height: 44px;
  margin-top: 12px;
  padding-top: 2px;
  border-top: 1px solid var(--line);
  color: var(--brand-ink);
  text-decoration: none;
  font-size: calc(13 * var(--ui));
  font-weight: 500;
  line-height: 1.35;
  transition: color 0.2s;
}
/* The label, and under it the note ("On-site service in Hong Kong only"); the arrow beside both. */
.site-link-label { grid-column: 1; min-width: 0; }
.site-link-note { grid-column: 1; min-width: 0; font-size: max(var(--ui-floor), calc(11 * var(--ui))); font-weight: 400; line-height: 1.3; color: var(--ink-3); }
.site-link svg { grid-column: 2; grid-row: 1 / span 2; width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; transition: transform 0.2s var(--ease); }
/* Desktop: the link shares the 深入啲 row while both fit, so the chapter card is no taller than
   without it (the story column has almost no room left at 1280x800). In Chinese on a 380 px column
   that leaves about 7 px to spare (washer; keep the gap and the label length as they are). It
   wraps to a row of its own when they don't fit (English, columns under 380 px) and while 深入啲
   is open. Phones: a block, so 深入啲 and the link stack exactly as above. */
@media (min-width: 901px) {
  .chapter-foot { display: flex; flex-wrap: wrap; align-items: flex-start; column-gap: 10px; }
  .chapter-foot > .deep { flex: 1000 1 auto; min-width: 0; }
  .chapter-foot > .deep:has(.deep-fold[open]) { flex-basis: 100%; }
  .chapter-foot > .site-link { flex: 1 1 auto; min-width: 0; min-height: 50px; margin-top: 14px; padding-top: 5px; } /* level with .deep-sum; on a row of its own it may wrap */
}

/* ---------- buttons ---------- */
.icon-btn {
  width: 32px;
  height: 32px;
  display: inline-grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--line-2);
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.03);
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s, opacity 0.2s, transform 0.15s;
}
.icon-btn:active:not(:disabled) { transform: scale(0.95); }
.icon-btn:disabled { opacity: 0.32; cursor: default; }
.icon-btn svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.icon-btn.small { width: 26px; height: 26px; margin-left: auto; flex: none; border-radius: 8px; }
.icon-btn.small svg { width: 11px; height: 11px; }

/* ================================================================== */
/* Panel (desktop: top-right, scrolls inside)                          */
/* ================================================================== */

.panel {
  position: absolute;
  top: 28px;
  right: var(--gutter);
  width: var(--panel-w);
  max-height: calc(100vh - 110px);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgba(170, 190, 220, 0.22) transparent;
  border-radius: var(--radius);
  padding: 2px 16px 14px;
}
.panel::-webkit-scrollbar { width: 6px; }
.panel::-webkit-scrollbar-thumb { background: rgba(170, 190, 220, 0.22); border-radius: 3px; }
.panel::-webkit-scrollbar-track { background: transparent; }

/* The controls fold is a phone affordance; on wide screens it is always open (index.html). */
.ctl-fold > summary { display: none; }
.ctl-fold-sum { list-style: none; }
.ctl-fold-sum::-webkit-details-marker { display: none; }

.panel .ctl { padding: 12px 0; border-bottom: 1px solid var(--line); }
.panel .ctl:last-child { border-bottom: 0; }
.panel .ctl-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin-bottom: 8px; }
.panel .ctl-label { font-size: calc(12.5 * var(--ui)); color: var(--ink-2); min-width: 0; }
.panel .ctl-label small { color: var(--ink-3); margin-left: 6px; font-size: max(var(--ui-floor), calc(11 * var(--ui))); }
.panel .ctl-value { font-family: var(--mono); font-size: calc(12.5 * var(--ui)); color: var(--ink); font-variant-numeric: tabular-nums; white-space: nowrap; }
.panel .ctl-value:empty { display: none; }

/* seg: one-of-many (radio) */
.seg {
  display: flex;
  flex-wrap: wrap;
  background: rgba(0, 0, 0, 0.28);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 2px;
  gap: 2px;
}
.seg button {
  flex: 1 1 0;
  min-width: max-content;
  min-height: 32px;
  border: 0;
  background: transparent;
  color: var(--ink-2);
  font-size: calc(12.5 * var(--ui));
  line-height: 1.2;
  padding: 6px 8px;
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.2s, color 0.2s, box-shadow 0.2s;
}
.seg button:focus-visible { outline-offset: -2px; }
/* Long option lists (six or more, e.g. fault symptoms) form an even grid instead of ragged rows. */
.seg:not(.actions):has(> button:nth-child(6)) { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.seg:not(.actions):has(> button:nth-child(6)) button { min-width: 0; white-space: normal; }
.seg button.on {
  background: var(--brand);
  color: #04121a;
  font-weight: 700;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 4px 14px -6px rgba(14, 169, 226, 0.8);
}
.seg.warn button.on,
.seg button.tone-warn.on { background: var(--warn); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), 0 4px 14px -6px rgba(255, 194, 75, 0.7); }
/* Per-option tone (engine/controls.js): the healthy 正常 in the amber 症狀 control stays brand blue. */
.seg.warn button.tone-neutral.on { background: var(--brand); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 4px 14px -6px rgba(14, 169, 226, 0.8); }

/* seg.actions: momentary buttons (lesson.act) — separate keys, not a radio group */
.panel .seg.actions { background: none; border: 0; padding: 0; gap: 6px; }
.panel .seg.actions button {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 34px;
  padding: 7px 12px;
  border: 1px solid var(--line-2);
  border-radius: 9px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.02));
  color: var(--ink);
  font-weight: 500;
  box-shadow: var(--edge), 0 2px 6px -3px rgba(0, 0, 0, 0.6);
  transition: background 0.2s, border-color 0.2s, transform 0.12s, opacity 0.2s;
}
.panel .seg.actions button::before {
  content: "";
  flex: none;
  width: 0;
  height: 0;
  border-left: 6px solid var(--brand-ink);
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  opacity: 0.9;
}
.panel .seg.actions button:active:not(:disabled) { transform: translateY(1px) scale(0.98); }
.panel .seg.actions button:disabled { opacity: 0.38; cursor: default; }
.panel .seg.actions button:disabled::before { border-left-color: var(--ink-3); }
.panel .seg.actions button.fired { border-color: var(--brand); }
.panel .seg.warn.actions button::before { border-left-color: var(--warn); }

/* toggle: role=switch, .on */
.panel .ctl-toggle { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.panel .ctl-toggle .ctl-head { margin-bottom: 0; flex: 1; min-width: 0; }
.panel .ctl .toggle {
  position: relative;
  flex: none;
  width: 42px;
  height: 24px;
  padding: 0;
  border-radius: 999px;
  border: 1px solid var(--line-2);
  background: rgba(0, 0, 0, 0.35);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.4);
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s;
}
.panel .ctl .toggle::before { content: ""; position: absolute; inset: -10px -6px; } /* hit area */
.panel .ctl .toggle::after {
  content: "";
  position: absolute;
  left: 2px;
  top: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--ink-2);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  transition: transform 0.22s var(--ease), background 0.2s;
}
.panel .ctl .toggle.on { background: var(--brand); border-color: var(--brand); box-shadow: 0 0 0 3px rgba(14, 169, 226, 0.16); }
.panel .ctl .toggle.on::after { transform: translateX(18px); background: #fff; }
.panel .ctl-toggle .ctl-value { color: var(--ink-3); font-size: max(var(--ui-floor), calc(11.5 * var(--ui))); }

/* details.ctl-section: a collapsible group of controls */
.panel .ctl-section { border-bottom: 1px solid var(--line); }
.panel .ctl-section:last-child { border-bottom: 0; }
.panel .ctl-section > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 42px;
  padding: 10px 0;
  font-size: max(var(--ui-floor), calc(11.5 * var(--ui)));
  font-weight: 500;
  letter-spacing: 0.1em;
  color: var(--ink-3);
  transition: color 0.2s;
}
.panel .ctl-section > summary > span { flex: 1; }
.panel .ctl-section > summary > span::before {
  content: "";
  display: inline-block;
  width: 5px;
  height: 5px;
  margin: 0 8px 1px 1px;
  border-radius: 50%;
  background: currentColor;
  vertical-align: middle;
  opacity: 0.7;
}
.panel .ctl-section[open] > summary { color: var(--ink-2); }
.panel .ctl-section[open] > summary > span::before { background: var(--brand); opacity: 1; }
.panel .ctl-section-body {
  margin: 0 0 10px 3px;
  padding-left: 12px;
  border-left: 1px solid rgba(14, 169, 226, 0.28);
}
.panel .ctl-section-body > .ctl { padding: 10px 0; }
.panel .ctl-section-body > .ctl:first-child { padding-top: 2px; }

/* range */
input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 24px;
  background: transparent;
  cursor: pointer;
  margin: 0;
  display: block;
}
input[type="range"]:disabled { cursor: default; }
input[type="range"]::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--brand) var(--p, 50%), var(--line-2) var(--p, 50%));
}
input[type="range"]::-moz-range-track { height: 4px; border-radius: 2px; background: var(--line-2); }
input[type="range"]::-moz-range-progress { height: 4px; border-radius: 2px; background: var(--brand); }
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  margin-top: -7px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid var(--brand);
  box-shadow: 0 0 0 4px rgba(14, 169, 226, 0.18), 0 2px 6px rgba(0, 0, 0, 0.5);
  transition: box-shadow 0.2s;
}
input[type="range"]:active::-webkit-slider-thumb { box-shadow: 0 0 0 7px rgba(14, 169, 226, 0.22), 0 2px 6px rgba(0, 0, 0, 0.5); }
input[type="range"]::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid var(--brand);
}
input[type="range"]:focus-visible { outline-offset: 4px; border-radius: 4px; }
.range-ticks { display: flex; justify-content: space-between; font-family: var(--mono); font-size: max(var(--ui-floor), calc(10 * var(--ui))); color: var(--ink-3); margin-top: 2px; }

/* Live output tray (status + chart), desktop. A darker "readout well" at the end of the
   scrolling panel that stays pinned to the panel's bottom edge, so a change made far down a
   long list of controls shows its effect without scrolling back:
   - below 1000px of height only the status line and the chart's title row are pinned; the
     rest of the chart sits just under the bottom edge (--peek, set by the glue script in
     index.html) and slides into view when the panel is scrolled to the end;
   - on tall screens the whole tray (status + chart) is pinned;
   - while a part card is open the card takes the pinned slot and the tray scrolls normally. */
.panel-live {
  position: sticky;
  bottom: calc(-14px - var(--peek, 0px)); /* -14px also covers the panel's bottom padding */
  z-index: 1;
  margin: 0 -16px -14px;
  padding: 0 16px 14px;
}
@media (min-height: 1250px) { .panel-live { bottom: -14px; } } /* 1000px at scale 1, times the desktop type scale at 1920 wide */
.panel-live:has(> .status:not(:empty)),
.panel-live:has(> .chart:not([hidden])) {
  margin-top: 14px;
  padding-top: 12px;
  background: #0b1019; /* opaque: controls scroll underneath */
  box-shadow: 0 -1px 0 var(--line);
}
/* Controls fade out as they slide under the tray instead of being cut off. */
.panel-live:has(> .status:not(:empty))::before,
.panel-live:has(> .chart:not([hidden]))::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% + 1px);
  height: 18px;
  background: linear-gradient(180deg, rgba(11, 16, 25, 0), rgba(11, 16, 25, 0.85));
  pointer-events: none;
}
.panel:has(> .part:not([hidden])) .panel-live { position: relative; bottom: auto; }
.panel:has(> .part:not([hidden])) .panel-live::before { display: none; }
.panel-live .chart { --lc-h: 80px; }
@media (min-width: 901px) { .panel-live .lc-head { cursor: pointer; } } /* peeks; click shows the chart */
.panel-live > .status { margin-top: 0; }
.panel-live > .status:empty + .chart { margin-top: 0; }

/* status line */
.status {
  position: relative;
  margin-top: 12px;
  padding: 10px 12px 10px 15px;
  border-radius: var(--radius-sm);
  background: rgba(14, 169, 226, 0.07);
  border: 1px solid rgba(14, 169, 226, 0.2);
  font-size: calc(12.5 * var(--ui));
  color: var(--ink-2);
  line-height: 1.55;
}
.status::before {
  content: "";
  position: absolute;
  left: -1px;
  top: 10px;
  bottom: 10px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--brand);
}
.status:empty { display: none; }
.status b { color: var(--ink); }
.status small { font-size: max(var(--ui-floor), 0.9em); color: var(--ink-3); }
.status.warn { background: rgba(255, 194, 75, 0.07); border-color: rgba(255, 194, 75, 0.28); }
.status.warn::before { background: var(--warn); }
.progress { height: 3px; border-radius: 2px; background: var(--line); margin-top: 8px; overflow: hidden; }
.progress i { display: block; height: 100%; background: var(--brand); width: 0; border-radius: 2px; }

/* ---------- live chart (engine/chart.js) ---------- */
.chart { margin-top: 14px; }
.lc-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 6px; }
.lc-title { font-size: calc(12.5 * var(--ui)); color: var(--ink-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lc-live { display: inline-flex; align-items: center; gap: 5px; font-family: var(--mono); font-size: max(var(--ui-floor), calc(10 * var(--ui))); letter-spacing: 0.12em; color: var(--ink-3); flex: none; }
.lc-live i { width: 6px; height: 6px; border-radius: 50%; background: var(--brand); box-shadow: 0 0 6px var(--brand); animation: lc-blink 2s ease-in-out infinite; }
@keyframes lc-blink { 50% { opacity: 0.35; } }
/* Legend: an aligned grid (label left, live value right) that reads like a small table. */
.lc-legend {
  list-style: none;
  margin: 0 0 8px;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(calc(118 * var(--ui)), 1fr));
  gap: 2px 16px;
}
.lc-key { display: flex; align-items: center; gap: 6px; min-width: 0; min-height: 20px; font-size: max(var(--ui-floor), calc(11.5 * var(--ui))); color: var(--ink-2); white-space: nowrap; }
.lc-l { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.lc-sw { width: 12px; height: 3px; border-radius: 2px; background: var(--c); flex: none; }
.lc-band .lc-sw { width: 8px; height: 8px; border-radius: 2px; }
.lc-v { margin-left: auto; flex: none; font-family: var(--mono); font-size: calc(12 * var(--ui)); font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.lc-v small { font-size: max(var(--ui-floor), calc(10 * var(--ui))); font-weight: 400; color: var(--ink-3); margin-left: 1px; }
.lc-v.off { color: var(--ink-3); font-weight: 400; }
.lc-plot { border-radius: 8px; background: rgba(0, 0, 0, 0.24); border: 1px solid var(--line); overflow: hidden; }
.lc-plot canvas { display: block; width: 100%; }
.lc-axis { display: flex; justify-content: space-between; margin-top: 4px; font-family: var(--mono); font-size: max(var(--ui-floor), calc(10 * var(--ui))); color: var(--ink-3); }

/* ---------- part card ---------- */
.part {
  margin-top: 14px;
  padding: 13px 14px 14px;
  border-radius: 12px;
  background: var(--glass-2);
  border: 1px solid var(--line-2);
  box-shadow: var(--edge), 0 -10px 30px -12px rgba(0, 0, 0, 0.6);
  animation: swap 0.3s var(--ease);
}
.panel .part {
  /* Pinned to the bottom of the scrolling panel so a picked part is always in view; the
     negative offset docks it on the panel's bottom edge (sticky respects the padding). */
  position: sticky;
  bottom: -13px;
  z-index: 2;
  background: linear-gradient(180deg, #172032, #121a28);
}
.part-head { display: flex; align-items: center; gap: 8px; min-height: 26px; }
.part-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--brand); box-shadow: 0 0 10px var(--brand); flex: none; }
.part-head strong { font-size: calc(15 * var(--ui)); font-weight: 700; min-width: 0; }
.part p { margin: 8px 0 0; font-size: calc(13 * var(--ui)); line-height: 1.65; color: var(--ink-2); }
.part .part-fault { color: var(--warn); }
.part .part-fault:empty { display: none; }
.part .part-fault::before { content: "常見問題　"; color: var(--ink-3); }
html[lang="en"] .part .part-fault::before { content: "Common problems: "; }
html[lang="ja"] .part .part-fault::before { content: "よくある不具合："; }
.part-photo {
  margin: 10px 0 0;
  border-radius: 10px;
  overflow: hidden;
  background: #0c121d;
  border: 1px solid var(--line);
}
/* Part photos from the parts DB are mostly product shots on white: show the whole part
   (contain) on a light mat so the letterbox blends with the photo's own background. */
.part-photo img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  max-height: 150px;
  object-fit: contain;
  background: #f3f5f8;
}
.part-photo figcaption { padding: 7px 10px 8px; font-size: max(var(--ui-floor), calc(11.5 * var(--ui))); line-height: 1.45; color: var(--ink-3); }
.part-photo figcaption:empty { display: none; }

/* ================================================================== */
/* Bottom bar (desktop)                                                */
/* ================================================================== */

.hint {
  position: absolute;
  left: var(--gutter);
  bottom: 24px;
  font-size: calc(12 * var(--ui));
  color: var(--ink-3);
  pointer-events: none;
}
.hint .gx { gap: 4px 14px; font-size: inherit; color: inherit; }
.hint .gx-i::before { width: calc(16 * var(--ui)); height: calc(16 * var(--ui)); }
.hint .gx-x { white-space: nowrap; }
.switcher {
  position: absolute;
  right: var(--gutter);
  bottom: 20px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.switcher-label { font-family: var(--mono); font-size: max(var(--ui-floor), calc(10.5 * var(--ui))); letter-spacing: 0.14em; color: var(--ink-3); text-shadow: 0 1px 10px rgba(10, 14, 23, 0.9); }
.switcher-track {
  display: flex;
  gap: 4px;
  padding: 4px;
  border-radius: 999px;
}
.switcher-track a {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 36px;
  padding: 0 14px 0 7px;
  border-radius: 999px;
  color: var(--ink-2);
  text-decoration: none;
  font-size: calc(13 * var(--ui));
  white-space: nowrap;
  transition: background 0.25s, color 0.25s;
}
.switcher-track a:focus-visible { outline-offset: -2px; }
.switcher-track a .num {
  font-family: var(--mono);
  font-size: max(var(--ui-floor), calc(10.5 * var(--ui)));
  width: calc(22 * var(--ui));
  height: calc(22 * var(--ui));
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--line-2);
  transition: background 0.25s, border-color 0.25s, color 0.25s;
}
.switcher-track a.on { background: rgba(14, 169, 226, 0.16); color: var(--ink); }
.switcher-track a.on .num { background: var(--brand); border-color: var(--brand); color: #04121a; font-weight: 600; }
.switcher-track a.soon { opacity: 0.45; pointer-events: none; }

/* Hover only where there is a real pointer (no sticky hover on touch). */
@media (hover: hover) {
  .icon-btn:hover:not(:disabled) { background: var(--brand-soft); border-color: var(--brand); }
  .chapter-dots button:hover:not(.on) { background: rgba(170, 190, 220, 0.5); }
  .seg button:hover:not(.on) { color: var(--ink); background: rgba(255, 255, 255, 0.05); }
  .panel .seg.actions button:hover:not(:disabled) { border-color: var(--brand); background: var(--brand-soft); }
  .panel .ctl .toggle:hover:not(.on) { border-color: rgba(14, 169, 226, 0.6); }
  .panel .ctl-section > summary:hover { color: var(--ink); }
  .deep-sum:hover .deep-title { color: #aee6fb; }
  .deep-sum:hover .deep-caret { border-color: var(--ink-2); }
  .site-link:hover { color: #aee6fb; }
  .site-link:hover svg { transform: translate(1px, -1px); }
  .lang-seg button:hover:not([aria-pressed="true"]) { color: var(--ink); background: rgba(255, 255, 255, 0.06); }
  .switcher-track a:hover:not(.on) { color: var(--ink); background: rgba(255, 255, 255, 0.05); }
}

/* ================================================================== */
/* Overlays                                                            */
/* ================================================================== */

.callouts { position: fixed; inset: 0; pointer-events: none; z-index: 8; }

.tooltip {
  position: fixed;
  z-index: 20;
  pointer-events: none;
  font-size: calc(12.5 * var(--ui));
  padding: 4px 10px;
  border-radius: 7px;
  background: rgba(8, 12, 20, 0.92);
  border: 1px solid var(--brand);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
  transform: translate(14px, 14px);
  white-space: nowrap;
}

.keys {
  position: fixed;
  inset: 0;
  z-index: 30;
  display: grid;
  place-items: center;
  padding: 16px;
  background: rgba(4, 7, 12, 0.62);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.keys-card {
  background: var(--glass-2);
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  box-shadow: var(--edge), var(--shadow);
  padding: 20px 24px;
  min-width: min(320px, 100%);
}
.keys-card h3 { margin: 0 0 12px; font-size: calc(15 * var(--ui)); }
.keys-card dl { display: grid; grid-template-columns: auto 1fr; gap: 9px 18px; margin: 0; font-size: calc(13 * var(--ui)); color: var(--ink-2); }
.keys-card dt { white-space: nowrap; }
.keys-card dd { margin: 0; }
.noscript { position: fixed; top: 40%; width: 100%; text-align: center; }

/* ================================================================== */
/* Mid widths: narrower columns so the 3D keeps a useful middle.        */
/* ================================================================== */

/* Desktop: slightly tighter system chips so a typical set of six fits in two rows. */
@media (min-width: 901px) {
  .panel .sys-chip { padding: 4px 10px 4px 8px; gap: 5px; }
}

@media (max-width: 1180px) and (min-width: 901px) {
  /* 29vw grown by the type scale: 29vw * scale is 29vw + 300px * (scale - 1) within 0.1px here. */
  :root { --gutter: calc(24 * var(--ui)); --story-w: clamp(calc(290 * var(--ui)), calc(29vw + 300 * (var(--ui) - 1px)), calc(340 * var(--ui))); --panel-w: calc(280 * var(--ui)); }
  h1 { font-size: calc(30 * var(--ui)); }
  /* The eyebrow at the 12px floor: a little less spacing keeps 「原理課 02 · 雙門雪櫃」 whole beside
     the 40px language button of a 1024px touch screen. */
  .eyebrow { letter-spacing: 0.1em; }
}
@media (pointer: coarse) and (min-width: 901px) {
  .icon-btn { width: 40px; height: 40px; }
  .icon-btn.small { width: 36px; height: 36px; }
  .seg button { min-height: 40px; }
  .panel .seg.actions button { min-height: 40px; }
  .switcher-track a { min-height: 42px; }
  .lang-seg { height: 40px; }
}

/* ================================================================== */
/* Phone and tablet portrait: stage on top, sheet below, dock at the   */
/* bottom. app.js (isNarrow) and controls.js share the 900px break.     */
/* ================================================================== */

@media (max-width: 900px) {
  :root {
    --stage-h: 52vh;
    --stage-h: max(300px, 52svh);
    --overlap: 14px;          /* the sheet's rounded top slides over the stage */
    --dock-h: 60px;
    --hud-h: 46px;
    --pad: 16px;
  }
  :root.stage-big { --stage-h: 72vh; --stage-h: max(300px, 72svh); }
  body { font-size: 15px; }

  #scene { height: var(--stage-h); }
  .callouts { bottom: auto; height: calc(var(--stage-h) - var(--overlap)); overflow: hidden; }

  /* The sheet. */
  .ui {
    inset: auto 0 0 0;
    top: calc(var(--stage-h) - var(--overlap));
    display: flex;
    flex-direction: column;
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: none;
    padding: 12px calc(var(--pad) + var(--safe-r)) calc(var(--dock-h) + var(--safe-b) + 28px) calc(var(--pad) + var(--safe-l));
    background: var(--bg);
    border-radius: 18px 18px 0 0;
    box-shadow: 0 -1px 0 var(--line), 0 -16px 36px rgba(0, 0, 0, 0.4);
    pointer-events: auto;
  }
  .ui::-webkit-scrollbar { display: none; }
  .ui::before { display: none; }
  .story, .panel { display: contents; }
  .story::after { display: none; } /* a display:contents box still makes pseudo-elements */

  /* Sheet order: hint, title, chapter, systems, status, controls, [anything else a page adds
     to the story or panel], chart, about. */
  .panel-live { display: contents; }
  .panel-live::before { display: none !important; } /* a display:contents box still makes pseudo-elements */
  .story > *, .panel > *, .panel-live > * { order: 6; }
  .hint { order: 0; }
  .story > h1 { order: 1; }
  .story > .chapter { order: 2; }
  .panel > #sysPicker { order: 3; }
  .panel-live > .status { order: 4; margin-top: 12px; }
  .panel > .ctl-fold { order: 5; }
  /* While a simulated fault is on (app.js .fault-live), the status line (what you see, why,
     repair progress) follows the controls, right under the symptom just tapped. */
  .ui.fault-live .panel-live > .status { order: 5; }
  .panel-live > .chart { order: 7; margin-top: 16px; }
  .story > .lede { order: 8; }

  .hint {
    position: static;
    align-self: center;
    margin: 0 0 10px;
    font-size: 11px;
    letter-spacing: 0.04em;
    text-align: center;
  }
  /* No grabber bar above the hint: the sheet doesn't drag yet, and a bar that looks like a handle
     but does nothing reads as broken. The draggable sheet brings a real one. */
  .hint .gx { display: flex; justify-content: center; gap: 3px 10px; }

  /* Brand bar over the top of the stage (measured as a top inset). */
  .brand {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 11;
    margin: 0;
    padding: calc(10px + var(--safe-t)) calc(14px + var(--safe-r)) 10px calc(14px + var(--safe-l));
    background: linear-gradient(180deg, rgba(10, 14, 23, 0.72), rgba(10, 14, 23, 0));
    pointer-events: none;
  }
  /* The logo gives way first on narrow phones (it scales down, never the switch or the button):
     115 px wide at 30 px tall, smaller only when the bar runs out of room. */
  .brand .logo { position: relative; flex: 0 1 auto; min-width: 0; pointer-events: auto; border-radius: 7px; }
  .brand .logo::after { content: ""; position: absolute; inset: -8px -4px; } /* 44px hit area */
  .stage-btn {
    position: relative;
    display: inline-grid;
    place-items: center;
    flex: none;
    width: 38px;
    height: 38px;
    margin: -4px -4px -4px auto;
    padding: 0;
    border-radius: 11px;
    border: 1px solid var(--line-2);
    background: rgba(9, 13, 21, 0.62);
    color: var(--ink);
    pointer-events: auto;
    cursor: pointer;
  }
  .stage-btn::before { content: ""; position: absolute; inset: -4px; } /* 46px hit area */
  .lang-seg { height: 38px; margin: -4px 0 -4px auto; border-radius: 11px; pointer-events: auto; }
  .lang-seg button { padding: 0 9px; border-radius: 8px; font-size: 13px; color: var(--ink-2); }
  .lang-seg button::before { inset: -4px 0; } /* 46px hit area */
  .lang-seg + .stage-btn { margin-left: -4px; } /* 8px between the two */
  .stage-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
  .stage-btn .i-shrink { display: none; }
  .stage-big .stage-btn .i-grow { display: none; }
  .stage-big .stage-btn .i-shrink { display: inline; }
  .stage-big .stage-btn { background: var(--brand-soft); border-color: rgba(14, 169, 226, 0.5); color: var(--brand-ink); }
  .brand .logo img { width: 115px; max-width: 100%; height: auto; }
  /* Out of the way while the 3D view is handled (the page script sets .is-away on a touch on the
     stage and clears it on a touch, wheel or focus in the sheet, or focus in the bar): the bar
     fades and lets touches through to the stage, but stays in the tab order and for screen
     readers. Its box stays put, so the insets and the framing don't move. */
  .brand { transition: opacity 0.2s ease; }
  .brand.is-away { opacity: 0; }
  .brand.is-away > * { pointer-events: none; }
  .eyebrow { font-size: 11px; }

  /* Live readouts as a HUD along the bottom of the stage (measured as a bottom inset). */
  .metrics {
    position: fixed;
    left: calc(10px + var(--safe-l));
    right: calc(10px + var(--safe-r));
    top: calc(var(--stage-h) - var(--overlap) - 8px - var(--hud-h));
    height: var(--hud-h);
    margin: 0;
    z-index: 11;
    pointer-events: none;
    border-radius: 12px;
    background: rgba(9, 13, 21, 0.74);
    box-shadow: 0 6px 20px -8px rgba(0, 0, 0, 0.6);
    -webkit-backdrop-filter: none; /* no per-frame blur over the live canvas on phones */
    backdrop-filter: none;
  }
  .metric { padding: 5px 9px 4px; }
  .metric dt { font-size: 11px; line-height: 1.3; }
  .metric dd { font-size: 14.5px; margin-top: 1px; }
  .metric dd small { font-size: 11px; }

  h1 { font-size: 21px; line-height: 1.3; margin: 2px 0 12px; }
  h1 em { display: inline; margin-left: 0.3em; }

  /* Sections of the sheet are flat, split by hairlines. */
  .chapter {
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .chapter-head { min-height: 20px; }
  .chapter-dots { margin: 10px 0 14px; }
  .chapter-dots button::before { inset: -20px 0; } /* 44px tall hit area over a 4px bar */
  .chapter h2 { font-size: 18.5px; }
  #chapterBody { font-size: 15px; line-height: 1.8; }
  .deep-body p { font-size: 14px; }

  #sysPicker,
  .ctl-fold,
  .chart,
  .lede {
    border-top: 1px solid var(--line);
    margin-top: 18px;
  }
  #sysPicker { padding-top: 14px; border-bottom: 0; }
  .status { margin-top: 12px; }
  .chart { padding-top: 14px; margin-top: 16px; }
  .lede { padding-top: 16px; font-size: 13.5px; color: var(--ink-3); }
  .lede::before { content: "關於呢課"; display: block; margin-bottom: 4px; font-size: 11px; letter-spacing: 0.12em; color: var(--ink-3); }
  html[lang="en"] .lede::before { content: "About this lesson"; letter-spacing: 0.04em; }
  html[lang="ja"] .lede::before { content: "この講座について"; letter-spacing: 0.04em; }

  /* Controls fold. */
  .ctl-fold > summary {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 52px;
    padding: 4px 0;
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
  }
  .ctl-fold-ico { width: 18px; height: 18px; flex: none; fill: none; stroke: var(--brand-ink); stroke-width: 1.6; stroke-linecap: round; }
  .ctl-fold-title { font-size: 15px; font-weight: 700; white-space: nowrap; }
  .ctl-fold-sub { flex: 1; min-width: 0; font-size: 12px; color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .fold-caret {
    flex: none;
    width: 8px;
    height: 8px;
    margin-right: 6px;
    border-right: 1.5px solid var(--ink-2);
    border-bottom: 1.5px solid var(--ink-2);
    transform: translateY(-2px) rotate(45deg);
    transition: transform 0.25s var(--ease);
  }
  .ctl-fold[open] .fold-caret { transform: translateY(2px) rotate(-135deg); }
  .ctl-fold[open] > #controls { animation: swap 0.3s var(--ease); }

  /* Touch-size controls. */
  .panel .ctl { padding: 14px 0; }
  .panel .ctl-label { font-size: 13.5px; }
  .panel .ctl-value { font-size: 13px; }
  .seg { border-radius: 12px; padding: 3px; }
  .seg button { min-height: 44px; font-size: 13.5px; border-radius: 9px; }
  /* Long option lists (the 症狀 grid): two columns, so a symptom such as 上下層都唔夠凍 stays on
     one line instead of leaving one character on the next. */
  .seg:not(.actions):has(> button:nth-child(6)) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .panel .seg.actions button { min-height: 44px; font-size: 13.5px; }
  .panel .ctl-toggle { min-height: 44px; }
  .panel .ctl .toggle { width: 50px; height: 30px; }
  .panel .ctl .toggle::after { width: 24px; height: 24px; }
  .panel .ctl .toggle.on::after { transform: translateX(20px); }
  .panel .ctl-section > summary { min-height: 48px; font-size: 12px; }
  input[type="range"] { height: 36px; }
  input[type="range"]::-webkit-slider-thumb { width: 24px; height: 24px; margin-top: -10px; }
  .range-ticks { font-size: 11px; }
  .status { font-size: 13px; }
  /* An action's answer (app.js showActionReply), under the tapped buttons: the status line that
     says it is off screen on phones. */
  .ctl-reply {
    margin: 10px 0 0;
    padding: 8px 11px 8px 13px;
    border-left: 3px solid var(--brand);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    background: rgba(14, 169, 226, 0.08);
    font-size: 13px;
    line-height: 1.5;
    color: var(--ink-2);
    animation: swap 0.3s var(--ease);
  }
  .ctl-reply.warn { border-left-color: var(--warn); background: rgba(255, 194, 75, 0.08); }
  .ctl-reply b { color: var(--ink); }
  .lc-plot canvas { touch-action: pan-y; }
  /* Phone text floor: nothing that carries information under 11px. */
  .lc-live, .lc-axis, .lc-v small { font-size: 11px; }

  /* Part card: a bottom card over the sheet, above the dock; the stage stays in view. */
  .part,
  .panel .part {
    position: fixed;
    left: calc(10px + var(--safe-l));
    right: calc(10px + var(--safe-r));
    bottom: calc(var(--dock-h) + var(--safe-b) + 8px);
    z-index: 14;
    margin: 0;
    /* Never taller than the sheet's visible part, so it never covers the stage. */
    max-height: calc(100vh - var(--stage-h) - var(--dock-h) - var(--safe-b) - 4px);
    max-height: calc(100svh - var(--stage-h) - var(--dock-h) - var(--safe-b) - 4px);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 14px 16px 16px;
    border-radius: 16px;
    background: linear-gradient(180deg, #172032, #121a28);
    box-shadow: var(--edge), 0 -12px 40px rgba(0, 0, 0, 0.55);
  }
  .part-head { min-height: 44px; }
  .part-head strong { font-size: 16px; }
  .part p { font-size: 14px; }
  .icon-btn.small { width: 44px; height: 44px; border-radius: 12px; margin-right: -6px; }
  .icon-btn.small svg { width: 13px; height: 13px; }

  /* Thumb dock: chapter nav bottom-left, lesson switcher bottom-right. */
  .dock {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    height: calc(var(--dock-h) + var(--safe-b));
    z-index: 12;
    background: rgba(10, 14, 23, 0.88);
    border-top: 1px solid var(--line);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
  }
  .chapter-nav {
    position: fixed;
    left: calc(10px + var(--safe-l));
    bottom: calc(var(--safe-b) + (var(--dock-h) - 44px) / 2);
    z-index: 13;
    gap: 8px;
  }
  .chapter-nav .icon-btn { width: 44px; height: 44px; border-radius: 12px; }
  #nextCh {
    width: auto;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 0 12px 0 16px;
    background: var(--brand);
    border-color: var(--brand);
    color: #04121a;
    font-size: 14px;
    font-weight: 700;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 6px 18px -8px rgba(14, 169, 226, 0.9);
  }
  #nextCh::before { content: "下一章"; }
  html[lang="en"] #nextCh::before { content: "Next chapter"; }
  html[lang="ja"] #nextCh::before { content: "次の章"; }
  #nextCh svg { stroke-width: 2.2; }
  #nextCh:disabled { opacity: 1; background: transparent; border-color: var(--line-2); color: var(--ink-3); box-shadow: none; }
  #nextCh:disabled::before { content: "最後一章"; }
  html[lang="en"] #nextCh:disabled::before { content: "Final chapter"; }
  html[lang="ja"] #nextCh:disabled::before { content: "最終章"; }
  #nextCh:disabled svg { display: none; }
  #nextCh:disabled { padding-right: 16px; }

  .switcher {
    position: fixed;
    right: calc(10px + var(--safe-r));
    bottom: calc(var(--safe-b) + (var(--dock-h) - 44px) / 2);
    z-index: 13;
  }
  .switcher-label { display: none; }
  .switcher-track {
    padding: 3px;
    background: rgba(255, 255, 255, 0.04);
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .switcher-track a { min-height: 38px; padding: 0 12px 0 5px; font-size: 13.5px; }
  .switcher-track a::after { content: ""; position: absolute; inset: -4px 0; } /* 44px hit area */
  .switcher-track a.soon { display: none; }

  #partInfo, #sysPicker, #status { scroll-margin: 12px 0 calc(var(--dock-h) + 24px); }

  /* 44px touch targets for the chips focus.css draws small (systems, repair items, the
     part's system tag): taller boxes where there is room, invisible hit areas elsewhere. */
  .panel .sys-chip { position: relative; min-height: 40px; }
  .panel .sys-chip::after { content: ""; position: absolute; inset: -3px -3px; }
  /* (A hit area is placed from inside the 1px border: box + 2 × inset − 2 = 44.) */
  .panel .sys-back { position: relative; min-height: 36px; }
  .panel .sys-back::after { content: ""; position: absolute; inset: -5px -2px; }
  .panel .rep-chip { position: relative; min-height: 40px; }
  .panel .rep-chip::after { content: ""; position: absolute; inset: -3px -2px; }
  .panel .part-repairs-chips { gap: 6px; } /* neighbouring hit areas don't overlap */
  .panel .part-sys { position: relative; min-height: 28px; padding: 3px 10px; }
  .panel .part-sys::after { content: ""; position: absolute; inset: -9px -4px; }
}

/* Wider sheets (tablet portrait): keep a readable measure. */
@media (max-width: 900px) and (min-width: 600px) and (min-height: 521px) {
  .ui { padding-left: max(24px, calc((100% - 620px) / 2)); padding-right: max(24px, calc((100% - 620px) / 2)); }
  .part, .panel .part { left: 0; right: 0; width: min(560px, calc(100% - 32px)); margin: 0 auto; }
  .metrics { left: 0; right: 0; width: min(600px, calc(100% - 20px)); margin: 0 auto; }
}

/* Small phones: tighter dock. */
@media (max-width: 400px) {
  .switcher-track a { padding: 0 11px; gap: 0; }
  .switcher-track a .num { display: none; }
  .metric { padding: 6px 7px 5px; }
  .metric dd { font-size: 13.5px; }
}
/* Very narrow phones: a tighter language switch. */
@media (max-width: 339.98px) {
  .lang-seg button { padding: 0 6px; }
}

/* Phone landscape: stage left, sheet right. */
@media (max-width: 900px) and (orientation: landscape) and (max-height: 520px) {
  :root, :root.stage-big { --stage-w: 56vw; --stage-h: 100vh; --stage-h: 100svh; --overlap: 0px; --dock-h: 56px; }
  .stage-btn { display: none; }
  .lang-seg { margin-right: -4px; }
  #scene { width: var(--stage-w); height: 100vh; height: 100svh; }
  .callouts { right: auto; width: var(--stage-w); height: 100%; }
  .ui {
    top: 0;
    left: var(--stage-w);
    border-radius: 0;
    box-shadow: -1px 0 0 var(--line);
    padding-top: calc(12px + var(--safe-t));
    padding-left: 18px;
  }
  .brand { right: auto; width: var(--stage-w); }
  .metrics { right: auto; width: calc(var(--stage-w) - 20px - var(--safe-l)); top: auto; bottom: calc(10px + var(--safe-b)); }
  .dock { left: var(--stage-w); }
  .chapter-nav { left: calc(var(--stage-w) + 10px); }
  .part, .panel .part { left: calc(var(--stage-w) + 10px); right: calc(10px + var(--safe-r)); width: auto; margin: 0; max-height: calc(100vh - var(--dock-h) - 24px); max-height: calc(100svh - var(--dock-h) - 24px); }
}

/* ================================================================== */
/* English (html[lang="en"], set by js/i18n.js): labels run two to     */
/* three times longer than the Chinese, so they may wrap; no rule here */
/* matches the Chinese page, which keeps its layout.                   */
/* ================================================================== */
/* The eyebrow wraps instead of ending in "…" (never clipped). MAINTAINING.md gives META.eyebrow_en
   a budget of 32 characters, which is two lines on a 360 px phone. */
html[lang="en"] .eyebrow { white-space: normal; line-height: 1.35; letter-spacing: 0.1em; }
@media (max-width: 380px) {
  html[lang="en"] .eyebrow { letter-spacing: 0.04em; }
}
/* Segmented controls: one line per option while the row has room, wrapping inside a button only
   when one label is wider than the whole control. Long option lists use two columns. */
html[lang="en"] .seg button { flex: 1 1 auto; min-width: 0; white-space: normal; overflow-wrap: break-word; hyphens: auto; }
html[lang="en"] .seg:not(.actions):has(> button:nth-child(6)) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
html[lang="en"] .panel .ctl-head { flex-wrap: wrap; row-gap: 2px; }
html[lang="en"] .panel .ctl-section > summary { letter-spacing: 0.04em; }
html[lang="en"] .sys-hint,
html[lang="en"] .ctl-fold-sub,
html[lang="en"] .deep-sub { white-space: normal; line-height: 1.35; }
html[lang="en"] .sys-chip,
html[lang="en"] .part-sys { white-space: normal; text-align: left; }
html[lang="en"] .lc-key { white-space: normal; }
/* Chart legend: wider cells so "Evaporator" and "Compressor" stay whole beside their value (one
   column in the desktop panel, two on phones). */
html[lang="en"] .lc-legend { grid-template-columns: repeat(auto-fill, minmax(calc(136 * var(--ui)), 1fr)); }
html[lang="en"] .keys-card dd { overflow-wrap: break-word; }
/* Part card: the system tag gets its own line under the part name, so a long name keeps the
   width up to the close button (never squeezed into a column or run under the tag). */
html[lang="en"] .part-head { display: grid; grid-template-columns: auto minmax(0, 1fr); grid-auto-columns: auto; column-gap: 8px; row-gap: 5px; }
html[lang="en"] .part-head > * { grid-row: 1; }
html[lang="en"] .part-head > #partName { grid-column: 2; }
html[lang="en"] .part-head > .part-sys { grid-row: 2; grid-column: 2; justify-self: start; max-width: 100%; }
html[lang="en"] .part-head > #partClose,
html[lang="en"] .part-head > .part-note { grid-row: 1 / span 2; align-self: start; } /* the tag tucks under the name */
/* A reading that is a word (the fridge's Compressor: Running, Won't start) uses the text face, a
   little smaller (app.js marks it .word). Desktop: it may take two lines, kept within a number's
   line height so the bar doesn't grow when the word changes. Phones: one line, sized to fit. */
html[lang="en"] .metric dd.word { font-family: var(--sans); font-weight: 700; }
@media (max-width: 900px) {
  html[lang="en"] .sys-chip { white-space: nowrap; } /* phones: one swipeable row of chips */
  html[lang="en"] .metric dd.word { font-size: 12px; line-height: 1.52; letter-spacing: -0.01em; }
  html[lang="en"] h1 em { margin-left: 0; } /* the English title has its own space before <em> */
}
@media (max-width: 369.98px) {
  html[lang="en"] .metric dd.word { font-size: 11px; }
}
@media (min-width: 901px) {
  /* Desktop: English runs longer than the Chinese in the same column. A slightly smaller title and
     chapter text (the lesson intro folds away after chapter 1 in both languages, see .lede); a
     chapter still taller than the column shows the .story.more fade. */
  html[lang="en"] h1 { font-size: calc(27 * var(--ui)); }
  html[lang="en"] #chapterBody { font-size: calc(14 * var(--ui)); line-height: 1.65; }
  html[lang="en"] .metric dd.word { display: flex; align-items: center; height: calc(22 * var(--ui)); font-size: calc(12.5 * var(--ui)); line-height: 1.1; white-space: normal; }
  /* A long status line (a simulated fault) scrolls inside its own box, so the controls above the
     pinned tray stay in view. */
  html[lang="en"] .panel-live > .status { max-height: min(32vh, calc(232 * var(--ui))); overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: rgba(170, 190, 220, 0.22) transparent; }
}
@media (max-width: 1180px) and (min-width: 901px) {
  html[lang="en"] h1 { font-size: calc(26 * var(--ui)); }
  /* Readings: labels a little smaller and on two lines if need be, never ending in "…". The one
     desktop text allowed under the 12px floor: a quarter of this column is too narrow for
     "Compressor" at 12px (it fits at 10 * --ui, 10–10.8px here; 11px and up from 1181px). */
  html[lang="en"] .metric { padding-left: 5px; padding-right: 5px; }
  html[lang="en"] .metric dt { font-size: calc(10 * var(--ui)); white-space: normal; text-overflow: clip; line-height: 1.25; }
  /* Bottom row: without its "Lessons" label the switcher leaves the gesture line room to end
     before it (the nav keeps its aria-label). */
  html[lang="en"] .switcher-label { display: none; }
  html[lang="en"] .metric dd.word { font-size: calc(12 * var(--ui)); }
}
/* The dock: "Next chapter" and the lesson names side by side on narrow sheets. */
@media (max-width: 440px) {
  html[lang="en"] #nextCh { padding: 0 8px 0 12px; font-size: 13px; }
  html[lang="en"] .switcher-track a { padding: 0 9px; font-size: 13px; }
}
@media (max-width: 900px) and (orientation: landscape) and (max-height: 520px) {
  html[lang="en"] #nextCh { padding: 0 8px 0 12px; font-size: 13px; }
  html[lang="en"] .switcher-track a { padding: 0 9px; gap: 0; font-size: 13px; }
  html[lang="en"] .switcher-track a .num { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}
