/* ============================================================
   CS PLATFORM — DESIGN SYSTEM
   Ahsan Saleem · Computer Science Educator
   "Don't just learn it. Experience it."

   Theme: clean light SaaS — white surfaces, slate ink, a blue/
   teal/amber signal palette, system-native type. Matches the
   "Chapter 5" reference build's look and feel.

   ONE rule: nothing in a chapter file may declare a colour,
   a font, or a radius. Everything comes from here.
   ============================================================ */

:root{
  /* ---- surfaces ---------------------------------------- */
  --ink:#0F172A;          /* body text — slate-900 */
  --ink-2:#475569;        /* secondary text — slate-600 */
  --ink-3:#94A3B8;        /* tertiary / captions — slate-400 */
  --surface:#FFFFFF;      /* cards */
  --surface-2:#F8FAFC;    /* page — slate-50 */
  --surface-3:#F1F5F9;    /* wells, table heads — slate-100 */
  --rule:#E2E8F0;         /* hairlines — slate-200 */
  --rule-2:#CBD5E1;       /* stronger hairlines — slate-300 */
  --chrome:#0F172A;       /* dark accent panels — slate-900 */
  --chrome-2:#1E293B;     /* slate-800 */
  --chrome-line:#334155;  /* slate-700 */
  --on-chrome:#F1F5F9;
  --on-chrome-2:#94A3B8;

  /* ---- SEMANTIC SIGNAL COLOURS -------------------------
     These three never change meaning, in any chapter, ever.
     input   : data arriving  — sensors, plaintext, read, RAM in
     process : computation    — CPU, algorithms, encryption, AI
     output  : action leaving — actuators, writes, results
     ------------------------------------------------------ */
  --input:#0284C7;        --input-bg:#F0F9FF;   --input-line:#BAE6FD;
  --process:#2563EB;      --process-bg:#EFF6FF; --process-line:#BFDBFE;
  --process-glow:rgba(37,99,235,.45);
  --output:#D97706;       --output-bg:#FFFBEB;  --output-line:#FDE68A;

  /* ---- state ------------------------------------------- */
  --ok:#0F766E;    --ok-bg:#F0FDFA;   --ok-line:#5EEAD4;
  --no:#DC2626;    --no-bg:#FEF2F2;   --no-line:#FCA5A5;
  --warn:#B45309;  --warn-bg:#FFFBEB; --warn-line:#FDE68A;
  --fcs-frame:#7A1F2B;   /* Flowchart Studio builder — canvas/trace-table frame accent (burgundy, per design reference) */

  /* ---- grade tracking (A*–U) — Cambridge's own 7-point scale,
     rendered as 7 steps blended ONLY from the three semantic tokens
     above (--ok/--warn/--no), never a new literal colour, so a grade
     badge always tracks whatever this theme's ok/warn/no currently
     mean. Declared once here (not repeated in the dark-theme block
     below) because color-mix() resolves var(--ok) etc. at paint time
     against whichever theme is active — see .grade-badge/.grade-bar
     for where these are used. The top three grades lean on --ok, the
     middle grade sits at pure --warn, the bottom three lean on --no
     — the same 70%/40% thresholds
     .pill.ok/.mid/.no already use elsewhere in this file, just
     resolved into seven distinct steps instead of three buckets. */
  --grade-astar: var(--ok);
  --grade-a:     color-mix(in srgb, var(--ok) 78%, var(--warn) 22%);
  --grade-b:     color-mix(in srgb, var(--ok) 45%, var(--warn) 55%);
  --grade-c:     var(--warn);
  --grade-d:     color-mix(in srgb, var(--warn) 55%, var(--no) 45%);
  --grade-e:     color-mix(in srgb, var(--warn) 22%, var(--no) 78%);
  --grade-u:     var(--no);

  /* ---- mind map accents (8-way categorical palette) ----
     Four reuse the semantic tokens above (process/output/
     input/ok) so they already have matching dark-theme
     values for free; the other four are new hues added
     purely for visual variety across mind-map branches. */
  --mm-1:#7C3AED; --mm-1-bg:#F5F3FF; --mm-1-line:#DDD6FE;
  --mm-2:var(--process); --mm-2-bg:var(--process-bg); --mm-2-line:var(--process-line);
  --mm-3:#059669; --mm-3-bg:#ECFDF5; --mm-3-line:#A7F3D0;
  --mm-4:var(--output); --mm-4-bg:var(--output-bg); --mm-4-line:var(--output-line);
  --mm-5:#DB2777; --mm-5-bg:#FDF2F8; --mm-5-line:#FBCFE8;
  --mm-6:var(--input); --mm-6-bg:var(--input-bg); --mm-6-line:var(--input-line);
  --mm-7:var(--ok); --mm-7-bg:var(--ok-bg); --mm-7-line:var(--ok-line);
  --mm-8:#4F46E5; --mm-8-bg:#EEF2FF; --mm-8-line:#C7D2FE;

  /* ---- system-bus accents (Von Neumann whole-system diagram) —
     alias onto existing categorical hues (same trick as --mm-2/4/6/7
     above) so they already flip correctly in the dark theme for free:
     address = red, data = green, control = indigo. -------- */
  --bus-addr:var(--no); --bus-data:var(--mm-3); --bus-ctrl:var(--mm-8);
  --bus-addr-line:var(--no-line); --bus-data-line:var(--mm-3-line); --bus-ctrl-line:var(--mm-8-line);
  --bus-addr-bg:var(--no-bg); --bus-data-bg:var(--mm-3-bg); --bus-ctrl-bg:var(--mm-8-bg);

  /* ---- number-system accents (Conversion Lab) — deliberately
     reuse the existing semantic tokens rather than add new hues,
     so the three systems stay subtly, not garishly, differentiated
     and already have matching dark-theme values for free. */
  --ns-den:var(--process); --ns-den-bg:var(--process-bg); --ns-den-line:var(--process-line);
  --ns-bin:var(--input);   --ns-bin-bg:var(--input-bg);   --ns-bin-line:var(--input-line);
  --ns-hex:var(--output);  --ns-hex-bg:var(--output-bg);  --ns-hex-line:var(--output-line);

  /* ---- RGB channel accents — reuse existing tokens rather than add
     new hues (mm-3 is already a clean green with a matching dark
     value, so red/green/blue borrow no/mm-3/process respectively). */
  --rgb-r:var(--no); --rgb-g:var(--mm-3); --rgb-b:var(--process);

  /* ---- type ---------------------------------------------
     System-native stack — no webfont loaded, matches the
     reference build exactly.
     ------------------------------------------------------ */
  --f-display:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;
  --f-body:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;
  --f-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,'Liberation Mono','Courier New',monospace;
  --f-hand:'Patrick Hand',Bradley Hand,'Segoe Print','Comic Sans MS',cursive;

  --t-xs:10px; --t-sm:13.5px; --t-base:15.5px; --t-lg:17px;
  --t-xl:21px; --t-2xl:26px; --t-3xl:33px;

  /* ---- geometry ---------------------------------------- */
  --r-s:8px; --r-m:12px; --r-l:16px; --r-full:9999px;
  --shadow:0 1px 2px rgba(15,23,42,.04),0 2px 8px rgba(15,23,42,.06);
  --shadow-lift:0 4px 6px rgba(15,23,42,.05),0 12px 24px rgba(15,23,42,.08);
  --rail-w:300px;         /* widened from 268px so topic/activity titles rarely wrap */
  --rail-w-collapsed:60px; /* slim icon-only rail — see .app.rail-collapsed */
  --header-h:70px;        /* room for the larger site-brand-img + header-tagline */
  --measure:66ch;         /* max line length for prose */
}

/* ---- handwriting webfont — the ONE deliberate exception to the "no webfont loaded, system-native only" rule above, kept just as offline-safe: Patrick Hand (SIL Open Font License 1.1) embedded as a base64 data: URI woff2, not a live Google Fonts request, so a single exported dist/cs-lab.html still needs no network access at all. Used only by the handwritten free-response answer widget (.calc-paper), never as a general body/heading font. ---- */
@font-face{
  font-family:'Patrick Hand';font-style:normal;font-weight:400;font-display:swap;
  src:url(data:font/woff2;charset=utf-8;base64,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) format('woff2');
}

/* ---- dark theme: same tokens, different values --------- */
[data-theme="dark"]{
  --ink:#F1F5F9; --ink-2:#94A3B8; --ink-3:#64748B;
  --surface:#1E293B; --surface-2:#0F172A; --surface-3:#334155;
  --rule:#334155; --rule-2:#475569;
  --chrome:#0B1220; --chrome-2:#1E293B; --chrome-line:#334155;
  --input:#38BDF8; --input-bg:#0C2A3D; --input-line:#155E75;
  --process:#60A5FA; --process-bg:#152A56; --process-line:#1E3A6E;
  --process-glow:rgba(96,165,250,.55);
  --output:#FBBF24; --output-bg:#3A2A0C; --output-line:#5B4212;
  --ok:#2DD4BF; --ok-bg:#0F2C29; --ok-line:#115E59;
  --no:#F87171; --no-bg:#3A1414; --no-line:#7F1D1D;
  --warn:#FBBF24; --warn-bg:#332708; --warn-line:#5B4212;
  --fcs-frame:#C2536A;
  --mm-1:#A78BFA; --mm-1-bg:#241C3D; --mm-1-line:#4C3A7A;
  --mm-3:#34D399; --mm-3-bg:#0F2C22; --mm-3-line:#115E45;
  --mm-5:#F472B6; --mm-5-bg:#3B1530; --mm-5-line:#701A4E;
  --mm-8:#818CF8; --mm-8-bg:#1E1B4B; --mm-8-line:#3730A5;
  --shadow:0 1px 2px rgba(0,0,0,.3),0 8px 24px rgba(0,0,0,.35);
  --shadow-lift:0 4px 8px rgba(0,0,0,.35),0 16px 40px rgba(0,0,0,.45);
}

*{box-sizing:border-box}
html,body{margin:0;padding:0}
body{
  font-family:var(--f-body);font-size:var(--t-base);line-height:1.6;
  background:var(--surface-2);color:var(--ink);-webkit-font-smoothing:antialiased;
}
h1,h2,h3,h4,h5,h6{font-family:var(--f-display);margin:0;line-height:1.18;letter-spacing:-.015em;font-weight:600}
p{margin:0 0 .85em;max-width:var(--measure)}
ul,ol{max-width:var(--measure)}
button{font-family:inherit;font-size:inherit;cursor:pointer;color:inherit}
:focus-visible{outline:2.5px solid var(--process);outline-offset:2px;border-radius:4px}
::selection{background:var(--process-bg);color:var(--ink)}
svg{display:block;max-width:100%}
.mono{font-family:var(--f-mono)}
.muted{color:var(--ink-2)}
.small{font-size:var(--t-sm)}
.hide{display:none!important}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-thumb{background:var(--rule-2);border-radius:6px}
::-webkit-scrollbar-track{background:transparent}

/* ============================================================
   MOTION
   A small, reused vocabulary of entrance/feedback animations —
   never a chapter concern, applied only from shell-level markup
   and shared component classes below.
   ============================================================ */
@keyframes fadeInUp{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
@keyframes popIn{0%{opacity:0;transform:scale(.88)}65%{opacity:1;transform:scale(1.035)}100%{transform:scale(1)}}
@keyframes shimmer{from{transform:translateX(-100%)}to{transform:translateX(100%)}}
@keyframes barGrow{from{transform:scaleX(0)}to{transform:scaleX(1)}}
@keyframes dotPulse{0%,100%{box-shadow:0 0 0 0 rgba(180,83,9,.35)}50%{box-shadow:0 0 0 4px rgba(180,83,9,0)}}
@keyframes pipeGlow{0%,100%{box-shadow:0 0 0 0 rgba(37,99,235,.4)}50%{box-shadow:0 0 0 5px rgba(37,99,235,0)}}
@keyframes sdPop{0%{transform:scale(.7)}60%{transform:scale(1.4)}100%{transform:scale(1.25)}}
@keyframes railGlowIn{
  0%{opacity:.5;box-shadow:0 0 0 1px var(--process-line) inset,0 0 0 0 var(--process-glow)}
  100%{opacity:1;box-shadow:0 0 0 1px var(--process-line) inset,0 0 14px 1px var(--process-glow)}
}
@keyframes railBlink{0%,100%{box-shadow:0 0 0 0 var(--process-glow);opacity:1}50%{box-shadow:0 0 0 5px transparent;opacity:.55}}

.stage-inner{animation:fadeInUp .32s cubic-bezier(.16,1,.3,1) both}
/* In-place re-renders of the SAME step (the core/shell.js renderStep() fast
   path used by every state-change re-render in interactive components like
   Flowchart Studio, the trace table, MCQs…) get the .rerender marker and must
   NOT replay the entrance animation — a fresh .stage-inner is created and
   would otherwise fade in from opacity 0 on every single click/keystroke,
   which reads as the whole page "blinking". Only a genuine navigation to a
   different step replays the entrance. */
.stage-inner.rerender{animation:none}
/* opacity-only for anything that might also be a hoverable/pressable card or
   button — a transform-based entrance would otherwise win the cascade over
   a plain :hover/:active transform declared below and silently block it */
.grid>*,.stage-inner>.card{animation:fadeIn .3s ease both}
.stage-inner>.defn,.stage-inner>.note,.stage-inner>.q{animation:fadeInUp .38s cubic-bezier(.16,1,.3,1) both}
.grid>*:nth-child(1),.stage-inner>.card:nth-child(1),.stage-inner>.defn:nth-child(1),.stage-inner>.note:nth-child(1),.stage-inner>.q:nth-child(1){animation-delay:.01s}
.grid>*:nth-child(2),.stage-inner>.card:nth-child(2),.stage-inner>.defn:nth-child(2),.stage-inner>.note:nth-child(2),.stage-inner>.q:nth-child(2){animation-delay:.05s}
.grid>*:nth-child(3),.stage-inner>.card:nth-child(3),.stage-inner>.defn:nth-child(3),.stage-inner>.note:nth-child(3),.stage-inner>.q:nth-child(3){animation-delay:.09s}
.grid>*:nth-child(4),.stage-inner>.card:nth-child(4),.stage-inner>.defn:nth-child(4),.stage-inner>.note:nth-child(4),.stage-inner>.q:nth-child(4){animation-delay:.13s}
.grid>*:nth-child(5),.stage-inner>.card:nth-child(5),.stage-inner>.defn:nth-child(5),.stage-inner>.note:nth-child(5),.stage-inner>.q:nth-child(5){animation-delay:.17s}
.grid>*:nth-child(6),.stage-inner>.card:nth-child(6),.stage-inner>.defn:nth-child(6),.stage-inner>.note:nth-child(6),.stage-inner>.q:nth-child(6){animation-delay:.21s}
.grid>*:nth-child(7),.stage-inner>.card:nth-child(7),.stage-inner>.defn:nth-child(7),.stage-inner>.note:nth-child(7),.stage-inner>.q:nth-child(7){animation-delay:.25s}
.grid>*:nth-child(8),.stage-inner>.card:nth-child(8),.stage-inner>.defn:nth-child(8),.stage-inner>.note:nth-child(8),.stage-inner>.q:nth-child(8){animation-delay:.29s}
.grid>*:nth-child(n+9),.stage-inner>.card:nth-child(n+9),.stage-inner>.defn:nth-child(n+9),.stage-inner>.note:nth-child(n+9),.stage-inner>.q:nth-child(n+9){animation-delay:.32s}

/* decorative glow behind the course-home hero */
.hero{position:relative}
.hero::before{
  content:'';position:absolute;top:-50px;left:-70px;width:440px;height:440px;z-index:-1;
  background:radial-gradient(circle,var(--process-bg) 0%,transparent 72%);
  opacity:.9;pointer-events:none
}

/* ============================================================
   SHELL
   ------------------------------------------------------------
   The chrome scaffold (header bar, rail drawer, footer utility bar,
   main column) below now leans on Bootstrap for its flex/spacing/
   overflow layout — self-hosted, vendor/bootstrap/css/bootstrap-
   utilities.min.css (see index.html) — instead of the hand-rolled
   flex CSS this file used to carry for those properties.

   Only the UTILITIES-only Bootstrap build is loaded here, never the
   full bootstrap.min.css used by the admin CMS: this platform's own
   .btn/.card/.badge/.grid/.stat/.pill/.bar/etc. vocabulary is shared
   identically between this shell AND every one of the hundreds of
   interactive step/quiz/simulation widgets across all 6 chapters
   (verified by grep before touching anything) — loading real
   Bootstrap component CSS would silently reskin every one of them.
   The utilities build has zero class-name overlap with this file
   (verified the same way), so it only ever adds display/flex/
   spacing/overflow declarations — nothing here that could collide.

   Bootstrap's utility classes all carry !important by design. Two
   existing mobile overrides below touch the exact same property a
   utility class now sets on the same element (the header bar's gap,
   and the footer bar's align-items) — those two lines now carry
   !important too, so the responsive behavior is unchanged; every
   other property below still cascades normally. ---------------- */

/* ---- global header: one continuous full-width bar ----------
   Sits ABOVE the rail+main grid (not inside it), so the logo on the
   far left and the login/account control on the far right always
   belong to the same component, at the same height, regardless of
   route. Branding + login live here; the rail below is dedicated
   purely to learning navigation (see .rail below). --------------- */
.app-header{
  height:var(--header-h);background:var(--surface);border-bottom:1px solid var(--rule);
  box-shadow:var(--shadow);position:sticky;top:0;z-index:45
}
.app-header-inner{height:100%;padding:0 24px;min-width:0}
.app-header-inner>*{flex-shrink:0}
.app-header-inner .header-brand{min-width:0;overflow:hidden}
.header-brand{text-decoration:none;color:inherit}
.header-spacer{flex:1}
.header-acct{gap:10px}
.header-acct-name{font-family:var(--f-display);font-weight:700;font-size:13px;color:var(--ink);
  max-width:140px}

/* Real logo asset (core/shell.js's LOGO_MAIN / LOGO_MAIN_DARK), not CSS-drawn
   text — swapped per theme with plain CSS, since this app's theme toggle sets
   data-theme on <html> rather than following prefers-color-scheme. Sized to
   fill the taller --header-h below, so the mark actually reads as the
   platform's primary identity rather than a small afterthought in the corner. */
.site-brand-img{height:42px;width:auto;max-width:160px;display:block}
.site-brand-img.dark{display:none}
[data-theme="dark"] .site-brand-img.light{display:none}
[data-theme="dark"] .site-brand-img.dark{display:block}

/* Course identity line, next to the logo — the same touch landing.html's
   nav gives the brand, so the in-app header reads as more than a bare
   wordmark floating in an otherwise empty bar. Hidden once the header gets
   tight (mobile / rail toggle button appears) rather than wrapping. */
.header-tagline{
  font-family:var(--f-mono);font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;
  color:var(--ink-3);border-left:1px solid var(--rule);padding-left:14px;white-space:nowrap
}
/* "Visualize Interactive Education World" is noticeably longer than the
   old "Cambridge Computer Science" line, so it needs to drop out sooner
   than the previous 640px threshold did — below ~750px (measured with
   the hamburger button, brand mark and login control all present) the
   header overflows horizontally rather than wrapping, since .app-header-
   inner is a nowrap flex row. 780px keeps a safety margin above that. */
@media (max-width:780px){ .header-tagline{display:none} }
/* Below ~400px the header row (hamburger + logo + spacer + fullscreen +
   Log in/out) no longer fits even with the logo already clamped to
   160px above — min-width:0/overflow:hidden on .header-brand only lets
   the LOGO clip, it can't invent extra width from nothing. The ⛶
   full-screen toggle is the one header control genuinely optional on a
   phone (CS.shell.toggleFullscreen() has no other entry point, but a
   320-375px screen gets little from "full screen" anyway), so it's the
   one dropped — freeing enough width that the logo only needs a modest
   further clamp rather than a jarring one. */
@media (max-width:400px){
  .site-fs-btn{display:none}
  .site-brand-img{max-width:104px}
}

/* ---- global footer: secondary utilities ---------------------
   Progress / theme / reset / "powered by" branding — the controls
   the rail used to carry. Deliberately plain document flow (not
   sticky) so it never competes with the sticky in-lesson Back/
   Continue bar (.footnav) for the bottom of the viewport; it reads
   as ordinary page-footer chrome, reached by scrolling. ---------- */
.app-footer{background:var(--chrome);border-top:1px solid var(--chrome-line);padding:16px 22px}
.app-footer-inner{align-items:center;gap:26px}
.footer-progress{gap:10px;flex:1;min-width:180px}
.footer-progress-label{font-family:var(--f-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--on-chrome-2);white-space:nowrap}
.footer-progress-bar{flex:1;max-width:200px}
.footer-progress-pct{font-family:var(--f-mono);font-size:12px;font-weight:700;color:#fff;min-width:30px;text-align:right}
.footer-btn{background:var(--chrome-2);border:1px solid var(--chrome-line);color:var(--on-chrome);
  padding:7px 13px;border-radius:var(--r-s);font-size:12.5px;cursor:pointer;white-space:nowrap;transition:background .15s ease,border-color .15s ease}
.footer-btn:hover{background:#243149}
/* Reset is a destructive action: coloured (not sized) to stand apart,
   so it reads as "be careful" without being the biggest, most
   clickable thing in the bar. */
.footer-btn.danger{color:#FCA5A5;border-color:rgba(220,38,38,.45)}
.footer-btn.danger:hover{background:rgba(220,38,38,.15);border-color:var(--no)}
.footer-brand{margin-left:auto}
.app-footer .pb-label{color:var(--on-chrome-2)}

.powered-by{display:flex;align-items:center;gap:8px}
.pb-label{font-family:var(--f-mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);white-space:nowrap}
.pb-chip{display:inline-flex;align-items:center;background:#fff;border:1px solid var(--rule);border-radius:var(--r-s);padding:3px 7px}
.pb-logo{height:22px;width:auto;display:block}

.app{display:grid;grid-template-columns:var(--rail-w) 1fr;min-height:calc(100vh - var(--header-h));transition:grid-template-columns .2s ease}
/* dvh fallback: on mobile Safari/Chrome 100vh is measured against the
   viewport with the address bar collapsed, so it overshoots the actual
   visible area while the bar is showing. Browsers that don't know dvh
   simply ignore this declaration and keep the vh value above — this is
   a progressive-enhancement override, not a replacement. */
@supports (height: 100dvh){ .app{min-height:calc(100dvh - var(--header-h))} }

.rail{
  background:var(--surface);color:var(--ink);border-right:1px solid var(--rule);
  position:sticky;top:var(--header-h);height:calc(100vh - var(--header-h));overflow:hidden
}
@supports (height: 100dvh){ .rail{height:calc(100dvh - var(--header-h))} }
/* The rail is ONLY learning navigation now (chapter → topic →
   subtopic → activity) — branding, login, theme and progress/reset
   all moved out to the header/footer above, so this never has to
   duplicate or fall out of sync with that state. */
.rail-scroll{padding:14px 10px 20px}

.rail-toolbar{display:flex;justify-content:flex-end;margin-bottom:6px}
.rail-collapse-btn{
  width:26px;height:26px;flex:0 0 auto;border-radius:var(--r-m);border:1px solid var(--rule);
  background:var(--surface);color:var(--ink-2);display:flex;align-items:center;justify-content:center;
  font-size:12px;line-height:1;cursor:pointer;transition:color .15s ease,background .15s ease
}
.rail-collapse-btn:hover{color:var(--process);background:var(--surface-3)}

/* ---- sidebar collapse (icon rail) -----------------------------
   Desktop alternative to the mobile hide/show behaviour further
   below: CS.shell.toggleRailCollapse() flips this class on .app,
   which just narrows the grid track and hides the TEXT parts of the
   rail's existing markup — the rail's HTML itself never changes
   between the two states, only what's visible. ------------------ */
.app.rail-collapsed{grid-template-columns:var(--rail-w-collapsed) 1fr}
.app.rail-collapsed .rail-toolbar{justify-content:center}
.app.rail-collapsed .paper-group,
.app.rail-collapsed .ch-chevron,
.app.rail-collapsed .ch-subnav{display:none}
.app.rail-collapsed .paper-list{display:block!important;padding-top:4px}
.app.rail-collapsed .ch-item{justify-content:center;padding:11px 0;gap:0}
.app.rail-collapsed .ch-item>span:not(.ch-num){display:none}
.app.rail-collapsed .ch-num{flex:0 0 auto;font-size:13px}
@media(max-width:860px){
  /* Mobile already collapses via toggleRail()'s full overlay — this
     desktop-only slim-rail control would just be a second, conflicting
     way to do the same job at that width. */
  .rail-collapse-btn{display:none}
}

.eyebrow{
  font-family:var(--f-mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink-3);display:block
}
.rail .eyebrow{color:var(--ink-3);padding:14px 10px 6px}

.paper-group{
  display:flex;align-items:center;gap:9px;width:100%;text-align:left;background:none;border:0;
  cursor:pointer;padding:16px 10px 12px;margin-top:2px;border-bottom:1px solid var(--rule);
  font-family:var(--f-display);font-size:14.5px;font-weight:700;letter-spacing:0;
  color:var(--ink);transition:color .15s ease
}
.paper-group:hover{color:var(--process)}
.pg-chev{display:inline-block;font-size:11px;color:var(--ink-3);transition:transform .2s cubic-bezier(.16,1,.3,1);flex:0 0 auto}
.paper-group.open .pg-chev{transform:rotate(90deg)}
.pg-label{flex:1}
.pg-count{font-family:var(--f-mono);font-size:10.5px;font-weight:600;color:var(--process);background:var(--process-bg);border-radius:var(--r-full);padding:2px 8px}
.paper-list{overflow:hidden;padding-top:8px}
.paper-list.closed{display:none}
.paper-list.open{display:block;animation:fadeIn .25s ease both}

/* CHAPTER level — the strongest, largest rail item. .ch-row pairs the
   chapter button itself (navigates) with its own independent .ch-chevron
   accordion toggle (expands/collapses just this chapter's topic list,
   see CS.shell.toggleChapterOpen — no navigation, and any number of
   chapters can be open at once). They're siblings, not nested, since a
   <button> can't contain another button. */
.ch-row{display:flex;align-items:stretch;gap:2px;margin-bottom:3px}
.ch-item{
  display:flex;align-items:center;gap:11px;width:100%;text-align:left;background:none;border:0;
  color:var(--ink-2);padding:11px 12px;margin-bottom:3px;border-radius:var(--r-m);font-size:14.5px;font-weight:600;
  transition:background .15s ease,color .15s ease
}
.ch-row .ch-item{flex:1;min-width:0;margin-bottom:0} /* .ch-row itself already carries the spacing */
.ch-item:hover{background:var(--surface-3);color:var(--ink)}
/* Active state reads as a glow, not a solid fill — a tinted surface plus a
   soft outer light in the process colour, so the current item lifts off the
   rail instead of sitting in a flat block. box-shadow (not filter/outline)
   keeps this cheap to animate and safe under prefers-reduced-motion, since
   the entrance is the only motion — the glow itself is static once settled. */
.ch-item.on{
  background:var(--process-bg);color:var(--process);font-weight:700;
  box-shadow:0 0 0 1px var(--process-line) inset,0 0 14px 1px var(--process-glow);
  animation:railGlowIn .35s cubic-bezier(.34,1.56,.64,1) both
}
.ch-num{font-family:var(--f-mono);font-size:11.5px;color:var(--ink-3);flex:0 0 22px;transition:color .15s ease}
.ch-item.on .ch-num{color:var(--process)}
/* One-time attention-getter on "Pseudocode Lab" until a student opens it
   for the first time (PR.labVisited() — core/progress.js/shell.js). A
   small pulsing dot rather than blinking the whole button: enough to
   catch the eye in peripheral vision without being the kind of flashing
   the rest of this platform deliberately avoids. Respects reduced-motion
   by just sitting solid instead of animating. */
.rail-blink-dot{
  flex:0 0 auto;width:7px;height:7px;border-radius:50%;background:var(--process);
  animation:railBlink 1.6s ease-in-out infinite
}
@media (prefers-reduced-motion: reduce){ .rail-blink-dot{animation:none} }
.ch-chevron{
  flex:0 0 28px;display:flex;align-items:center;justify-content:center;background:none;border:0;
  border-radius:var(--r-m);color:var(--ink-3);font-size:11px;cursor:pointer;
  transition:background .15s ease,color .15s ease,transform .2s cubic-bezier(.16,1,.3,1)
}
.ch-chevron:hover{background:var(--surface-3);color:var(--ink)}
.ch-chevron.open{transform:rotate(90deg)}
.ch-subnav{padding-top:1px;padding-bottom:3px;animation:fadeIn .2s ease both}
/* TOPIC / subtopic / activity level — readable, with a left accent
   bar on the active item so state never depends on colour alone. */
.tp-item{
  display:flex;align-items:center;gap:9px;width:100%;text-align:left;background:none;border:0;
  border-left:3px solid transparent;color:var(--ink-2);padding:9px 11px 9px 30px;margin-bottom:1px;
  border-radius:var(--r-s);font-size:13.5px;line-height:1.35;
  transition:background .15s ease,color .15s ease,padding-left .15s ease,border-color .15s ease
}
.tp-item:hover{background:var(--surface-3);color:var(--ink);padding-left:33px}
.tp-item.on{color:var(--process);background:var(--process-bg);font-weight:700;border-left-color:var(--process)}
.tp-ref{font-family:var(--f-mono);font-size:10.5px;color:var(--ink-3);margin-left:auto;flex:0 0 auto}

.dot{width:8px;height:8px;border-radius:var(--r-full);border:1.5px solid var(--rule-2);flex:0 0 auto;transition:all .2s ease}
.dot.part{border-color:var(--warn);background:linear-gradient(90deg,var(--warn) 50%,transparent 50%);animation:dotPulse 1.8s ease-in-out infinite}
.dot.done{background:var(--ok);border-color:var(--ok);animation:popIn .3s cubic-bezier(.34,1.56,.64,1)}

.bar{height:5px;background:var(--chrome-2);border-radius:var(--r-full);overflow:hidden;position:relative}
.bar>i{display:block;height:100%;border-radius:var(--r-full);background:linear-gradient(90deg,var(--input),var(--process));
  transition:width .5s cubic-bezier(.2,.8,.2,1);transform-origin:left;animation:barGrow .7s cubic-bezier(.16,1,.3,1) both;
  position:relative;overflow:hidden}
.bar>i::after{content:'';position:absolute;inset:0;animation:shimmer 2.4s linear infinite;animation-delay:.7s;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.45),transparent)}
.bar.light{background:var(--surface-3)}

/* ---- main column --------------------------------------- */
.main{min-width:0}
.topbar{
  display:flex;align-items:center;gap:14px;padding:4px 20px;border-bottom:1px solid var(--rule);
  background:var(--surface);position:sticky;top:var(--header-h);z-index:20
}
.topbar .where{flex:1;min-width:0}
/* Pseudocode Lab's Playground/Idle/Practice/Detailed views (core/pseudo-
   lab.js, pseudo-idle.js, pseudo-practice.js, pseudo-detailed.js) render
   their own title block straight into #top using .stage-head instead of
   the .where wrapper every other page uses — so it inherited ".topbar
   h1"'s white-space:nowrap/ellipsis rule above without ALSO getting the
   flex:1;min-width:0 that makes that ellipsis actually truncate instead
   of just growing the h1's box to its one-line content width and
   pushing the whole page into horizontal scroll (confirmed: happens on
   every one of these four pages, at any width narrow enough that the
   title doesn't fit on one line — not Playground-specific). */
.topbar .stage-head{flex:1;min-width:0}
.topbar h1{font-size:var(--t-lg);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.stagedots{display:flex;gap:6px;align-items:center}
.sd{width:9px;height:9px;border-radius:var(--r-full);background:var(--rule-2);border:0;padding:0;transition:background .2s ease}
.sd.on{background:var(--process);animation:sdPop .3s cubic-bezier(.34,1.56,.64,1) both}
.sd.past{background:var(--ok)}
.stagecount{font-family:var(--f-mono);font-size:11px;color:var(--ink-3);margin-left:8px}

.stage{flex:1;padding:36px 32px 40px}
.stage-inner{max-width:1000px;margin:0 auto}
.stage-inner.narrow{max-width:760px}
/* 1000px is a measure for prose, not for a diagram workspace: inside the
   1000px column the seven-symbol strip wrapped onto a second row and the
   example algorithm (640px wide) had to be scrolled sideways. The
   flowbuilder step is the only one that mounts #fcs-mount, so scope the
   wider column to it exactly as the full-screen rule below does. */
.stage-inner:has(#fcs-mount){max-width:1480px;width:100%}

/* site-wide full screen (core/shell.js's enterFullscreen/exitFullscreen,
   triggered by the "⛶" control in the app header) — ".main" is the
   one element every page's #top/#stage/#foot live inside, created once
   in app.html/app-db.html and never replaced by navigation, so unlike
   the Pseudocode Lab's own full screen (core/pseudo-editor.js) this
   needs no per-render resync: the class and the injected bar just
   stay put while a student moves between chapters/activities/exam
   mode with it on. */
body.site-fs-lock{overflow:hidden}
.main.site-fs-active{
  position:fixed;inset:0;z-index:1300;background:var(--surface-2);overflow:hidden;
  display:flex;flex-direction:column;
}
.site-fs-bar{
  flex:0 0 auto;display:flex;align-items:center;gap:14px;padding:10px 22px;
  background:var(--surface);border-bottom:1px solid var(--rule);box-shadow:var(--shadow);
}
.site-fs-logo{height:28px;width:auto;display:block}
.site-fs-tag{font-size:var(--t-sm);color:var(--ink-3);font-weight:600;letter-spacing:.01em}
.site-fs-exit{margin-left:auto}
/* Same override as ".main.step-solo .topbar" below, and for the same reason:
   this mode pins .main over the app header too, so the base rule's
   top:var(--header-h) has no header to sit under and just opens the page
   with a 70px gap. The two selectors can be active at once — solo mode plus
   the studio's own full screen button — so both need it independently. */
.main.site-fs-active .topbar{flex:0 0 auto;position:static}
.main.site-fs-active .stage{flex:1;min-height:0;overflow-y:auto;display:flex}
.main.site-fs-active .footnav{flex:0 0 auto;position:static}
@media(max-width:780px){ .site-fs-tag{display:none} }

/* Flowchart Studio specifically, while the site-wide full screen above is
   active: widen this step's content column past the site's normal 1000px
   reading cap and let the canvas/pseudocode/trace columns stretch to fill
   the freed vertical space, so the studio genuinely uses the full screen
   instead of sitting narrow inside it. Scoped with :has() to this one
   step's markup (#fcs-mount) so no other chapter's full-screen reading
   width is affected. */
.main.site-fs-active .stage-inner:has(#fcs-mount){max-width:none;width:100%;
  display:flex;flex-direction:column;min-height:0;flex:1}
.main.site-fs-active .stage:has(#fcs-mount .fcs-wrap){padding:16px 20px}
.main.site-fs-active #fcs-mount{flex:1;min-height:0;display:flex;flex-direction:column}
.main.site-fs-active .fcs-wrap{flex:1;min-height:0;display:flex;flex-direction:column;gap:8px}
/* .fcs-studio's base rule (tokens.css, "palette: horizontal symbol
   strip" section below) never sets grid-template-rows — normally that's
   fine, the browser auto-sizes two implicit rows (the palette, then
   canvas+right) to their own content and the grid's overall height is
   just whatever that adds up to. But giving the GRID a flex-grown,
   min-height:0 box (needed so it can actually fill the screen's freed
   vertical space in full screen) makes an auto-sized row's track height
   become ambiguous once the grid is asked to size to a definite height
   that doesn't match its content's natural total — in practice this
   collapsed the palette's own row track almost to nothing, and the
   canvas/right row started almost immediately under it, so the
   palette's real (un-collapsed) content rendered down into — and
   visually on top of — the canvas panel below. grid-template-rows
   makes this deterministic instead of implicit: the first row is
   pinned to `auto` (exactly the palette's own content height, never
   smaller) and the second is `1fr` (takes 100% of whatever vertical
   space is actually left) — removing the ambiguity rather than fighting
   its symptom. */
.main.site-fs-active .fcs-studio{flex:1;min-height:0;align-items:stretch;
  grid-template-rows:auto 1fr}
.main.site-fs-active .fcs-canvas-panel,
.main.site-fs-active .fcs-right{height:auto;min-height:0}
/* Belt-and-suspenders: the palette is a single fixed-height toolbar
   strip, not a panel that should stretch — align-self keeps it pinned
   to its own (now correctly auto-sized) row even though the grid
   container above sets align-items:stretch for the row below it. */
.main.site-fs-active .fcs-palette{align-self:start;height:fit-content}

/* ---- "solo" steps: one step owns the whole viewport -----------
   A step marked solo:true (core/shell.js's renderStep/isSoloStep) opts out
   of the lesson frame: the app header, the chapter rail and the global
   footer end up behind .main, #top collapses to a slim bar with a single
   exit control, and #foot is empty. The layout below is deliberately the
   same shape as ".main.site-fs-active" above — same fixed/inset/z-index
   trick over the same chrome, same flex column, same
   grid-template-rows:auto 1fr fix for .fcs-studio — because the two modes
   have to look and behave identically; solo is just the one a step can ask
   for on arrival, instead of the one a student has to go looking for.

   .main is never re-created (app.html/app-db.html build it once), so these
   classes are pure state — see applySoloMode() in core/shell.js. */
body.step-solo-lock{overflow:hidden}
.main.step-solo{
  position:fixed;inset:0;z-index:1300;background:var(--surface-2);overflow:hidden;
  display:flex;flex-direction:column
}
/* position:static, overriding the .topbar base rule's position:sticky /
   top:var(--header-h) — and that override is the whole point.

   A sticky topbar's job is to stay pinned directly beneath the app header
   while a long lesson scrolls under it, which is exactly what --header-h
   (70px) positions it for. Solo mode has no app header to sit beneath: it
   pins .main itself over the header, rail and footer, so that 70px offset
   resolved against nothing and pushed the bar down 70px, opening the
   full-bleed editor with a dead band of empty page above it. Static puts
   the bar back at the true top of the viewport, which is the only place it
   belongs when the thing above it no longer exists.

   Scoped to .main.step-solo on purpose: on an ordinary lesson page the
   sticky header is a real feature and removing it there would be a
   regression for the rest of the course. */
.main.step-solo .topbar{flex:0 0 auto;position:static}
.main.step-solo .stage{flex:1;min-height:0;overflow:hidden;display:flex;padding:16px 20px}
.main.step-solo .footnav{display:none}
.solo-bar{display:flex;align-items:center;gap:14px;width:100%;min-width:0}
.solo-bar .solo-title{font-size:var(--t-lg);font-weight:600;min-width:0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.solo-bar .solo-step{font-family:var(--f-mono);font-size:10.5px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink-3);white-space:nowrap}
.solo-bar .solo-exit{margin-left:auto;flex:0 0 auto}
/* The workspace gets exactly what the slim bar leaves behind. The :has()
   variant is required, not belt-and-suspenders: ".stage-inner:has(#fcs-mount)"
   alone is specificity (1,2,0) — the #id inside :has() counts — which beats
   any plain class chain here, exactly as it does for the full-screen rules. */
.main.step-solo .stage-inner{max-width:none;width:100%;flex:1;min-height:0;
  display:flex;flex-direction:column}
.main.step-solo .stage-inner:has(#fcs-mount){max-width:none;width:100%}
.main.step-solo #fcs-mount{flex:1;min-height:0;display:flex;flex-direction:column}
.main.step-solo .fcs-wrap{flex:1;min-height:0;display:flex;flex-direction:column;gap:8px}
.main.step-solo .fcs-studio{flex:1;min-height:0;align-items:stretch;
  grid-template-rows:auto 1fr}
.main.step-solo .fcs-canvas-panel,
.main.step-solo .fcs-right{height:auto;min-height:0}
.main.step-solo .fcs-palette{align-self:start;height:fit-content}
/* In solo mode the studio's chrome rows (action bar, palette, guide, status
   bar) all keep their natural height and the canvas takes whatever is left,
   so the panels can stop guessing at 72vh and simply fill the column — the
   whole point of solo mode is that the canvas should never be shorter than
   the space actually available. These are grid items of .fcs-studio, so it is
   align-items:stretch plus height:auto that does the work, not a flex
   shorthand. */
.main.step-solo .fcs-canvas-panel,
.main.step-solo .fcs-right{height:auto;min-height:0}
/* .fcs-wrap is a flex column here, and its children default to
   flex-shrink:1 — on a short viewport that silently squashed the action bar
   and status bar and clipped their text. They are chrome: they keep their
   height, and the canvas absorbs the squeeze instead. (.fcs-help is no longer
   in this list: the guide is a dialog in document.body now, not a row here.) */
.main.step-solo .fcs-toolbar,
.main.step-solo .fcs-exec-status,
.main.step-solo .fcs-status{flex:0 0 auto}
/* The studio's own "⛶ Full Screen" button is deliberately LEFT in place here
   even though the step already fills the viewport: the only thing it adds on
   top of the CSS above is the browser's own native fullscreen (requestFullscreen
   hides the tab and address bars too), and the site-header control that
   normally offers exactly that is itself hidden by solo mode. Pressing it
   stacks "site-fs-active" on top of "step-solo" — the two rule sets are
   written to be compatible — and "✕ Exit full screen" drops back to solo. */

/* ---- footer nav + abstraction strip (SIGNATURE) -------- */
.footnav{
  position:sticky;bottom:0;background:var(--surface);border-top:1px solid var(--rule);
  padding:12px 32px;display:flex;align-items:center;gap:20px;z-index:20
}
.pipeline{flex:1;display:flex;align-items:center;gap:0;min-width:0;overflow-x:auto}
.pl{
  font-family:var(--f-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;
  padding:5px 10px;border-radius:var(--r-s);color:var(--ink-3);background:var(--surface-3);white-space:nowrap;
  transition:background .3s ease,color .3s ease
}
.pl.on{color:#fff;background:var(--process);animation:pipeGlow 2.2s ease-in-out infinite}
.pl.i.on{background:var(--input)}
.pl.o.on{background:var(--output)}
.pl-sep{color:var(--rule-2);padding:0 6px;flex:0 0 auto}

/* ============================================================
   COMPONENTS
   ============================================================ */
.btn{
  display:inline-flex;align-items:center;gap:8px;border:1px solid var(--process);background:var(--process);
  color:#fff;padding:9px 16px;border-radius:var(--r-s);font-weight:600;font-size:var(--t-sm);
  transition:opacity .15s,transform .15s cubic-bezier(.34,1.56,.64,1),box-shadow .15s;box-shadow:0 1px 2px rgba(37,99,235,.15)
}
.btn:hover{opacity:.92;transform:translateY(-1px);box-shadow:0 6px 14px rgba(37,99,235,.25)}
.btn:active{transform:translateY(0);box-shadow:0 1px 2px rgba(37,99,235,.15)}
.btn.ghost{background:var(--surface);color:var(--ink-2);border-color:var(--rule-2);box-shadow:none}
.btn.ghost:hover{background:var(--surface-3);color:var(--ink);opacity:1;box-shadow:var(--shadow)}
.btn.sm{padding:6px 11px;font-size:var(--t-xs)}
.btn:disabled{opacity:.35;cursor:not-allowed;transform:none!important;box-shadow:none!important}
.btn-row{display:flex;gap:8px;flex-wrap:wrap;margin-top:16px}

.card{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-m);padding:20px;box-shadow:var(--shadow);
  color:var(--ink); /* reset explicitly: a .card nested inside a dark
    instrument panel (.instr, used by core/trace.js, sim.js, circuit.js,
    sqlrun.js) would otherwise inherit that panel's pale --on-chrome
    text colour onto its own light surface — near-invisible pseudocode */
  transition:box-shadow .2s ease,border-color .2s ease,transform .2s cubic-bezier(.16,1,.3,1)}
.card+.card{margin-top:14px}
.card.i{border-left:3px solid var(--input)}
.card.p{border-left:3px solid var(--process)}
.card.o{border-left:3px solid var(--output)}
button.card,.kv[onclick],button.kv{cursor:pointer}
button.card:hover{transform:translateY(-3px);box-shadow:var(--shadow-lift);border-color:var(--process-line)}
button.card:active{transform:translateY(-1px)}
button.kv:hover{background:var(--surface-3)}

.grid{display:grid;gap:14px}
.g2{grid-template-columns:repeat(2,1fr)}
.g3{grid-template-columns:repeat(3,1fr)}
.g4{grid-template-columns:repeat(4,1fr)}

.defn{background:var(--surface);border:1px solid var(--rule);border-left:3px solid var(--process);
  border-radius:0 var(--r-m) var(--r-m) 0;padding:16px 18px;margin:16px 0}
.defn .term{font-family:var(--f-display);font-weight:600;margin-bottom:4px}
.defn p{margin:0}

.note{background:var(--warn-bg);border:1px solid var(--warn-line);border-radius:var(--r-m);padding:14px 16px;margin:16px 0;font-size:var(--t-sm)}
.note.ext{background:var(--process-bg);border-color:var(--process-line)}
.note.info{background:var(--input-bg);border-color:var(--input-line)}
.note p:last-child{margin-bottom:0}

.badge{display:inline-block;font-family:var(--f-mono);font-size:9.5px;letter-spacing:.13em;
  text-transform:uppercase;padding:3px 7px;border-radius:4px;background:var(--surface-3);color:var(--ink-2)}
.badge.ext{background:var(--process);color:#fff}
.badge.src{background:var(--surface-3);color:var(--ink-2)}
.badge.marks{background:var(--process-bg);color:var(--process)}
.badge.ao1{background:var(--process-bg);color:var(--process)}
.badge.ao2{background:var(--input-bg);color:var(--input)}
.badge.ao3{background:var(--output-bg);color:var(--output)}
/* Real colour for the status-word badges core/pseudo-lab.js's/pseudo-
   idle.js's statusPill() and the Files panel (core/pseudo-idle.js's
   filesView()) already name — "process"/"warn"/"ok"/"no" — rather than
   the flat grey base .badge falls back to with no matching rule. */
.badge.process{background:var(--process-bg);color:var(--process)}
.badge.warn{background:var(--warn-bg);color:var(--warn)}
.badge.ok{background:var(--ok-bg);color:var(--ok)}
.badge.no{background:var(--no-bg);color:var(--no)}

/* ---- assessment-objective dashboard (core/sections.js: objectives()) --- */
.ao-card{border-left:3px solid var(--rule-2);padding-left:16px}
.ao-card.ao1{border-left-color:var(--process)}
.ao-card.ao2{border-left-color:var(--input)}
.ao-card.ao3{border-left-color:var(--output)}
.ao-compare{display:flex;flex-direction:column;gap:10px;margin-top:6px}
.ao-compare .row{display:grid;grid-template-columns:96px 1fr 1fr 52px;align-items:center;gap:10px;font-size:var(--t-sm)}
.ao-compare .row .lab{font-family:var(--f-mono);font-size:11px;color:var(--ink-2)}
.ao-compare .row .bar{height:8px}
.ao-compare .row .pct{text-align:right;font-family:var(--f-mono);font-size:11.5px;color:var(--ink-2)}
.ao-compare .bar.target>i{background:var(--ink-3);opacity:.55}
.ao-compare .bar.actual.ao1>i{background:var(--process)}
.ao-compare .bar.actual.ao2>i{background:var(--input)}
.ao-compare .bar.actual.ao3>i{background:var(--output)}

/* ---- optional external-resource link (labs/*.html, self-hosted, offline) ----
   Deliberately styled as a normal card with a dashed edge, not a highlighted
   feature — every concept the linked lab covers is already taught natively,
   above, in this platform's own design system. This is a "go further" pointer
   out to its own tab, never an iframe inside a themed page. */
.lab-link{border-style:dashed;background:var(--surface-3)}

.pill{display:inline-block;padding:2px 9px;border-radius:var(--r-full);font-size:11.5px;
  font-family:var(--f-mono);background:var(--surface-3);color:var(--ink-2)}
.pill.ok{background:var(--ok-bg);color:var(--ok)}
.pill.no{background:var(--no-bg);color:var(--no)}
.pill.mid{background:var(--warn-bg);color:var(--warn)}

/* ---- grade badges (A*–U) — solid fill + white text, same pattern
   as .badge.ext above, coloured from the --grade-* scale so every
   individual grade reads as its own step rather than one of three
   buckets. Used for the letter itself; percentage-based ok/mid/no
   pills above still cover the coarser 3-tier displays elsewhere. */
.grade-badge{display:inline-flex;align-items:center;justify-content:center;min-width:30px;
  padding:2px 8px;border-radius:var(--r-full);font-family:var(--f-mono);font-weight:700;
  font-size:11.5px;color:#fff;line-height:1.6}
.grade-badge.g-astar{background:var(--grade-astar)}
.grade-badge.g-a{background:var(--grade-a)}
.grade-badge.g-b{background:var(--grade-b)}
.grade-badge.g-c{background:var(--grade-c)}
.grade-badge.g-d{background:var(--grade-d)}
.grade-badge.g-e{background:var(--grade-e)}
.grade-badge.g-u{background:var(--grade-u)}
[data-mode="exam"] .grade-badge{background:transparent!important;border:1px solid var(--rule-2);color:var(--ink-2)}

/* ---- grade-progression bars (Performance tab) — one bar per
   attempt, oldest to newest, height by percentage, coloured by the
   same seven-step scale as the badges above. */
.grade-bar-track{display:flex;align-items:flex-end;gap:4px;height:90px;overflow-x:auto;padding-bottom:2px}
.grade-bar{flex:0 0 14px;width:14px;border-radius:3px 3px 0 0;min-height:6px;transition:transform .15s ease}
.grade-bar:hover{transform:scaleY(1.03)}
.grade-bar.g-astar{background:var(--grade-astar)}
.grade-bar.g-a{background:var(--grade-a)}
.grade-bar.g-b{background:var(--grade-b)}
.grade-bar.g-c{background:var(--grade-c)}
.grade-bar.g-d{background:var(--grade-d)}
.grade-bar.g-e{background:var(--grade-e)}
.grade-bar.g-u{background:var(--grade-u)}

.kv{display:flex;justify-content:space-between;gap:16px;padding:8px 0;border-bottom:1px solid var(--rule);font-size:var(--t-sm);transition:background .15s ease}
.kv:last-child{border-bottom:0}
.kv b{font-family:var(--f-mono);font-weight:500;text-align:right}

.stat{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-m);padding:16px;transition:box-shadow .2s ease,transform .2s ease}
.stat .n{font-family:var(--f-display);font-size:var(--t-2xl);font-weight:600;line-height:1;animation:popIn .45s cubic-bezier(.34,1.56,.64,1) both}
.stat .l{font-family:var(--f-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-top:6px}

table.t{width:100%;border-collapse:collapse;font-size:var(--t-sm);background:var(--surface);color:var(--ink);
  /* reset explicitly, same reason as .card/.chip above: a table.t nested
     inside a dark instrument panel (.instr — core/sqlrun.js's query
     results table) would otherwise inherit that panel's pale
     --on-chrome text colour onto its own light --surface background. */
  border:1px solid var(--rule);border-radius:var(--r-m);overflow:hidden}
table.t th{background:var(--surface-3);text-align:left;font-family:var(--f-display);font-size:var(--t-xs);padding:10px 12px;border-bottom:1px solid var(--rule)}
table.t td{padding:10px 12px;border-bottom:1px solid var(--rule);vertical-align:top}
table.t tr:last-child td{border-bottom:0}

/* Generic horizontal-scroll rescue for any table.t that's wider than its
   container on a phone (trace tables, SQL results, Practice's test-case
   grid, etc. — see core/ui.js's table()). Forces the table to its natural
   content width and keeps cells on one line, so a narrow viewport scrolls
   the table sideways instead of squeezing every column into illegible
   wrapped text — same fix already proven by .pgrid-wrap below, just
   generalised for ui.table()'s own wrapper div. */
.tbl-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%}
.tbl-scroll table.t{width:auto;min-width:100%}
.tbl-scroll table.t th,.tbl-scroll table.t td{white-space:nowrap}

.pc{display:grid;grid-template-columns:1fr 1fr;border:1px solid var(--rule);border-radius:var(--r-m);
  overflow:hidden;background:var(--surface)}
.pc>div{padding:16px 18px}
.pc>div:first-child{border-right:1px solid var(--rule)}
.pc h5{font-family:var(--f-mono);font-size:var(--t-xs);letter-spacing:.12em;text-transform:uppercase;margin:0 0 10px}
.pc ul{margin:0;padding-left:18px;font-size:var(--t-sm)}
.pc li{margin-bottom:7px}
.pc .adv h5{color:var(--ok)} .pc .dis h5{color:var(--no)}

/* ---- glossary flashcards -------------------------------- */
.flashcard-wrap{perspective:1400px;max-width:480px;margin:0 auto}
.flashcard{position:relative;min-height:220px;transform-style:preserve-3d;
  transition:transform .5s cubic-bezier(.4,.2,.2,1);cursor:pointer}
.flashcard.flipped{transform:rotateY(180deg);cursor:default}
.flashcard-face{position:absolute;inset:0;backface-visibility:hidden;
  background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-l);
  box-shadow:var(--shadow);padding:26px 24px;display:flex;flex-direction:column;
  align-items:center;justify-content:center;text-align:center}
.flashcard-front{}
.flashcard-back{transform:rotateY(180deg);border-color:var(--process-line);background:var(--process-bg)}
.flashcard:focus-visible{outline:2px solid var(--process);outline-offset:4px;border-radius:var(--r-l)}
@media (prefers-reduced-motion: reduce){ .flashcard{transition:none} }

/* ---- formula sheet --------------------------------------- */
.fsheet-title{font-size:var(--t-xl);margin:0 0 16px}
.fsheet-card .fsheet-formula{background:var(--surface-3);border-radius:var(--r-s);padding:8px 12px;
  font-size:15px;margin:0;display:inline-block}
.fsheet-footer{text-align:center;margin-top:20px}
/* Print only the formula sheet itself — hide the app chrome (rail, header,
   footer nav) regardless of where they sit in the DOM. Toggling visibility
   rather than display keeps layout calculations simple and is the standard
   "print just this element" technique. */
@media print{
  body *{visibility:hidden}
  #formula-sheet-page,#formula-sheet-page *{visibility:visible}
  #formula-sheet-page{position:absolute;left:0;top:0;width:100%}
  #formula-sheet-page .fsheet-hide-print,#formula-sheet-actions{display:none!important}
}

/* ---- form controls ------------------------------------- */
.ctrl{margin-bottom:16px}
.ctrl label{display:flex;justify-content:space-between;align-items:baseline;gap:12px;margin-bottom:7px}
.ctrl label b{font-family:var(--f-display);font-size:var(--t-sm);font-weight:600}
.ctrl label .v{font-family:var(--f-mono);font-size:var(--t-sm);color:var(--ink-2)}
input[type=range]{width:100%;accent-color:var(--process);height:26px}
select,input[type=text],input[type=search],input[type=email],input[type=password],input[type=number],input[type=tel],textarea{
  width:100%;font-family:inherit;font-size:var(--t-base);padding:10px 12px;border:1px solid var(--rule-2);
  border-radius:var(--r-s);background:var(--surface);color:var(--ink);transition:border-color .15s ease,box-shadow .15s ease
}
select:focus,input[type=text]:focus,input[type=search]:focus,input[type=email]:focus,input[type=password]:focus,input[type=number]:focus,input[type=tel]:focus,textarea:focus{
  outline:none;border-color:var(--process);box-shadow:0 0 0 3px var(--process-bg)
}
textarea{resize:vertical;min-height:110px;line-height:1.55}
/* iOS Safari auto-zooms the whole page on focus of any text input under
   16px — --t-base is 15.5px, just under that threshold. Overriding only
   below 480px (where iPhone-width visitors actually are) keeps every
   other platform's font size exactly as designed. */
@media (max-width:480px){
  select,input[type=text],input[type=search],input[type=email],input[type=password],input[type=number],input[type=tel],textarea{font-size:16px}
}
.seg{display:inline-flex;flex-wrap:wrap;border:1px solid var(--rule-2);border-radius:var(--r-s);overflow:hidden;background:var(--surface)}
.seg button{background:var(--surface);border:0;padding:8px 14px;font-size:var(--t-sm);color:var(--ink-2);border-right:1px solid var(--rule);transition:background .15s ease,color .15s ease}
.seg button:last-child{border-right:0}
.seg button.on{background:var(--process);color:#fff;font-weight:600}

/* ---- account / auth modal ------------------------------- */
.modal-scrim{position:fixed;inset:0;background:rgba(15,23,42,.55);display:flex;align-items:center;justify-content:center;z-index:90;padding:20px;animation:fadeIn .18s ease both}
/* z-index:90 assumes nothing on the page sits above it — but the two
   full-bleed modes (.main.step-solo, .main.site-fs-active) are themselves
   position:fixed at z-index:1300, so any dialog appended to document.body
   (the first-run guide, auth, onboarding, engagement) rendered BEHIND the
   page: the popup opened with no visible effect at all. Solo mode is where
   this bites hardest, since the guide opens there by default.

   Raise the scrim above 1300 in those two states only — on an ordinary
   lesson page .main is static and 90 stays correct. Keyed off the body
   classes both modes already set (step-solo-lock, site-fs-lock) rather than
   the .main classes: .main lives inside .app, so it is not a sibling of a
   scrim appended to body and a ".main.site-fs-active ~" selector never
   matches. */
body.step-solo-lock .modal-scrim,
body.site-fs-lock .modal-scrim{z-index:1400}
.modal{background:var(--surface);border-radius:var(--r-l);box-shadow:var(--shadow-lift);padding:28px;max-width:368px;width:100%;position:relative;animation:popIn .22s cubic-bezier(.34,1.56,.64,1) both}
.modal.modal-wide{max-width:640px;max-height:82vh;overflow-y:auto}
@supports (height: 100dvh){ .modal.modal-wide{max-height:82dvh} }
.modal-x{position:absolute;top:14px;right:14px;background:none;border:0;color:var(--ink-3);font-size:15px;line-height:1;cursor:pointer;width:28px;height:28px;border-radius:var(--r-s);transition:background .15s ease,color .15s ease}
.modal-x:hover{background:var(--surface-3);color:var(--ink)}
.modal .seg{width:100%;margin:2px 0 20px}
.modal .seg button{flex:1;text-align:center}
.modal h3{font-family:var(--f-display);font-size:var(--t-lg);margin:0 0 4px}
.modal p.lede{color:var(--ink-2);font-size:var(--t-sm);margin:0 0 18px}

/* .modal-scroll: keeps the title/tabs/close button fixed at the top and
   scrolls only the form fields beneath — used by the auth modal and the
   onboarding modal so neither one can ever be clipped top-or-bottom on a
   short viewport (a phone in landscape, a small laptop with the address
   bar showing, or the auth modal once the country picker adds height to
   the sign-up form). Overrides .modal-wide's own overflow-y/max-height
   (same specificity, later in the cascade) since the SCROLLING now
   happens on .modal-scroll-body instead of the .modal box itself —
   scrolling both would nest two scrollbars for no reason. */
.modal.modal-scroll{display:flex;flex-direction:column;max-height:88vh;overflow:hidden;padding:0}
@supports (height: 100dvh){ .modal.modal-scroll{max-height:88dvh} }
.modal-scroll .modal-head{padding:28px 28px 0;flex:none}
.modal-scroll .modal-head .lede{margin-bottom:14px}
.modal-scroll .modal-head #authTabs{margin-bottom:0}
.modal-scroll-body{overflow-y:auto;overscroll-behavior:contain;padding:14px 28px 28px;flex:1;min-height:0}
/* html.modal-open-lock: applied while ANY modal (auth/onboarding/the
   engagement popup) is open, so scrolling the page behind it is disabled
   without fighting whatever inline styles a chapter screen sets on its
   own scroll containers. */
html.modal-open-lock{overflow:hidden}
html.modal-open-lock body{overflow:hidden}

/* one-time "welcome" onboarding modal (core/shell.js openOnboarding()) —
   country/city side-by-side on wide screens, stacked on narrow ones, plus
   a plain-text "skip this" link so it never feels like a second signup
   form the student is forced through. */
.onboard-row{display:grid;grid-template-columns:1fr 1fr;gap:14px}
@media (max-width:480px){ .onboard-row{grid-template-columns:1fr} }
.onboard-skip{display:block;text-align:center;margin-top:14px;font-size:var(--t-sm);color:var(--ink-3);
  background:none;border:0;cursor:pointer;text-decoration:underline;text-underline-offset:3px;width:100%}
.onboard-skip:hover{color:var(--ink-2)}

/* sign-up: country/flag calling-code picker (core/shell.js phoneFieldHtml
   et al.) — a button showing the selected flag+dial code, sat beside the
   plain number input, with a small searchable listbox panel underneath. */
.phone-field{display:flex;gap:8px}
.phone-code-btn{display:inline-flex;align-items:center;gap:7px;flex:none;border:1px solid var(--rule-2);
  background:var(--surface);border-radius:var(--r-s);padding:0 12px;font-size:var(--t-base);color:var(--ink);
  cursor:pointer;transition:border-color .15s ease,background .15s ease}
.phone-code-btn:hover{background:var(--surface-3)}
.phone-field .phone-code-flag{font-size:17px;line-height:1}
.phone-code-dial{font-family:var(--f-mono);font-weight:600;white-space:nowrap}
.phone-code-caret{font-size:9px;color:var(--ink-3)}
#rg-whatsapp-num{flex:1;min-width:0}
.phone-code-menu{position:relative;margin-top:8px;border:1px solid var(--rule-2);border-radius:var(--r-s);
  background:var(--surface);box-shadow:var(--shadow-lift);overflow:hidden;animation:popIn .15s ease both}
.phone-code-menu .phone-code-search{border:0;border-bottom:1px solid var(--rule);border-radius:0}
.phone-code-menu .phone-code-search:focus{box-shadow:none;border-bottom-color:var(--process)}
.phone-code-list{max-height:220px;overflow-y:auto}
.phone-code-opt{display:flex;align-items:center;gap:10px;width:100%;padding:9px 12px;background:none;border:0;
  font-size:var(--t-sm);color:var(--ink);cursor:pointer;text-align:left;transition:background .1s ease}
.phone-code-opt:hover,.phone-code-opt:focus-visible{background:var(--process-bg);outline:none}
.phone-code-opt.sel{font-weight:600;color:var(--process)}
.phone-code-opt .phone-code-dial{font-weight:500;color:var(--ink-3)}
.phone-code-opt.sel .phone-code-dial{color:var(--process)}

/* scroll-triggered "Track Your Progress" sign-up prompt (core/shell.js
   openEngagement()) — reuses .modal-scrim/.modal exactly like the auth and
   onboarding modals, so a third full-screen prompt never introduces a
   second visual language; only the benefit list and button stack below
   are specific to it. */
.engage-benefits{margin:16px 0 0;padding:0;list-style:none}
.engage-benefits li{display:flex;gap:10px;align-items:flex-start;padding:7px 0;font-size:var(--t-sm);color:var(--ink-2)}
.engage-benefits li .tick{flex:none;width:18px;height:18px;border-radius:50%;background:var(--ok-bg);color:var(--ok);
  display:flex;align-items:center;justify-content:center;font-size:11px;margin-top:1px}
.engage-actions{display:flex;flex-direction:column;gap:10px;margin-top:22px}
.engage-actions .btn.ghost{color:var(--ink-3)}

.chips{display:flex;flex-wrap:wrap;gap:7px;margin:10px 0}
.chip{border:1px solid var(--rule-2);background:var(--surface);color:var(--ink);border-radius:var(--r-full);padding:7px 14px;font-size:var(--t-sm);
  /* reset explicitly, same reason as .card above: a .chip nested inside
     a dark instrument panel (.instr — core/sqlrun.js's example chips,
     among others) would otherwise inherit that panel's pale --on-chrome
     text colour onto its own light --surface background — near-
     invisible text, exactly the bug already fixed for .card. */
  transition:border-color .15s ease,background .15s ease,color .15s ease,transform .1s ease}
.chip:hover{border-color:var(--ink-3)}
.chip:active{transform:scale(.96)}
.chip.on{background:var(--process);color:#fff;border-color:var(--process);animation:popIn .25s cubic-bezier(.34,1.56,.64,1)}
.chip.right{background:var(--ok-bg);border-color:var(--ok);color:var(--ok);animation:popIn .25s cubic-bezier(.34,1.56,.64,1)}
.chip.wrong{background:var(--no-bg);border-color:var(--no);color:var(--no);animation:popIn .25s cubic-bezier(.34,1.56,.64,1)}
.chip.static{cursor:default}

/* ---- quiz ---------------------------------------------- */
.q{border:1px solid var(--rule);background:var(--surface);border-radius:var(--r-m);padding:18px 20px;margin-bottom:14px}
.q-head{display:flex;gap:10px;align-items:flex-start;margin-bottom:12px}
.q-num{font-family:var(--f-mono);font-size:var(--t-xs);color:var(--ink-2);background:var(--surface-3);padding:3px 7px;border-radius:4px;flex:0 0 auto}
.q-text{font-weight:500;flex:1}
.opt{display:block;width:100%;text-align:left;background:var(--surface);border:1px solid var(--rule);
  border-radius:var(--r-s);padding:11px 14px;margin-bottom:7px;font-size:var(--t-sm);min-height:44px;
  transition:border-color .15s ease,background .15s ease,transform .1s ease}
.opt:hover{border-color:var(--process-line);background:var(--surface-2)}
.opt:active{transform:scale(.99)}
.opt.sel{border-color:var(--process);background:var(--process-bg);font-weight:500;animation:popIn .22s cubic-bezier(.34,1.56,.64,1)}
.opt.right{border-color:var(--ok);background:var(--ok-bg);animation:popIn .25s cubic-bezier(.34,1.56,.64,1)}
.opt.wrong{border-color:var(--no);background:var(--no-bg);animation:popIn .25s cubic-bezier(.34,1.56,.64,1)}
.opt .glyph{font-family:var(--f-mono);font-weight:700;margin-right:8px}
.opt:disabled{cursor:default}

.fb{margin-top:12px;padding:13px 15px;border-radius:var(--r-s);font-size:var(--t-sm);border:1px solid;animation:fadeInUp .3s cubic-bezier(.16,1,.3,1) both}
.fb.ok{background:var(--ok-bg);border-color:var(--ok-line);color:var(--ok)}
.fb.no{background:var(--no-bg);border-color:var(--no-line);color:var(--no)}
.fb.info{background:var(--input-bg);border-color:var(--input-line);color:var(--ink)}
.fb.warn{background:var(--warn-bg);border-color:var(--warn-line);color:var(--warn)}
.fb h6{font-family:var(--f-display);font-size:var(--t-sm);margin:0 0 5px;display:flex;align-items:center;gap:7px}
.fb ul{margin:8px 0 0;padding-left:18px}
.fb li{margin-bottom:5px}
.fb p:last-child{margin-bottom:0}

/* ---- instrument panel (simulations) -------------------- */
.instr{background:var(--chrome);border-radius:var(--r-m);padding:18px 20px 14px;color:var(--on-chrome)}
.instr-title{font-family:var(--f-mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--on-chrome-2);margin-bottom:14px;display:flex;justify-content:space-between}
.bus{display:flex;align-items:stretch}
.bus-stage{flex:1;text-align:center;padding:0 4px;position:relative}
.bus-stage .node{height:46px;border:1.5px solid var(--chrome-line);border-radius:var(--r-s);
  display:flex;align-items:center;justify-content:center;font-family:var(--f-mono);font-size:10.5px;
  letter-spacing:.08em;text-transform:uppercase;color:var(--on-chrome-2);background:var(--chrome-2);transition:all .3s}
.bus-stage.live .node{color:#fff;border-color:var(--input);background:var(--input-bg)}
.bus-stage.live.p .node{border-color:var(--process);background:var(--process-bg)}
.bus-stage.live.o .node{border-color:var(--output);background:var(--output-bg)}
.bus-stage .val{font-family:var(--f-mono);font-size:10.5px;color:#fff;margin-top:7px;min-height:15px}
.bus-stage:not(:last-child):after{content:'';position:absolute;top:23px;right:-6px;width:12px;height:1.5px;background:var(--chrome-line)}
.instr-msg{margin-top:14px;border-top:1px solid var(--chrome-line);padding-top:11px;
  font-family:var(--f-mono);font-size:11.5px;color:var(--on-chrome-2);min-height:36px}
.instr-msg b{color:#fff;font-weight:500}
.transport{display:flex;gap:6px;margin-top:12px;flex-wrap:wrap}
.tbtn{background:var(--chrome-2);border:1px solid var(--chrome-line);color:var(--on-chrome);
  padding:8px 13px;border-radius:var(--r-s);font-family:var(--f-mono);font-size:11px;
  letter-spacing:.08em;text-transform:uppercase;min-height:36px}
.tbtn:hover{background:#243149}
.tbtn.pri{background:var(--input);border-color:var(--input);color:#04222A;font-weight:600}

/* ============================================================
   PSEUDOCODE LAB · SHARED CODE EDITOR (core/pseudo-editor.js)
   ------------------------------------------------------------
   Gutter + syntax-highlighted overlay + transparent textarea,
   all three kept to an identical box model (padding, font, line-
   height, no wrapping) so the highlighted text lines up exactly
   under what the student is actually typing. Token colours are
   drawn ONLY from tokens already declared above (--mm-1/3/5,
   --process, --output, --ink-3) — a real semantic reuse, not a
   second palette, and every one of those already has a dark-
   theme value, so highlighting needs no dark-mode rules of its
   own here. */
.pe-wrap{display:flex;border:1px solid var(--rule);border-radius:var(--r-m);overflow:hidden;background:var(--surface)}
.pe-gutter{flex:0 0 auto;overflow:hidden;padding:12px 0;text-align:right;
  font-family:var(--pe-ff,var(--f-mono));font-size:var(--pe-fs,var(--t-sm));font-weight:var(--pe-fw,400);
  line-height:1.7;color:var(--ink-3);
  background:var(--surface-3);user-select:none;height:420px}
.pe-gutter-row{padding:0 10px}
.pe-gutter-row-current{color:var(--process);font-weight:700}
.pe-wrap.pe-no-linenum .pe-gutter{display:none}
.pe-body{position:relative;flex:1;min-width:0;height:420px}
/* "Highlight current line" (IDLE settings table) — a plain bar behind the
   text, positioned/sized in JS by core/pseudo-editor.js's mount() from
   the textarea's own measured line-height, so it tracks any font-size
   change made in the same Settings panel. Hidden by default/when the
   setting is off (JS also sets display:none, this is the no-JS/initial-
   paint fallback); .pe-show-curline is the modifier settingsClassAndStyle()
   adds on .pe-wrap when the setting is on. */
.pe-current-line{position:absolute;left:0;right:0;display:none;background:var(--process-bg);
  pointer-events:none;z-index:0}
.pe-wrap.pe-show-curline .pe-current-line{display:block}
.pe-highlight,.pe-input,.pe-guides{position:absolute;inset:0;margin:0;padding:12px;
  font-family:var(--pe-ff,var(--f-mono));font-size:var(--pe-fs,var(--t-sm));font-weight:var(--pe-fw,400);
  line-height:1.7;
  white-space:var(--pe-ws,pre);overflow:auto;box-sizing:border-box;border:0}
.pe-wrap.pe-wrap-on .pe-highlight,.pe-wrap.pe-wrap-on .pe-input,.pe-wrap.pe-wrap-on .pe-guides{
  white-space:pre-wrap;word-break:break-word}
.pe-highlight{color:var(--ink);pointer-events:none}
.pe-input{width:100%;height:100%;resize:none;background:transparent;color:transparent;
  caret-color:var(--ink);z-index:1}
/* block-aware indentation guides (core/pseudo-editor.js's guidesHtml(),
   driven by core/pseudo-indent.js's real block-stack analysis) — sits
   BELOW .pe-highlight in the same box, so it never intercepts clicks/
   typing (pointer-events:none) and never tints the coloured syntax text
   sitting on top of it. Each "│" is one logical nesting level: the
   level a line's OWN content sits at is green/red from real analysis,
   every shallower ancestor level is the neutral rail colour (it shows
   containment only, never a correctness verdict it didn't measure). */
.pe-guides{color:transparent;pointer-events:none;z-index:0}
.pe-g-ok{color:var(--ok)}
.pe-g-bad{color:var(--no)}
.pe-g-rail{color:var(--rule-2)}
/* IntelliSense dropdown (core/pseudo-editor.js's mount(), suggestions
   from core/pseudo-intellisense.js) — a small positioned card, never
   wider/taller than it needs to be, sitting above the editor layers
   (.pe-input is z-index:1) so it's never hidden behind the caret
   layer, but still closes on blur/click/Escape rather than ever
   trapping keyboard focus. */
/* Each row is icon + name + category label on one line, with an
   optional second line (type/signature) below it — section 6's own
   layout. Rows without a `detail` stay single-line and compact
   (section 6: "do not make each row excessively tall"); the category
   LABEL, not colour, is what every row relies on to say what it is
   (section 5: icon+label must work with colour off — the per-kind
   colour below is a secondary accent only). */
.pe-suggest{position:absolute;z-index:5;min-width:190px;max-width:300px;max-height:220px;
  overflow-y:auto;background:var(--surface);border:1px solid var(--rule-2);border-radius:var(--r-s);
  box-shadow:var(--shadow-lift);font-family:var(--f-mono);font-size:var(--t-sm);padding:4px}
.pe-suggest-item{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:7px;
  padding:5px 8px;border-radius:var(--r-s);cursor:pointer;color:var(--ink)}
.pe-suggest-item:hover{background:var(--surface-3)}
.pe-suggest-item-active{background:var(--process-bg);color:var(--process)}
.pe-suggest-icon{flex:0 0 14px;text-align:center;font-weight:700;font-size:11px;line-height:1.4;color:var(--ink-3)}
.pe-suggest-main{flex:1 1 auto;min-width:0;display:flex;align-items:baseline;gap:7px;white-space:nowrap;overflow:hidden}
.pe-suggest-name{overflow:hidden;text-overflow:ellipsis}
.pe-suggest-cat{flex:0 0 auto;margin-left:auto;font-size:9px;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-3)}
.pe-suggest-item-active .pe-suggest-cat{color:var(--process)}
.pe-suggest-detail{flex:1 0 100%;margin-left:21px;font-size:10px;color:var(--ink-3);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pe-suggest-item-active .pe-suggest-detail{color:var(--process)}
/* Per-kind colour accent on the icon glyph only — a secondary cue on
   top of the icon shape + text label, never the sole differentiator. */
.pe-sk-variable{color:var(--ink-2)}
.pe-sk-constant{color:var(--no)}
.pe-sk-parameter{color:var(--mm-6)}
.pe-sk-procedure{color:var(--ok)}
.pe-sk-function,.pe-sk-builtin{color:var(--process)}
.pe-sk-conditional{color:var(--mm-5)}
.pe-sk-loop{color:var(--mm-3)}
.pe-sk-datatype{color:var(--mm-8)}
.pe-sk-operator{color:var(--mm-1)}
.pe-sk-keyword{color:var(--ink-3)}
.pe-suggest-item-active .pe-suggest-icon{color:var(--process)}
/* ---- IntelliSense upgrade: hover docs, signature help, find-
   references badge (core/pseudo-intellisense.js's hoverInfo()/
   signatureHelp()/findReferences(), rendered by core/pseudo-
   editor.js's mount()) — same floating-box visual language as
   .pe-suggest above (surface/border/shadow/mono font), so the three
   popups read as one consistent "editor assist" family rather than
   three differently-styled widgets. */
.pe-hover,.pe-sighelp,.pe-refbadge{position:absolute;z-index:6;background:var(--surface);
  border:1px solid var(--rule-2);border-radius:var(--r-s);box-shadow:var(--shadow-lift);
  font-family:var(--f-mono);font-size:var(--t-sm);pointer-events:none}
.pe-hover{max-width:320px;padding:7px 10px}
.pe-hover-title{display:flex;align-items:baseline;gap:7px;font-weight:600;color:var(--ink)}
.pe-hover-icon{flex:0 0 14px;text-align:center;font-weight:700;font-size:11px;color:var(--ink-3)}
.pe-hover-detail{margin-top:4px;color:var(--ink-3);font-size:10px;line-height:1.5;max-width:300px}
.pe-sighelp{padding:6px 10px;white-space:nowrap;color:var(--ink-2)}
.pe-sighelp-name{font-weight:600;color:var(--process)}
.pe-sighelp-param{color:var(--ink-3)}
.pe-sighelp-param-active{color:var(--process);font-weight:600}
.pe-sighelp-comma{color:var(--ink-3)}
.pe-sighelp-returns{margin-left:6px;font-size:9px;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-3)}
.pe-sighelp-desc{margin-top:3px;white-space:normal;max-width:300px;font-size:10px;color:var(--ink-3)}
.pe-refbadge{padding:3px 8px;font-size:9px;letter-spacing:.03em;color:var(--process);
  background:var(--process-bg);border-color:var(--process)}
.pe-input::placeholder{color:var(--ink-3);opacity:1}
.pe-input::selection{background:var(--process-line)}
.pe-kw{color:var(--mm-1);font-weight:600}
.pe-fn{color:var(--process);font-weight:600}
.pe-type{color:var(--output)}
/* Enumeration MEMBER names (e.g. "Wednesday" in TYPE TDay = (...)) —
   deliberately a DIFFERENT colour from .pe-type (the enum TYPE name
   itself keeps the ordinary data-type colour above), applied only via
   symbol-table information from core/pseudo-intellisense.js's
   collectSymbols(), never a blanket "capitalized word" rule — see
   core/pseudo-editor.js's highlightSpans(). */
.pe-enummember{color:var(--mm-4,var(--mm-3));font-weight:600}
.pe-str{color:var(--mm-3)}
.pe-num{color:var(--mm-5)}
.pe-com{color:var(--ink-3);font-style:italic}
.pe-op{color:var(--input)}
/* read-only mode (program running/completed/error) — same colours,
   plus a highlighted current line; replaces the old plain black-and-
   white read-only view every Lab surface used to render by hand. */
.pe-ro-wrap{border:1px solid var(--rule);border-radius:var(--r-m);padding:12px 0;background:var(--surface);
  font-family:var(--pe-ff,var(--f-mono));font-size:var(--pe-fs,var(--t-sm));font-weight:var(--pe-fw,400);
  line-height:1.7;height:420px;overflow:auto;white-space:var(--pe-ws,pre)}
.pe-ro-wrap.pe-wrap-on{white-space:pre-wrap;word-break:break-word}
.pe-ro-wrap.pe-no-linenum .pe-ro-num{display:none}
.pe-ro-line{padding:0 12px;border-radius:var(--r-s)}
.pe-ro-line-current{background:var(--process-bg);color:var(--process);font-weight:600}
.pe-ro-num{display:inline-block;width:26px;text-align:right;margin-right:8px;color:var(--ink-3)}
.pe-ro-line-current .pe-ro-num{color:var(--process)}
.pe-ro-code{color:var(--ink)}
.pe-ro-line-current .pe-kw,.pe-ro-line-current .pe-fn,.pe-ro-line-current .pe-type,
.pe-ro-line-current .pe-enummember,
.pe-ro-line-current .pe-str,.pe-ro-line-current .pe-num,.pe-ro-line-current .pe-op{color:inherit}
/* ---- IDLE Dark theme (Settings panel) ----
   Python IDLE's two shipped themes are "IDLE Classic" (follows the
   system/editor colours — what this component already looked like, so
   "Classic" needs no rule of its own) and "IDLE Dark", a fixed dark
   palette independent of the site's own light/dark mode. Scoped entirely
   under .pe-theme-dark so it can never affect Playground/Practice (which
   never pass settings.theme) or any other part of the page. */
.pe-wrap.pe-theme-dark,.pe-ro-wrap.pe-theme-dark{background:#2b2b2b;border-color:#1e1e1e}
.pe-wrap.pe-theme-dark .pe-gutter{background:#232323;color:#6d6d6d}
.pe-wrap.pe-theme-dark .pe-gutter-row-current{color:#ffd966}
.pe-wrap.pe-theme-dark .pe-highlight{color:#f2f2f2}
.pe-wrap.pe-theme-dark .pe-input{caret-color:#f2f2f2}
.pe-wrap.pe-theme-dark .pe-current-line{background:rgba(255,255,255,.07)}
.pe-wrap.pe-theme-dark .pe-g-rail{color:#3c3c3c}
.pe-ro-wrap.pe-theme-dark .pe-ro-num{color:#6d6d6d}
.pe-ro-wrap.pe-theme-dark .pe-ro-code{color:#f2f2f2}
.pe-ro-wrap.pe-theme-dark .pe-ro-line-current{background:rgba(255,217,102,.16);color:#ffd966}
.pe-ro-wrap.pe-theme-dark .pe-ro-line-current .pe-ro-num{color:#ffd966}
.pe-wrap.pe-theme-dark .pe-kw,.pe-ro-wrap.pe-theme-dark .pe-kw{color:#ff8000;font-weight:600}
.pe-wrap.pe-theme-dark .pe-fn,.pe-ro-wrap.pe-theme-dark .pe-fn{color:#ae81ff;font-weight:600}
.pe-wrap.pe-theme-dark .pe-type,.pe-ro-wrap.pe-theme-dark .pe-type{color:#66d9ef}
.pe-wrap.pe-theme-dark .pe-enummember,.pe-ro-wrap.pe-theme-dark .pe-enummember{color:#e6db74;font-weight:600}
.pe-wrap.pe-theme-dark .pe-str,.pe-ro-wrap.pe-theme-dark .pe-str{color:#98c379}
.pe-wrap.pe-theme-dark .pe-num,.pe-ro-wrap.pe-theme-dark .pe-num{color:#f472b6}
.pe-wrap.pe-theme-dark .pe-com,.pe-ro-wrap.pe-theme-dark .pe-com{color:#898989}
.pe-wrap.pe-theme-dark .pe-op,.pe-ro-wrap.pe-theme-dark .pe-op{color:#f2f2f2}
/* the split code/output workspace itself (section 2/3 of the editor
   brief) — code left, tabs/output right, each independently
   scrollable, both sized to fit the viewport instead of the page
   growing under them. Falls back to stacked on narrow widths. */
.pe-split{display:grid;grid-template-columns:1.1fr 1fr;gap:16px;align-items:start}
/* Grid items default to min-width:auto (shrink-to-fit on their CONTENT's
   intrinsic size), not 0 — so a long nowrap pseudocode line inside
   .pe-ro-wrap (core/pseudo-editor.js's read-only/step-highlighted view;
   it already has its own overflow:auto, but that only helps once this
   track is actually allowed to be narrower than the line) can force the
   whole 1.1fr track wider than its share and push the grid, and the
   whole page, into horizontal scroll — confirmed with a TYPE declaration
   long enough to not fit a 320px phone. */
.pe-split>div{min-width:0}
@media (max-width:900px){.pe-split{display:block}}
/* "Output panel: beside editor / below editor" (IDLE's Shell Window
   setting) — Idle's own choice to stack output under the code instead
   of beside it, independent of the ≤900px responsive stacking above. */
.pe-split.pe-split-stacked{display:block}
.pe-split.pe-split-stacked>*+*{margin-top:16px}

/* ---- Idle's horizontal split-resize handle (core/pseudo-idle.js's
   splitHandleHtml()/initSplitResize()) ----
   A thin draggable gutter between the editor (".instr") and the
   Output/Variables/Trace table/Errors panel ("#pi-tabs"), both still
   children of the SAME ".pe-split" grid Playground also uses — but
   only Idle's markup adds this extra ".pe-split-resizable" class and
   the handle button itself, so ordinary Playground/".pe-split" pages
   are completely unaffected. The JS sets the live column widths via
   inline style on #pi-split (3 tracks: editor / 10px handle / 1fr
   output) — this rule is only the no-JS/first-paint fallback and the
   ratio used once idleSettings.splitWidth is null again (reset). */
.pe-split.pe-split-resizable{grid-template-columns:1.1fr 10px 1fr}
.pe-split.pe-split-resizable.pe-split-stacked{display:block}
.pe-split-handle{
  /* No explicit grid-row/grid-column here on purpose — giving this item
     ANY explicit placement made Chromium's grid auto-placement treat it
     as "already placed" and position it before the other two (fully
     auto) items, which shifted .instr and #pi-tabs over by one track
     each (confirmed in isolation). All three children of #pi-split stay
     on plain DOM-order auto-placement — leave this alone. */
  justify-self:center;align-self:stretch;
  width:10px;min-height:120px;padding:0;margin:0;border:0;background:transparent;
  cursor:col-resize;touch-action:none;position:relative;
  display:flex;align-items:center;justify-content:center;
}
.pe-split-handle::before{
  content:'';width:3px;height:100%;border-radius:var(--r-full);
  background:var(--rule);transition:background .12s ease,width .12s ease;
}
.pe-split-handle:hover::before,
.pe-split-handle:focus-visible::before,
.pe-split-handle.dragging::before{background:var(--process);width:4px}
.pe-split-handle:focus-visible{outline:2px solid var(--process);outline-offset:2px;border-radius:var(--r-s)}
/* Below the responsive stacking breakpoint, and whenever Idle's own
   "Output panel: below editor" setting is active, there is no
   side-by-side split to resize — splitHandleHtml() already omits the
   handle for the stacked case; this covers the ≤900px breakpoint,
   where .pe-split itself collapses to a single column regardless. */
@media (max-width:900px){ .pe-split-handle{display:none} }
/* A 10px drag target is fine for a mouse pointer but hard to grab with a
   finger. core/pseudo-idle.js always sets the grid's own column widths via
   inline style (so a grid-template-columns override here would never be
   honoured) — but it never touches the handle BUTTON's own width, so
   widening just that, on coarse/touch pointers only, gives a real ~28px
   hit target that simply overlaps a few px into each neighbouring panel
   (harmless — the handle sits on top, centered) without changing the
   10px track or disturbing mouse users' drag math at all. */
@media (pointer:coarse){ .pe-split-handle{width:28px} }

/* ---- Idle's Settings panel (Font/Tab/Theme/Auto-indent/… — the
   "Recommended IDLE configuration" table) ----
   A true modal: a dimmed backdrop behind a centred card with its own
   internal scroll, appended straight to document.body by
   core/pseudo-idle.js's openSettingsModal() rather than rendered inline
   in "#stage" — see that function's own comment for why (the same
   transform-containing-block reason core/pseudo-editor.js's full-screen
   overlay already lives at document.body). z-index is higher than
   .pe-fs-overlay's (1200) so the SAME modal still opens correctly on top
   of full screen, not hidden behind it — "Settings" works identically
   whether or not full screen is currently active. */
.idle-settings-overlay{position:fixed;inset:0;z-index:1300;
  background:rgba(15,23,42,.55);display:flex;align-items:center;justify-content:center;
  padding:24px;box-sizing:border-box}
body.idle-settings-lock{overflow:hidden}
.idle-settings-modal{background:var(--surface);border-radius:var(--r-m);box-shadow:var(--shadow-lift);
  width:min(760px,100%);max-height:min(640px,85vh);display:flex;flex-direction:column;overflow:hidden}
.idle-settings-head{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;
  padding:16px 20px;border-bottom:1px solid var(--rule)}
.idle-settings-scroll{flex:1 1 auto;overflow-y:auto;padding:18px 20px}
.idle-settings-foot{flex:0 0 auto;padding:14px 20px;border-top:1px solid var(--rule)}
.idle-set-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:14px 20px}
.idle-set-row{display:flex;flex-direction:column;gap:5px;font-size:var(--t-sm)}
.idle-set-row select{padding:6px 8px;border:1px solid var(--rule-2);border-radius:var(--r-s);
  background:var(--surface);color:var(--ink);font-family:inherit;font-size:var(--t-sm)}
.idle-set-label{font-weight:600;color:var(--ink)}
.idle-set-toggle{flex-direction:row;align-items:center;flex-wrap:wrap;gap:8px}
.idle-set-toggle .idle-set-label{order:1}
.idle-set-toggle input[type=checkbox]{order:0;width:16px;height:16px;accent-color:var(--process)}
.idle-set-toggle .idle-set-help{order:2;flex-basis:100%}
.idle-set-help{font-size:11px;color:var(--ink-3)}
@media (max-width:560px){
  .idle-settings-overlay{padding:0}
  .idle-settings-modal{width:100%;height:100%;max-height:100%;border-radius:0}
}

/* full screen (core/pseudo-editor.js's enterFullscreen/exitFullscreen) —
   ".pe-fs-overlay" is appended straight to <body>, and the live
   ".pe-split" node is moved into its ".pe-fs-body" (see
   pseudo-editor.js's header comment: ".stage-inner"'s permanent
   post-animation `transform` would otherwise turn position:fixed
   descendants left inside #stage into something pinned to the stage's
   own box instead of the viewport, and every Step/Run/Reset would
   discard the fullscreen state along with #stage's whole innerHTML). */
.pe-fs-btn{font-size:14px;line-height:1;padding:5px 9px}
.pe-dl-btn,.pe-ul-btn{font-family:var(--f-mono);font-size:10.5px;letter-spacing:.02em;text-transform:none;padding:5px 10px}
/* Groups Upload/Download/Full-screen as one unit pinned to the right of
   .instr-title, so adding the Upload button (section 29) doesn't throw
   off .instr-title's justify-content:space-between across a growing
   number of top-level items — this is the only flex item that needs
   pushing right, the title/status stay where space-between already
   puts them. */
.pe-title-actions{display:flex;align-items:center;gap:6px;margin-left:auto}

/* ---- .psd filename bar (section 29 items 6-8/14) --------------------
   Sits between .instr-title and the editor itself — the file this
   editor's contents came from/will be saved as, editable in place, with
   an asterisk while the editor differs from the last loaded/saved
   version. Shares the dark "instrument panel" palette .instr-title
   already uses (--on-chrome-2/--chrome-2/--chrome-line), never the
   light --surface tokens, since it lives inside the same .instr panel. */
.pe-filebar{display:flex;align-items:center;gap:7px;margin:-2px 0 12px;
  font-family:var(--f-mono);font-size:11px;color:var(--on-chrome-2)}
.pe-file-icon{font-size:13px;line-height:1}
.pe-filename-input{flex:0 1 220px;min-width:0;background:var(--chrome-2);
  border:1px solid var(--chrome-line);color:var(--on-chrome);
  font-size:11px;padding:4px 8px;border-radius:var(--r-s)}
.pe-filename-input:focus{outline:2px solid var(--process);outline-offset:1px}
.pe-dirty{color:var(--no);font-weight:700}

/* ---- Files panel (core/pseudo-idle.js's filesView()) ----
   The "live virtual environment" for Cambridge 2210 file handling: a
   clickable row per simulated file, an inline content preview below
   it. Lives inside the same .card the other Idle tabs (Output,
   Variables, Trace table) already use, so it needs no card chrome of
   its own — just the list/preview layout and a restrained, reduced-
   motion-respecting entrance for a newly-created file. */
.pf-upload-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding-bottom:12px;border-bottom:1px solid var(--rule)}
.pf-filelist{display:flex;flex-direction:column;gap:6px}
.pf-file-item{display:flex;align-items:center;gap:9px;width:100%;text-align:left;
  background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-s);
  padding:9px 11px;cursor:pointer;transition:background .15s ease,border-color .15s ease;
  animation:fadeInUp .25s cubic-bezier(.16,1,.3,1) both}
.pf-file-item:hover{background:var(--surface-3)}
.pf-file-item.on{border-color:var(--process);background:var(--process-bg)}
.pf-file-icon{font-size:15px;line-height:1;flex:0 0 auto}
.pf-file-name{flex:1;font-size:13px;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-preview{margin-top:14px;border-top:1px solid var(--rule);padding-top:14px}
.pf-preview-head{display:flex;align-items:center;gap:9px;margin-bottom:10px}
.pf-preview-body{background:var(--surface-3);border-radius:var(--r-s);padding:11px 13px;
  white-space:pre-wrap;word-break:break-word;margin:0}
.pf-foot{margin-top:16px;padding-top:12px;border-top:1px solid var(--rule)}
@media (prefers-reduced-motion:reduce){
  .pf-file-item{animation:none}
}
@media (max-width:480px){
  .instr-title{flex-wrap:wrap;gap:6px 0}
  .pe-title-actions{margin-left:0;flex-wrap:wrap}
  .pe-filename-input{flex:1 1 auto}
}
.pe-fs-overlay{
  position:fixed;inset:0;z-index:1200;background:var(--surface-2);
  display:flex;flex-direction:column;
}
body.pe-fs-lock{overflow:hidden}
.pe-fs-bar{
  flex:0 0 auto;display:flex;align-items:center;gap:14px;padding:10px 22px;
  background:var(--surface);border-bottom:1px solid var(--rule);box-shadow:var(--shadow);
}
.pe-fs-logo{height:28px;width:auto;display:block}
.pe-fs-tag{font-size:var(--t-sm);color:var(--ink-3);font-weight:600;letter-spacing:.01em}
.pe-fs-exit{margin-left:auto}
.pe-fs-body{flex:1;min-height:0;overflow:hidden;padding:20px 26px;display:flex}
.pe-fs-body .pe-split{flex:1;min-height:0;align-items:stretch}
.pe-fs-body .pe-split > div{min-height:0;display:flex;flex-direction:column}
.pe-fs-body .pe-split .instr-title{flex:0 0 auto}
.pe-fs-body .pe-split .pe-wrap{flex:1;min-height:0}
.pe-fs-body .pe-split .pe-ro-wrap{flex:1;min-height:0;height:auto!important}
.pe-fs-body .pe-split .pe-gutter,
.pe-fs-body .pe-split .pe-body{height:auto!important}
.pe-fs-body .pe-split .transport{flex:0 0 auto}
.pe-fs-body .pe-split > div:last-child > .seg{flex:0 0 auto}
.pe-fs-body .pe-split > div:last-child > .card{flex:1;min-height:0;height:auto!important;overflow:auto}
@media(max-width:900px){
  .pe-fs-body{overflow:auto;display:block;padding:14px}
  .pe-fs-body .pe-split{display:block}
  .pe-fs-body .pe-split .pe-gutter,
  .pe-fs-body .pe-split .pe-body,
  .pe-fs-body .pe-split .pe-ro-wrap{height:420px!important}
  .pe-fs-body .pe-split > div:last-child > .card{height:420px!important}
  .pe-fs-tag{display:none}
}
/* The fixed 420px stacked panels above are tuned for a tall-enough
   viewport; on a short landscape phone three of them (editor, output,
   plus the line-number gutter) can together need more height than the
   whole screen, with .pe-fs-body's own overflow:auto as the only
   fallback and no sense that more exists below. Shrinking them when the
   viewport itself is short (independent of the ≤900px WIDTH rule above —
   this fires whenever height is tight, landscape or not) keeps the
   editor/output genuinely usable instead of merely scrollable. */
@media (max-height:560px){
  .pe-fs-body .pe-split .pe-gutter,
  .pe-fs-body .pe-split .pe-body,
  .pe-fs-body .pe-split .pe-ro-wrap,
  .pe-fs-body .pe-split > div:last-child > .card{height:min(38vh,420px)!important}
}

.svgwrap{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-m);padding:14px;overflow-x:auto}
.svgwrap.dark{background:var(--chrome);border-color:var(--chrome-line)}

/* Cursor-following tooltip (Ch3 Von Neumann system diagram and any future
   click/hover diagram). Positioned absolute against .vn-wrap (NOT fixed
   against the viewport — a transformed ancestor elsewhere in the page
   chrome turns out to redefine the containing block for position:fixed,
   which silently teleports a fixed tooltip away from the cursor; absolute
   positioning computed against a rect we fetch fresh in JS sidesteps that
   entirely) and repositioned on every pointer move, rather than a static
   feedback box the eye has to leave the diagram to read. pointer-events:
   none so it never itself becomes the thing blocking the hover it is
   reporting on. */
.vn-wrap{position:relative}
.vn-tip{
  position:absolute;z-index:200;max-width:280px;pointer-events:none;
  background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-m);
  box-shadow:var(--shadow-lift);padding:11px 14px;font-size:var(--t-sm);color:var(--ink);line-height:1.45
}
.vn-tip .eyebrow{font-size:10px;margin-bottom:0}

/* ---- 3.1.2b FDE worked-example diagram — a token flies between SVG
   register/memory boxes along whichever bus is active; sharing the
   same --bus-* palette as the 3.1.1 diagram. fde2-pop is the same
   "value just landed" pop the register boxes used before this became
   a diagram; fde2-pulse is the CU/CIR "thinking" glow during DECODE,
   built the same way as the existing aluAdderPulse filter-pulse. ---- */
.fde2-bus{display:inline-flex;align-items:center;gap:7px;font-family:var(--f-mono);font-size:11px;letter-spacing:.04em;
  text-transform:uppercase;font-weight:700;padding:5px 12px;border-radius:var(--r-full);border:1.5px solid;background:var(--surface)}
.fde2-bus .dot{width:9px;height:9px;border-radius:50%;display:inline-block;animation:fde2Pulse 1.1s ease-in-out infinite}
@keyframes fde2Pulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.4);opacity:.5}}
.fde2-pop{animation:popIn .4s cubic-bezier(.34,1.56,.64,1) both;transform-box:fill-box;transform-origin:center}
.fde2-pulse{animation:fde2FilterPulse 1s ease-in-out infinite}
@keyframes fde2FilterPulse{0%,100%{filter:drop-shadow(0 0 0 transparent)}50%{filter:drop-shadow(0 0 7px var(--bus-ctrl))}}
.fde2-token.think{animation:fde2Think .9s ease-in-out infinite}
@keyframes fde2Think{0%,100%{r:9}50%{r:14}}

/* ---- 3.1.2b FDE live simulator — a monospace code editor, a
   memory grid that scales to whatever the student assembles (the
   old fixed 6-box layout only fit one hard-coded program), and a
   scrolling cycle log (reuses .rt-log-panel/.rt-log from the ch2
   packet router as-is). The diagram itself, the token flight and
   the CU/ALU pulse all reuse .fde2-pop/.fde2-pulse/.fde2-token/
   .fde2-bus untouched from the block above. ---- */
.code-editor{font-family:var(--f-mono);font-size:13px;line-height:1.55;white-space:pre;tab-size:4;min-height:170px}
.asm-editor{display:flex;flex-direction:column;gap:10px;margin-bottom:16px}
.asm-editor-row{display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end}
.asm-input-label{display:flex;flex-direction:column;gap:4px;font-size:var(--t-sm);color:var(--ink-2);flex:1 1 220px;min-width:180px}
.asm-input-label input{width:100%}
.asm-mem-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(72px,1fr));gap:6px;align-content:start}
.asm-mem-cell{border:1.5px solid var(--rule-2);border-radius:var(--r-s);background:var(--surface);padding:5px 7px;
  display:flex;flex-direction:column;gap:2px;transition:border-color .2s ease,box-shadow .2s ease}
.asm-mem-cell.pc{border-color:var(--process);box-shadow:0 0 0 2px var(--process-bg)}
.asm-mem-cell.active{animation:asmCellPulse .9s ease-in-out infinite}
.asm-mem-cell.empty{opacity:.4}
.asm-mem-cell.empty .asm-mem-val{color:var(--ink-3)}
@keyframes asmCellPulse{0%,100%{transform:scale(1)}50%{transform:scale(1.05)}}
.asm-mem-addr{font-family:var(--f-mono);font-size:9.5px;letter-spacing:.03em;color:var(--ink-3)}
.asm-mem-addr i{font-style:normal;color:var(--process)}
.asm-mem-val{font-family:var(--f-mono);font-size:11px;line-height:1.25;color:var(--ink);word-break:break-word}
.asm-output{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-bottom:12px}
.asm-output .eyebrow{width:100%;margin-bottom:2px}

/* ---- LMC-style three-panel layout (Program / CPU / RAM+IO), for the
   "Write and run your own program" step — the arrangement most Little
   Man Computer simulators use, built from this platform's own .card
   and .eyebrow rather than a new visual language. .lmc-shell>.card
   overrides the general .card+.card sibling margin (tokens.css above)
   which would otherwise misalign the three panels vertically once
   they sit side by side in a grid instead of stacked in normal flow. */
.lmc-shell{display:grid;grid-template-columns:1fr 1.25fr .95fr;gap:14px;align-items:start}
.lmc-shell>.card{margin-top:0}
.lmc-panel .eyebrow{margin-bottom:10px}
@media (max-width:1020px){
  .lmc-shell{grid-template-columns:1fr 1fr}
  .lmc-shell>.card:first-child{grid-column:1 / -1}
}
@media (max-width:640px){
  .lmc-shell{grid-template-columns:1fr}
  .lmc-shell>.card:first-child{grid-column:auto}
}

/* ---- 3.1.3 cores/cache/clock performance widget — three live panels
   (cores lighting up, a pulsing clock ring, a cache fill + hit/miss
   dot strip), all pure CSS animation driven by an inline
   animation-duration set from the current clock slider in JS, so
   there is no setInterval/rAF loop to leak when the student leaves
   the step — the same "re-render on state change" pattern the rest
   of the platform already uses. Reuses .bar/.bar.light, .card and
   .eyebrow rather than inventing new base vocabulary. ---- */
.perf-panels{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
@media (max-width:760px){.perf-panels{grid-template-columns:1fr}}
.perf-panel{border:1px solid var(--rule-2);border-radius:var(--r-m);background:var(--surface);padding:14px}
.perf-panel h5{margin:0 0 10px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);font-weight:700}

.perf-cores-grid{display:flex;flex-wrap:wrap;gap:6px}
.perf-core{width:30px;height:30px;border-radius:7px;display:flex;align-items:center;justify-content:center;
  font-family:var(--f-mono);font-size:10.5px;font-weight:700;border:1.5px solid var(--rule-2);
  background:var(--surface-3);color:var(--ink-3)}
.perf-core.active{background:var(--process);border-color:var(--process);color:#fff;animation:perfPulse 1s ease-in-out infinite}
.perf-core.idle{opacity:.4}
@keyframes perfPulse{0%,100%{transform:scale(1);box-shadow:0 0 0 0 rgba(37,99,235,.35)}50%{transform:scale(1.1);box-shadow:0 0 0 5px rgba(37,99,235,0)}}

.perf-clock-ring{width:72px;height:72px;margin:4px auto 0;border-radius:50%;border:3px solid var(--process);
  display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--process);
  font-family:var(--f-mono);animation:perfPulse 1s ease-in-out infinite}
.perf-clock-ring b{font-size:15px;line-height:1.1}
.perf-clock-ring span{font-size:9px;letter-spacing:.05em;color:var(--ink-3)}

.perf-cache-fill-track{height:10px;border-radius:var(--r-full);background:var(--surface-3);overflow:hidden}
.perf-cache-fill{height:100%;border-radius:var(--r-full);background:var(--mm-1);transition:width .25s ease}
.perf-dots{display:flex;flex-wrap:wrap;gap:4px}
.perf-dot{width:9px;height:9px;border-radius:50%;display:inline-block}
.perf-dot.hit{background:var(--ok)}
.perf-dot.miss{background:var(--warn)}

.perf-subbar{margin:4px 0 10px}
.perf-subbar .row{display:flex;align-items:center;gap:8px;margin:5px 0}
.perf-subbar .row .lbl{flex:0 0 44px;font-size:11.5px;color:var(--ink-2)}
.perf-subbar .row .bar{flex:1 1 auto;height:6px}
.perf-subbar .row .val{flex:0 0 34px;text-align:right;font-family:var(--f-mono);font-size:11px;color:var(--ink-3)}

@media (prefers-reduced-motion: reduce){
  .perf-core.active,.perf-clock-ring{animation:none}
}

/* ---- perf-widget input controls: compact circular "dial" gauges
   replacing three full-width range sliders, so the control row
   takes noticeably less vertical space. A custom SVG control, not
   a native <input> — see W.perfDialsBind/perfDialAngle in
   ch3-screens.js for the drag/keyboard logic. ---- */
.perf-dial-row{display:flex;flex-wrap:wrap;justify-content:center;gap:36px;margin:18px 0 8px}
.perf-dial-wrap{display:flex;flex-direction:column;align-items:center;gap:8px}
.perf-dial-controls{display:flex;align-items:center;justify-content:center;gap:10px}
.perf-dial{position:relative;width:132px;height:132px;cursor:pointer;touch-action:none;border-radius:50%;-webkit-user-select:none;user-select:none;flex:none}
.perf-dial:focus-visible{outline:2px solid var(--process);outline-offset:3px;border-radius:50%}
.perf-dial svg{display:block;width:100%;height:100%}
.perf-dial-inner{position:relative;width:132px;height:132px;display:block}
.perf-dial-center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;font-family:var(--f-mono)}
.perf-dial-center b{font-size:24px;line-height:1.1;color:var(--ink-1)}
.perf-dial-center span{font-size:11px;letter-spacing:.04em;color:var(--ink-3);text-transform:uppercase;margin-top:2px}
.perf-dial-label{font-size:12.5px;font-weight:700;color:var(--ink-2)}
.perf-dial-step{flex:none;width:32px;height:32px;border-radius:50%;border:1.5px solid var(--rule-2);background:var(--surface);
  color:var(--process);font-family:var(--f-mono);font-size:17px;line-height:1;font-weight:700;cursor:pointer;
  display:flex;align-items:center;justify-content:center;transition:background .15s ease,border-color .15s ease}
.perf-dial-step:hover{background:var(--surface-3);border-color:var(--process)}
.perf-dial-step:active{background:var(--process);color:#fff;border-color:var(--process)}
.perf-dial-step:focus-visible{outline:2px solid var(--process);outline-offset:2px}
@media (max-width:600px){
  .perf-dial-row{gap:20px}
  .perf-dial,.perf-dial-inner{width:104px;height:104px}
  .perf-dial-center b{font-size:19px}
  .perf-dial-controls{gap:6px}
  .perf-dial-step{width:28px;height:28px;font-size:15px}
}
@media (max-width:420px){
  .perf-dial-row{gap:6px}
  .perf-dial-controls{gap:4px}
  .perf-dial,.perf-dial-inner{width:64px;height:64px}
  .perf-dial-center b{font-size:13px}
  .perf-dial-center span{font-size:8px}
  .perf-dial-step{width:20px;height:20px;font-size:13px}
  .perf-dial-label{font-size:10.5px}
}

/* ---- 3.1.3 overclocking lab: reuses .perf-dial/.perf-dial-step
   (an added rated-limit tick and red-over-limit fill, via JS) and
   .perf-subbar for the two risk meters, so this doesn't invent a
   second dial vocabulary. See W.ocRender/W.ocDialBind in
   ch3-screens.js. ---- */
.oc-panel{position:relative;border:1px solid var(--rule-2);border-radius:var(--r-m);background:var(--surface);padding:18px;margin-top:14px}
.oc-dial-area{display:flex;flex-direction:column;align-items:center;text-align:center;transition:opacity .2s ease}
.oc-badge{display:inline-block;padding:4px 12px;border-radius:var(--r-full);font-family:var(--f-mono);font-size:11px;font-weight:700;letter-spacing:.02em}
.oc-badge.ok{background:rgba(22,163,74,.12);color:var(--ok)}
.oc-badge.warn{background:rgba(217,119,6,.12);color:var(--warn)}
.oc-badge.crit{background:rgba(220,38,38,.14);color:var(--no)}

.oc-cycle-area{display:flex;align-items:center;gap:14px;margin-top:16px;width:100%;max-width:420px;flex-wrap:wrap;justify-content:center}
.oc-cycle-ring{flex:none;width:52px;height:52px;border-radius:50%;border:3px solid var(--process);display:flex;align-items:center;justify-content:center;
  font-family:var(--f-mono);font-size:10px;font-weight:700;color:var(--process);animation:perfPulse 1s ease-in-out infinite;transition:border-color .2s ease,color .2s ease}
.oc-cycle-ring.is-hot{border-color:var(--no);color:var(--no)}
.oc-instr-wrap{flex:1 1 180px;min-width:160px;text-align:left}
.oc-instr-track{height:12px;border-radius:var(--r-full);background:var(--surface-3);overflow:hidden}
.oc-instr-fill{height:100%;width:100%;border-radius:var(--r-full);background:linear-gradient(90deg,var(--input),var(--process));
  animation:ocInstrFill 1s linear infinite;transform-origin:left center}
@keyframes ocInstrFill{0%{transform:scaleX(0)}48%{transform:scaleX(1)}100%{transform:scaleX(1)}}
.oc-instr-label{display:block;margin-top:4px;font-size:10.5px;color:var(--ink-3)}
.oc-cycle-area.is-desync .oc-instr-label{color:var(--no);font-weight:600}

.oc-meters{margin-top:16px}
.oc-meters .perf-subbar{margin:0}
.oc-meters .perf-subbar .row .lbl{flex:0 0 150px}
@media (max-width:480px){ .oc-meters .perf-subbar .row .lbl{flex-basis:112px;font-size:11px} }

.oc-panel.is-crashed .oc-dial-area,.oc-panel.is-crashed .oc-meters{opacity:.25;pointer-events:none;filter:saturate(.3)}
.oc-crash{margin-top:14px;border:1.5px solid var(--no);border-radius:var(--r-m);background:rgba(220,38,38,.08);padding:16px;text-align:center;animation:ocGlitch 2.4s ease-in-out infinite}
.oc-crash b{color:var(--no);font-family:var(--f-mono);font-size:13px;letter-spacing:.02em}
.oc-crash p{margin:8px 0 14px;font-size:13px;color:var(--ink-2)}
@keyframes ocGlitch{0%,100%{transform:translateX(0)}92%{transform:translateX(0)}93%{transform:translateX(-2px)}94%{transform:translateX(2px)}95%{transform:translateX(-1px)}96%{transform:translateX(0)}}

@media (prefers-reduced-motion: reduce){
  .oc-cycle-ring,.oc-instr-fill,.oc-crash{animation:none}
}

/* ---- 3.1.3 core communication channels (Figures 3.4/3.5): a
   complete graph of pairwise "channels" between cores. ---- */
.occ-diagram{display:flex;justify-content:center;margin-bottom:10px}
.occ-diagram svg{max-width:280px;width:100%;height:auto}

/* ---- 3.1.2a system-buses picker — three clickable lane cards
   (address/data/control), an animated flow lane whose dots travel
   one-way or both-ways depending on the bus picked, and a small
   width workbench (address: 2^n locations; data: word as byte
   tiles; control: fixed 8 pulsing signal lines). Same --bus-*
   palette as the 3.1.1 diagram and the FDE worked example, so the
   colour language is consistent everywhere a bus appears. ---- */
.bus-picker{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:10px 0 4px}
.bus-pick{display:flex;flex-direction:column;align-items:center;gap:6px;text-align:center;
  border:1.5px solid var(--rule-2);border-radius:var(--r-m);background:var(--surface);
  padding:14px 8px 12px;cursor:pointer;transition:border-color .15s ease,transform .12s ease,background .15s ease;
  font-family:inherit;color:var(--ink)}
.bus-pick:hover{transform:translateY(-2px)}
.bus-pick:focus-visible{outline:2px solid var(--process);outline-offset:2px}
.bus-pick-icon{width:26px;height:26px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.bus-pick b{font-size:var(--t-sm)}
.bus-pick .bus-pick-dir{font-family:var(--f-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3)}
.bus-pick.addr{border-color:var(--bus-addr-line,var(--rule-2))}
.bus-pick.data{border-color:var(--bus-data-line,var(--rule-2))}
.bus-pick.ctrl{border-color:var(--bus-ctrl-line,var(--rule-2))}
.bus-pick.on{color:#fff;transform:translateY(-2px)}
.bus-pick.addr.on{background:var(--bus-addr);border-color:var(--bus-addr)}
.bus-pick.data.on{background:var(--bus-data);border-color:var(--bus-data)}
.bus-pick.ctrl.on{background:var(--bus-ctrl);border-color:var(--bus-ctrl)}
.bus-pick.on .bus-pick-dir{color:rgba(255,255,255,.85)}

.bus-detail{border:1px solid var(--rule-2);border-radius:var(--r-m);background:var(--surface);padding:16px 18px;margin-top:4px}
.bus-detail .eyebrow{margin-bottom:6px}

.bus-lane{display:flex;align-items:center;gap:10px;margin:14px 0 4px}
.bus-lane-end{flex:0 0 auto;font-family:var(--f-mono);font-size:11px;font-weight:700;letter-spacing:.04em;
  text-transform:uppercase;padding:7px 11px;border-radius:var(--r-s);background:var(--surface-3);color:var(--ink-2);white-space:nowrap}
.bus-lane-track{position:relative;flex:1 1 auto;height:46px;display:flex;flex-direction:column;justify-content:center;gap:5px;padding:0 2px}
.bus-wire{height:3px;border-radius:2px;background:var(--rule-2);opacity:.4}
.bus-dot{position:absolute;top:50%;width:11px;height:11px;margin-top:-5.5px;border-radius:50%;background:var(--ink-3);
  box-shadow:0 0 0 3px var(--surface)}
.bus-dot.fwd{left:0;animation:busFlowFwd 1.7s linear infinite}
.bus-dot.back{left:100%;animation:busFlowBack 1.7s linear infinite}
@media (prefers-reduced-motion: reduce){.bus-dot.fwd,.bus-dot.back{animation:none;left:50%}}
@keyframes busFlowFwd{0%{left:0;opacity:0}8%{opacity:1}92%{opacity:1}100%{left:calc(100% - 11px);opacity:0}}
@keyframes busFlowBack{0%{left:calc(100% - 11px);opacity:0}8%{opacity:1}92%{opacity:1}100%{left:0;opacity:0}}

.bus-workbench{margin-top:16px;padding-top:14px;border-top:1px dashed var(--rule-2)}
.bus-formula{font-family:var(--f-mono);font-size:var(--t-lg);font-weight:700;margin-top:10px;color:var(--ink)}
.bus-formula .eq{color:var(--ink-3);font-weight:400;margin:0 8px}
.bus-formula .hit{padding:2px 8px;border-radius:var(--r-s);animation:popIn .3s cubic-bezier(.34,1.56,.64,1) both}

.bus-bytes{display:flex;flex-wrap:wrap;gap:10px;margin-top:12px}
.bus-byte{display:flex;gap:3px;padding:6px;border-radius:var(--r-s);border:1.5px solid var(--rule-2);background:var(--surface-3)}
.bus-byte .bit-tile.mini{width:22px;height:22px;font-size:10px;border-radius:5px;animation:bitEnter .4s cubic-bezier(.34,1.56,.64,1) both}

.bus-ctrl-grid{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.bus-ctrl-chip{display:flex;align-items:center;gap:6px;font-family:var(--f-mono);font-size:10.5px;letter-spacing:.03em;
  padding:6px 10px;border-radius:var(--r-full);border:1.5px solid var(--bus-ctrl-line,var(--bus-ctrl));color:var(--ink-2);background:var(--surface-3)}
.bus-ctrl-chip .dot{width:7px;height:7px;border-radius:50%;background:var(--bus-ctrl);animation:fde2Pulse 1.2s ease-in-out infinite}

@media (max-width:640px){
  .bus-picker{grid-template-columns:1fr}
  .bus-lane{flex-direction:column;align-items:stretch}
  .bus-lane-end{align-self:flex-start}
}

/* ---- 3.1.4 anatomy-of-an-instruction: opcode/operand split as two
   coloured chunks, each explained by a speech-bubble callout (the
   coursebook's own diagram convention), then fed into the .bus-lane
   travelling-dot flow defined just above. ---- */
.ia-instr{display:flex;gap:10px;margin:14px 0 4px;flex-wrap:wrap}
.ia-chunk{font-family:var(--f-mono);font-size:22px;font-weight:700;padding:10px 20px;border-radius:var(--r-m);color:#fff;letter-spacing:.02em}
.ia-chunk.ia-opcode{background:var(--process)}
.ia-chunk.ia-operand{background:var(--output)}

.ia-bubbles{display:flex;gap:16px;flex-wrap:wrap;margin:20px 0 18px}
.ia-bubble{position:relative;flex:1 1 220px;border:1.5px solid var(--rule-2);border-radius:var(--r-m);background:var(--surface);padding:12px 14px 13px;animation:popIn .3s cubic-bezier(.34,1.56,.64,1) both}
.ia-bubble::before{content:'';position:absolute;top:-8px;left:22px;width:14px;height:14px;background:var(--surface);
  border-left:1.5px solid var(--rule-2);border-top:1.5px solid var(--rule-2);transform:rotate(45deg)}
.ia-bubble b{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}
.ia-bubble-opcode b{color:var(--process)}
.ia-bubble-operand b{color:var(--output)}
.ia-bubble p{margin:0;font-size:13.5px;color:var(--ink-2);line-height:1.5}

.ia-chips{display:flex;flex-wrap:wrap;gap:14px;margin:14px 0 4px}
.ia-chip{flex:1 1 200px;border:1.5px solid var(--rule-2);border-radius:var(--r-m);background:var(--surface);padding:14px;text-align:center}
.ia-chip-name{font-weight:700;font-size:14px}
.ia-chip-isa{font-family:var(--f-mono);font-size:10.5px;letter-spacing:.03em;color:var(--ink-3);text-transform:uppercase;margin-top:2px}
.ia-chip-instr{font-family:var(--f-mono);font-size:16px;font-weight:700;margin-top:10px;padding:6px 0;border-radius:var(--r-s);background:var(--surface-3)}
.ia-chip-ok{margin-top:10px;font-size:12px;font-weight:700;color:var(--ok);animation:popIn .3s cubic-bezier(.34,1.56,.64,1) both}
@media (prefers-reduced-motion: reduce){ .ia-bubble,.ia-chip-ok{animation:none} }

/* ---- 3.1.5 embedded systems: shared small "eyebrow tag" caption
   used above a diagram to name its coursebook source (a real figure
   number, or a plain description when no figure was supplied). ---- */
.eu-tag{display:inline-block;font-family:var(--f-mono);font-size:10px;text-transform:uppercase;letter-spacing:.05em;
  color:var(--ink-3);background:var(--surface-3);padding:4px 10px;border-radius:var(--r-full);margin-bottom:10px}

/* ---- 3.1.5 embedded systems: hardware bases — a chip outline
   holding "on chip" pills, with anything a microprocessor still
   needs shown as separate dashed pills underneath it. ---- */
.eb-wrap{display:flex;flex-direction:column;gap:10px}
.eb-chip{border:2px solid var(--process-line);border-radius:var(--r-m);background:var(--process-bg);padding:16px;
  animation:popIn .3s cubic-bezier(.34,1.56,.64,1) both}
.eb-chip-label{font-weight:700;font-size:14px;margin-bottom:10px;text-align:center;color:var(--ink)}
.eb-chip-pills{display:flex;flex-wrap:wrap;gap:8px;justify-content:center}
.eb-pill{font-family:var(--f-mono);font-size:11px;font-weight:700;letter-spacing:.03em;padding:6px 12px;
  border-radius:var(--r-full);animation:popIn .3s cubic-bezier(.34,1.56,.64,1) both}
.eb-pill.on{background:var(--process);color:#fff}
.eb-pill.off{background:transparent;border:1.5px dashed var(--rule-2);color:var(--ink-3)}
.eb-offchip-label{font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--ink-3);text-align:center;margin-top:4px}
.eb-offchip-row{display:flex;flex-wrap:wrap;gap:8px;justify-content:center}
@media (prefers-reduced-motion: reduce){ .eb-chip,.eb-pill{animation:none} }

/* ---- 3.1.5 embedded systems: benefits/drawbacks grid — reuses the
   platform's own .flashcard 3D-flip mechanic (built for glossary
   review) at a smaller, grid-friendly size, with each card flippable
   independently in either direction. ---- */
.ebd-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;margin:14px 0}
/* .flashcard-wrap's own max-width:480px;margin:0 auto (sized for a single
   centred card in normal document flow) collapses to 0 width as a CSS grid
   item instead: auto margins on a grid item disable the default "stretch"
   sizing, and .flashcard's own content is 0 (both faces are position:
   absolute, so they don't contribute to its intrinsic size) — so it shrinks
   to nothing. Forcing an explicit width and no auto margins keeps it filled
   to its grid cell instead. */
.ebd-grid .flashcard-wrap{max-width:none;width:100%;margin:0;perspective:1000px}
.ebd-grid .flashcard{min-height:180px}
.ebd-grid .flashcard-face{padding:16px 14px}
@media (max-width:480px){
  .ebd-grid{grid-template-columns:1fr 1fr}
}

/* ---- 3.1.5 embedded systems: usecase input/output hotspots — a
   .bus-lane-end promoted to a real button, with a CSS-only tooltip
   on hover/focus (same technique as .usb-wire-hot/.usb-tip above),
   rather than a plain unlabelled bus lane. ---- */
.bus-lane-end.eu-hot{border:1px solid transparent;margin:0;font:inherit;cursor:pointer;position:relative;
  font-family:var(--f-mono);letter-spacing:.04em;text-transform:uppercase}
.bus-lane-end.eu-hot:focus-visible{outline:2px solid var(--process);outline-offset:2px}
.eu-tip{position:absolute;left:50%;bottom:calc(100% + 9px);transform:translate(-50%,4px);
  display:flex;flex-direction:column;gap:3px;width:max-content;max-width:220px;
  background:#1e293b;color:#fff;padding:8px 12px;border-radius:var(--r-s);
  white-space:normal;text-align:left;line-height:1.35;opacity:0;visibility:hidden;
  transition:opacity .15s ease,transform .15s ease;pointer-events:none;box-shadow:var(--shadow-lift);z-index:5}
.eu-tip b{display:block;font-size:11.5px;font-weight:700;text-transform:none;letter-spacing:0}
.eu-tip span{font-size:10.5px;font-weight:400;color:#cbd5e1;text-transform:none;letter-spacing:0}
.eu-tip::after{content:'';position:absolute;top:100%;left:50%;transform:translateX(-50%);
  border:5px solid transparent;border-top-color:#1e293b}
.eu-hot:hover .eu-tip,.eu-hot:focus-visible .eu-tip,.eu-hot.is-open .eu-tip{opacity:1;visibility:visible;transform:translate(-50%,0)}
.eu-group{margin-bottom:6px}
.eu-group-label{font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--ink-3);font-weight:700;margin:14px 0 2px}
.eu-group:first-of-type .eu-group-label{margin-top:0}
@media (max-width:640px){
  .eu-tip{max-width:170px;padding:6px 9px}
}

/* ============================================================
   CALC PAPER (self-marked calculation answer — ruled exercise-
   book paper styling, handwriting font, 101computing-style
   check/cross/retry/reveal action strip). --f-hand is the one
   deliberate self-hosted webfont exception in this file (see
   @font-face above) — used ONLY here, never as a body/heading
   font, so the rest of the type system is untouched.
   ============================================================ */
.calc-wrap{display:flex;gap:10px;align-items:stretch}
.calc-paper{
  flex:1;min-width:0;
  font-family:var(--f-hand);font-size:22px;line-height:36px;color:var(--ink);
  padding:10px 14px 10px 34px;border:1px solid var(--rule-2);border-radius:var(--r-m);
  background-color:var(--surface);
  /* the red margin line must be the TOP layer — multiple CSS backgrounds
     paint first-listed-on-top, and the ruled-lines layer beneath would
     otherwise sit over it. The ruled lines themselves are dotted — a
     small radial-gradient dot tiled across the row (like a fill-in-the-
     blank "...................." line) rather than a solid rule — with
     the dot pinned to the bottom edge of its own tile (at 50% 97%) so it
     lands where the old solid line sat, and the surface's own background-
     color (not an opaque fill layer) shows through everywhere else. */
  background-image:
    linear-gradient(90deg, transparent 0 26px, var(--no-line) 26px 27px, transparent 27px),
    radial-gradient(circle at 50% 97%, var(--rule-2) 1px, transparent 1.3px);
  background-size:100% 100%, 8px 36px;
  background-position:0 0, 0 4px;
  background-repeat:no-repeat, repeat;
  resize:vertical;min-height:144px;width:100%;
  box-shadow:var(--shadow);
}
[data-theme="dark"] .calc-paper{
  background-image:
    linear-gradient(90deg, transparent 0 26px, var(--no) 26px 27px, transparent 27px),
    radial-gradient(circle at 50% 97%, var(--rule-2) 1px, transparent 1.3px);
}
.calc-paper::placeholder{font-family:var(--f-body);font-size:var(--t-sm);color:var(--ink-3);line-height:1.5}
.calc-paper:focus-visible{outline:2.5px solid var(--process);outline-offset:2px}
.calc-actions{display:flex;flex-direction:column;gap:8px;justify-content:flex-start}
.calc-act{
  width:46px;height:46px;border-radius:var(--r-m);border:1px solid var(--rule-2);
  background:var(--surface);display:flex;align-items:center;justify-content:center;
  font-family:var(--f-display);font-weight:800;font-size:19px;line-height:1;
  box-shadow:var(--shadow);transition:transform .12s ease,box-shadow .12s ease;flex-shrink:0
}
.calc-act:hover{transform:translateY(-1px);box-shadow:var(--shadow-lift)}
.calc-act:active{transform:translateY(0)}
.calc-act[disabled]{opacity:.4;cursor:not-allowed;transform:none;box-shadow:var(--shadow)}
.calc-act.ok{color:var(--ok);border-color:var(--ok-line)}
.calc-act.no{color:var(--no);border-color:var(--no-line)}
.calc-act.reset{color:var(--process);border-color:var(--process-line)}
.calc-act.ans{color:var(--output);border-color:var(--output-line)}
.calc-verdict{margin-top:10px;animation:fadeInUp .3s cubic-bezier(.16,1,.3,1) both}
.calc-verdict.ok{color:var(--ok)}
.calc-verdict.no{color:var(--no)}
.calc-worked{margin-top:10px}
.calc-worked pre{
  font-family:var(--f-mono);font-size:var(--t-sm);background:var(--surface-3);
  border-radius:var(--r-s);padding:10px 12px;overflow-x:auto;white-space:pre-wrap
}
/* .wa-paper: the same ruled/handwriting .calc-paper look, reused stand-alone
   (no .calc-wrap side-button strip) for the platform's other free-text answer
   boxes — Exam practice (core/assess.js written()) and Activities (core/
   sections.js actCard()) — both shared across every chapter. Taller than a
   single-calculation box by default, since these invite a multi-sentence or
   multi-mark answer rather than one line of working. */
.wa-paper{min-height:220px}
@media (max-width:640px){
  .calc-wrap{flex-direction:column}
  .calc-actions{flex-direction:row;justify-content:stretch}
  .calc-act{flex:1;width:auto}
  .calc-paper{font-size:19px;line-height:32px;padding:8px 12px 8px 28px}
  .calc-paper{
    background-size:100% 100%, 8px 32px;
    background-position:0 0, 0 4px;
  }
}

/* ============================================================
   BIT TILES (ch1: registers, shifts, hex grouping)
   A bigger, clearly-labelled 0/1 toggle, replacing plain text/
   button-label bits. Flip/enter/drop states are pure CSS
   animations triggered by a class present on mount, so no JS
   timers are needed to keep state changes synchronous (the
   automated test suite reads the DOM immediately after a click).
   ============================================================ */
.bit-row{display:flex;gap:8px;flex-wrap:wrap;position:relative}
.bit-row.shiftable{padding:2px 54px}
.bit-tile{
  display:inline-flex;align-items:center;justify-content:center;
  width:46px;height:46px;border-radius:var(--r-m);
  font-family:var(--f-mono);font-size:var(--t-lg);font-weight:700;line-height:1;
  border:1.5px solid var(--rule-2);background:var(--surface-3);color:var(--ink-2);
  transition:background .15s ease,border-color .15s ease,color .15s ease,transform .12s ease;
  user-select:none
}
button.bit-tile{cursor:pointer}
button.bit-tile:hover{border-color:var(--process-line);transform:translateY(-2px)}
button.bit-tile:active{transform:translateY(0)}
.bit-tile.mini{width:32px;height:32px;font-size:var(--t-sm);border-radius:var(--r-s)}
.bit-tile.one{background:var(--process);color:#fff;border-color:var(--process)}
.bit-tile.sign{border-color:var(--no);box-shadow:0 0 0 2px var(--no-line)}
.bit-tile.sign.one{background:var(--no);border-color:var(--no)}
.bit-tile.flip{animation:bitFlip .38s cubic-bezier(.34,1.56,.64,1)}
.bit-tile.bit-enter{animation:bitEnter .45s cubic-bezier(.34,1.56,.64,1)}
.bit-tile.bit-drop-left,.bit-tile.bit-drop-right{position:absolute;top:2px;pointer-events:none;z-index:2}
.bit-tile.bit-drop-left{left:0;animation:bitDropLeft .8s ease forwards}
.bit-tile.bit-drop-right{right:0;animation:bitDropRight .8s ease forwards}
.bit-caption{font-family:var(--f-mono);font-size:11.5px;color:var(--ink-2);margin-top:10px;min-height:32px;line-height:1.5}
.bit-caption b{color:var(--ink)}
@keyframes bitFlip{0%{transform:scale(1) rotateX(0)}40%{transform:scale(.7) rotateX(90deg)}100%{transform:scale(1) rotateX(0)}}
@keyframes bitEnter{0%{opacity:0;transform:scale(.3)}100%{opacity:1;transform:scale(1)}}
@keyframes bitDropLeft{0%{opacity:1;transform:translateX(0) scale(1)}100%{opacity:0;transform:translateX(-46px) scale(.4)}}
@keyframes bitDropRight{0%{opacity:1;transform:translateX(0) scale(1)}100%{opacity:0;transform:translateX(46px) scale(.4)}}

.hex-digit{display:inline-flex;align-items:center;justify-content:center;min-width:42px;height:42px;padding:0 6px;
  border-radius:var(--r-s);background:var(--output-bg);color:var(--output);font-family:var(--f-mono);
  font-weight:800;font-size:var(--t-lg);border:1.5px solid var(--output-line)}
.hex-bracket{font-family:var(--f-mono);color:var(--ink-3);font-size:11px;text-align:center;margin:4px 0 10px;letter-spacing:.06em}

/* ---- long addition / subtraction ripple table (ch1: add/subtract two bytes) ----
   4 rows (carry, A, B, result) × 9 columns (overflow/underflow cell, then
   bit 7..bit 0), built column-major in the DOM so grid-auto-flow:column
   lays each column's 4 cells out top-to-bottom automatically. Every real
   value change is synchronous; the ripple is purely a staggered
   animation-delay per column (see ch1-screens.js), so there is nothing
   for a JS timer to race with the test suite over. ---- */
.add-grid{display:grid;grid-template-rows:repeat(4,auto);grid-auto-flow:column;grid-auto-columns:minmax(34px,1fr);
  gap:6px 4px;align-items:center;justify-items:center;margin:16px 0;max-width:600px;overflow-x:auto;padding-bottom:4px}
.add-carry{font-family:var(--f-mono);font-size:11px;font-weight:700;height:14px;color:transparent;
  display:flex;align-items:center;justify-content:center;min-width:16px}
.add-carry.on{color:var(--process);animation:bitEnter .3s ease both}
.add-carry.on.warn{color:var(--no);font-size:13px}
.add-result-tag{font-family:var(--f-mono);font-size:9px;color:transparent;text-align:center;font-weight:700;white-space:nowrap}
.add-result-tag.on{color:var(--no);animation:bitEnter .3s ease both}

/* ---- Unicode character -> code point -> binary flow (ch1: text) ---- */
.uni-flow{display:flex;align-items:center;justify-content:center;gap:10px;flex-wrap:wrap;
  background:var(--surface-2);border:1px solid var(--rule);border-radius:var(--r-m);padding:22px 16px}
.uni-stage{display:flex;flex-direction:column;align-items:center;gap:8px;min-width:96px}
.uni-glyph{font-family:var(--f-mono);font-size:32px;font-weight:700;color:var(--ink);
  min-width:64px;height:64px;display:flex;align-items:center;justify-content:center;
  background:var(--surface);border:1.5px solid var(--rule-2);border-radius:var(--r-m)}
.uni-bin{font-size:var(--t-sm);letter-spacing:.03em;word-break:break-all;text-align:center;max-width:230px}
.uni-arrow{color:var(--ink-3);font-size:18px}
@media(max-width:640px){.uni-flow{flex-direction:column}.uni-arrow{transform:rotate(90deg)}}

/* ---- chapter 1 mind map (chapter home hero + printable PDF) ---
   A hub-and-spoke radial map: a central "DATA REPRESENTATION" hub,
   four topics orbiting it on curved SVG connectors (drawn by
   CS.ch1.mindMapDrawLines from real measured layout — see
   chapters/ch1-screens.js), and the remaining topics continuing
   below on a simple spine. Every topic gets one of 8 categorical
   accent colours (--mm-1..--mm-8, defined in :root above) via its
   data-accent attribute, applied consistently to its badge, icon,
   connector line and hover states. --------------------------- */
.mm-wrap{margin:28px 0 8px;background:var(--surface-2);border:1px solid var(--rule);border-radius:var(--r-l);padding:30px 26px}
.mm-header{position:relative;text-align:center;margin-bottom:8px;padding:2px 10px 4px}
.mm-header .eyebrow{display:block}
.mm-chapter-pill{display:inline-block;font-family:var(--f-mono);font-size:10.5px;font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;color:var(--on-chrome);background:var(--chrome);
  padding:5px 14px;border-radius:var(--r-full);margin:10px 0 12px}
.mm-title{font-family:var(--f-display);font-weight:800;font-size:clamp(21px,3.2vw,30px);
  letter-spacing:-.01em;color:#fff;background:linear-gradient(135deg,var(--process) 0%,#1D4ED8 100%);
  display:inline-block;padding:13px 28px;border-radius:var(--r-l);box-shadow:var(--shadow-lift);margin:0 0 10px}
.mm-subtitle{color:var(--ink-2);font-size:var(--t-base);margin:0 0 2px;font-weight:600}
.mm-instruction{color:var(--ink-3);font-size:var(--t-sm);margin:0}
.mm-spark{position:absolute;color:var(--process-line);font-size:15px;opacity:.65;pointer-events:none}
.mm-spark.s1{top:2px;left:6%}
.mm-spark.s2{top:38px;right:8%;font-size:11px}
.mm-spark.s3{bottom:2px;left:24%;font-size:11px}

.mm-canvas{position:relative;margin-top:8px}
.mm-lines{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:0}
.mm-line{fill:none;stroke-width:2.25;stroke-linecap:round;opacity:.55}
.mm-dot{fill-opacity:.9;stroke:var(--surface-2);stroke-width:2}
.mm-line[data-accent="1"],.mm-dot[data-accent="1"]{stroke:var(--mm-1)} .mm-dot[data-accent="1"]{fill:var(--mm-1)}
.mm-line[data-accent="2"],.mm-dot[data-accent="2"]{stroke:var(--mm-2)} .mm-dot[data-accent="2"]{fill:var(--mm-2)}
.mm-line[data-accent="3"],.mm-dot[data-accent="3"]{stroke:var(--mm-3)} .mm-dot[data-accent="3"]{fill:var(--mm-3)}
.mm-line[data-accent="4"],.mm-dot[data-accent="4"]{stroke:var(--mm-4)} .mm-dot[data-accent="4"]{fill:var(--mm-4)}
.mm-line[data-accent="5"],.mm-dot[data-accent="5"]{stroke:var(--mm-5)} .mm-dot[data-accent="5"]{fill:var(--mm-5)}
.mm-line[data-accent="6"],.mm-dot[data-accent="6"]{stroke:var(--mm-6)} .mm-dot[data-accent="6"]{fill:var(--mm-6)}
.mm-line[data-accent="7"],.mm-dot[data-accent="7"]{stroke:var(--mm-7)} .mm-dot[data-accent="7"]{fill:var(--mm-7)}
.mm-line[data-accent="8"],.mm-dot[data-accent="8"]{stroke:var(--mm-8)} .mm-dot[data-accent="8"]{fill:var(--mm-8)}

.mm-orbit{position:relative;z-index:1;display:grid;
  grid-template-columns:1fr 220px 1fr;grid-template-rows:auto auto;
  grid-template-areas:"tl hub tr" "ml hub mr";column-gap:38px;row-gap:22px;align-items:center}
.mm-pos-tl{grid-area:tl} .mm-pos-tr{grid-area:tr} .mm-pos-ml{grid-area:ml} .mm-pos-mr{grid-area:mr}
.mm-hub{grid-area:hub;justify-self:center;text-align:center;width:200px;height:200px;border-radius:50%;
  background:radial-gradient(circle at 34% 28%,var(--surface) 0%,var(--surface-2) 72%);
  border:3px solid var(--process-line);box-shadow:var(--shadow-lift);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7px;padding:16px}
.mm-hub-icon{width:36px;height:36px;fill:none;stroke:var(--process);stroke-width:1.5}
.mm-hub-icon-text{font-family:var(--f-mono);font-size:5px;font-weight:700;fill:var(--process);stroke:none}
.mm-hub-title{font-family:var(--f-display);font-weight:800;font-size:14.5px;letter-spacing:.005em;color:var(--ink);line-height:1.22}
.mm-hub-tagline{font-size:10.5px;color:var(--ink-3);line-height:1.4;max-width:150px;margin:0}

.mm-spine{width:2px;height:26px;background:var(--rule-2);margin:16px auto}
.mm-grid-bottom{position:relative;z-index:1;display:grid;grid-template-columns:repeat(4,1fr);gap:14px}

.mm-card{position:relative;background:var(--surface);border:1.5px solid var(--rule);border-radius:var(--r-m);
  overflow:visible;display:flex;flex-direction:column;transition:border-color .15s ease,box-shadow .15s ease,transform .15s ease}
.mm-card:hover{border-color:var(--mm-card-accent,var(--process-line));box-shadow:var(--shadow-lift);transform:translateY(-2px)}
.mm-card[data-accent="1"]{--mm-card-accent:var(--mm-1);--mm-card-accent-bg:var(--mm-1-bg)}
.mm-card[data-accent="2"]{--mm-card-accent:var(--mm-2);--mm-card-accent-bg:var(--mm-2-bg)}
.mm-card[data-accent="3"]{--mm-card-accent:var(--mm-3);--mm-card-accent-bg:var(--mm-3-bg)}
.mm-card[data-accent="4"]{--mm-card-accent:var(--mm-4);--mm-card-accent-bg:var(--mm-4-bg)}
.mm-card[data-accent="5"]{--mm-card-accent:var(--mm-5);--mm-card-accent-bg:var(--mm-5-bg)}
.mm-card[data-accent="6"]{--mm-card-accent:var(--mm-6);--mm-card-accent-bg:var(--mm-6-bg)}
.mm-card[data-accent="7"]{--mm-card-accent:var(--mm-7);--mm-card-accent-bg:var(--mm-7-bg)}
.mm-card[data-accent="8"]{--mm-card-accent:var(--mm-8);--mm-card-accent-bg:var(--mm-8-bg)}
.mm-badge{position:absolute;top:-11px;left:14px;font-family:var(--f-mono);font-size:10.5px;font-weight:800;
  color:#fff;background:var(--mm-card-accent,var(--process));padding:3px 9px;border-radius:var(--r-full);
  box-shadow:var(--shadow);letter-spacing:.02em;z-index:2}
.mm-card-main{display:flex;flex-direction:column;gap:6px;text-align:left;background:none;border:0;
  cursor:pointer;padding:20px 14px 12px;font:inherit;color:inherit}
.mm-card-main:hover .mm-card-title{color:var(--mm-card-accent,var(--process))}
.mm-card-top{display:flex;align-items:center;justify-content:space-between;color:var(--mm-card-accent,var(--process))}
.mm-icon{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.mm-card-title{font-family:var(--f-display);font-weight:700;font-size:var(--t-base);line-height:1.3;transition:color .15s ease;color:var(--ink)}
.mm-card-blurb{font-size:11.5px;color:var(--ink-2);line-height:1.45}
.mm-card-state{margin-top:2px}
.mm-card-toggle{background:var(--surface-2);border:0;border-top:1px solid var(--rule);
  font-family:var(--f-mono);font-size:10.5px;letter-spacing:.04em;text-transform:uppercase;
  color:var(--ink-3);padding:7px 14px;cursor:pointer;text-align:left}
.mm-card-toggle:hover{color:var(--ink);background:var(--surface-3)}
.mm-sub{display:flex;flex-direction:column;border-top:1px solid var(--rule)}
/* display:flex/align-items/gap here are additive — a sub-item with only
   text (chapter 1's activity list) lays out exactly as before; a
   sub-item that also passes a leading .dot/.mono ref span (chapter 2's
   branch->topic mind map) gets them inline instead of stacked. */
.mm-sub-item{display:flex;align-items:center;gap:8px;background:none;border:0;border-bottom:1px solid var(--rule);text-align:left;
  padding:9px 14px;font-size:12.5px;color:var(--ink-2);cursor:pointer}
.mm-sub-item:last-child{border-bottom:0}
.mm-sub-item:hover{color:var(--mm-card-accent,var(--process));background:var(--mm-card-accent-bg,var(--process-bg))}

.mm-legend{display:flex;flex-wrap:wrap;align-items:center;gap:18px;margin-top:24px;
  padding-top:16px;border-top:1px solid var(--rule);font-size:12.5px;color:var(--ink-2)}
.mm-legend span{display:flex;align-items:center;gap:6px}
.mm-legend-dot{font-style:normal;color:var(--process);font-size:13px}
.mm-legend .btn{margin-left:auto}

@media(max-width:1100px){
  .mm-orbit{grid-template-columns:1fr 1fr;grid-template-areas:"hub hub" "tl tr" "ml mr"}
  .mm-lines{display:none}
  .mm-grid-bottom{grid-template-columns:repeat(3,1fr)}
}
@media(max-width:820px){
  .mm-orbit{grid-template-columns:1fr;grid-template-areas:"hub" "tl" "tr" "ml" "mr";justify-items:center}
  .mm-hub{width:180px;height:180px}
  .mm-grid-bottom{grid-template-columns:repeat(2,1fr)}
}
@media(max-width:520px){
  .mm-grid-bottom{grid-template-columns:1fr}
  .mm-wrap{padding:18px 14px}
  .mm-title{font-size:19px;padding:11px 18px}
  .mm-legend{gap:12px}
  .mm-legend .btn{margin-left:0}
}

/* ---- mind map: print / PDF export (window.print, no library) ---- */
.mm-print-sheet{display:none}
.mm-print-header{display:flex;align-items:center;gap:14px;border-bottom:2px solid #1D1D3C;padding-bottom:12px;margin-bottom:18px}
.mm-print-logo{width:40px;height:40px;object-fit:contain;flex:0 0 auto}
.mm-print-brand{font-family:var(--f-display);font-weight:800;font-size:16px;flex:0 0 auto}
.mm-print-brand span{color:var(--process)}
.mm-print-titles{flex:1}
.mm-print-titles h1{font-size:20px;margin:0}
.mm-print-titles p{font-size:11px;color:var(--ink-3);margin:2px 0 0;text-transform:uppercase;letter-spacing:.08em}
.mm-print-code{font-size:10px;color:var(--ink-3);flex:0 0 auto}
.mm-print-tree{column-count:2;column-gap:28px}
.mm-print-topic{break-inside:avoid;margin-bottom:14px;padding-left:9px;border-left:3px solid var(--rule-2)}
.mm-print-topic[data-accent="1"]{border-left-color:var(--mm-1)} .mm-print-topic[data-accent="2"]{border-left-color:var(--mm-2)}
.mm-print-topic[data-accent="3"]{border-left-color:var(--mm-3)} .mm-print-topic[data-accent="4"]{border-left-color:var(--mm-4)}
.mm-print-topic[data-accent="5"]{border-left-color:var(--mm-5)} .mm-print-topic[data-accent="6"]{border-left-color:var(--mm-6)}
.mm-print-topic[data-accent="7"]{border-left-color:var(--mm-7)} .mm-print-topic[data-accent="8"]{border-left-color:var(--mm-8)}
.mm-print-topic h3{font-size:13px;border-bottom:1px solid var(--rule-2);padding-bottom:4px;margin:0 0 6px}
.mm-print-topic h3 .mono{color:var(--ink-3);font-size:11px;margin-right:6px}
.mm-print-topic ul{margin:0;padding-left:18px;font-size:11.5px;color:var(--ink-2);line-height:1.7}
.mm-print-footer{display:flex;justify-content:space-between;margin-top:22px;padding-top:10px;
  border-top:1px solid var(--rule-2);font-size:10px;color:var(--ink-3)}
/* ---- Conversion Lab (ch1: six conversions, hub, explorer) ----
   Reuses .card/.kv/.fb/table.t/.bit-tile/.hex-digit throughout;
   this block only adds the handful of genuinely new shapes: the
   staggered worked-example reveal, the hub's triangle diagram and
   its accent-tinted launch cards, and a narrower hex-digit select. */
.conv-anim-row{animation:fadeInUp .35s cubic-bezier(.16,1,.3,1) both}
.conv-place-table td:nth-child(2){color:var(--ink-3)}
.hexsel{max-width:120px;font-family:var(--f-mono);font-weight:600;text-align:center}
.tri-diagram{position:relative;max-width:420px;margin:20px auto 4px;aspect-ratio:300/220}
.tri-lines{position:absolute;inset:0;width:100%;height:100%}
.tri-lines line{stroke:var(--rule-2);stroke-width:1.5}
.tri-node{position:absolute;transform:translate(-50%,-50%);padding:9px 16px;border-radius:var(--r-full);
  font-family:var(--f-display);font-weight:700;font-size:var(--t-sm);border:1.5px solid;white-space:nowrap;
  background:var(--surface);box-shadow:var(--shadow)}
.tri-node.tri-den{left:50%;top:15%;color:var(--ns-den);border-color:var(--ns-den-line);background:var(--ns-den-bg)}
.tri-node.tri-bin{left:15%;top:85%;color:var(--ns-bin);border-color:var(--ns-bin-line);background:var(--ns-bin-bg)}
.tri-node.tri-hex{left:85%;top:85%;color:var(--ns-hex);border-color:var(--ns-hex-line);background:var(--ns-hex-bg)}
.conv-hub-card{border-left:3px solid var(--rule-2);transition:border-color .15s ease,transform .15s ease}
.conv-hub-card:hover{transform:translateY(-2px)}
.conv-hub-card[data-ns-accent="den"]{border-left-color:var(--ns-den)}
.conv-hub-card[data-ns-accent="bin"]{border-left-color:var(--ns-bin)}
.conv-hub-card[data-ns-accent="hex"]{border-left-color:var(--ns-hex)}
@media (max-width:640px){
  .tri-diagram{max-width:280px}
  .tri-node{padding:7px 12px;font-size:12.5px}
}

/* ---- place-value grid (binary → denary) -----------------
   One shared CSS grid — repeat(8, minmax(0,1fr)) — carries both the
   place-value card and its bit control in the same grid cell, so the
   two rows share identical column widths and can never drift apart
   the way two independently-spaced flex rows can. */
.bit-tile.dim{opacity:.62}
.pv-grid8{display:grid;grid-template-columns:repeat(8,minmax(0,1fr));gap:6px;max-width:440px;margin-bottom:6px}
.pv-grid8-col{display:flex;flex-direction:column;align-items:stretch;gap:6px;min-width:0}
.pv-card{display:flex;flex-direction:column;align-items:center;font-family:var(--f-mono);font-size:11px;color:var(--ink-3);
  border-radius:var(--r-s);padding:4px 2px;transition:color .15s ease}
.pv-card .pv-exp{font-size:12px}
.pv-card .pv-arrow{color:var(--ns-bin);line-height:1.1}
.pv-card .pv-val{font-weight:700;color:var(--ink-2)}
.pv-grid8-col .bit-tile{width:100%;height:auto;aspect-ratio:1;padding:0;font-size:var(--t-sm)}
.pv-grid8-col.hl .pv-card{color:var(--ns-bin)}
.pv-grid8-col.hl .pv-card .pv-val{color:var(--ns-bin)}
.pv-grid8-col.hl .pv-card{animation:pvColHl 1s ease both}
@keyframes pvColHl{
  0%{background:transparent}
  35%{background:var(--ns-bin-bg);box-shadow:0 0 0 2px var(--ns-bin-line)}
  100%{background:var(--ns-bin-bg);box-shadow:0 0 0 1px var(--ns-bin-line)}
}
.terms-expansion{display:flex;align-items:center;flex-wrap:wrap;gap:8px;font-family:var(--f-mono);font-size:var(--t-base)}
.term-chip{display:inline-flex;align-items:center;justify-content:center;min-width:36px;height:32px;padding:0 8px;
  border-radius:var(--r-s);background:var(--ns-bin-bg);color:var(--ns-bin);font-weight:700;border:1px solid var(--ns-bin-line)}
.term-chip.zero{background:var(--surface-3);color:var(--ink-3);border-color:var(--rule-2)}
.term-plus{color:var(--ink-3);font-weight:600}
.term-eq{margin-left:4px;font-size:var(--t-lg)}
.conv-details{margin-top:16px;border:1px solid var(--rule-2);border-radius:var(--r-s);padding:10px 14px;background:var(--surface-3)}
.conv-details summary{cursor:pointer;font-size:var(--t-sm);font-weight:600;color:var(--ink-2);list-style:revert}
.conv-details summary:hover{color:var(--ns-bin)}
.conv-details table.t{margin-top:12px}

/* ---- division ladder (denary → binary) ------------------
   Each row is nudged rightward by a per-row inline transform (a
   "staircase" cascade, matching the hand-drawn long-division ladder
   this was modelled on) while remaining real <tr>/<th> elements —
   the Divisor | Value | Remainder columns stay exactly as before.
   Alongside it, a curved SVG arrow (rather than a straight rail)
   sweeps from the bottom row up to the top, "drawn on" with a
   stroke-dashoffset animation once the ladder reaches 0. */
.ladder-wrap{display:flex;align-items:stretch;gap:10px;overflow-x:auto}
.ladder-wrap .ladder-table{flex:1 1 auto;min-width:260px}
.ladder-table th:nth-child(3),.ladder-table td:nth-child(3){text-align:center}
.ladder-row{animation:ladderRowIn .35s cubic-bezier(.16,1,.3,1) both}
@keyframes ladderRowIn{from{opacity:0;transform:translate(var(--stair-x,0),10px)}to{opacity:1;transform:translate(var(--stair-x,0),0)}}
.ladder-rem-cell{font-family:var(--f-mono);font-weight:700}
.ladder-rem-cell.lit{animation:ladderLit .6s ease both}
.ladder-arrow-rail{position:relative;flex:0 0 40px;width:40px;background:var(--surface-3);
  border-radius:var(--r-s);border:1px solid var(--rule-2);overflow:hidden}
.ladder-curl-svg{position:absolute;inset:0;width:100%;height:100%}
.ladder-curl-path{fill:none;stroke:var(--ns-bin);stroke-width:3;stroke-linecap:round;
  stroke-dasharray:260;stroke-dashoffset:260}
.ladder-wrap.assembling .ladder-curl-path{animation:ladderCurlDraw 1.6s ease forwards}
.ladder-curl-head{fill:none;stroke:var(--ns-bin);stroke-width:3;stroke-linecap:round;opacity:0}
.ladder-wrap.assembling .ladder-curl-head{animation:ladderCurlHead .5s ease 1.25s forwards}
.ladder-answer{margin-top:16px}
@keyframes ladderLit{
  0%{background:transparent;transform:scale(1)}
  45%{background:linear-gradient(90deg,var(--ns-bin),var(--ns-den));color:#fff;transform:scale(1.14)}
  100%{background:var(--ns-bin-bg);color:var(--ns-bin);transform:scale(1)}
}
@keyframes ladderCurlDraw{from{stroke-dashoffset:260}to{stroke-dashoffset:0}}
@keyframes ladderCurlHead{from{opacity:0}to{opacity:1}}
@media (max-width:520px){
  .pv-grid8{gap:3px;max-width:100%}
  .pv-card{font-size:9px;padding:3px 1px}
  .pv-card .pv-exp{font-size:9px}
  .pv-grid8-col .bit-tile{font-size:11px}
  .ladder-arrow-rail{flex-basis:28px;width:28px}
}

/* ---- Module 1.1: "From Voltage to Binary" lab ------------ */
.volt-readout{display:flex;align-items:center;gap:10px;margin-top:6px}
.volt-v{font-family:var(--f-mono);font-weight:700;font-size:var(--t-lg);color:var(--ink-2)}
.volt-arrow{color:var(--ink-3)}
.volt-schem{display:flex;align-items:center;gap:0;margin-top:8px;flex-wrap:wrap}
.volt-schem-node{border:1.5px solid var(--rule-2);border-radius:var(--r-s);padding:8px 12px;
  font-size:var(--t-xs);text-align:center;color:var(--ink-3);background:var(--surface-3);
  transition:border-color .2s ease,color .2s ease,background .2s ease;min-width:64px}
.volt-schem-node.active{border-color:var(--process);color:var(--process);background:var(--process-bg)}
.volt-schem-line{width:26px;height:2px;background:var(--rule-2);transition:background .2s ease}
.volt-schem-line.active{background:var(--process)}
.volt-schem-switch{font-family:var(--f-mono);font-size:14px;padding:0 6px;color:var(--ink-3);letter-spacing:1px}
.volt-schem-switch.closed{color:var(--process)}
.volt-wave{width:100%;max-width:260px;height:auto;margin-top:6px}
.pcm-wave{width:100%;max-width:480px;height:auto;display:block}
.conveyor{position:relative;height:64px;margin-top:6px;overflow:hidden;border-radius:var(--r-s);
  border:1px solid var(--rule-2);background:var(--surface-3)}
.conveyor-belt{position:absolute;left:6px;right:6px;bottom:14px;height:6px;border-radius:3px;background:var(--rule-2);overflow:hidden}
.conveyor-marks{position:absolute;inset:0;background:repeating-linear-gradient(90deg,var(--ink-3) 0 8px,transparent 8px 20px);
  background-position:0 0;opacity:.5}
.conveyor.running .conveyor-marks{animation:conveyorMove 1s linear infinite}
.conveyor-box{position:absolute;left:6px;bottom:18px;width:16px;height:16px;border-radius:3px;background:var(--ink-3)}
.conveyor.running .conveyor-box{background:var(--process);animation:conveyorSlide 2s linear infinite}
@keyframes conveyorMove{from{background-position:0 0}to{background-position:-20px 0}}
@keyframes conveyorSlide{0%{left:6px}100%{left:calc(100% - 22px)}}
.sig-pipe{display:flex;align-items:center;flex-wrap:wrap;gap:4px;margin-top:8px}
.sig-stage{opacity:0;visibility:hidden;border:1.5px solid var(--rule-2);border-radius:var(--r-s);
  padding:8px 10px;text-align:center;min-width:88px;font-size:var(--t-xs);color:var(--ink-3);background:var(--surface-3);
  font-family:inherit;cursor:pointer;transition:border-color .2s ease,color .2s ease,background .2s ease,transform .12s ease}
.sig-stage.shown{opacity:1;visibility:visible}
.sig-stage:hover{border-color:var(--process-line);transform:translateY(-1px)}
.sig-stage:active{transform:translateY(0)}
.sig-stage:focus-visible{outline:2px solid var(--process);outline-offset:2px}
.sig-stage.active{border-color:var(--process);color:var(--process);background:var(--process-bg);
  animation:sigStageGlow 1.6s ease-in-out infinite}
.sig-stage-icon{display:block;width:20px;height:20px;margin:0 auto 4px;fill:none;stroke:currentColor;
  stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.sig-num{display:block;font-family:var(--f-mono);font-weight:700;font-size:11px;color:var(--ink-3);margin-bottom:2px}
.sig-stage.active .sig-num{color:var(--process)}
.sig-label{display:block}
.sig-arrow{color:var(--rule-2);font-weight:700;transition:color .2s ease}
.sig-arrow.active{color:var(--process)}
@keyframes sigStageGlow{
  0%,100%{box-shadow:0 0 0 0 var(--process-bg)}
  50%{box-shadow:0 0 0 5px var(--process-bg)}
}
.safe-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px;margin-top:8px}
.safe-chip{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 12px;
  border-radius:var(--r-s);border:1.5px solid var(--rule-2);background:var(--surface-3);color:var(--ink-2);
  text-align:left;font-size:var(--t-sm);cursor:pointer;transition:border-color .15s ease,background .15s ease,color .15s ease}
.safe-chip.ok{border-color:var(--ok-line);background:var(--ok-bg);color:var(--ok)}
.safe-chip.fault{border-color:var(--no-line);background:var(--no-bg);color:var(--no)}
.safe-val{font-family:var(--f-mono);font-weight:700}
@media (max-width:560px){
  .sig-stage{min-width:74px;font-size:11px;padding:6px 8px}
  .volt-schem-node{min-width:52px;font-size:11px;padding:6px 8px}
  .volt-schem-line{width:16px}
}

/* ---- Module 1.3 Scenario B: colourful status-flag badges + an
   animated robot scene. Each flag gets its own token colour so the
   dashboard reads as genuinely colour-coded rather than blue-on-blue,
   and the SVG scene reacts live: wheels spin while patrolling, a
   halted robot flashes red eyes, low battery blinks amber, GPS lock
   pings green. ------------------------------------------------ */
.stat-badge-row{display:flex;gap:10px;flex-wrap:wrap;margin:12px 0 18px}
.stat-badge{display:flex;align-items:center;gap:8px;flex:1 1 150px;padding:9px 12px;
  border-radius:var(--r-s);border:1.5px solid var(--rule-2);background:var(--surface-3);
  color:var(--ink-3);transition:border-color .2s ease,background .2s ease,color .2s ease}
.stat-badge-icon{width:20px;height:20px;flex:0 0 auto;fill:none;stroke:currentColor;
  stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.stat-badge-label{font-size:var(--t-xs);font-weight:600;flex:1}
.stat-badge-state{font-family:var(--f-mono);font-size:10px;font-weight:700;letter-spacing:.03em;
  padding:2px 7px;border-radius:var(--r-full);background:var(--surface-2);color:var(--ink-3)}
.stat-badge.active{animation:statBadgePop .4s cubic-bezier(.34,1.56,.64,1)}
.stat-badge.active .stat-badge-state{color:var(--surface)}
.stat-badge-motor.active{border-color:var(--process);background:var(--process-bg);color:var(--process)}
.stat-badge-motor.active .stat-badge-state{background:var(--process)}
.stat-badge-battery.active{border-color:var(--warn);background:var(--warn-bg);color:var(--warn)}
.stat-badge-battery.active .stat-badge-state{background:var(--warn)}
.stat-badge-obstacle.active{border-color:var(--no);background:var(--no-bg);color:var(--no)}
.stat-badge-obstacle.active .stat-badge-state{background:var(--no)}
.stat-badge-gps.active{border-color:var(--ok);background:var(--ok-bg);color:var(--ok)}
.stat-badge-gps.active .stat-badge-state{background:var(--ok)}
@keyframes statBadgePop{0%{transform:scale(1)}45%{transform:scale(1.06)}100%{transform:scale(1)}}
.robot-scene-wrap{margin-top:4px}
.robot-scene{display:block;width:100%;max-width:420px;height:auto;background:var(--surface-3);
  border:1px solid var(--rule-2);border-radius:var(--r-m);padding:4px}
.robot-scene-caption{margin-top:8px}
.robot-wheel.spinning{animation:robotWheelSpin .6s linear infinite}
@keyframes robotWheelSpin{to{transform:rotate(360deg)}}
.robot-body.moving{animation:robotPatrol 3.2s ease-in-out infinite}
@keyframes robotPatrol{0%,100%{transform:translateX(0)}50%{transform:translateX(90px)}}
.robot-eye-alert{animation:robotEyeFlash .5s steps(1) infinite}
@keyframes robotEyeFlash{0%,49%{opacity:1}50%,100%{opacity:.15}}
.robot-gps .gps-ping{transform-box:fill-box;transform-origin:center;animation:gpsPing 1.8s ease-out infinite}
@keyframes gpsPing{0%{r:5;opacity:.85}100%{r:15;opacity:0}}
.robot-obstacle-mark{animation:obstacleFlash .8s ease-in-out infinite}
@keyframes obstacleFlash{0%,100%{opacity:1}50%{opacity:.35}}
.robot-battery-mark{animation:batteryBlink 1.2s ease-in-out infinite}
@keyframes batteryBlink{0%,100%{opacity:1}50%{opacity:.3}}
.stat-recap{margin-top:16px}
.stat-recap summary{cursor:pointer;font-size:var(--t-xs);color:var(--ink-3);padding:4px 0;list-style:revert}
.stat-recap summary:hover{color:var(--process)}
@media (max-width:560px){
  .stat-badge{flex:1 1 100%}
}

/* ---- Module 1.1c: two's complement range/overflow number line -------- */
.rangeline-wrap{margin:20px 0 4px}
.rangeline-labels{display:flex;justify-content:space-between;font-family:var(--f-mono);
  font-size:12px;color:var(--ink-3);margin-bottom:6px}
.rangeline-track{position:relative;height:10px;border-radius:var(--r-full);
  background:linear-gradient(90deg,var(--process-bg),var(--surface-3) 50%,var(--process-bg));
  border:1px solid var(--rule-2)}
.rangeline-tick{position:absolute;top:-4px;width:2px;height:18px;
  background:var(--ink-3);transform:translateX(-1px)}
.rangeline-marker{position:absolute;top:50%;width:20px;height:20px;border-radius:50%;
  background:var(--process);border:3px solid var(--surface);box-shadow:0 0 0 2px var(--process);
  transform:translate(-50%,-50%);transition:left .35s cubic-bezier(.34,1.56,.64,1)}
.rangeline-marker.pop{background:var(--no);box-shadow:0 0 0 2px var(--no);
  animation:rangelinePop .5s cubic-bezier(.34,1.56,.64,1)}
@keyframes rangelinePop{0%{transform:translate(-50%,-50%) scale(1.9)}100%{transform:translate(-50%,-50%) scale(1)}}
.rangeline-current{text-align:center;min-width:90px}
.rangeline-current .mono{font-size:22px;font-weight:700}
#range-overflow-fb:empty{display:none}

/* ---- Module 1.4: colourful ALU-adder & phase-cancellation scenarios */
.alu-scene-wrap{margin:14px 0 4px}
.alu-scene{display:block;width:100%;max-width:460px;height:auto;background:var(--surface-3);
  border:1px solid var(--rule-2);border-radius:var(--r-m);padding:6px}
.alu-node rect{fill:var(--surface);stroke-width:2}
.alu-node text{text-anchor:middle;font-family:var(--f-mono)}
.alu-node-label{font-size:10px;font-weight:700}
.alu-node-val{font-size:13px;font-weight:700}
.alu-node-a rect{stroke:var(--process)}
.alu-node-a .alu-node-label,.alu-node-a .alu-node-val{fill:var(--process)}
.alu-node-b rect{stroke:var(--warn)}
.alu-node-b .alu-node-label,.alu-node-b .alu-node-val{fill:var(--warn)}
.alu-wire{fill:none;stroke:var(--rule-2);stroke-width:2}
.alu-wire-a{stroke:var(--process-line)}
.alu-wire-b{stroke:var(--warn-line)}
.alu-wire-out{stroke:var(--ok-line)}
.alu-dot{fill:var(--process)}
.alu-dot-b{fill:var(--warn)}
.alu-dot-out{fill:var(--ok)}
.alu-adder rect{fill:var(--process-bg);stroke:var(--process);stroke-width:2.5;
  animation:aluAdderPulse 1.6s ease-in-out infinite}
.alu-adder-sym{text-anchor:middle;dominant-baseline:middle;font-size:26px;font-weight:700;fill:var(--process)}
@keyframes aluAdderPulse{
  0%,100%{filter:drop-shadow(0 0 0 transparent)}
  50%{filter:drop-shadow(0 0 5px var(--process-line))}
}
.alu-carry{opacity:.22}
.alu-carry-wire{stroke:var(--no-line);stroke-dasharray:4,3}
.alu-carry circle{fill:var(--surface);stroke:var(--no);stroke-width:2}
.alu-carry-label{font-family:var(--f-mono);font-size:9px;fill:var(--no)}
.alu-carry.active{opacity:1;animation:aluCarryFlash 1s ease-in-out infinite}
@keyframes aluCarryFlash{0%,100%{opacity:1}50%{opacity:.35}}
.alu-result rect{stroke:var(--ink-3)}
.alu-result .alu-node-label,.alu-result .alu-node-val{fill:var(--ink-2)}
.alu-result.ok rect{stroke:var(--ok);fill:var(--ok-bg)}
.alu-result.ok .alu-node-label,.alu-result.ok .alu-node-val{fill:var(--ok)}
.alu-scene-caption{margin-top:8px}
.cancel-scene-wrap{margin:14px 0 4px}
.cancel-scene{display:block;width:100%;max-width:420px;height:auto;background:var(--surface-3);
  border:1px solid var(--rule-2);border-radius:var(--r-m);padding:6px}
.cancel-speaker path{stroke:none}
.cancel-speaker-a path{fill:var(--process)}
.cancel-speaker-b path{fill:var(--warn)}
.cancel-ring{fill:none;stroke-width:2;transform-box:fill-box;transform-origin:center}
.cancel-ring-a{stroke:var(--process);animation:cancelTravelA 1.4s linear infinite}
.cancel-ring-b{stroke:var(--warn);animation:cancelTravelB 1.4s linear infinite}
@keyframes cancelTravelA{0%{transform:translateX(0);opacity:.9}100%{transform:translateX(100px);opacity:0}}
@keyframes cancelTravelB{0%{transform:translateX(0);opacity:.9}100%{transform:translateX(-100px);opacity:0}}
.cancel-line{stroke:var(--ok-line);stroke-width:2;stroke-dasharray:4,3}
.cancel-badge{fill:var(--ok-bg);stroke:var(--ok);stroke-width:2;transform-box:fill-box;
  transform-origin:center;animation:cancelBadgePulse 1.4s ease-in-out infinite}
.cancel-badge-label{text-anchor:middle;dominant-baseline:middle;font-family:var(--f-mono);
  font-weight:700;font-size:13px;fill:var(--ok)}
@keyframes cancelBadgePulse{0%,100%{transform:scale(1)}50%{transform:scale(1.15)}}
.cancel-scene-caption{margin-top:8px}

/* ---- Module 1.2: sample-rate scenario & bit-rate/file-size chips -- */
.srcompare-wrap{margin:14px 0 4px}
.srcompare-row{display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap}
.srcompare-chip{flex:0 0 auto;font-family:var(--f-mono);font-size:11px;font-weight:700;
  padding:4px 9px;border-radius:var(--r-full);white-space:nowrap}
.srcompare-chip-cd{background:var(--process-bg);color:var(--process);border:1px solid var(--process-line)}
.srcompare-chip-tel{background:var(--warn-bg);color:var(--warn);border:1px solid var(--warn-line)}
.srcompare-wave{flex:1 1 260px;min-width:200px;display:block;width:100%;max-width:460px;height:auto;
  background:var(--surface-3);border:1px solid var(--rule-2);border-radius:var(--r-s)}
.srcompare-grid{stroke:var(--rule-2);stroke-width:1}
.srcompare-path{fill:none;stroke-width:2.5}
.srcompare-path-cd{stroke:var(--process)}
.srcompare-path-tel{stroke:var(--warn)}
.srcompare-dot-cd{fill:var(--process)}
.srcompare-dot-tel{fill:var(--warn)}
.srcompare-caption{margin-top:4px}
.brf-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:14px}
.brf-chip{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:100px;
  padding:8px 12px;border-radius:var(--r-s);border:1.5px solid var(--rule-2);background:var(--surface-3);
  animation:statBadgePop .35s cubic-bezier(.34,1.56,.64,1)}
.brf-chip-label{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.02em;color:var(--ink-3)}
.brf-chip-val{font-family:var(--f-mono);font-weight:700;font-size:13px;color:var(--ink)}
.brf-chip-process{border-color:var(--process);background:var(--process-bg)}
.brf-chip-process .brf-chip-label,.brf-chip-process .brf-chip-val{color:var(--process)}
.brf-chip-warn{border-color:var(--warn);background:var(--warn-bg)}
.brf-chip-warn .brf-chip-label,.brf-chip-warn .brf-chip-val{color:var(--warn)}
.brf-chip-ok{border-color:var(--ok);background:var(--ok-bg)}
.brf-chip-ok .brf-chip-label,.brf-chip-ok .brf-chip-val{color:var(--ok)}
.brf-chip-result{border-color:var(--ink-3);background:var(--surface-2)}
.brf-op{font-size:20px;font-weight:700;color:var(--ink-3)}
@media (max-width:560px){
  .srcompare-wave{min-width:0}
  .brf-chip{min-width:84px;padding:7px 9px}
}

/* ---- Module 1.3b: compression real-world use cases -------- */
.comp-media-grid{display:flex;flex-direction:column;gap:16px;margin:16px 0}
.comp-media-row{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-m);
  padding:16px 18px;box-shadow:var(--shadow)}
.comp-media-head{display:flex;align-items:center;gap:10px;margin-bottom:12px}
.comp-media-icon{width:24px;height:24px;fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round;color:var(--process);flex-shrink:0}
.comp-media-head h4{margin:0;font-family:var(--f-display);font-size:var(--t-base);font-weight:700}
.comp-panels{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.comp-panels .fb{margin-top:0}
@media (max-width:640px){
  .comp-panels{grid-template-columns:1fr}
}

/* ---- Module 1.2: RGB colour & memory map ------------------ */
/* Compact channel picker: a number stepper + a thin read-only intensity
   meter, three across (see core/ch1-screens.js W.rgbRender) — replaces
   three full-width drag sliders stacked vertically, so all three
   channels sit side by side in far less vertical space, and typing/
   nudging a value lands on an exact figure instead of dragging for it. */
.rgb-picker{margin-top:4px}
.rgb-chan{background:var(--surface-2);border:1px solid var(--rule);border-radius:var(--r-m);padding:10px 12px}
.rgb-chan-head{display:flex;align-items:center;gap:7px;margin-bottom:8px;font-size:13.5px}
.rgb-chan-dot{width:10px;height:10px;border-radius:var(--r-full);flex:0 0 auto}
.rgb-chan[data-ch="r"] .rgb-chan-dot{background:var(--rgb-r)}
.rgb-chan[data-ch="g"] .rgb-chan-dot{background:var(--rgb-g)}
.rgb-chan[data-ch="b"] .rgb-chan-dot{background:var(--rgb-b)}
.rgb-chan-stepper{display:flex;align-items:center;gap:6px}
.rgb-step-btn{
  width:28px;height:28px;flex:0 0 auto;border-radius:var(--r-s);border:1px solid var(--rule-2);
  background:var(--surface);color:var(--ink-2);font-size:16px;line-height:1;padding:0;cursor:pointer;
  display:flex;align-items:center;justify-content:center;transition:background .15s ease,color .15s ease
}
.rgb-step-btn:hover{background:var(--surface-3);color:var(--ink)}
.rgb-chan-input{
  width:100%;min-width:0;text-align:center;font-family:var(--f-mono);font-size:15px;font-weight:700;
  border:1px solid var(--rule-2);border-radius:var(--r-s);padding:5px 2px;background:var(--surface);color:var(--ink);
  -moz-appearance:textfield
}
.rgb-chan-input::-webkit-outer-spin-button,.rgb-chan-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.rgb-chan-meter{height:6px;border-radius:var(--r-full);background:var(--rule);margin-top:9px;overflow:hidden}
.rgb-chan-meter>i{display:block;height:100%;border-radius:var(--r-full);transition:width .15s ease}
.rgb-chan[data-ch="r"] .rgb-chan-meter>i{background:var(--rgb-r)}
.rgb-chan[data-ch="g"] .rgb-chan-meter>i{background:var(--rgb-g)}
.rgb-chan[data-ch="b"] .rgb-chan-meter>i{background:var(--rgb-b)}
@media(max-width:480px){
  .rgb-picker.g3{grid-template-columns:1fr} /* three columns get too cramped for the stepper buttons below phone width */
}
.rgb-swatch{height:110px;border-radius:var(--r-m);border:1px solid var(--rule);margin-top:6px}
/* A real subpixel dims toward black as intensity falls, not toward
   white — so this sits on the same always-dark "instrument panel"
   tone as .instr/.svgwrap.dark, not the ordinary card surface. */
.subpixel-cell{display:flex;gap:6px;height:80px;margin-top:6px;padding:10px;background:var(--chrome);
  border-radius:var(--r-s);border:1px solid var(--chrome-line);align-items:flex-end}
.subpixel{flex:1;height:100%;border-radius:3px 3px 0 0;transition:opacity .15s ease}
.sp-r{background:var(--rgb-r)}
.sp-g{background:var(--rgb-g)}
.sp-b{background:var(--rgb-b)}
.mem-table{cursor:pointer}
.mem-row{cursor:pointer;transition:background .15s ease}
.mem-row:hover{background:var(--surface-3)}
.mem-row:focus-visible{outline:2px solid var(--process);outline-offset:-2px}
.mem-row.sel{background:var(--process-bg)}
.mem-row.sel td{color:var(--process);font-weight:700}

/* ---- pixel canvas (ch1: draw with mouse/touch/stylus) --- */
.pixel-grid{display:grid;gap:2px;touch-action:none;background:var(--surface-3);padding:2px;
  border-radius:var(--r-s);border:1px solid var(--rule);width:100%;max-width:420px}
.pixel-cell{aspect-ratio:1;border-radius:2px;cursor:pointer;transition:transform .08s ease;min-width:0}
.pixel-cell:hover{transform:scale(1.08);z-index:1}
/* ---- bit-grid variant (ch1: a monochrome bitmap's literal 0/1 per pixel) --- */
.pixel-cell.bit-cell{display:flex;align-items:center;justify-content:center;
  font-family:var(--f-mono);font-weight:700;font-size:12px;user-select:none;border:1px solid var(--rule)}
.swatch-row{display:flex;flex-wrap:wrap;gap:7px;margin:10px 0}
.swatch{width:30px;height:30px;border-radius:var(--r-s);border:2px solid var(--rule-2);cursor:pointer;
  padding:0;transition:transform .12s cubic-bezier(.34,1.56,.64,1),border-color .15s ease}
.swatch:hover{transform:translateY(-2px)}
.swatch.on{border-color:var(--ink);transform:scale(1.1);box-shadow:var(--shadow)}
.swatch.static{cursor:default}
.swatch.static:hover{transform:none}

/* ---- colour-depth ladder (ch1: 2^n shades, static greyscale display) --- */
.cd-ladder{margin:18px 0 6px}
.cd-ladder-row{margin-bottom:16px}
.cd-ladder-row:last-child{margin-bottom:0}
.cd-ladder-label{font-family:var(--f-mono);font-size:13px;color:var(--ink-2)}
.cd-ladder-label sup{font-size:10px}
.cd-band-strip{display:flex;height:30px;border-radius:var(--r-s);margin-top:6px;
  border:2px solid var(--rule-2);overflow:hidden}
.cd-band{flex:1 1 0}
.cd-ladder-spectrum{height:30px;border-radius:var(--r-s);margin-top:6px;border:2px solid var(--rule-2);
  background:linear-gradient(to right,#fff,#000)}

/* ---- real-photo pixelation widget (ch1) ----------------- */
.pxl-canvas-wrap{margin-top:4px}
.pxl-canvas-wrap canvas{display:block;width:100%;max-width:300px;height:auto;
  image-rendering:pixelated;background:var(--surface-3);border:1px solid var(--rule-2);border-radius:var(--r-s)}
.pxl-canvas-wrap.zoomed canvas{max-width:480px}

/* ---- sound playback control ----------------------------- */
.snd-status{font-family:var(--f-mono);font-size:11.5px;color:var(--ink-2);margin-top:8px}

/* ---- exam mode: deliberately austere ------------------- */
[data-mode="exam"]{--surface-2:#FFFFFF}
[data-mode="exam"] .card,[data-mode="exam"] .q{box-shadow:none;border-color:var(--rule-2)}
[data-mode="exam"] .stage-inner{max-width:760px}
[data-mode="exam"] .badge,[data-mode="exam"] .pill{background:transparent;border:1px solid var(--rule-2);color:var(--ink-2)}
[data-mode="exam"] .footnav .pipeline{opacity:.25}

.timer{font-family:var(--f-mono);font-size:var(--t-lg);font-weight:600;font-variant-numeric:tabular-nums}
.timer.low{color:var(--no)}

/* ---- responsive ---------------------------------------- */
.menubtn{display:none}
@media(max-width:920px){
  .g3,.g4{grid-template-columns:repeat(2,1fr)}
}
@media(max-width:860px){
  .app,.app.rail-collapsed{grid-template-columns:1fr} /* the icon-rail collapse is desktop-only (see .rail-collapse-btn) — if the window is resized down while collapsed, mobile's own full-overlay rail still needs the full-width track, not the narrow desktop one */
  .rail{position:fixed;z-index:60;width:var(--rail-w);transform:translateX(-100%);transition:transform .25s;box-shadow:0 0 50px rgba(15,23,42,.25)}
  .rail.open{transform:none}
  .menubtn{display:inline-flex}
  .g2,.g3,.g4{grid-template-columns:1fr}
  .pc{grid-template-columns:1fr}
  .pc>div:first-child{border-right:0;border-bottom:1px solid var(--rule)}
  .topbar,.footnav{padding-left:16px;padding-right:16px}
  .stage{padding:24px 16px 32px}
  /* controls go BELOW output on mobile, never beside it */
  .simwrap{display:flex;flex-direction:column-reverse;gap:14px}
  .pipeline{font-size:9px}
  body{font-size:16px}
  /* Touch-target pass: Run/Step/Reset (.tbtn), example/level chips (.chip),
     and panel-tab segments (.seg button) are the controls a student taps
     constantly, and at ~32-36px tall they're cramped for a thumb. Bumped
     only below this tablet/mobile breakpoint — desktop/mouse sizing is
     untouched. */
  .tbtn{min-height:44px;padding:10px 14px}
  .chip{padding:10px 16px}
  .seg button{padding:11px 16px}
  .phone-code-opt{padding:12px 12px;min-height:44px}
}
@media(max-width:700px){
  .app-footer-inner{flex-direction:column;align-items:stretch;gap:12px}
  .footer-progress-bar{max-width:none}
  .footer-actions{justify-content:space-between}
  .footer-brand{margin-left:0;justify-content:center}
}
@media(max-width:520px){
  /* gap:16px on .app-header-inner now comes from the gap-3 utility
     class (!important by design) — this mobile override needs the
     same to still win at this breakpoint. */
  .app-header-inner{padding:0 14px;gap:10px!important}
  .header-acct-name{display:none}
  .app-footer{padding:14px}
}
.scrim{position:fixed;inset:0;background:rgba(15,23,42,.45);z-index:55}

@media(prefers-reduced-motion:reduce){
  *{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}
}
@media print{
  /* Without this, .app stays a 2-column grid (rail + content) even
     though the rail is hidden, leaving the content squeezed into the
     rail's now-empty ~270px track instead of the full page width. */
  .app{display:block}
  .app-header,.app-footer,.rail,.footnav,.topbar,.transport{display:none}
  .stage{padding:0}
  .card{break-inside:avoid;box-shadow:none}

  /* Chapter 1 mind map -> PDF (window.print(), see CS.ch1.mindMapPrint):
     while body.mm-printing is set, show only the branded print sheet. */
  body.mm-printing .stage-inner>*:not(.mm-print-sheet){display:none!important}
  body.mm-printing .mm-print-sheet{display:block!important}
}
@page{size:landscape;margin:14mm}

/* ============================================================
   CMS PHASE 8 · content-block rendering (api/content/reconstruct.php)
   Every value below is an existing var(--token) from this same file —
   no new colors/fonts/radii introduced (section 10 discipline extended
   to admin-authored content, same as admin/admin.css). Only the block
   types with no pre-existing equivalent (image/code/quote/timeline/
   video) get new rules; definition/note/card/table/list/callout reuse
   the classes already defined above.
   ============================================================ */
.cb-figure{margin:16px 0}
.cb-figure img{display:block;max-width:100%;border-radius:var(--r-m);border:1px solid var(--rule)}
.cb-figure figcaption{margin-top:6px}
/* CS.ui.figure()'s graceful-degradation state (core/ui.js) — same
   project-wide image system as the CMS 'image' block above, so a
   hand-coded chapters/*.js page that references a media id which is
   offline, archived, or 404s never loses the step's meaning, only the
   picture; a screen reader still gets the alt text via aria-label. */
.cb-media-fallback{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;
  min-height:140px;padding:24px 16px;text-align:center;color:var(--ink-3);
  background:var(--surface-3);border:1px dashed var(--rule-2);border-radius:var(--r-m);font-size:var(--t-sm)}
.cb-media-fallback .small{color:var(--ink-2)}
.cb-video{position:relative;padding-top:56.25%;margin:16px 0;border-radius:var(--r-m);overflow:hidden;background:var(--surface-3)}
.cb-video iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
.cb-code{background:var(--surface-3);border:1px solid var(--rule);border-radius:var(--r-m);
  padding:12px 14px;margin:16px 0;overflow-x:auto;font-family:var(--f-mono);font-size:var(--t-sm)}
.cb-quote{background:var(--surface);border:1px solid var(--rule);border-left:3px solid var(--process);
  border-radius:var(--r-m);padding:14px 16px;margin:16px 0}
.cb-quote footer{margin-top:6px}

/* ============================================================
   CH2 · packet structure diagram (click-to-expand)
   Header/Payload/Trailer sit edge-to-edge as one strip; each block's
   own panel expands independently via the grid-template-rows 0fr->1fr
   technique below (animatable natively, no JS height-measuring).
   ============================================================ */
.pkt-diagram{display:flex;gap:10px;flex-wrap:wrap;margin:16px 0}
.pkt-block{flex:1 1 230px;min-width:220px;background:var(--surface);border:1.5px solid var(--rule);
  border-radius:var(--r-m);overflow:hidden;box-shadow:var(--shadow)}
.pkt-block[data-accent="process"]{--pkt-accent:var(--process);--pkt-accent-bg:var(--process-bg)}
.pkt-block[data-accent="mm-3"]{--pkt-accent:var(--mm-3);--pkt-accent-bg:var(--mm-3-bg)}
.pkt-block[data-accent="mm-1"]{--pkt-accent:var(--mm-1);--pkt-accent-bg:var(--mm-1-bg)}
.pkt-block-head{display:flex;align-items:center;gap:10px;width:100%;padding:14px 16px;
  background:var(--pkt-accent-bg);border:0;cursor:pointer;text-align:left;color:var(--pkt-accent);
  font:inherit}
.pkt-block-head:hover{filter:brightness(.97)}
.pkt-block-icon{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto}
.pkt-block-title{font-family:var(--f-display);font-weight:700;font-size:var(--t-base);flex:1}
.pkt-block-count{font-family:var(--f-mono);font-size:10.5px;font-weight:700;background:var(--surface);
  color:var(--pkt-accent);border-radius:var(--r-full);padding:2px 8px}
.pkt-chev{transition:transform .25s cubic-bezier(.16,1,.3,1);font-size:12px;flex:0 0 auto}
.pkt-block-head.open .pkt-chev{transform:rotate(180deg)}
.pkt-panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows .32s cubic-bezier(.16,1,.3,1)}
.pkt-panel.open{grid-template-rows:1fr}
.pkt-panel-inner{overflow:hidden;min-height:0}
.pkt-block-blurb{margin:14px 16px 4px;font-size:var(--t-sm);color:var(--ink-2)}
.pkt-field{display:flex;gap:10px;align-items:flex-start;padding:10px 16px;border-top:1px solid var(--rule)}
.pkt-field-icon{width:18px;height:18px;fill:none;stroke:var(--pkt-accent);stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto;margin-top:1px}
/* direction-of-transmission example icons: same stroke-icon convention,
   using currentColor so they pick up whatever colour the surrounding
   button/banner text already is (white on an "on" button, ink on ghost,
   --ok inside the green result banner) with no extra colour rules needed */
.dir-icon{width:20px;height:20px;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
.dir-mode-icon{width:19px;height:19px;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.9;
  stroke-linecap:round;stroke-linejoin:round}

/* opening "transmission mode" tree diagram: connector lines are a
   backdrop SVG in a fixed coordinate space (see DIR_TREE_W/H in
   ch2-screens.js); the three leaf boxes are real buttons positioned by
   percentage on top of it, same technique as .dx-device above. */
.dir-tree-stage{position:relative;width:100%;aspect-ratio:700/250;margin:18px 0}
.dir-tree-svg{position:absolute;inset:0;width:100%;height:100%;display:block}
.dir-tree-node{position:absolute;transform:translate(-50%,-50%);min-width:150px;
  padding:12px 18px;border-radius:var(--r-m);background:var(--surface);border:2px solid var(--process);
  color:var(--process);font-family:var(--f-display);font-weight:700;font-size:13.5px;text-align:center;
  cursor:pointer;box-shadow:var(--shadow);transition:transform .15s cubic-bezier(.34,1.56,.64,1),box-shadow .15s,background .15s}
.dir-tree-node:hover{transform:translate(-50%,-50%) translateY(-3px);box-shadow:var(--shadow-lift);background:var(--process-bg)}
.dir-tree-node:focus-visible{outline:2.5px solid var(--process);outline-offset:3px}
@media (max-width:560px){ .dir-tree-node{font-size:11px;padding:9px 10px;min-width:104px} }

/* Chapter 7's "Your mission" opener — the four-stage Program
   Development Life Cycle drawn as an actual CYCLE (four curved arrows
   running clockwise round a ring, closing from the last stage back to
   the first), matching the source PDF's own circular diagram, instead
   of four stages sitting in a plain 2x2 grid with no visual relationship
   between them. Same backdrop-SVG-plus-overlay-buttons technique as
   .dir-tree-stage above: the ring and its arrowheads are a fixed-
   coordinate-space SVG backdrop (PDLC_CYCLE_SIZE in ch7-screens.js),
   and each stage is a real, keyboard-focusable <button> positioned by
   percentage on top of it. Nodes are coloured by the SAME input/
   process/output role each stage already carries elsewhere in this
   topic (W.LIFECYCLE_STAGES / the `roles` array on the topic itself) —
   Analysis is the input into the cycle, Testing is the output that
   closes it, Design and Coding are both process — not four arbitrary
   colours invented just for this diagram. */
.pdlc-cycle-stage{position:relative;width:100%;max-width:480px;aspect-ratio:1/1;margin:22px auto}
.pdlc-cycle-svg{position:absolute;inset:0;width:100%;height:100%;display:block}
.pdlc-node{position:absolute;transform:translate(-50%,-50%);width:132px;
  padding:12px 14px;border-radius:var(--r-m);background:var(--surface);border:2px solid var(--ink-3);
  color:var(--ink);font-family:var(--f-display);text-align:center;cursor:pointer;box-shadow:var(--shadow);
  transition:transform .15s cubic-bezier(.34,1.56,.64,1),box-shadow .15s,background .15s}
.pdlc-node .pdlc-node-n{display:block;font-size:10px;font-weight:700;letter-spacing:.08em;opacity:.75}
.pdlc-node .pdlc-node-t{display:block;font-size:13px;font-weight:700;margin-top:2px;line-height:1.2}
.pdlc-node:hover{transform:translate(-50%,-50%) translateY(-3px);box-shadow:var(--shadow-lift)}
.pdlc-node:focus-visible{outline:2.5px solid var(--process);outline-offset:3px}
.pdlc-node.role-i{border-color:var(--input);color:var(--input)}
.pdlc-node.role-i:hover{background:var(--input-bg)}
.pdlc-node.role-p{border-color:var(--process);color:var(--process)}
.pdlc-node.role-p:hover{background:var(--process-bg)}
.pdlc-node.role-o{border-color:var(--output);color:var(--output)}
.pdlc-node.role-o:hover{background:var(--output-bg)}
@media (max-width:560px){ .pdlc-node{width:104px;padding:9px 8px} .pdlc-node .pdlc-node-t{font-size:11.5px} }
/* a brief pulse on whichever card the "types of data transmission" tree
   diagram was clicked through to — acknowledges the branch without
   permanently restyling anything (cleared again after ~2s in JS) */
.card.sp-highlight{outline:3px solid var(--process);outline-offset:2px;animation:popIn .3s cubic-bezier(.34,1.56,.64,1)}

/* "Race: one wire vs many" — serial sends one bit-tile down a single
   wire per clock pulse, parallel sends eight bit-tiles down eight wires
   at once; both are absolutely-positioned .bit-tile chips whose `left`
   is animated via CSS transition (JS just flips the style, same
   snap-back-with-.no-anim technique as the routing/direction demos).
   Each lane is bookended by a small "BYTE" pill, echoing the sender/
   receiver byte containers in the reference diagram. */
.sp-race{display:flex;gap:22px;flex-wrap:wrap}
.sp-race-col{flex:1 1 220px;min-width:200px}
.sp-race-h{margin:0 0 8px;font-size:var(--t-sm);color:var(--ink-2);text-transform:uppercase;letter-spacing:.03em}
.sp-race-byte{flex:0 0 auto;padding:6px 11px;border-radius:var(--r-full);border:1.5px solid var(--process);
  background:var(--process-bg);color:var(--process);font-family:var(--f-mono);font-size:9.5px;
  letter-spacing:.1em;text-align:center}
.sp-race-track{display:flex;align-items:center;gap:8px}
.sp-race-wire-wrap{position:relative;flex:1 1 auto;height:46px;background:var(--surface-3);
  border:1.5px solid var(--rule-2);border-radius:var(--r-m)}
.sp-race-parallel-wrap{display:flex;align-items:center;gap:8px}
.sp-race-track.parallel{flex:1 1 auto;display:flex;flex-direction:column;align-items:stretch;gap:4px}
.sp-race-plane{position:relative;height:32px;background:var(--surface-3);border:1.5px solid var(--rule-2);
  border-radius:var(--r-s)}
.sp-race-wire{position:absolute;left:8px;right:8px;top:50%;height:2px;background:var(--rule-2);
  transform:translateY(-50%)}
.sp-race-bit{position:absolute !important;top:50%;transform:translateY(-50%);
  transition:left .55s linear}
.sp-race-bit.no-anim{transition:none}
@media (max-width:640px){
  .sp-race{gap:14px}
  .sp-race-byte{font-size:8px;padding:5px 7px}
}

/* USB wire explorer — a labelled cable diagram (computer -> plug ->
   device) as a fixed-coordinate-space SVG backdrop, with a real HTML
   button hotspot laid over each wire's full length: :hover/:focus-visible
   reveals a CSS-only tooltip (no JS needed for the preview), and a click
   pins that wire selected — same backdrop-plus-overlay technique as
   .dir-tree-stage above. */
.usb-diagram-stage{position:relative;width:100%;aspect-ratio:640/210;margin:16px 0 10px}
.usb-diagram-svg{position:absolute;inset:0;width:100%;height:100%;display:block}
.usb-devbox{fill:var(--surface);stroke:var(--rule-2);stroke-width:1.5}
.usb-devlabel{font-family:var(--f-mono);font-size:11.5px;font-weight:700;fill:var(--ink);text-anchor:middle;letter-spacing:.04em}
.usb-devlabel.dim{font-size:9.5px;font-weight:400;fill:var(--ink-3);letter-spacing:.02em;text-transform:none}
.usb-plug-body{fill:var(--surface-3);stroke:var(--rule-2);stroke-width:1.5}
.usb-plug-tab{fill:var(--ink-3)}
.usb-wire-hot{position:absolute;background:transparent;border:0;padding:0;margin:0;cursor:pointer;
  border-radius:var(--r-full)}
.usb-wire-hot:focus-visible{outline:2px solid var(--process);outline-offset:3px}
.usb-wire-hot .usb-tip{position:absolute;left:50%;bottom:calc(100% + 9px);transform:translate(-50%,4px);
  display:flex;flex-direction:column;gap:3px;width:max-content;max-width:230px;
  background:#1e293b;color:#fff;padding:8px 12px;border-radius:var(--r-s);
  white-space:normal;text-align:left;line-height:1.35;opacity:0;visibility:hidden;
  transition:opacity .15s ease,transform .15s ease;pointer-events:none;box-shadow:var(--shadow-lift);z-index:3}
.usb-wire-hot .usb-tip b{font-size:11.5px;font-weight:700}
.usb-wire-hot .usb-tip span{font-size:10.5px;font-weight:400;color:#cbd5e1}
.usb-wire-hot .usb-tip::after{content:'';position:absolute;top:100%;left:50%;transform:translateX(-50%);
  border:5px solid transparent;border-top-color:#1e293b}
.usb-wire-hot:hover .usb-tip,.usb-wire-hot:focus-visible .usb-tip{opacity:1;visibility:visible;transform:translate(-50%,0)}
@media (max-width:640px){
  .usb-devlabel{font-size:9.5px}
  .usb-devlabel.dim{font-size:8px}
  .usb-wire-hot .usb-tip{max-width:170px;padding:6px 9px}
  .usb-wire-hot .usb-tip b{font-size:10px}
  .usb-wire-hot .usb-tip span{font-size:9.5px}
}
.pkt-field b{font-size:var(--t-sm)}
@media (max-width:520px){ .pkt-diagram{flex-direction:column} }

/* ============================================================
   CH2 · real-CRC (extension) step-by-step visualiser
   Reuses ch1's .bit-tile/.bit-row vocabulary for the register digits,
   adding highlight modifiers for "XOR happened" (amber/output) vs a
   "zero shift" (cyan/input) vs the final remainder (violet, matching
   the trailer's own accent in .pkt-diagram above).
   ============================================================ */
.crcx-trail{display:flex;flex-wrap:wrap;gap:2px;margin:14px 0;font-size:13px;align-items:center}
.crcx-trail-bit{display:inline-flex;align-items:center;justify-content:center;width:20px;height:24px;
  border-radius:4px;background:var(--surface-3);color:var(--ink);font-family:var(--f-mono)}
.crcx-trail-bit.used{opacity:.3}
.crcx-trail-bit.active{background:var(--process);color:#fff;font-weight:700}
.crcx-trail-next{margin-left:8px;font-size:11px;color:var(--ink-3)}
.crcx-wrap{background:var(--surface-3);border-radius:var(--r-m);padding:14px 16px;margin:10px 0}
.crcx-row{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:6px 0}
.crcx-label{flex:0 0 auto;min-width:190px;font-size:var(--t-sm);color:var(--ink-2)}
.bit-tile.hi{box-shadow:0 0 0 3px var(--output-line);border-color:var(--output)}
.bit-tile.lo{box-shadow:0 0 0 3px var(--input-line);border-color:var(--input)}
.bit-tile.remainder{background:var(--mm-1);color:#fff;border-color:var(--mm-1)}
.crcx-fly{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-top:16px;padding:14px 16px;
  background:var(--mm-1-bg);border:1px solid var(--mm-1-line);border-radius:var(--r-m);
  animation:fadeInUp .4s cubic-bezier(.16,1,.3,1) both}
.crcx-remainder{display:flex;flex-direction:column;gap:6px}
.crcx-arrow{font-size:22px;color:var(--mm-1)}
.crcx-trailer-chip{display:flex;flex-direction:column;gap:2px;background:var(--surface);border:1.5px solid var(--mm-1);
  border-radius:var(--r-s);padding:8px 14px;animation:popIn .4s cubic-bezier(.34,1.56,.64,1) both}
.crcx-trailer-chip b{color:var(--mm-1);font-family:var(--f-display)}

/* ============================================================
   CH2 · packet transmission & routing animation
   .rt-stage keeps the fixed 900x260 coordinate space (RT_W/RT_H in
   ch2-screens.js) in sync with percentage-positioned chips at any
   render width, via aspect-ratio rather than a fixed pixel height —
   so the JS's x/RT_W*100% math always lines up with the static SVG.
   ============================================================ */
.rt-stage{position:relative;width:100%;aspect-ratio:900/260;background:var(--surface);
  border:1px solid var(--rule);border-radius:var(--r-m);overflow:hidden}
.rt-svg{position:absolute;inset:0;width:100%;height:100%;display:block}
.rt-packet{position:absolute;transform:translate(-50%,-50%);width:34px;height:34px;border-radius:var(--r-full);
  display:flex;align-items:center;justify-content:center;font-family:var(--f-mono);font-size:11px;font-weight:800;
  color:#fff;box-shadow:var(--shadow-lift);opacity:0;
  transition:left .85s ease-in-out,top .85s ease-in-out,opacity .3s ease;z-index:2}
.rt-packet.live{opacity:1}
.rt-packet.no-anim{transition:none}
.rt-packet[data-accent="input"]{background:var(--input)}
.rt-packet[data-accent="output"]{background:var(--output)}
.rt-packet[data-accent="ok"]{background:var(--ok)}
.rt-packet.dropped{background:var(--no);opacity:.6;border:2px dashed var(--no);box-shadow:none}
.rt-packet.arrived{opacity:0}
.rt-buffer-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 0}
.rt-buffer-label{flex:0 0 auto;min-width:190px;font-size:var(--t-sm);color:var(--ink-2)}
.rt-chip{display:inline-flex;align-items:center;justify-content:center;min-width:34px;height:28px;
  padding:0 8px;border-radius:var(--r-s);color:#fff;font-family:var(--f-mono);font-weight:700;font-size:12px}
.rt-chip.pop{animation:popIn .35s cubic-bezier(.34,1.56,.64,1) both}
.rt-chip[data-accent="input"]{background:var(--input)}
.rt-chip[data-accent="output"]{background:var(--output)}
.rt-chip[data-accent="ok"]{background:var(--ok)}
.rt-buffer-sep{color:var(--ink-3)}
@media (max-width:520px){ .rt-packet{width:28px;height:28px;font-size:9.5px} }
.rt-log-panel{margin-top:10px;background:var(--surface-3);border:1px solid var(--rule);
  border-radius:var(--r-m);padding:10px 14px;max-height:150px;overflow-y:auto}
.rt-log{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px;
  font-size:12.5px;font-family:var(--f-mono)}
.rt-log li{padding-left:16px;position:relative;color:var(--ink-2)}
.rt-log li::before{content:'›';position:absolute;left:0;color:var(--ink-3)}
.rt-log-ok{color:var(--ok)}
.rt-log-no{color:var(--no)}
.rt-log-resend{color:var(--process);font-weight:700}
/* hop simulation: a pulse ring on the router (the "hop" itself) and a
   floating label (what is actually being "read" at that hop) */
.rt-router-ring{opacity:0;stroke:var(--process);stroke-width:3;transform-box:fill-box;transform-origin:center}
.rt-router-ring.pulse{animation:rtRouterPulse .65s ease-out}
.rt-router-ring.pulse-no{stroke:var(--no)}
@keyframes rtRouterPulse{0%{opacity:.9;transform:scale(1)}100%{opacity:0;transform:scale(1.7)}}
.rt-read-flash{position:absolute;transform:translate(-50%,-150%);background:var(--surface);
  border:1px solid var(--process);color:var(--process);font-family:var(--f-mono);font-size:11px;
  font-weight:700;padding:3px 9px;border-radius:var(--r-full);white-space:nowrap;opacity:0;
  pointer-events:none;transition:opacity .25s ease;z-index:3}
.rt-read-flash.show{opacity:1}
.rt-read-flash.bad{border-color:var(--no);color:var(--no)}
@media (max-width:520px){ .rt-read-flash{font-size:9.5px;padding:2px 7px} }

/* ---------- direction of transmission: animated mode demos ----------
   Same fixed-coordinate-space technique as .rt-stage above (see
   DX_W/DX_H in ch2-screens.js) but built from plain boxes/bars instead
   of an SVG, since the layout here is only two devices and 1-2 lanes. */
.dx-stage{position:relative;width:100%;aspect-ratio:640/190;background:var(--surface);
  border:1.5px solid var(--rule);border-radius:var(--r-l);overflow:hidden}
.dx-device{position:absolute;transform:translate(-50%,-50%);width:92px;height:58px;
  border-radius:var(--r-m);display:flex;align-items:center;justify-content:center;
  font-family:var(--f-display);font-weight:700;font-size:12.5px;text-align:center;z-index:2;
  background:var(--process-bg);border:2px solid var(--process);color:var(--process)}
.dx-device.b{background:var(--ok-bg);border-color:var(--ok);color:var(--ok)}
.dx-lane{position:absolute;height:6px;border-radius:var(--r-full);background:var(--rule-2);transform:translateY(-50%)}
.dx-lane.active{background:var(--process-line)}
.dx-lane.rev{background:var(--output-line)}
.dx-lane.blocked{background:repeating-linear-gradient(90deg,var(--no) 0 8px,transparent 8px 16px);opacity:.55}
.dx-lane-arrow{position:absolute;transform:translate(-50%,-50%);font-size:16px;color:var(--ink-3);z-index:1}
.dx-lane-label{position:absolute;transform:translate(-50%,-50%);font-size:10px;
  font-family:var(--f-mono);color:var(--ink-3);white-space:nowrap}
.dx-badge{position:absolute;transform:translate(-50%,-50%);font-size:10px;font-family:var(--f-mono);
  font-weight:700;padding:2px 9px;border-radius:var(--r-full);z-index:2;white-space:nowrap}
.dx-badge.waiting{background:var(--warn-bg);color:var(--warn);border:1px solid var(--warn-line)}
.dx-packet{position:absolute;transform:translate(-50%,-50%);width:26px;height:26px;
  border-radius:var(--r-full);color:#fff;box-shadow:var(--shadow);z-index:3;
  transition:left 1.25s linear,opacity .35s ease}
.dx-packet[data-accent="input"]{background:var(--input)}
.dx-packet[data-accent="process"]{background:var(--process)}
.dx-packet[data-accent="output"]{background:var(--output)}
.dx-packet[data-accent="ok"]{background:var(--ok)}
.dx-packet.gone{opacity:0}
.dx-packet.corrupt{background:var(--no)!important;animation:dxShake .28s ease 2}
.dx-burst{position:absolute;transform:translate(-50%,-50%);width:10px;height:10px;
  border-radius:50%;background:var(--no);z-index:4;animation:dxBurst .55s ease-out forwards}
@keyframes dxShake{0%,100%{margin-left:0}25%{margin-left:-3px}75%{margin-left:3px}}
@keyframes dxBurst{0%{width:10px;height:10px;opacity:.85}100%{width:70px;height:70px;opacity:0}}
@media (max-width:560px){
  .dx-device{width:62px;height:46px;font-size:10px}
  .dx-packet{width:20px;height:20px}
  .dx-lane-label{font-size:8.5px}
}
.cb-timeline{display:grid;gap:10px;margin:16px 0}

/* ---------- 2D parity block: click-to-locate exam-style table ----------
   Reuses table.t and .bit-tile/.bit-tile.mini (see BIT TILES above)
   rather than inventing new cell primitives; only the layout, the check
   indicators and the guess-state colours are specific to this widget. */
.pgrid-wrap{overflow-x:auto;background:var(--surface);border:1px solid var(--rule);
  border-radius:var(--r-m);padding:6px}
.pgrid-wrap table.t{width:auto;min-width:100%}
.pgrid-wrap table.t th,.pgrid-wrap table.t td{padding:6px 7px;white-space:nowrap;text-align:center}
.pgrid-wrap table.t th:first-child,.pgrid-wrap table.t td:first-child{text-align:left;white-space:nowrap}
.pgrid-wrap .bit-tile{width:30px;height:30px}
.pgrid-wrap .bit-tile.pgrid-static{opacity:.6;border-style:dashed;cursor:default}
.pgrid-wrap button.bit-tile:disabled{cursor:default}
.pgrid-wrap button.bit-tile:disabled:hover{transform:none;border-color:var(--rule-2)}
.pgrid-wrap .bit-tile.one:disabled:hover{border-color:var(--process)}
.pgrid-wrap .bit-tile.pg-wrong{border-color:var(--no)!important;box-shadow:0 0 0 2px var(--no-line);
  animation:dxShake .28s ease 2}
.pgrid-wrap .bit-tile.pg-found{background:var(--ok)!important;border-color:var(--ok)!important;
  box-shadow:0 0 0 3px var(--ok-line)}
.pgrid-chk{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;
  border-radius:var(--r-full);font-weight:700;font-size:13px}
.pgrid-chk.ok{background:var(--ok-bg);color:var(--ok)}
.pgrid-chk.no{background:var(--no-bg);color:var(--no)}
.pgrid-chk.dash{color:var(--ink-3)}

/* ---------- checksum simulator: sender/receiver stepper ----------
   Same fixed-coordinate-space + persistent-packet technique as
   .dx-stage above; reuses the generic .chip pill for the byte-value and
   checksum-trailer display instead of inventing a new chip shape, and
   reuses the dxShake/dxBurst keyframes defined with .dx-packet above (a
   generic "corrupted-in-flight" effect) rather than redefining them. */
.cks-stage{position:relative;width:100%;aspect-ratio:640/190;background:var(--surface);
  border:1.5px solid var(--rule);border-radius:var(--r-l);overflow:hidden}
.cks-device{position:absolute;transform:translate(-50%,-50%);width:104px;height:56px;
  border-radius:var(--r-m);display:flex;align-items:center;justify-content:center;
  font-family:var(--f-display);font-weight:700;font-size:12px;text-align:center;z-index:2;
  background:var(--process-bg);border:2px solid var(--process);color:var(--process)}
.cks-device.b{background:var(--ok-bg);border-color:var(--ok);color:var(--ok)}
.cks-lane{position:absolute;height:6px;border-radius:var(--r-full);background:var(--rule-2);transform:translateY(-50%)}
.cks-packet{position:absolute;transform:translate(-50%,-50%);display:flex;gap:4px;padding:5px;
  border-radius:var(--r-m);background:var(--surface);border:1.5px solid var(--process-line);
  box-shadow:var(--shadow);z-index:3;transition:left 1.3s linear;white-space:nowrap}
.cks-packet .chip{padding:3px 8px;font-size:11px}
.cks-packet .cks-trailer{background:var(--warn-bg);border-color:var(--warn);color:var(--warn)}
.cks-packet.gone{opacity:0;transition:opacity .35s ease}
.cks-packet.cks-corrupt{animation:dxShake .28s ease 2}
.cks-packet .chip.cks-flip{background:var(--no)!important;border-color:var(--no)!important;color:#fff!important}
.cks-burst{position:absolute;transform:translate(-50%,-50%);width:10px;height:10px;
  border-radius:50%;background:var(--no);z-index:4;animation:dxBurst .55s ease-out forwards}
.cks-trailer{background:var(--warn-bg);border-color:var(--warn);color:var(--warn)}
.cks-corrupt-chip{background:var(--no-bg)!important;border-color:var(--no)!important;color:var(--no)!important}
.cks-cmpbox{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-m);padding:16px;text-align:center}
.cks-cmpbox.match{border-color:var(--ok);background:var(--ok-bg)}
.cks-cmpbox.mismatch{border-color:var(--no);background:var(--no-bg)}
.cks-cmpval{font-size:var(--t-lg);font-weight:700;margin:8px 0 0}
@media (max-width:560px){
  .cks-device{width:70px;height:46px;font-size:9.5px}
  .cks-packet .chip{padding:2px 6px;font-size:10px}
}

/* ---------- echo check: animated round trip ----------
   Same fixed-coordinate-space + persistent-packet technique as
   .cks-stage/.dx-stage above, re-namespaced per-widget as usual; the
   travelling chip is plain text (a data string, not a byte/bit value)
   so it gets its own small pill rather than reusing .chip or .bit-tile. */
.echo-stage{position:relative;width:100%;aspect-ratio:640/190;background:var(--surface);
  border:1.5px solid var(--rule);border-radius:var(--r-l);overflow:hidden}
.echo-device{position:absolute;transform:translate(-50%,-50%);width:104px;height:56px;
  border-radius:var(--r-m);display:flex;align-items:center;justify-content:center;
  font-family:var(--f-display);font-weight:700;font-size:12px;text-align:center;z-index:2;
  background:var(--process-bg);border:2px solid var(--process);color:var(--process)}
.echo-device.b{background:var(--ok-bg);border-color:var(--ok);color:var(--ok)}
.echo-lane{position:absolute;height:6px;border-radius:var(--r-full);background:var(--rule-2);transform:translateY(-50%)}
.echo-packet{position:absolute;transform:translate(-50%,-50%);padding:5px 12px;border-radius:var(--r-full);
  font-family:var(--f-mono);font-size:12px;font-weight:700;white-space:nowrap;
  background:var(--process);color:#fff;box-shadow:var(--shadow);z-index:3;transition:left 1.1s linear,opacity .35s ease}
.echo-packet.gone{opacity:0}
.echo-packet.echo-corrupt{background:var(--no);animation:dxShake .28s ease 2}
.echo-packet.echo-resend{background:var(--warn)}
@media (max-width:560px){
  .echo-device{width:70px;height:46px;font-size:9.5px}
  .echo-packet{padding:4px 8px;font-size:10px}
}

/* ---------- ARQ: animated acknowledgement/timeout exchange ----------
   Same fixed-coordinate-space + persistent-packet technique as
   .echo-stage/.cks-stage above, re-namespaced per-widget as usual. */
.arq-stage{position:relative;width:100%;aspect-ratio:640/190;background:var(--surface);
  border:1.5px solid var(--rule);border-radius:var(--r-l);overflow:hidden}
.arq-device{position:absolute;transform:translate(-50%,-50%);width:104px;height:56px;
  border-radius:var(--r-m);display:flex;align-items:center;justify-content:center;
  font-family:var(--f-display);font-weight:700;font-size:12px;text-align:center;z-index:2;
  background:var(--process-bg);border:2px solid var(--process);color:var(--process)}
.arq-device.b{background:var(--ok-bg);border-color:var(--ok);color:var(--ok)}
.arq-lane{position:absolute;height:6px;border-radius:var(--r-full);background:var(--rule-2);transform:translateY(-50%)}
.arq-packet{position:absolute;transform:translate(-50%,-50%);padding:5px 12px;border-radius:var(--r-full);
  font-family:var(--f-mono);font-size:11.5px;font-weight:700;white-space:nowrap;
  color:#fff;box-shadow:var(--shadow);z-index:3;transition:left 1s linear,opacity .35s ease}
.arq-packet.gone{opacity:0}
.arq-packet.arq-data{background:var(--process)}
.arq-packet.arq-ack{background:var(--ok)}
.arq-packet.arq-nack{background:var(--no);animation:dxShake .28s ease 2}
@media (max-width:560px){
  .arq-device{width:70px;height:46px;font-size:9.5px}
  .arq-packet{padding:4px 8px;font-size:10px}
}

/* ---------- Asymmetric encryption: animated walkthrough ----------
   Same fixed-coordinate-space + persistent-packet technique as
   .echo-stage/.arq-stage above, taller to fit the algorithm boxes and
   key inputs above/below the lane, re-namespaced .asym- as usual. */
.asym-stage{position:relative;width:100%;aspect-ratio:640/220;background:var(--surface);
  border:1.5px solid var(--rule);border-radius:var(--r-l);overflow:hidden}
.asym-node{position:absolute;transform:translate(-50%,-50%);width:106px;height:60px;
  border-radius:var(--r-m);display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:3px;text-align:center;z-index:2;background:var(--process-bg);border:2px solid var(--process)}
.asym-node.b{background:var(--ok-bg);border-color:var(--ok)}
.asym-node-label{font-family:var(--f-display);font-weight:700;font-size:11.5px;color:var(--process)}
.asym-node.b .asym-node-label{color:var(--ok)}
.asym-node-doc{font-family:var(--f-mono);font-size:9.5px;color:var(--ink-2);
  max-width:92px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.asym-lane{position:absolute;height:6px;border-radius:var(--r-full);background:var(--rule-2);transform:translateY(-50%)}
.asym-box{position:absolute;transform:translate(-50%,-50%);width:110px;height:56px;
  border-radius:var(--r-m);display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:1px;text-align:center;z-index:2;background:var(--surface-3);border:2px solid var(--rule-2);
  font-family:var(--f-display);font-weight:700;font-size:10px;line-height:1.3;color:var(--ink-2);
  transition:background .25s ease,border-color .25s ease,color .25s ease}
.asym-box.active{background:var(--warn-bg);border-color:var(--warn);color:var(--warn)}
.asym-cloud{position:absolute;transform:translate(-50%,-50%);display:flex;flex-direction:column;
  align-items:center;gap:2px;z-index:1;color:var(--ink-3)}
.asym-cloud svg{width:46px;height:29px;fill:var(--surface-3);stroke:var(--rule-2);stroke-width:1.5}
.asym-cloud span{font-family:var(--f-display);font-weight:700;font-size:8.5px;letter-spacing:.04em}
.asym-key{position:absolute;transform:translate(-50%,-50%);padding:5px 10px;border-radius:var(--r-full);
  font-family:var(--f-display);font-weight:600;font-size:9.5px;white-space:nowrap;z-index:2;
  background:var(--surface-3);border:1.5px solid var(--rule-2);color:var(--ink-2);opacity:.55;
  transition:opacity .25s ease,border-color .25s ease,color .25s ease,background .25s ease}
.asym-key.active{opacity:1;background:var(--warn-bg);border-color:var(--warn);color:var(--warn)}
.asym-eaves{position:absolute;transform:translate(-50%,-50%);display:flex;flex-direction:column;
  align-items:center;gap:1px;z-index:2;opacity:.5;transition:opacity .2s ease}
.asym-eaves.active{opacity:1}
.asym-eaves-icon{font-size:16px;line-height:1}
.asym-eaves-label{font-family:var(--f-display);font-weight:700;font-size:7.5px;letter-spacing:.05em;color:var(--ink-2)}
.asym-eaves-tag{font-family:var(--f-mono);font-size:8px;font-weight:700;color:var(--no);opacity:0;transition:opacity .2s ease}
.asym-eaves.active .asym-eaves-tag{opacity:1}
.asym-packet{position:absolute;transform:translate(-50%,-50%);padding:5px 11px;border-radius:var(--r-full);
  font-family:var(--f-mono);font-size:10.5px;font-weight:700;white-space:nowrap;
  box-shadow:var(--shadow);z-index:3;transition:left 1s linear,top 1s linear,opacity .35s ease}
.asym-packet.gone{opacity:0}
.asym-packet.asym-plain{background:var(--process);color:#fff}
.asym-packet.asym-cipher{background:var(--ink);color:var(--surface)}
.asym-packet.asym-spy{border:2px dashed var(--no)}
@media (max-width:560px){
  .asym-stage{aspect-ratio:640/250}
  .asym-node{width:76px;height:50px}
  .asym-node-label{font-size:9.5px}
  .asym-node-doc{font-size:8px;max-width:64px}
  .asym-box{width:78px;height:48px;font-size:8.5px}
  .asym-cloud svg{width:34px;height:22px}
  .asym-key{padding:4px 7px;font-size:8px}
  .asym-packet{padding:4px 8px;font-size:9px}
}

/* ---------- Diffie–Hellman key exchange (syllabus extension) ----------
   Three declarative info cards (Sender / Eve / Recipient) — rebuilt in
   full on every stage change, since nothing inside them is mid-
   transition — plus a slim animated "wire" strip below using the same
   fixed-coordinate-space + persistent-packet technique as the other
   widgets above, just for the one moment the public values physically
   cross the network. */
.dh-cols{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px}
.dh-col{background:var(--surface);border:1.5px solid var(--rule);border-radius:var(--r-l);padding:14px 16px}
.dh-col-eve{background:var(--warn-bg);border-color:var(--warn-line)}
@media (max-width:720px){ .dh-cols{grid-template-columns:1fr} }
.dh-wire{position:relative;width:100%;aspect-ratio:640/70;margin-top:14px}
.dh-lane{position:absolute;height:4px;border-radius:var(--r-full);background:var(--rule-2);transform:translateY(-50%)}
.dh-eaves{position:absolute;transform:translate(-50%,-50%);display:flex;flex-direction:column;
  align-items:center;gap:1px;z-index:2;opacity:.55;transition:opacity .2s ease}
.dh-eaves.active{opacity:1}
.dh-eaves-icon{font-size:17px;line-height:1}
.dh-eaves-tag{font-family:var(--f-mono);font-size:8px;font-weight:700;color:var(--warn);opacity:0;
  transition:opacity .2s ease;white-space:nowrap}
.dh-eaves.active .dh-eaves-tag{opacity:1}
.dh-packet{position:absolute;transform:translate(-50%,-50%);padding:4px 10px;border-radius:var(--r-full);
  font-family:var(--f-mono);font-size:11px;font-weight:700;white-space:nowrap;
  background:var(--process);color:#fff;box-shadow:var(--shadow);z-index:3;
  transition:left 1s linear,top 1s linear,opacity .35s ease}
.dh-packet.gone{opacity:0}
.dh-packet.dh-spy{background:var(--warn);border:2px dashed var(--no)}
@media (max-width:560px){
  .dh-col{padding:10px 12px}
  .dh-packet{padding:3px 7px;font-size:9.5px}
}

/* ---------- Symmetric encryption: letter-shift stepper (Figure 2.19) ----------
   Plain declarative re-render on every tick — nothing here is a CSS
   transition that a re-render could interrupt, unlike the packet-travel
   widgets above, so no persistent-element trickery is needed. Word
   blocks get one of three pastel tints purely to group letters visually;
   the tint carries no other meaning. */
.symc-wrap{display:flex;flex-wrap:wrap;gap:12px}
.symc-word{border-radius:var(--r-m);padding:8px 6px}
.symc-tint-a{background:var(--ok-bg)}
.symc-tint-b{background:var(--warn-bg)}
.symc-tint-c{background:var(--process-bg)}
.symc-row{display:flex;gap:2px}
.symc-row+.symc-row{margin-top:2px}
.symc-cell{width:26px;height:26px;display:flex;align-items:center;justify-content:center;
  font-family:var(--f-mono);font-weight:700;font-size:13px;border-radius:6px;
  background:var(--surface);border:1px solid var(--rule);color:var(--ink)}
.symc-cell.symc-k{color:var(--ink-3);opacity:.35;transition:opacity .2s ease,color .2s ease}
.symc-cell.symc-k.revealed{opacity:1;color:var(--ink-2)}
.symc-cell.symc-c{color:var(--ink-3)}
.symc-cell.symc-c.revealed{background:var(--process);color:#fff;border-color:var(--process)}
.symc-cell.current{outline:2px solid var(--warn);outline-offset:1px}
@media (max-width:560px){
  .symc-cell{width:19px;height:19px;font-size:10.5px}
}

/* ---------- Public-key sharing at scale (Figure 2.22) ----------
   Five fixed pentagon positions shared by both modes (star / mesh); only
   the SVG connector lines and each card's key icons change on toggle —
   a plain declarative re-render, no travelling elements involved. */
.pks-stage{position:relative;width:100%;aspect-ratio:400/320}
.pks-svg{position:absolute;inset:0;width:100%;height:100%}
.pks-card{position:absolute;transform:translate(-50%,-50%);width:100px;min-height:58px;
  background:var(--surface);border:2px solid var(--rule-2);border-radius:var(--r-m);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
  padding:6px 4px;text-align:center;z-index:2;font-family:var(--f-display)}
.pks-card.pks-hub{border-color:var(--process);background:var(--process-bg)}
.pks-card-name{font-weight:700;font-size:11.5px;color:var(--ink)}
.pks-card-key{font-size:9px;color:var(--ink-2)}
.pks-card-key-priv{color:var(--warn)}
@media (max-width:560px){
  .pks-card{width:72px;min-height:48px;padding:4px 3px}
  .pks-card-name{font-size:9px}
  .pks-card-key{font-size:7px}
}


.btn.danger{background:var(--no);box-shadow:none}
.btn.danger:hover{opacity:.9}


/* ---------- Flowchart Studio · "Draw Your Own Flowchart" builder
   (core/flowbuilder.js) — three columns: Symbol palette (narrow) |
   Flowchart canvas (dominant — sized to its own content, not a fixed
   oversized box) | a right-hand column stacking a live, collapsible
   Pseudocode panel above the Trace Table, each independently
   scrollable so the page itself never has to scroll to see all three
   at once. Dark "Symbol" header reuses the platform's --chrome panel
   tokens; the canvas and trace-table frames use the --fcs-frame
   accent declared alongside the other semantic colours near the top
   of this file (light+dark). No dialog boxes anywhere in this tool —
   .fcs-toast replaces alert(), the "click again" armed button state
   replaces confirm(), and .fcs-edge-label-input / .fcs-node-text
   (contenteditable) replace prompt(). ---------- */
.fcs-wrap{display:flex;flex-direction:column;gap:8px}

/* No title bar here, on purpose. One was built and then removed: it repeated
   the step title that the shell's own solo bar already shows ("Draw Your Own
   Flowchart" — core/shell.js's soloBar), and its save-state chip said exactly
   what .fcs-status's Saved/Unsaved readout says. Two bars restating the same
   two facts is one bar too many on a page whose whole purpose is to hand the
   canvas the room. Its three controls (guide / PDF / full screen) moved into
   .fcs-toolbar-end below, which is available in every mode rather than one. */

/* ---- action bar ----
   Compact and clustered: toolbarHtml() hands one .fcs-toolbar-group per
   cluster with hairline separators between them, and .fcs-toolbar-end is
   pinned right so the always-available guide / PDF / full-screen cluster
   holds the same position in every mode. It wraps only where wrapping is
   invisible cost (wide screens); the RESPONSIVE ladder below turns it into
   one swipeable row once a single column has no width to spare. */
.fcs-toolbar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;
  position:relative}
.fcs-toolbar-group{display:flex;flex-wrap:wrap;gap:4px;align-items:center;
  flex:0 0 auto;min-width:0}
.fcs-toolbar .btn.sm{padding:4px 9px}
.fcs-toolbar-end{margin-left:auto}
/* Disabled actions are dimmed rather than hidden, so the bar's layout — and
   therefore muscle memory — never shifts as the canvas fills up. */
.fcs-toolbar .btn[disabled],.fcs-toolbar-end .btn[disabled]{opacity:.38;pointer-events:none}
.fcs-toolbar-sep{width:1px;align-self:stretch;min-height:18px;background:var(--rule-2)}

/* ---- "Flowchart Examples and Pseudocode" resource button ----
   One more flex item inside .fcs-toolbar itself (appended last by
   toolbarHtml(), after utilityToolbarHtml()'s cluster — see
   core/flowbuilder.js's resourceBtnHtml()), not a separate row: above
   ≤999px, where the toolbar still wraps normally, ordinary flex-wrap
   puts it right after the utility cluster when there's room and drops it
   to its own line when there isn't, so it no longer always claims a row
   the toolbar may not have needed. Past ≤999px the toolbar becomes one
   non-wrapping, horizontally-scrollable row (further down this file) and
   this button rides along in that swipe exactly like every other toolbar
   action already does. flex:0 0 auto keeps it from shrinking/wrapping its
   own text mid-word the way a generic flex child could. */
.fcs-resource-btn{flex:0 0 auto;padding:11px 20px;font-size:var(--t-sm);gap:10px;max-width:100%}
.fcs-resource-icon{width:18px;height:18px;flex:0 0 auto}
.fcs-resource-btn span{overflow-wrap:anywhere}
.fcs-zoom{display:flex;align-items:center;gap:6px;font-family:var(--f-mono);font-size:12px;color:var(--ink-2)}
.fcs-zoom button{width:24px;height:24px;border-radius:var(--r-s);border:1px solid var(--rule-2);
  background:var(--surface);color:var(--ink);font-weight:700;cursor:pointer;line-height:1}
.fcs-zoom button:hover{border-color:var(--process-line);color:var(--process)}
.fcs-zoom button.fcs-zoom-fit{width:auto;padding:0 8px;font-size:11px;font-weight:600;letter-spacing:.01em}
.fcs-zoom button[disabled]{opacity:.4;pointer-events:none}
.fcs-zoom-val{min-width:44px;text-align:center;font-variant-numeric:tabular-nums}

/* Distraction-free mode: hide secondary clusters (Example/Saved/Clear, Save as PDF)
   while keeping Save, Undo/Redo, Pseudocode/Run, Focus and Reset Layout visible. */
.fcs-focus .fcs-nonessential{display:none!important}

/* ---- status bar ----
   Mode, document size, what is selected / what to do next, save state and
   zoom — the row an editor keeps permanently on screen. It replaces the old
   full-width .fcs-hint strip that sat between the toolbar and the diagram,
   so the same guidance now costs one 30px row pinned to the bottom instead
   of a full-height band above the canvas. */
.fcs-status{display:flex;align-items:center;gap:12px;min-width:0;
  background:var(--chrome);color:var(--on-chrome,#F1F5F9);
  border-radius:var(--r-m);padding:6px 14px;
  font-family:var(--f-mono);font-size:11.5px;line-height:1.5}
.fcs-status-mode{font-family:var(--f-display);font-weight:800;font-size:10px;letter-spacing:.1em;
  text-transform:uppercase;background:var(--chrome-2);border:1px solid var(--chrome-line);
  border-radius:999px;padding:2px 9px;flex:0 0 auto}
.fcs-status-counts{flex:0 0 auto;white-space:nowrap;color:var(--on-chrome-2)}
.fcs-status-dirty{flex:0 0 auto;white-space:nowrap;color:#FCA5A5}
/* The context line is the one part allowed to shrink: it ellipsises and
   carries the full text in its title, so a long instruction never widens the
   bar and pushes the zoom control off the right edge. */
.fcs-status-detail{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;opacity:.85}
.fcs-status-spacer{flex:0 0 8px}
.fcs-status-save{flex:0 0 auto;white-space:nowrap;color:var(--on-chrome-2)}
.fcs-status .fcs-zoom{flex:0 0 auto}
.fcs-status .fcs-zoom button{background:var(--chrome-2);border-color:var(--chrome-line);color:var(--on-chrome)}
.fcs-status .fcs-zoom button:hover{background:#2A3A57;border-color:var(--on-chrome-2);color:#fff}
/* The context line is NOT dropped at any width here. It used to be, at
   ≤820px — but a tablet in portrait has ~600px of bar to work with, where
   an ellipsised context line plus a title tooltip beats no context at all.
   It now only goes in the ≤400px rung of the RESPONSIVE block below, where
   there genuinely is no room for it. */

/* ---- shared panel header: one slim tab bar for all three panels ----
   Replaces the old centred, 3px-bordered, uppercase poster banners
   (.fcs-canvas-head / .fcs-trace-head / .fcs-pseudo-head), which together
   cost ~90px of canvas height and read as signs on a worksheet rather than
   as tabs in a tool. Left-aligned, quiet, with an optional right-hand meta
   slot the panel uses to report its own size. */
.fcs-panel-head{display:flex;align-items:center;gap:10px;flex:0 0 auto;
  padding:7px 12px;background:var(--surface-2);
  font-family:var(--f-display);font-weight:800;font-size:11.5px;letter-spacing:.6px;
  text-transform:uppercase;color:var(--ink-3);border-bottom:1px solid var(--rule)}
.fcs-panel-meta{margin-left:auto;font-family:var(--f-mono);font-size:11px;font-weight:400;
  letter-spacing:0;text-transform:none;color:var(--ink-3)}
/* Small icon ahead of each panel's label (core/flowbuilder.js's panelIcon())
   — purely decorative, the text already names the panel, so it just takes
   the header's own muted ink colour via currentColor rather than a new
   token. */
.fcs-panel-icon{width:14px;height:14px;flex:0 0 auto;color:var(--ink-3)}
/* Trace Table's live-mode indicator: a small pill with a pulsing dot,
   replacing the old plain "live" text — reuses --ok/--ok-bg (the same
   teal-green pair used for correct/passing states elsewhere on the site)
   rather than inventing a one-off green, so it stays correct in both
   themes for free. */
.fcs-live-badge{margin-left:auto;display:inline-flex;align-items:center;gap:5px;
  padding:2px 9px 2px 7px;border-radius:999px;background:var(--ok-bg);color:var(--ok);
  font-family:var(--f-mono);font-size:10.5px;font-weight:700;letter-spacing:.03em;
  text-transform:none}
.fcs-live-dot{width:6px;height:6px;border-radius:50%;background:var(--ok);flex:0 0 auto;
  animation:fcsLivePulse 1.6s ease-in-out infinite}
@keyframes fcsLivePulse{0%,100%{opacity:1}50%{opacity:.35}}
@media (prefers-reduced-motion:reduce){.fcs-live-dot{animation:none}}

/* ---- "LOOKUP TABLE" — opens the Flowchart Symbols reference modal ----
   Deliberately fixed white/black/red, not design-token colours that would
   flip with the site theme: this is meant to read as a reference sticker
   ("look here"), the same in light or dark mode, not as a themed editor
   control — closer kin to the loop badges below than to .btn. Sits in the
   canvas panel's own header (.fcs-panel-head, pushed right by
   .fcs-panel-meta's margin-left:auto), never inside .fcs-toolbar, so it
   can never overlap or be confused with an editor command. */
.fcs-lookup-btn{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  padding:5px 14px;background:#FFFFFF;border:2px solid #111318;border-radius:var(--r-s);
  color:#D41E1E;font-family:var(--f-display);font-weight:800;font-size:11.5px;
  letter-spacing:.5px;text-transform:uppercase;cursor:pointer;white-space:nowrap;
  transition:background .15s ease,transform .1s ease,box-shadow .15s ease}
.fcs-lookup-btn:hover{background:#FFF1F1;box-shadow:0 2px 6px rgba(0,0,0,.2)}
.fcs-lookup-btn:active{transform:translateY(1px)}

/* Wider than the shared .modal-wide (640px) — this dialog carries a real
   three-column Symbol/Name/Usage table, not prose, so it needs the extra
   room to avoid cramming the Usage column. Still capped well under the
   viewport and still scrolls internally via .modal-scroll-body. */
.fcs-symbols-modal{max-width:760px}
.fcs-symbols-modal .modal-scroll-body table{width:100%}
.fcs-symbols-modal .modal-scroll-body td:first-child{width:110px;text-align:center}

/* Palette ~21% | Canvas dominant (~46%, and always the largest track) |
   Right column ~33% — three real fractions rather than fixed pixels,
   so the canvas keeps the majority of the width at every size down to
   the tablet breakpoint below. */
/* The symbol palette used to be a 7-row column in a 21fr side panel. That
   made "where do I add a symbol?" the first thing a student had to hunt
   for, and it stole width from the canvas — the panel that actually needs
   it. It is now a single strip across the top of the studio, so every
   symbol is one click away at the same height as the canvas, and the
   freed column width goes to the diagram. */
/* 58/42 — widened from the previous 66/34 so the trace table and
   pseudocode panel (the two panels a student is actually reading while
   tracing a program) get materially more width, while the canvas still
   keeps the larger share and the example algorithm (640px of diagram)
   still fits inside its column without sideways scrolling. */
.fcs-studio{display:grid;grid-template-columns:58fr 42fr;gap:12px;align-items:start;position:relative}
/* Draggable vertical splitter between the canvas and the pseudocode/trace
   column (core/flowbuilder.js's splitDrag / onDocMouseDown / render()).
   position:absolute removes it from the grid's normal auto-placement flow
   (so it never shifts the palette/canvas/right items around or needs its
   own cell), while grid-row:2 + grid-column:1/-1 constrains WHERE that
   absolute box resolves against — the content row only (row 2: canvas,
   splitter, right — row 1 is the palette strip), spanning its full width
   so `left` can be set as a plain percentage of the studio's own width,
   matching how the inline grid-template-columns split is computed. */
.fcs-split-v{position:absolute;grid-row:2;grid-column:1/-1;top:0;bottom:0;width:11px;
  margin-left:-5px;cursor:col-resize;z-index:5;touch-action:none}
.fcs-split-v::after{content:'';position:absolute;top:0;bottom:0;left:5px;width:2px;
  background:var(--rule-2);transition:background .12s ease,width .12s ease}
.fcs-split-v:hover::after,.fcs-split-v.dragging::after{background:var(--process);width:3px;left:4px}
@media (max-width:999px){ .fcs-split-v{display:none} }

/* Draggable horizontal splitter between the pseudocode and trace panels,
   a plain flex sibling of both inside .fcs-right (no absolute positioning
   needed — .fcs-right's own children aren't reordered at any breakpoint
   the way the studio's top-level grid items are). Hidden by
   rightColumnHtml() whenever either panel is collapsed, since there is
   nothing left to drag against a panel pinned to its header height. */
.fcs-split-h{flex:0 0 9px;cursor:row-resize;position:relative;touch-action:none}
.fcs-split-h::after{content:'';position:absolute;left:0;right:0;top:3px;height:2px;
  background:var(--rule-2);transition:background .12s ease,height .12s ease}
.fcs-split-h:hover::after,.fcs-split-h.dragging::after{background:var(--process);height:3px;top:2.5px}
/* Static example viewer (chapters/ch11-examples-data.js, flowbuilder.js's
   renderReadOnly) — same node/edge/pseudocode markup as the live editor,
   with every drag/resize/connect affordance hidden and pointer events
   disabled on the node text so the diagram can be looked at but not
   accidentally dragged, resized or typed into. */
.fcs-readonly .fcs-handle,.fcs-readonly .fcs-resize-handle,.fcs-readonly .fcs-node-del{display:none!important}
.fcs-readonly .fcs-node-text{pointer-events:none;cursor:default}
.fcs-readonly .fcs-node{cursor:default}
.fcs-readonly-studio{grid-template-columns:62fr 38fr}
/* Every breakpoint for this studio lives in ONE labelled block at the end of
   the section (search "RESPONSIVE") rather than as a set of scattered
   max-width queries next to whichever rule happened to need one — with three
   columns, three panels, an action bar, a status bar and an insert palette,
   the breakpoints interact, and scattering them made it impossible to reason
   about what a given viewport actually gets. */

/* -- palette: the editor's "Insert" strip --
   Still one horizontal row directly under the action bar, so every symbol is
   a single click away at the same height as the canvas, but no longer a
   coloured header block on a 3px frame: a quiet label, hairline borders, and
   the symbol's own thumbnail as the affordance. That buys back ~30px of
   canvas height over the old banner. */
.fcs-palette{grid-column:1/-1;background:var(--surface);border:1px solid var(--rule);
  border-radius:var(--r-m);overflow:hidden;display:flex;flex-direction:row;align-items:stretch;height:fit-content}
.fcs-palette-head{background:var(--surface-2);color:var(--ink-3);font-family:var(--f-display);
  font-weight:800;font-size:10.5px;letter-spacing:.5px;text-transform:uppercase;padding:0 10px;
  display:flex;align-items:center;flex:0 0 auto;border-right:1px solid var(--rule)}
.fcs-pal-body{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;padding:4px 6px}
/* Shape-tray cards: the thumbnail IS the symbol (real geometry, real
   tokens, and the connector dots a selected canvas node grows), so the
   seven cards are told apart by silhouette rather than by hue. Fixed
   width keeps the row even; the label sits under the shape the way a
   shape palette in a diagram tool does.

   Compact card: 64px wide with a 46x20 thumb (was 82px / 70x30). The
   thumbnails are viewBox-scaled SVGs, so one CSS size change shrinks the
   icons with everything else and the drawn proportions stay honest.
   Labels may wrap to a second line at this width ("Start / Stop"), which
   is fine — the list stretches its cards to a common height, so the row
   stays even. */
.fcs-pal-list{display:flex;flex-direction:row;flex-wrap:wrap;gap:4px}
.fcs-pal-item{display:flex;flex-direction:column;align-items:center;gap:1px;width:64px;padding:4px 3px 3px;
  background:var(--surface);border:1px solid var(--rule);border-radius:6px;cursor:grab;
  font-family:var(--f-display);font-size:9.5px;font-weight:700;color:var(--ink-2);text-align:center;
  transition:border-color .12s ease,background .12s ease,color .12s ease}
.fcs-pal-item:hover{background:var(--surface-3);border-color:var(--process);color:var(--ink)}
.fcs-pal-item:hover .fcs-pal-thumb circle{fill:var(--process)}
.fcs-pal-item:focus-visible{outline:2px solid var(--process);outline-offset:2px}
.fcs-pal-item:active{cursor:grabbing;transform:translateY(0)}
.fcs-pal-thumb{width:46px;height:20px;display:block;overflow:visible;flex:0 0 auto}
.fcs-pal-thumb circle{transition:fill .12s ease}
.fcs-pal-label{line-height:1.2;white-space:normal}
/* "Flow line" is a how-to, not a symbol you can drop: dashed and calm, so
   it can't be mistaken for a seventh shape. */
.fcs-pal-item-info{border-style:dashed;cursor:pointer}
.fcs-pal-item-info:hover{border-color:var(--ink-3);transform:none}


/* -- canvas: the dominant panel, sized to its own content via inline
   style from canvasSize() rather than a fixed oversized box --
   Hairline frame, not a 3px poster border: the canvas is a workspace, and
   the ruler-and-chrome look of an editor's document area is what the rest of
   this studio is now built to match. */
.fcs-canvas-panel{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-m);
  overflow:hidden;display:flex;flex-direction:column;height:min(86vh,920px);min-height:420px}
.fcs-canvas-scroll{position:relative;flex:1 1 auto;overflow:auto;background:var(--surface-2);
  background-image:radial-gradient(var(--rule) 1px,transparent 1px);background-size:22px 22px}
.fcs-canvas-area{position:relative;transform-origin:0 0}
.fcs-canvas-area.connecting,.fcs-canvas-area.linking{cursor:crosshair}
.fcs-canvas-area *{-webkit-user-drag:none}
.fcs-edges{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible}
.fcs-edge-hit{cursor:pointer;pointer-events:stroke}
.fcs-edge-label-hit{pointer-events:all}
/* Second SVG layer, stacked above every symbol div (z-index higher than
   any .fcs-node state below ever sets) — see its own comment in
   canvasPanelHtml(). Holds only the selected flowline's own controls, so
   a handle sitting right on a symbol's border is never covered by that
   symbol and stays clickable/draggable. */
.fcs-edge-ctrl-layer{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;z-index:4}
.fcs-empty-hint{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:min(350px,92%);
  text-align:center;font-family:var(--f-display);font-size:13px;color:var(--ink-2);line-height:1.55}
.fcs-empty-badge{display:inline-block;padding:3px 14px;border-radius:999px;background:var(--fcs-frame);color:#fff;
  font-size:11px;font-weight:800;letter-spacing:1.5px;text-transform:uppercase;margin-bottom:10px}
.fcs-empty-lead{margin:0 0 8px;font-weight:700;color:var(--ink-2)}
.fcs-empty-steps{margin:0 auto 14px;padding:0;list-style:none;text-align:left;display:inline-block;color:var(--ink)}
.fcs-empty-steps li{margin:3px 0;font-size:12.5px}
.fcs-empty-steps b{color:var(--process)}
.fcs-empty-actions{display:flex;justify-content:center;gap:8px;flex-wrap:wrap}
.fcs-empty-more{margin:12px 0 0;font-size:12px;color:var(--ink-3)}
.fcs-empty-more b{color:var(--process)}

.fcs-node{position:absolute;transform:translate(-50%,-50%);box-sizing:border-box;
  padding:8px 12px;text-align:center;font-family:var(--f-display);font-size:12.5px;font-weight:700;
  color:var(--ink);cursor:grab;user-select:none;box-shadow:var(--shadow);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  transition:outline-color .12s ease}
.fcs-node:active{cursor:grabbing}
.fcs-node .fcs-node-text{white-space:normal;word-break:break-word;max-width:100%;
  /* The shape itself no longer clips (overflow:hidden on .fcs-node was
     silently cropping each connection dot to its inner half — 46% of the
     hit area — and the × delete button to 7-23%, making both fiddly to
     use). Clipping moved onto the text itself, so long text still cannot
     spill out of a resized symbol: as a flex child with min-height:0 it
     shrinks to whatever room the box has. */
  flex:0 1 auto;min-height:0;overflow:hidden}
/* Every symbol TYPE gets its own colour, so the shape+colour combination
   is identifiable at a glance even before reading the label — reusing the
   platform's existing multi-colour token set (--mm-1..8, already defined
   with full light+dark values elsewhere) rather than inventing a second
   palette. Start/Stop stays a neutral grey (the conventional "boundary"
   shape, matching the oval drawn with no fill in most textbooks); Process
   keeps the existing blue; Subroutine moves off that same blue onto its
   own indigo (--mm-8) now that it has a colour of its own to carry, on
   top of the double side-bar that already marks it as a subroutine call;
   Input and Output — previously the same teal for both, differentiated
   only by their "INPUT "/"OUTPUT " text prefix — now split onto
   --input (teal, --mm-6) and --output (amber, --mm-4) respectively via
   the .fcs-io-input/.fcs-io-output modifier class renderNode() adds
   alongside the shared .io shape class; Decision moves onto its own pink
   (--mm-5) now that Output no longer needs to borrow it. */
.fcs-node.terminator{background:var(--surface-3);border:2px solid var(--ink-2);border-radius:999px}
.fcs-node.process{background:var(--process-bg);border:2px solid var(--process-line)}
.fcs-node.subroutine{background:var(--mm-8-bg);border:2px solid var(--mm-8-line);
  box-shadow:inset 4px 0 0 var(--mm-8-line),inset -4px 0 0 var(--mm-8-line)}
.fcs-node.io{background:var(--input-bg);border:2px solid var(--input-line);
  clip-path:polygon(12% 0,100% 0,88% 100%,0 100%)}
.fcs-node.fcs-io-output{background:var(--mm-4-bg);border:2px solid var(--mm-4-line)}
.fcs-node.decision{background:var(--mm-5-bg);border:2px solid var(--mm-5-line);border-radius:0;
  clip-path:polygon(50% 4%,96% 50%,50% 96%,4% 50%)}
.fcs-node.selected{outline:3px solid var(--process);outline-offset:2px;z-index:3}
/* Multi-selected (via Ctrl+A or a marquee drag over several symbols): a
   dashed accent outline distinguishes "part of a group, drag any one to
   move them all" from the single-selection solid outline, which also still
   carries the resize/connect handles and the delete button that a group
   member deliberately does not render. */
.fcs-node.fcs-node-multisel{outline:3px dashed var(--process);outline-offset:2px;z-index:2}
/* Rubber-band marquee drawn while dragging over empty canvas (see
   onDocMouseDown/renderMarquee in flowbuilder.js). Lives inside #fcs-canvas
   so it sits in the same unscaled coordinate space as every node — zoom
   scales it along with everything else for free. */
.fcs-marquee{position:absolute;border:1px dashed var(--process);
  background:color-mix(in srgb, var(--process) 12%, transparent);
  pointer-events:none;z-index:8}
/* Sits just clear of the top-right corner, offset in from the tr resize
   handle: parked on the corner it shared that handle (plus the t
   connection dot) and lost ~half its hit area, because the handles are
   emitted after it at the same z-index and therefore paint over it. */
.fcs-node-del{position:absolute;top:-9px;right:22px;width:20px;height:20px;border-radius:50%;
  background:var(--no);color:#fff;border:2px solid var(--surface);font-size:12px;line-height:1;
  display:flex;align-items:center;justify-content:center;cursor:pointer;font-weight:700;padding:0;z-index:6}
.fcs-node-prefix{display:block;font-size:10px;font-weight:800;letter-spacing:.5px;color:var(--ink-3);
  margin-bottom:2px;pointer-events:none;flex:0 0 auto}
.fcs-node-text{display:inline-block;min-width:1ch;outline:none;border-radius:3px;padding:1px 3px;
  cursor:text}
.fcs-node-text:focus{background:var(--surface);box-shadow:0 0 0 2px var(--process);cursor:text}
.fcs-handle{position:absolute;width:12px;height:12px;border-radius:50%;background:var(--surface);
  border:2px solid var(--process);cursor:crosshair;z-index:4}
.fcs-handle:hover{background:var(--process)}
.fcs-handle.t{top:-6px;left:50%;transform:translateX(-50%)}
.fcs-handle.b{bottom:-6px;left:50%;transform:translateX(-50%)}
.fcs-handle.l{left:-6px;top:50%;transform:translateY(-50%)}
.fcs-handle.r{right:-6px;top:50%;transform:translateY(-50%)}
/* A clip-path clips its children too, so the diamond and parallelogram
   cannot host a dot that straddles their border — a clip-path has no
   border box to straddle, and outside the polygon simply isn't painted.
   Those two shapes get their dots inset onto the painted area instead
   (measured against the polygons in .fcs-node.io / .fcs-node.decision);
   every other shape keeps the conventional dot-on-the-border. */
.fcs-node.decision .fcs-handle.t{top:13%}
.fcs-node.decision .fcs-handle.b{bottom:13%}
.fcs-node.decision .fcs-handle.l{left:12%}
.fcs-node.decision .fcs-handle.r{right:12%}
.fcs-node.io .fcs-handle.t{top:1px}
.fcs-node.io .fcs-handle.b{bottom:1px}
.fcs-node.io .fcs-handle.l{left:10%}
.fcs-node.io .fcs-handle.r{right:10%}
.fcs-node.decision .fcs-resize-handle{width:12px;height:12px}
.fcs-node.decision .fcs-resize-handle.tl{top:30%;left:30%}
.fcs-node.decision .fcs-resize-handle.tr{top:30%;right:30%}
.fcs-node.decision .fcs-resize-handle.bl{bottom:30%;left:30%}
.fcs-node.decision .fcs-resize-handle.br{bottom:30%;right:30%}
/* The × and the corner handles are children of the shape, so on the two
   clip-path shapes they can only exist where the polygon is actually
   painted. The parallelogram's top edge cuts through the old parked
   position (only ~55% of the button was left), and the diamond has no
   painted area near its box corners at all — its × was 0% clickable,
   i.e. invisible. Both are placed on the shape's widest band instead,
   verified by hit-testing every pixel of each control. */
.fcs-node.io .fcs-node-del{top:1px}
.fcs-node.decision .fcs-node-del{top:50%;right:20%;transform:translateY(-50%)}
.fcs-node.io .fcs-resize-handle{width:12px;height:12px}
.fcs-node.io .fcs-resize-handle.tl{top:8%;left:12%}
.fcs-node.io .fcs-resize-handle.tr{top:8%;right:2%}
.fcs-node.io .fcs-resize-handle.bl{bottom:8%;left:7%}
.fcs-node.io .fcs-resize-handle.br{bottom:8%;right:8%}
.fcs-resize-handle{position:absolute;width:12px;height:12px;background:var(--surface);
  border:2px solid var(--fcs-frame);border-radius:2px;z-index:5}
.fcs-resize-handle.tl{top:-6px;left:-6px;cursor:nwse-resize}
.fcs-resize-handle.tr{top:-6px;right:-6px;cursor:nesw-resize}
.fcs-resize-handle.bl{bottom:-6px;left:-6px;cursor:nesw-resize}
.fcs-resize-handle.br{bottom:-6px;right:-6px;cursor:nwse-resize}
.fcs-edge-label-input{position:absolute;transform:translate(-50%,-50%);width:64px;padding:3px 4px;
  text-align:center;font-family:var(--f-display);font-size:11px;font-weight:800;color:var(--process);
  background:var(--surface);border:2px solid var(--process);border-radius:4px;z-index:5}

/* -- execution highlighting -- */
.fcs-node-current{outline:3px solid var(--process);outline-offset:3px;box-shadow:0 0 0 6px color-mix(in srgb, var(--process) 18%, transparent),var(--shadow);z-index:3}
.fcs-node-visited{opacity:.62}
.fcs-edge-executed{stroke:var(--process,#2563eb)!important;opacity:.75}
.fcs-edge-current{stroke:var(--process,#2563eb)!important;stroke-width:3.5!important}
.fcs-edge-inactive{opacity:.25;stroke-dasharray:3,4}

/* -- selected flowline (a line is now a selectable object in its own
   right, separate from node selection — see ST.selectedEdge). The real
   line stays visible, just a touch thicker; the actual "selected" signal
   is the dashed blue overlay drawn on top of it, below. -- */
.fcs-edge-selected{stroke-width:2.5!important}
.fcs-edge-select-overlay{stroke:var(--process,#2563eb);stroke-width:2.5;stroke-dasharray:6,5;pointer-events:none}

/* -- selected-flowline handles: start (remove), middle (drag to adjust the
   line's actual route — elbowPath()'s lenAdj), end (drag to reconnect to
   a different node) — drawn directly on the selected line itself, in
   place of separate toolbar buttons. Positions come from the path's own
   rendered geometry every time it's drawn, so they stay correctly placed
   through zoom, pan, and node moves without any CSS of their own doing
   that work. -- */
.fcs-edge-start-handle,.fcs-edge-mid-handle,.fcs-edge-end-handle{
  stroke-width:1.5;stroke:var(--surface,#fff);transition:filter .1s ease;pointer-events:all;touch-action:none}
.fcs-edge-start-handle{fill:var(--yes,#16a34a);cursor:pointer}
.fcs-edge-mid-handle{fill:#60a5fa;cursor:ns-resize}
.fcs-edge-mid-handle[data-axis="horizontal"]{cursor:ew-resize}
.fcs-edge-end-handle{fill:var(--process,#2563eb);cursor:grab}
.fcs-edge-start-handle:hover,.fcs-edge-mid-handle:hover,.fcs-edge-end-handle:hover{filter:brightness(1.15)}
.fcs-edge-ctrl-glyph{font-size:11px;font-weight:700;fill:var(--surface,#fff)}

/* -- live preview while dragging the end handle to reconnect a line, and
   the hover highlight on whatever node is currently a valid drop target
   for it (see endDrag in core/flowbuilder.js) -- */
.fcs-edge-reconnect-preview{stroke:var(--process,#2563eb);opacity:.6}
.fcs-edge-reconnect-preview.fcs-edge-reconnect-valid{opacity:1}
.fcs-node-drop-hover{box-shadow:0 0 0 3px var(--process-glow,rgba(37,99,235,.45))!important}
@media (prefers-reduced-motion:no-preference){
  .fcs-node-current{animation:fcsPulse 1.1s ease-in-out infinite}
}
@keyframes fcsPulse{ 0%,100%{box-shadow:0 0 0 6px color-mix(in srgb, var(--process) 18%, transparent),var(--shadow)} 50%{box-shadow:0 0 0 10px color-mix(in srgb, var(--process) 10%, transparent),var(--shadow)} }

/* -- right column: pseudocode above trace table, each own scroller --
   Height cap raised from 72vh/760px to 86vh/920px, and the 50/50 split
   tilted toward the trace table (now 44/56) — the trace table is the
   panel students watch populate row by row while running a program and
   is usually the wider one (variables + array indices + Output), so it
   gets the larger share of the extra room. */
.fcs-right{display:flex;flex-direction:column;gap:12px;height:min(86vh,920px);min-height:420px}
.fcs-pseudo-panel{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-m);
  display:flex;flex-direction:column;overflow:hidden;flex:0 1 60%;min-height:120px}
.fcs-collapse-btn{width:22px;height:22px;border-radius:50%;border:1px solid var(--rule-2);
  background:var(--surface);color:var(--ink-2);font-weight:800;line-height:1;cursor:pointer;
  flex:0 0 auto;padding:0;margin-left:auto}
.fcs-collapse-btn:hover{border-color:var(--process-line);color:var(--process)}
.fcs-pseudo-body{flex:1 1 auto;overflow:auto;padding:12px 14px}
.fcs-loop-summary{display:flex;flex-direction:column;gap:6px;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid var(--rule)}
.fcs-loop-row{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;font-size:12px}
.fcs-loop-badge{display:inline-block;flex:0 0 auto;padding:2px 8px;border-radius:999px;font-weight:800;
  font-size:10.5px;letter-spacing:.03em;text-transform:uppercase;color:#fff;white-space:nowrap}
.fcs-loop-while{background:var(--process)}
.fcs-loop-repeat{background:var(--fcs-frame)}
.fcs-loop-cond{flex:0 0 auto;font-family:var(--f-mono);font-weight:700;color:var(--ink-2)}
.fcs-loop-note{color:var(--ink-3);font-size:11.5px}
/* Line-numbered gutter: one .fcs-code-row per statement (core/flowbuilder.js's
   pseudoPanelHtml()) rather than a single white-space:pre block, so the
   numbers stay pinned to their own line through wrapping/scrolling instead
   of being a separately-counted overlay that can drift out of sync. */
.fcs-code{font-family:var(--f-mono);font-size:12.5px;line-height:1.7;
  overflow-x:auto;color:var(--ink)}
.fcs-code-row{display:flex;align-items:flex-start}
.fcs-code-ln{flex:0 0 auto;width:2.4em;margin-right:12px;padding-right:4px;text-align:right;
  color:var(--ink-3);opacity:.6;-webkit-user-select:none;user-select:none;border-right:1px solid var(--rule)}
.fcs-code-text{flex:1 1 auto;white-space:pre;min-width:0}
/* Reserved words vs string literals — one accent colour each, both pulled
   from tokens already used for exactly this distinction elsewhere (the
   process accent for "this means something to the language", ink-2 for
   literal text), so a highlighted line still reads correctly in dark mode
   without a second palette to maintain. */
.fcs-pc-kw{color:var(--process);font-weight:700}
.fcs-pc-str{color:var(--ink-2)}
.fcs-errors{margin:0;padding-left:18px}
.fcs-errors li{color:var(--warn);margin-bottom:6px}
.fcs-empty{color:var(--ink-3);font-size:12.5px;margin:0}

/* -- trace table: manual grid, columns kept in sync with detected
   variables (see analyzeVariables/syncTraceColumns) -- */
.fcs-trace-panel{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-m);
  overflow:hidden;display:flex;flex-direction:column;flex:1 1 40%;min-height:180px}
.fcs-trace-body{flex:1 1 auto;overflow:auto;padding:10px}
.fcs-trace-table{width:100%;border-collapse:collapse}
.fcs-trace-table th,.fcs-trace-table td{border:1px solid var(--rule-2);padding:0;position:relative}
.fcs-trace-table th input,.fcs-trace-table td input{width:100%;border:none;background:transparent;
  padding:7px 22px 7px 8px;font-family:var(--f-display);font-size:12.5px;color:var(--ink);text-align:center}
.fcs-trace-table th input{font-weight:800;color:var(--ink-2);padding-right:22px}
.fcs-trace-table th{background:var(--surface-2);position:sticky;top:0;z-index:2}
.fcs-trace-table td input{padding-right:8px}
.fcs-trace-table td.fcs-trace-del{border:none;width:26px;text-align:center;background:var(--surface)}
.fcs-col-del{position:absolute;top:2px;right:2px;width:16px;height:16px;border-radius:50%;
  border:none;background:transparent;color:var(--ink-3);font-size:11px;line-height:1;
  cursor:pointer;padding:0;opacity:.6}
.fcs-col-del:hover{opacity:1;background:var(--surface-3)}
/* The Output column is guaranteed, so it renders as plain text rather than
   a renameable/deletable input — a visual cue (accent background, like the
   read-only table's .fcs-th-ro) that this one column always stays. */
.fcs-th-locked{background:color-mix(in srgb, var(--fcs-frame) 8%, var(--surface));
  font-weight:800;color:var(--fcs-frame);font-family:var(--f-display);font-size:12.5px;
  padding:7px 8px;text-align:center}
.fcs-trace-actions{display:flex;gap:8px;padding:8px 12px;border-top:1px solid var(--rule);flex:0 0 auto}

/* -- First-run "How to use" guide: a dismissible strip above the studio,
   open by default and remembered per device via localStorage (see
   helpDismissed()/toggleHelp() in core/flowbuilder.js) -- */
/* -- First-run "How to use" guide: a modal (see openHelp in
   core/flowbuilder.js), not an inline strip. It is open by default on a
   first visit, so as a strip it pushed the canvas below the fold before the
   student had drawn anything; as a dialog it asks once and then gets out of
   the way. Reuses the shell's .modal-scrim / .modal-wide / .modal-head /
   .modal-scroll-body so it looks and behaves like every other dialog on the
   site (auth, onboarding) rather than inventing a second dialog style. -- */
.fcs-help-modal .modal-head h3{font-size:var(--t-xl)}
.fcs-help-modal .modal-scroll-body{padding-top:4px}
.fcs-help-steps{margin:0 0 20px;padding-left:20px;display:grid;gap:10px 28px;
  grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}
.fcs-help-steps li{font-family:var(--f-display);font-size:var(--t-base);line-height:1.5;color:var(--ink-2)}
.fcs-help-steps b{color:var(--process)}
/* The numbers come from the <ol> itself; the accent is on the verb in each
   instruction instead, so there is no counter to keep in sync. */
.fcs-help-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  border-top:1px solid var(--rule);padding-top:16px}

/* -- toast: replaces alert() -- */
.fcs-toast{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);z-index:60;
  background:var(--chrome);color:var(--on-chrome,#F1F5F9);font-family:var(--f-display);font-size:12.5px;
  font-weight:600;padding:10px 18px;border-radius:999px;box-shadow:var(--shadow);
  max-width:min(88vw,420px);text-align:center}

@media (prefers-reduced-motion:no-preference){
  .fcs-toast{animation:fcsToastIn .16s ease-out}
}
@keyframes fcsToastIn{ from{opacity:0;transform:translate(-50%,8px)} to{opacity:1;transform:translate(-50%,0)} }

/* -- Run/execution UI: in-canvas input form, contextual toolbar,
   status bar and read-only live trace table. No browser dialogs. -- */
.fcs-toolbar-label{font-family:var(--f-display);font-size:var(--t-xs);color:var(--ink-3);
  align-self:center;margin-right:2px}
.fcs-speed-group{gap:4px}
/* .fcs-hint used to live here: a full-width dashed strip between the
   toolbar and the canvas carrying the same four messages the status bar's
   context line now carries. Removed — see .fcs-status above. */
.fcs-run-panel{position:absolute;inset:14px;z-index:10;background:var(--surface);
  border:2px solid var(--fcs-frame);border-radius:var(--r-m);box-shadow:var(--shadow);
  padding:20px;width:max-content;min-width:min(400px,86%);max-width:min(560px,90%);margin:auto;
  height:fit-content;max-height:calc(100% - 28px);display:flex;flex-direction:column;gap:10px}
.fcs-run-panel h3{margin:0;font-family:var(--f-display);font-size:15px;letter-spacing:.4px;
  text-transform:uppercase;color:var(--fcs-frame)}
.fcs-run-fields{display:flex;flex-direction:column;gap:10px;max-height:44vh;overflow:auto}
.fcs-run-field{display:flex;flex-direction:column;gap:4px;font-family:var(--f-display);font-size:12.5px;
  font-weight:700;color:var(--ink-2)}
.fcs-run-field input{border:1px solid var(--rule-2);border-radius:var(--r-s);padding:8px 10px;
  font-family:var(--f-mono);font-size:13px;color:var(--ink);background:var(--surface)}
.fcs-run-field input:focus{outline:none;border-color:var(--process);box-shadow:0 0 0 2px color-mix(in srgb, var(--process) 22%, transparent)}
.fcs-run-actions{display:flex;gap:8px;margin-top:4px}
.fcs-run-actions .btn{flex:1 1 auto;justify-content:center;padding:9px 16px;font-size:var(--t-sm)}

.fcs-exec-status{display:flex;flex-wrap:wrap;align-items:center;gap:6px 16px;
  background:var(--surface-2);border:1px solid var(--rule);border-radius:var(--r-s);
  padding:8px 14px;font-family:var(--f-mono);font-size:12.5px;color:var(--ink)}
.fcs-exec-step{font-weight:700}
.fcs-exec-error{color:var(--warn)}
.fcs-exec-done{color:var(--yes,#16A34A);font-weight:700}
.fcs-exec-outputs{color:var(--ink-2)}

.fcs-trace-table.fcs-trace-readonly th,.fcs-trace-table.fcs-trace-readonly td{
  padding:7px 10px;font-family:var(--f-mono);font-size:12.5px;text-align:center}
.fcs-th-ro{background:color-mix(in srgb, var(--fcs-frame) 8%, var(--surface));
  font-weight:800;color:var(--fcs-frame);font-family:var(--f-display)!important}
.fcs-row-current{background:color-mix(in srgb, var(--process) 12%, transparent);font-weight:700}

/* -- Full Screen: delegates entirely to the site-wide mechanism above
   (CS.shell.toggleFullscreen / .main.site-fs-active) rather than running
   a second, competing fullscreen layout — see the ".main.site-fs-active
   .stage-inner:has(#fcs-mount)" rules near line 465 for the studio-
   specific width/stretch overrides applied while it's active. -- */
/* Sizing now comes entirely from the shared .btn.ghost.sm base (same as
   .pe-fs-btn/.site-fs-btn elsewhere) so the box grows to fit "Exit Full
   Screen" instead of staying pinned to a 34px icon-button width while
   the label text overflowed it — that mismatch was pushing the whole
   toolbar (and the page under it) into horizontal scroll at every
   viewport width. This rule now only adds this studio's own look. */
.fcs-fs-btn{font-size:11px;font-weight:700}
.fcs-fs-glyph{font-size:13px;line-height:1}

/* ============================================================
   Flowchart Studio · RESPONSIVE
   ------------------------------------------------------------
   One breakpoint ladder for the whole editor, in the order a
   viewport actually gets narrower. Each rung answers "what has
   to give way first?" — and the order matters, because the canvas
   is the one thing that must survive all of it:

     ≥1400px  three columns, canvas dominant         (nothing clipped)
     ≤1399px  nudge the ratio toward the canvas      (still one row)
     ≤1180px  the output panels stop claiming desktop height
     ≤999px   ONE column: canvas, then palette, then the output panels;
              the action bar stops wrapping and swipes like the palette
     ≤760px   palette becomes a single swipeable row instead of 3 stacked;
              action-bar separators drop, gaps tighten
     ≤560px   compact buttons, smaller symbols, tighter chrome
     ≤400px   the widest possible canvas: only mode + zoom survive
     short    landscape phones / split-screen: drop minimum heights

   Height is treated as its own axis, not as a side effect of width:
   min-height:420px on the canvas is generous on a 900px-tall desktop and an
   overflow on a 420px-tall phone in landscape. */

/* ---- ≤1399px: slightly wider canvas column, tighter gaps ---- */
@media (max-width:1399px){
  .fcs-studio{grid-template-columns:60fr 40fr;gap:10px}
}

/* ---- ≤1180px: stop the output panels claiming desktop height ----
   Past this width the canvas column is already cramped, and holding
   .fcs-right at 72vh forces the student to scroll past two stacked
   output panels before reaching the diagram. */
@media (max-width:1180px){
  .fcs-right{height:auto}
  .fcs-pseudo-panel,.fcs-trace-panel{max-height:400px}
}

/* ---- ≤999px: single column, and the canvas goes FIRST ----
   Order matters more than the grid here: the diagram is what is being
   worked on, so it must never sit below two panels of generated output. */
@media (max-width:999px){
  .fcs-studio{grid-template-columns:1fr;gap:10px}
  .fcs-canvas-panel{order:1}
  .fcs-palette{order:2}
  .fcs-right{order:3}
  .fcs-canvas-panel{height:min(62vh,560px);min-height:340px}
  /* The action bar stops wrapping and becomes one swipeable row — the same
     treatment the palette gets at ≤760px, but needed earlier: a
     single-column layout has no width to spare for a second wrapped row of
     buttons, and hiding labels would trade a few pixels of chrome for
     muscle memory. Labels stay on and ellipsize; the status bar carries
     the run-mode instruction whenever the hint truncates. */
  .fcs-toolbar{flex-wrap:nowrap;overflow-x:auto;overscroll-behavior-x:contain;
    scrollbar-width:thin;padding:3px 0 5px}
  .fcs-toolbar-group{flex-wrap:nowrap}
  .fcs-toolbar-label{min-width:0;overflow:hidden;text-overflow:ellipsis;
    white-space:nowrap}
}

/* ---- solo (full-bleed) mode has its own ladder ----
   Solo mode strips the app header, rail and footer and hands the whole
   viewport to the editor, which makes the stacked ≤999px layout far more
   wasteful than it is in a normal page: there is no surrounding chrome to
   justify giving up 60% of the screen to the pseudocode panel. So solo
   keeps two columns down to 760px — which only works because the panels
   are stacked in that column, not side by side. */
@media (max-width:999px){
  .main.step-solo .fcs-studio{grid-template-columns:minmax(0,1.6fr) minmax(240px,.9fr)}
  .main.step-solo .fcs-canvas-panel{order:0}
  .main.step-solo .fcs-palette{order:0;grid-column:1/-1}
  .main.step-solo .fcs-right{order:0}
}
@media (max-width:760px){
  .main.step-solo .fcs-studio{grid-template-columns:1fr}
  .main.step-solo .fcs-canvas-panel{order:1}
  .main.step-solo .fcs-right{order:3}
}

/* ---- ≤760px: the palette swipes instead of stacking ----
   Seven 64px cards need ~470px, and below this width they used to spill onto
   extra rows and cost canvas height on a screen with little to give. One
   horizontally-scrollable row keeps the palette a single row tall at every
   width — what every mobile diagram tool does — and the first cards stay
   exactly where they were, so muscle memory survives the change. */
@media (max-width:760px){
  .fcs-palette{display:block}
  .fcs-palette-head{display:inline-flex;padding:6px 12px;border-right:0;
    border-bottom:1px solid var(--rule)}
  .fcs-pal-body{padding:6px}
  .fcs-pal-list{flex-wrap:nowrap;overflow-x:auto;overscroll-behavior-x:contain;
    scrollbar-width:thin;padding-bottom:2px}
  .fcs-pal-item{flex:0 0 auto}
  /* The action bar has been swiping since ≤999px; this is where the
     hairline separators between clusters cost more than they clarify
     inside a scrolling row, so they go and the gap tightens. */
  .fcs-toolbar{gap:4px}
  .fcs-toolbar-sep{display:none}
}

/* ---- ≤560px: compact chrome and compact symbols ---- */
@media (max-width:560px){
  /* One notch tighter still: the swiping bar's buttons start crowding each
     other at this width, and every pixel here is a pixel the canvas keeps. */
  .fcs-toolbar .btn.sm{padding:4px 8px}
  /* The full-screen control carries the longest label in the bar ("Exit Full
     Screen"), so below this width it shows the glyph alone. The label is a
     separate element precisely so this is a display toggle rather than a
     font-size hack — ::first-letter does not apply to an inline-flex button.
     The selector is spelled out so it out-orders the tight .btn.sm padding
     above and keeps the glyph button a comfortable tap target. */
  .fcs-fs-label{display:none}
  .fcs-fs-glyph{font-size:15px}
  .fcs-toolbar .btn.sm.fcs-fs-btn{padding:5px 10px}

  /* The button now swipes along with the rest of the toolbar at this width
     (see the ≤999px rung above) rather than sitting on its own full-width
     row, so it gets the same tightened padding as every other button in
     that row instead of a width:100% override that no longer applies to a
     flex item inside a non-wrapping, horizontally-scrolling bar. */
  .fcs-resource-btn{padding:9px 14px}

  .fcs-status{gap:8px;padding:5px 10px;font-size:11px}
  .fcs-status-counts{font-size:10.5px}
  .fcs-zoom button{width:22px;height:22px}
  .fcs-zoom button.fcs-zoom-fit{width:auto;padding:0 6px;font-size:10px}
  .fcs-zoom-val{min-width:38px;font-size:11px}

  .fcs-node{min-width:96px;max-width:150px;padding:8px 10px;font-size:11px}
  .fcs-canvas-panel{height:58vh;min-height:300px}
  .fcs-pseudo-panel,.fcs-trace-panel{max-height:300px}
  .fcs-run-panel{inset:8px;padding:14px}
  .fcs-trace-table th input,.fcs-trace-table td input{padding:7px 6px;font-size:12px}
  /* The guide is a centred dialog on its own scrim (see openHelp in
     core/flowbuilder.js). Below this width the shell's 640px cap crowds the
     instructions and stacks the two actions awkwardly, so it tightens its own
     padding and lets both actions share the row evenly. */
  .fcs-help-modal{padding:22px 18px}
  .fcs-help-steps{grid-template-columns:1fr}
  .fcs-help-actions .btn{flex:1 1 auto;justify-content:center}
}

/* ---- Touch support for Flowchart Studio ----
   core/flowbuilder.js now wires its node-drag/resize/connect/marquee state
   machine to Pointer Events, so it fires for touch and pen as well as a
   mouse — but two things CSS still has to provide for that to actually
   work and be usable on a real touchscreen:
     1. touch-action:none on anything that gesture is supposed to drag,
        so the browser's own scroll/pan/zoom doesn't steal the gesture
        before a pointermove ever reaches the handler (the splitters
        above already had this; nodes/handles/canvas did not).
     2. Bigger hit targets on a coarse (touch) pointer — a precise mouse
        cursor can land on a 12px dot, a finger cannot. Scaling the
        existing dot/handle up (rather than resizing+repositioning each
        shape-specific offset variant above) keeps every decision/io
        corner-case already tuned above correct without duplicating it. */
.fcs-node,.fcs-canvas-area,.fcs-handle,.fcs-resize-handle{touch-action:none}
@media (pointer:coarse){
  .fcs-handle,.fcs-resize-handle{transform:scale(1.7)}
  .fcs-handle.t,.fcs-handle.b{transform:translateX(-50%) scale(1.7)}
  .fcs-handle.l,.fcs-handle.r{transform:translateY(-50%) scale(1.7)}
  .fcs-node-del{width:30px;height:30px;font-size:15px}
  .fcs-zoom button{width:36px;height:36px}
  .fcs-zoom button.fcs-zoom-fit{width:auto;padding:0 12px}
}

/* ---- ≤400px: give the canvas every last pixel ----
   Mode and zoom are the only two status-bar items that still earn their
   width on a 360px phone; the rest is either truncated past usefulness or
   already stated elsewhere on screen. */
@media (max-width:400px){
  .fcs-status-detail,.fcs-status-save,.fcs-status-dirty,.fcs-status-counts{display:none}
  .fcs-status-spacer{display:none}
  .fcs-status{justify-content:space-between}
  .fcs-empty-hint{width:min(300px,94%)}
  /* Palette cards no longer shrink here: the base card is already the
     compact 64px one, and pushing below that made the labels overflow the
     card they sit in. Phones keep the same card and swipe the row. */
}

/* ---- short viewports: landscape phones, split-screen windows ---- */
@media (max-height:560px){
  .fcs-canvas-panel{height:min(74vh,420px);min-height:220px}
  .fcs-run-fields{max-height:34vh}
  .fcs-toast{bottom:12px}
  /* Playground/Idle, normal (non-fullscreen) mode: the editor (.pe-body,
     normally a fixed 420px) and the Output/Variables/Trace/Errors panel
     (.pe-output-panel, normally a fixed 486px) stack to 906px total on
     narrow widths — on a landscape phone (~360-400px tall) that's more
     than double the viewport, with no "more below" affordance. Shrinking
     both, same min()-with-floor technique as .fcs-canvas-panel above,
     keeps each one tall enough to actually use while the pair fits a
     short viewport far better. */
  .pe-body{height:min(50vh,420px)!important}
  .pe-output-panel{height:min(40vh,486px)!important}
}
@media (max-height:420px){
  .fcs-canvas-panel{min-height:160px}
  /* Four instructions in a short viewport: stack them in one column and let
     the dialog's own .modal-scroll-body scroll (it is max-height:82vh), so no
     instruction is ever cut off. */
  .fcs-help-steps{grid-template-columns:1fr;gap:8px}
  .fcs-help-steps li{font-size:var(--t-sm)}
}

/* ---- the solo stage needs its own padding ladder ----
   .main.step-solo pins the stage edge-to-edge, so the normal 16px/20px
   inset that looked right against the app chrome is dead space against a
   viewport edge on a phone. */
@media (max-width:760px){
  .main.step-solo .stage{padding:10px 12px}
  /* Solo mode deliberately gives the panels height:auto so the canvas fills
     the column (see the rule above). On a short viewport that leaves the
     canvas with whatever is left AFTER the action bar, the palette and the
     status bar have each taken their share — which on a landscape phone can
     be almost nothing. So on short viewports the panels give part of it
     back: a floor, not a fixed height, so a tall screen still stretches. */
  .main.step-solo .fcs-canvas-panel{min-height:220px}
  .main.step-solo .fcs-studio{min-height:0}
}
@media (max-width:400px){
  .main.step-solo .stage{padding:8px}
}

/* ---- very wide screens: keep the frame from stretching apart ----
   The canvas is happy to be wide, but the action bar's buttons and the
   status bar's context line are not — left alone they end up three screen-
   widths apart. Capping the frame costs the diagram nothing (its content
   has a fixed minimum size) and keeps the chrome readable. */
@media (min-width:2000px){
  .fcs-wrap{max-width:1900px;margin:0 auto;width:100%}
}

/* -- Download PDF: reuses the formula-sheet's "hide everything except
   my print target" technique (see the @media print block above this
   one in the file) so #fcs-print-sheet is the only thing shown when
   window.print() is called from CS.flowbuilder.downloadPdf(). The
   report is fully self-contained — no PDF library is bundled; the
   browser's own "Save as PDF" print destination produces crisp,
   vector-text output without adding weight to this single-file app. -- */
#fcs-print-sheet{display:none}
@media print{
  #fcs-print-sheet,#fcs-print-sheet *{visibility:visible!important}
  #fcs-print-sheet{display:block!important;position:absolute;left:0;top:0;width:100%}
}
.fcs-print-page{position:relative;padding:18px 6px;font-family:var(--f-display),sans-serif;color:#1a1a2e;background:#fff;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='420' height='420'%3E%3Ctext x='0' y='230' font-family='Georgia,serif' font-size='34' font-weight='700' fill='%237A1F2B' fill-opacity='0.055' transform='rotate(-30 210 210)'%3EViewTheConcept.com%3C/text%3E%3C/svg%3E");
  background-repeat:repeat;background-size:420px 420px}
.fcs-print-header{display:flex;justify-content:space-between;align-items:flex-start;
  border-bottom:3px solid #7A1F2B;padding-bottom:12px;margin-bottom:14px}
.fcs-print-logo{display:flex;align-items:center;gap:10px}
.fcs-print-logo-mark{width:38px;height:38px;border-radius:9px;background:#7A1F2B;color:#fff;
  display:flex;align-items:center;justify-content:center;font-weight:800;font-size:15px;flex:0 0 auto}
.fcs-print-logo-text{font-size:19px;font-weight:800;color:#7A1F2B;line-height:1.2}
.fcs-print-logo-dim{color:#1a1a2e;font-weight:600}
.fcs-print-tagline{font-size:10px;color:#666;font-style:italic;margin-top:1px}
.fcs-print-meta{text-align:right;font-size:11px;color:#555;line-height:1.5}
.fcs-print-title{font-size:16px;margin:0 0 4px;color:#1a1a2e}
.fcs-print-sub{font-size:11.5px;color:#555;margin:0 0 8px}
.fcs-print-section-title{font-size:12.5px;font-weight:800;text-transform:uppercase;letter-spacing:.6px;
  color:#7A1F2B;border-bottom:2px solid #7A1F2B;padding-bottom:4px;margin:18px 0 10px}
.fcs-print-canvas-frame{border:2px solid #7A1F2B;border-radius:8px;padding:10px;background:#fff;
  position:relative;overflow:hidden}
.fcs-print-canvas-frame .fcs-canvas-area{position:relative}
.fcs-print-code{white-space:pre;font-family:var(--f-mono);font-size:11px;line-height:1.6;
  background:#f7f7fa;border:1px solid #ddd;border-radius:6px;padding:10px 12px;page-break-inside:avoid}
.fcs-print-table{width:100%;border-collapse:collapse;font-size:11px;page-break-inside:avoid}
.fcs-print-table th,.fcs-print-table td{border:1.5px solid #7A1F2B;padding:6px 8px;text-align:center}
.fcs-print-table th{background:#F6E9EA;color:#7A1F2B;font-weight:800}
.fcs-print-footer{margin-top:20px;padding-top:8px;border-top:1px solid #ccc;font-size:9.5px;
  color:#888;text-align:center}
