/* ── css/base.css ── */
/* ============================================================
   BASE — reset, document defaults, boot fallback
   ============================================================ */
*,*::before,*::after{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0;
  background:var(--bg);
  color:var(--ink);
  font-family:var(--f-body);
  font-size:var(--fs-base);
  line-height:var(--lh);
  -webkit-font-smoothing:antialiased;
  overflow:hidden;
}
button{font:inherit;color:inherit;background:none;border:none;padding:0;cursor:pointer}
h1,h2,h3,p,ul{margin:0}
ul{padding:0;list-style:none}
:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
/* the attribute has to outrank the class rules that set a display, or anything
   hidden from js stays on screen: [hidden] is one selector and .tbtn is one
   selector, and the later rule wins. Both #domainBtn and .slide-foot are
   hidden that way, and both looked fine until the case that hides them. */
[hidden]{display:none!important}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}

/* the app is a three-row grid: chrome, stage, chrome.
   minmax(0,1fr) is load-bearing: with an auto column the topbar's min-content
   width would stretch the whole grid — and the stage with it — past a narrow
   window, and the unit would never reach the margins. */
#app{display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto 1fr auto;height:100%}

/* ------------------------------------------------------------
   BOOT ERROR — the corpus is fetched, and ES modules are used,
   so the folder has to be served over http, not opened as a file
   ------------------------------------------------------------ */
#bootError{
  position:fixed;inset:0;z-index:999;display:grid;place-items:center;
  background:#14161a;color:#e8eaee;padding:24px;
  font:14px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
}
#bootError[hidden]{display:none}
#bootError .box{max-width:520px}
#bootError b{display:block;font-size:17px;margin-bottom:10px}
#bootError code{
  display:block;margin:12px 0;padding:10px 12px;border-radius:4px;
  background:#000;color:#9fe6c0;font-family:ui-monospace,"SF Mono",Menlo,monospace;font-size:13px;
}
#bootError p{color:#a8adb6;margin-top:10px}
/* ── css/themes.css ── */
/* ============================================================
   COLOUR SCHEMES  —  <html data-color="…">
   --up   = deeper / harder      --down = simpler / easier
   --lat  = lateral / sibling    --key  = a door in the prose
   --seq  = the reading order — the spine both webs are drawn on
   ============================================================ */
[data-color="vellum"]{
  --bg:#efe7d8; --bg-2:#e6dcc9; --panel:#f6f0e3; --surface:#fbf7ec; --surface-2:#f2ead9;
  --ink:#2e2820; --ink-2:#6b6152; --ink-3:#9c9182;
  --line:#3a3225; --line-soft:#c3b7a0;
  --up:#9b3521; --down:#4a6b43; --lat:#8a7a4e;
  --seq:#57503f;
  --key:#2f6a8c;
  --focus:#9b3521; --shadow:rgba(60,48,30,.16); --sel:rgba(155,53,33,.10);
}
[data-color="bone"]{
  --bg:#f4f4f2; --bg-2:#e9e9e6; --panel:#fdfdfc; --surface:#ffffff; --surface-2:#f6f6f4;
  --ink:#16161a; --ink-2:#5c5c66; --ink-3:#9a9aa4;
  --line:#16161a; --line-soft:#cfcfd4;
  --up:#c02434; --down:#1e4fd8; --lat:#7a7a86;
  --seq:#41414c;
  --key:#6d31c4;
  --focus:#1e4fd8; --shadow:rgba(20,20,26,.14); --sel:rgba(30,79,216,.08);
}
[data-color="blueprint"]{
  --bg:#0d2a4a; --bg-2:#0a2340; --panel:#123255; --surface:#123a60; --surface-2:#0f3255;
  --ink:#e8f2ff; --ink-2:#a9c6e6; --ink-3:#6e93bd;
  --line:#8fc0f0; --line-soft:#2d5a86;
  --up:#ffd166; --down:#7ee0c7; --lat:#9db8d8;
  --seq:#c4dbf4;
  --key:#c9b6ff;
  --focus:#ffd166; --shadow:rgba(0,0,0,.35); --sel:rgba(255,209,102,.12);
}
[data-color="slate"]{
  --bg:#16181c; --bg-2:#101215; --panel:#1e2126; --surface:#22262c; --surface-2:#1a1d22;
  --ink:#e6e8ec; --ink-2:#a2a8b2; --ink-3:#6d7480;
  --line:#3a4048; --line-soft:#2a2f36;
  --up:#f0a35e; --down:#5ec2c6; --lat:#8b93a0;
  --seq:#c3c9d3;
  --key:#b0a4f7;
  --focus:#5ec2c6; --shadow:rgba(0,0,0,.5); --sel:rgba(94,194,198,.10);
}
[data-color="ember"]{
  --bg:#1d1512; --bg-2:#150f0d; --panel:#261b17; --surface:#2c201b; --surface-2:#221815;
  --ink:#f2e6dc; --ink-2:#c3a894; --ink-3:#8d7565;
  --line:#4a352b; --line-soft:#33241d;
  --up:#ff8a5b; --down:#b9c46a; --lat:#a08a76;
  --seq:#dcc6b3;
  --key:#7fc4d8;
  --focus:#ff8a5b; --shadow:rgba(0,0,0,.5); --sel:rgba(255,138,91,.10);
}
[data-color="neon"]{
  --bg:#08090d; --bg-2:#050609; --panel:#0e1017; --surface:#11141d; --surface-2:#0c0e15;
  --ink:#e9ecff; --ink-2:#9aa0c4; --ink-3:#5e6489;
  --line:#2a3050; --line-soft:#1a1e33;
  --up:#ff4d9d; --down:#5ef0b0; --lat:#6a72a8;
  --seq:#c2c8e8;
  --key:#6fb6ff;
  --focus:#5ef0b0; --shadow:rgba(0,0,0,.6); --sel:rgba(255,77,157,.10);
}
/* ── css/typography.css ── */
/* ============================================================
   FONT SCHEMES  —  <html data-font="…">
   ============================================================ */
[data-font="editorial"]{
  --f-display:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
  --f-body:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  --f-mono:ui-monospace,"SF Mono",Menlo,monospace;
  --fs-base:15px; --lh:1.55; --w-display:600; --tr-display:-.012em;
  --tt-label:uppercase; --tr-label:.10em; --fs-display:clamp(26px,3.1vw,40px);
}
[data-font="humanist"]{
  --f-display:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  --f-body:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  --f-mono:ui-monospace,"SF Mono",Menlo,monospace;
  --fs-base:15px; --lh:1.55; --w-display:640; --tr-display:-.022em;
  --tt-label:uppercase; --tr-label:.08em; --fs-display:clamp(25px,3vw,38px);
}
[data-font="grotesk"]{
  --f-display:"Helvetica Neue",Helvetica,Arial,sans-serif;
  --f-body:"Helvetica Neue",Helvetica,Arial,sans-serif;
  --f-mono:ui-monospace,"SF Mono",Menlo,monospace;
  --fs-base:14.5px; --lh:1.5; --w-display:700; --tr-display:-.03em;
  --tt-label:uppercase; --tr-label:.14em; --fs-display:clamp(26px,3.3vw,42px);
}
[data-font="scholar"]{
  --f-display:Baskerville,"Times New Roman",Georgia,serif;
  --f-body:Charter,Georgia,"Times New Roman",serif;
  --f-mono:ui-monospace,"SF Mono",Menlo,monospace;
  --fs-base:16px; --lh:1.68; --w-display:500; --tr-display:.004em;
  --tt-label:small-caps; --tr-label:.06em; --fs-display:clamp(27px,3.2vw,41px);
}
[data-font="mono"]{
  --f-display:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
  --f-body:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
  --f-mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
  --fs-base:13.5px; --lh:1.6; --w-display:600; --tr-display:-.02em;
  --tt-label:uppercase; --tr-label:.12em; --fs-display:clamp(22px,2.6vw,32px);
}
[data-font="geometric"]{
  --f-display:Futura,"Avenir Next","Century Gothic",sans-serif;
  --f-body:"Avenir Next",Avenir,-apple-system,sans-serif;
  --f-mono:ui-monospace,"SF Mono",Menlo,monospace;
  --fs-base:15px; --lh:1.58; --w-display:600; --tr-display:.004em;
  --tt-label:uppercase; --tr-label:.16em; --fs-display:clamp(25px,3vw,39px);
}
[data-font="scholar"] .label,[data-font="scholar"] .kicker{font-variant:small-caps;text-transform:none}
/* ── css/scaffold.css ── */
/* ============================================================
   SCAFFOLD SCHEMES  —  <html data-scaffold="…">
   frames · lines · arrows · background texture
   ============================================================ */
[data-scaffold="blueprint"]{
  --fw:1px; --fr:0px; --fstyle:solid; --box-shadow:none;
  --corner:1; --edge-w:1.1; --edge-dash:none; --node-r:2px; --port:1;
  --chip-r:0px; --chip-fw:1px;
}
[data-scaffold="circuit"]{
  --fw:1.5px; --fr:3px; --fstyle:solid; --box-shadow:none;
  --corner:0; --edge-w:1.6; --edge-dash:none; --node-r:3px; --port:1;
  --chip-r:2px; --chip-fw:1.5px;
}
[data-scaffold="organic"]{
  --fw:0px; --fr:22px; --fstyle:solid; --box-shadow:0 18px 44px -18px var(--shadow);
  --corner:0; --edge-w:1.8; --edge-dash:none; --node-r:999px; --port:0;
  --chip-r:999px; --chip-fw:0px;
}
[data-scaffold="brutal"]{
  --fw:2.5px; --fr:0px; --fstyle:solid; --box-shadow:6px 6px 0 0 var(--line);
  --corner:0; --edge-w:3; --edge-dash:none; --node-r:0px; --port:0;
  --chip-r:0px; --chip-fw:2px;
}
[data-scaffold="hairline"]{
  --fw:1px; --fr:8px; --fstyle:solid; --box-shadow:none;
  --corner:0; --edge-w:.75; --edge-dash:none; --node-r:6px; --port:0;
  --chip-r:6px; --chip-fw:1px;
}
[data-scaffold="manuscript"]{
  --fw:1px; --fr:5px; --fstyle:dashed; --box-shadow:0 10px 26px -20px var(--shadow);
  --corner:1; --edge-w:1.2; --edge-dash:5 4; --node-r:5px; --port:0;
  --chip-r:4px; --chip-fw:1px;
}
[data-scaffold="hairline"] .frame,[data-scaffold="organic"] .frame{border-color:var(--line-soft)}
[data-scaffold="organic"] .frame{background:var(--surface)}

/* background textures, per scaffold */
.tex{position:absolute;inset:0;pointer-events:none;opacity:.5;transition:opacity .3s ease}
[data-scaffold="blueprint"] .tex{
  background-image:
    linear-gradient(to right,var(--line-soft) 1px,transparent 1px),
    linear-gradient(to bottom,var(--line-soft) 1px,transparent 1px);
  background-size:32px 32px;opacity:.28;
}
[data-scaffold="circuit"] .tex{
  background-image:radial-gradient(var(--line-soft) 1.1px,transparent 1.1px);
  background-size:26px 26px;opacity:.4;
}
[data-scaffold="brutal"] .tex{
  background-image:
    linear-gradient(to right,var(--line-soft) 2px,transparent 2px),
    linear-gradient(to bottom,var(--line-soft) 2px,transparent 2px);
  background-size:96px 96px;opacity:.35;
}
[data-scaffold="manuscript"] .tex{
  background-image:linear-gradient(to bottom,var(--line-soft) 1px,transparent 1px);
  background-size:100% 30px;opacity:.35;
}
[data-scaffold="organic"] .tex,[data-scaffold="hairline"] .tex{
  background-image:radial-gradient(ellipse at 50% 0%,var(--sel),transparent 70%);opacity:.9;
}

/* frames + corner ticks */
.frame{
  border:var(--fw) var(--fstyle) var(--line);
  border-radius:var(--fr);
  box-shadow:var(--box-shadow);
  position:relative;
}
.corners{position:absolute;inset:-1px;pointer-events:none;opacity:var(--corner)}
.corners i{position:absolute;width:9px;height:9px;border:1.5px solid var(--line)}
.corners i:nth-child(1){top:-3px;left:-3px;border-right:0;border-bottom:0}
.corners i:nth-child(2){top:-3px;right:-3px;border-left:0;border-bottom:0}
.corners i:nth-child(3){bottom:-3px;left:-3px;border-right:0;border-top:0}
.corners i:nth-child(4){bottom:-3px;right:-3px;border-left:0;border-top:0}

/* connectors + arrowheads — colour comes from the direction class */
.mk-up{fill:var(--up);stroke:var(--up)}
.mk-down{fill:var(--down);stroke:var(--down)}
.mk-lat{fill:var(--lat);stroke:var(--lat)}
/* the reading itself — not a direction through the difficulty, which is what
   the other three are, but the step the author wrote next */
.mk-seq{fill:var(--seq);stroke:var(--seq)}
.e-up{stroke:var(--up)}
.e-down{stroke:var(--down)}
.e-lat{stroke:var(--lat)}
.e-seq{stroke:var(--seq)}
/* --wm is the weight a chapter chord carries (how many terms it stands for),
   --eo how far back a web has pushed it. Both are properties rather than
   widths and opacities, so lighting one can reach past whatever it was set to */
/* the 1px is load-bearing: --edge-w and --wm are both bare numbers, and a
   calc() that comes out unitless is not a stroke-width Firefox will take —
   it drops the whole declaration and draws a hairline instead */
.edge{
  fill:none;stroke-width:calc(var(--edge-w) * var(--wm,1) * 1px);
  stroke-dasharray:var(--edge-dash);stroke-linecap:round;stroke-linejoin:round;
  opacity:var(--eo,1);transition:opacity .15s,stroke-width .15s;
}
.edge.faint{opacity:.16}
/* hovered or pinned: the one line the reader is asking about */
.edge.lit{opacity:1;stroke-width:calc(var(--edge-w) * var(--wm,1) * 2.2px)}
.port-tick{stroke:var(--line);stroke-width:1.25;fill:var(--surface);opacity:var(--port)}
/* ── css/chrome.css ── */
/* ============================================================
   APP CHROME — topbar, statusbar, stage, drawer, tooltip
   ============================================================ */
.topbar{
  display:flex;align-items:center;gap:18px;padding:10px 16px;
  background:var(--panel);border-bottom:1px solid var(--line-soft);
  position:relative;z-index:40;
}
.brand{display:flex;align-items:baseline;gap:9px;min-width:0}
.brand b{font-family:var(--f-display);font-weight:var(--w-display);letter-spacing:var(--tr-display);font-size:17px}

.crumbs{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--ink-2);min-width:0;flex:1}
.crumbs button{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:22ch}
.crumbs button:hover{color:var(--ink);text-decoration:underline;text-underline-offset:3px}
.crumbs .sep{color:var(--ink-3);font-size:10px}
.crumbs .here{color:var(--ink);font-weight:600}

.seg{display:flex;border:1px solid var(--line-soft);border-radius:var(--chip-r)}
.seg button{
  padding:6px 11px;font-size:10.5px;letter-spacing:var(--tr-label);
  text-transform:var(--tt-label);color:var(--ink-2);white-space:nowrap;
}
.seg button+button{border-left:1px solid var(--line-soft)}
.seg button[aria-pressed="true"]{background:var(--sel);color:var(--ink);box-shadow:inset 0 -2px 0 var(--focus)}
.seg button:hover{color:var(--ink)}

.tbtn{
  display:inline-flex;align-items:center;gap:6px;padding:6px 11px;
  border:1px solid var(--line-soft);border-radius:var(--chip-r);
  font-size:11.5px;color:var(--ink-2);white-space:nowrap;
}
.tbtn:hover{color:var(--ink);border-color:var(--line)}
.tbtn[aria-expanded="true"],.tbtn.on{background:var(--sel);color:var(--ink);border-color:var(--focus)}
.tools{display:flex;align-items:center;gap:8px}
/* the language codes are two letters and read as a pair, so they are tighter
   than the layer switch beside them — a wide EN|HU would read as a third layer */
.lang-seg button{padding:6px 9px;font-family:var(--f-mono);letter-spacing:.06em}
.domain-picker{position:relative}
.domain-menu{
  position:absolute;top:calc(100% + 7px);right:0;z-index:70;
  min-width:230px;max-width:min(320px,calc(100vw - 20px));
  max-height:min(360px,64vh);overflow:auto;padding:5px;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--chip-r);
  box-shadow:0 18px 44px -20px var(--shadow);
}
.domain-menu button{
  display:block;width:100%;padding:8px 10px;border-radius:calc(var(--chip-r) - 2px);
  color:var(--ink-2);font-size:12px;line-height:1.25;text-align:left;
}
.domain-menu button:hover,.domain-menu button:focus-visible{background:var(--bg-2);color:var(--ink)}
.domain-menu button[aria-checked="true"]{
  background:var(--sel);color:var(--ink);box-shadow:inset 2px 0 0 var(--focus);
}

.statusbar{
  display:flex;align-items:center;gap:14px;padding:7px 16px;
  background:var(--panel);border-top:1px solid var(--line-soft);
  font-size:11px;color:var(--ink-3);
}
.statusbar kbd{
  font-family:var(--f-mono);font-size:10px;padding:1px 5px;
  border:1px solid var(--line-soft);border-radius:3px;color:var(--ink-2);
}
.statusbar .spacer{flex:1}
/* the bar is one line, and stays one line: the stage is what is left of the
   window after it, and a status long enough to wrap the hints would shorten
   the stage under a unit view that has already been laid out against it.
   So the hints keep their width and the status gives, which is what its
   ellipsis is for */
.statusbar span{white-space:nowrap}
#statusLeft{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* long / short labels — the topbar must never be wider than the window */
.seg .n,.tbtn .n{display:none}

/* ------------------------------------------------------------
   STAGE + VIEW SWITCHING — one continuous zoom in three stops
   ------------------------------------------------------------ */
.stage{position:relative;overflow:hidden;background:var(--bg)}
.view{
  position:absolute;inset:0;
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .34s ease,transform .34s cubic-bezier(.4,.02,.2,1),visibility .34s;
}
.view.active{opacity:1;visibility:visible;pointer-events:auto;transform:none}
/* zooming OUT: the leaving view swells and dissolves; the arriving one rises from small */
.view.from-in{transform:scale(1.35)}
.view.from-out{transform:scale(.72)}

/* a layer being carried by the box it shares with the next one: the transform
   is written from the two boxes, frame by frame, in js/views/morph.js — so all
   the stylesheet owes it is an origin the maths can assume, and a view that
   stays on screen while it is no longer the active one */
.view.morph{visibility:visible;transform-origin:0 0;will-change:transform,opacity}
/* the furniture is not part of the picture being zoomed; left in, it balloons */
.view.morph .tex,.view.morph .axis,.view.morph .legend,
.view.morph .mapctl,.view.morph .zoomout-hint{opacity:0}

/* ------------------------------------------------------------
   SCHEMES DRAWER
   ------------------------------------------------------------ */
.drawer{
  position:absolute;top:0;right:0;bottom:0;width:320px;z-index:50;
  background:var(--panel);border-left:1px solid var(--line);
  transform:translateX(100%);transition:transform .28s cubic-bezier(.4,.02,.2,1);
  overflow-y:auto;padding:16px 18px 40px;
}
.drawer.open{transform:none}
.drawer h2{
  font-size:10px;letter-spacing:var(--tr-label);text-transform:var(--tt-label);
  color:var(--ink-3);margin:22px 0 10px;padding-bottom:6px;border-bottom:1px solid var(--line-soft);
}
.drawer h2:first-of-type{margin-top:14px}
.drawer-head{display:flex;align-items:center;justify-content:space-between}
.drawer-head b{font-family:var(--f-display);font-size:15px;font-weight:var(--w-display)}
.opts{display:grid;grid-template-columns:1fr 1fr;gap:7px}
.opt{
  display:flex;flex-direction:column;gap:7px;padding:9px;text-align:left;
  border:1px solid var(--line-soft);border-radius:var(--chip-r);
}
.opt:hover{border-color:var(--line)}
.opt[aria-pressed="true"]{border-color:var(--focus);background:var(--sel);box-shadow:inset 0 0 0 1px var(--focus)}
.opt .n{font-size:11px;color:var(--ink-2)}
.opt[aria-pressed="true"] .n{color:var(--ink)}
.sw{display:flex;height:22px;border-radius:2px;overflow:hidden;border:1px solid rgba(128,128,128,.25)}
.sw i{flex:1}
.spec{height:30px;display:flex;align-items:center;justify-content:center;font-size:19px;line-height:1;color:var(--ink)}
.scaf{height:34px;display:grid;place-items:center}
.toggles{display:flex;flex-direction:column;gap:8px}
.tog{display:flex;align-items:center;justify-content:space-between;font-size:12px;color:var(--ink-2)}
.tog button{
  width:38px;height:20px;border-radius:999px;background:var(--bg-2);
  border:1px solid var(--line-soft);position:relative;flex:none;
}
.tog button::after{
  content:"";position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;
  background:var(--ink-3);transition:transform .18s,background .18s;
}
.tog button[aria-pressed="true"]{background:var(--sel);border-color:var(--focus)}
.tog button[aria-pressed="true"]::after{transform:translateX(18px);background:var(--focus)}
.note{font-size:10.5px;color:var(--ink-3);line-height:1.5;margin-top:10px}
.note code{font-family:var(--f-mono);font-size:10px;color:var(--ink-2)}
.scrim{position:absolute;inset:0;z-index:45;background:transparent;display:none}
.scrim.on{display:block}

/* ------------------------------------------------------------
   TOOLTIP — the detail a title-only node no longer carries
   ------------------------------------------------------------ */
.tip{
  position:fixed;z-index:60;max-width:260px;padding:10px 12px;pointer-events:none;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--chip-r);
  box-shadow:0 14px 34px -16px var(--shadow);opacity:0;transition:opacity .12s;
}
.tip.on{opacity:1}
.tip .t{font-family:var(--f-display);font-size:13.5px;font-weight:var(--w-display);display:block;margin-bottom:5px}
.tip ul li{font-size:11px;color:var(--ink-2);padding:1.5px 0}
.tip .k{margin-top:7px;font-size:10px;color:var(--ink-3);font-family:var(--f-mono)}

@media (max-width:900px){
  .drawer{width:min(320px,88vw)}
  .crumbs{font-size:11px}
  .crumbs button{max-width:18ch}
  .crumbs .sep,.crumbs button:not(:first-of-type){display:none}
}
@media (max-width:640px){
  .topbar{gap:8px;padding:8px 10px;justify-content:space-between}
  .brand b{font-size:15px}
  .seg .w,.tbtn .w{display:none}
  .seg .n{display:inline}
  .seg button{padding:6px 8px}
  .tbtn{padding:6px 9px}
  .tools{gap:6px}
  .statusbar{padding:6px 10px;gap:10px}
  .statusbar span:not(.spacer):not(#statusLeft){display:none}
  .tip{display:none}
}
/* ── css/unit.css ── */
/* ============================================================
   LAYER 1 — THE UNIT
   A title, its bullets, and the doors hidden inside the prose.
   The slide is edge-to-edge as soon as the window gets narrow.
   ============================================================ */
/* the pages of a unit, side by side: the slide, then whatever it needs drawn.
   Only ever moved by a transform — the strip is wider than the stage, and
   scrolling it instead would leave the browser and js/views/unit.js
   disagreeing about where it stands */
.unit-strip{
  --slide-gutter:28px;
  --unit-frame-w:620px;
  position:absolute;inset:0;display:flex;
  touch-action:pan-y;
  transition:transform .34s cubic-bezier(.4,.02,.2,1);
}
.unit-strip.dragging{transition:none}
.unit-strip>.page{flex:0 0 100%;min-width:0;position:relative}

.unit-stage{
  display:grid;
  /* minmax(0,1fr), not auto: an auto track would size to the text and let the
     slide grow past the window instead of wrapping inside it */
  grid-template-columns:minmax(0,1fr);
  grid-template-rows:1fr auto 1fr;
  place-items:center;
  padding:0 var(--slide-gutter);
}
.paths,.ports{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
.paths{z-index:1}
.ports{z-index:3}
.chips{position:absolute;inset:0;pointer-events:none;z-index:4}

.slide{
  grid-row:2;z-index:2;
  width:min(var(--unit-frame-w),100%);
  background:var(--surface);padding:26px 30px 20px;
}
.slide h1{
  font-family:var(--f-display);font-weight:var(--w-display);letter-spacing:var(--tr-display);
  font-size:var(--fs-display);line-height:1.14;margin-bottom:16px;
}
.bullets li{
  display:flex;gap:11px;padding:6px 0;font-size:calc(var(--fs-base) + 1px);
  color:var(--ink);border-top:1px solid var(--line-soft);
}
.bullets li:first-child{border-top:0}
.bullets li::before{
  content:"—";font-size:calc(var(--fs-base) + 1px);color:var(--ink-3);
  flex:none;line-height:inherit;
}

/* both ways out of a unit, on the slide's own footing — outside the card they
   would be standing in the space the travel chips flow across.
   The reading order takes the two ends and the figures the middle, so the
   steps sit where a page turn is looked for and never move as a unit changes:
   a disabled step keeps its place rather than letting the other slide across */
.slide-foot{
  display:flex;align-items:center;justify-content:space-between;
  gap:10px;margin-top:14px;
}
.slide-foot .mid{display:flex;align-items:center;gap:10px;flex:none}
.lateral{
  display:inline-flex;align-items:center;gap:7px;padding:5px 10px;
  border:1px solid var(--line-soft);border-radius:var(--chip-r);
  font-size:10.5px;letter-spacing:var(--tr-label);text-transform:var(--tt-label);
  color:var(--ink-3);transition:color .15s,border-color .15s,gap .15s;
}
.lateral:hover{color:var(--ink);border-color:var(--line);gap:10px}

/* a step names the unit it leads to: "next" alone asks the reader to press it
   to find out. The title is allowed one line and no more — the footing is a
   footing, and a long title that wrapped it would push the prose up the page
   as the reading moved, which is the one thing a steady frame must not do */
.step{
  display:inline-flex;align-items:center;gap:8px;min-width:0;flex:1 1 0;
  padding:4px 2px;color:var(--ink-3);text-align:left;
  transition:color .15s,gap .15s,opacity .15s;
}
.step.on{justify-content:flex-end;text-align:right}
.step .arrow{flex:none;font-size:15px;line-height:1;color:var(--ink-3)}
.step .lab{display:flex;flex-direction:column;min-width:0}
.step .t{
  font-size:11.5px;line-height:1.25;color:var(--ink-2);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* the chapter is named only where the step crosses into one, which is the one
   seam in the whole reading and the only thing the arrow cannot say */
.step .ch{
  font-size:9.5px;letter-spacing:var(--tr-label);text-transform:var(--tt-label);
  color:var(--key);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.step .ch:empty{display:none}
.step:hover:not(:disabled){color:var(--ink);gap:11px}
.step:hover:not(:disabled) .t,.step:hover:not(:disabled) .arrow{color:var(--ink)}
/* the ends of the corpus are shown, not removed: a step that vanished at the
   last unit would move the one opposite it just as the reading arrived */
.step:disabled{opacity:.3;cursor:default}

.seq-at{
  flex:none;font-family:var(--f-mono);font-size:10px;color:var(--ink-3);
  padding:3px 6px;border-radius:var(--chip-r);transition:color .15s,background .15s;
}
.seq-at:hover{color:var(--ink);background:var(--bg-2)}

/* keywords live inside the sentence — the word itself is the door,
   so it is coloured to read as a door while still reading as prose */
.kw{
  display:inline;font:inherit;font-weight:600;color:var(--key);text-align:left;
  padding:0;border:0;border-radius:2px;position:relative;
  text-decoration:underline;text-decoration-style:dotted;
  text-decoration-color:color-mix(in srgb,var(--key) 55%,transparent);
  text-underline-offset:3px;text-decoration-thickness:1px;
  transition:color .15s,text-decoration-color .15s,background .15s,box-shadow .15s;
}
.kw:hover{
  text-decoration-color:var(--key);
  background:color-mix(in srgb,var(--key) 10%,transparent);
}
.kw[data-open="1"]{
  background:color-mix(in srgb,var(--key) 18%,transparent);
  text-decoration-style:solid;text-decoration-color:var(--key);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--key) 18%,transparent);
}

/* travel chips — the thread a keyword opens, laid out rung by rung
   above and below the prose by js/views/unit-chain.js */
.chip{
  position:absolute;pointer-events:auto;text-align:left;max-width:min(230px,72vw);
  padding:7px 11px 8px;background:var(--surface);
  border:var(--chip-fw) solid var(--line-soft);border-radius:var(--chip-r);
  box-shadow:var(--box-shadow);
  animation:pop .22s cubic-bezier(.2,1.3,.4,1) backwards;
}
@keyframes pop{from{opacity:0;transform:translateY(var(--pop,6px)) scale(.96)}}
.chip:hover{border-color:currentColor;background:var(--surface)}
.chip .dir{
  display:block;font-size:8.5px;letter-spacing:var(--tr-label);text-transform:var(--tt-label);
  margin-bottom:3px;
}
.chip .t{display:block;font-size:12.5px;color:var(--ink);line-height:1.3}
.chip .via{display:block;font-size:10px;color:var(--ink-3);margin-top:3px;font-family:var(--f-mono)}
.chip.up{color:var(--up)}
.chip.up .dir::before{content:"▲ "}
.chip.down{color:var(--down)}
.chip.down .dir::before{content:"▼ "}
.chip.lat{color:var(--lat)}
.chip.lat .dir::before{content:"◆ "}
.chip[data-dim="1"]{opacity:.34}

/* a rung further along the thread than the step you would take next. The whole
   chain has to stand in the space the prose leaves, so a chip that far off says
   less: one line, and the direction kept as its mark rather than its word */
.chip.tight{
  display:flex;align-items:baseline;gap:8px;
  padding:5px 10px;max-width:min(320px,84vw);
}
.chip.tight .dir{display:inline;flex:none;margin:0}
.chip.tight .t{
  flex:0 1 auto;min-width:0;font-size:11.5px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.chip.tight .via{flex:none;margin:0;font-size:9.5px}

/* the top of the thread. Nothing deeper carries the word — but the engine
   still offers the nearest deeper unit in this chapter, and that unit is a
   stand-in rather than a rung, so the chip says which it is */
.chip.cap{
  color:var(--up);border-style:dashed;background:var(--surface-2);
  max-width:min(262px,78vw);
}
.chip.cap .dir::before{content:"▲ "}
.chip.cap .t{font-size:12px}
.chip.cap.tight{max-width:min(320px,84vw)}
.chip.cap.dead,.chip.cap.dead:hover{cursor:default;border-color:var(--line-soft)}

/* the window was too short for the whole ladder, so a rung is missing between
   this chip and the prose — the break is drawn on the side it went */
.chip.gap::before{
  content:"";position:absolute;left:2px;right:2px;
  border-top:1px dashed var(--line);opacity:.75;
}
.chip.up.gap::before,.chip.cap.gap::before{bottom:-11px}
.chip.down.gap::before,.chip.def.gap::before{top:-11px}

/* the floor: where the way down runs out, the term answers for itself */
/* wider than a travel chip, and for the height rather than the room: the floor
   is a sentence, and a sentence set to a proper measure is two lines where a
   chip's width would make it four — which is the difference between the ladder
   fitting under the prose and giving up a rung to stand there. Its label and
   its word share a line for the same reason. */
.chip.def{
  display:grid;grid-template-columns:1fr auto;column-gap:14px;align-items:baseline;
  color:var(--ink-3);border-style:dashed;background:var(--surface-2);
  max-width:min(520px,86vw);cursor:default;
}
.chip.def .dir::before{content:"≡ "}
.chip.def .dir{grid-area:1/1;margin:0}
.chip.def .t{grid-area:2/1/3/3;margin-top:5px;color:var(--ink-2);font-size:12px;line-height:1.35}
.chip.def .w{
  grid-area:1/2;font-size:11px;color:var(--ink);text-align:right;
  font-family:var(--f-display);letter-spacing:var(--tr-display);
}
.chip.def:hover{border-color:var(--line-soft)}
/* trimmed to the room a short window leaves it — the whole sentence is on the
   chip's title, and the ellipsis is what says so. A height and not a
   line-clamp: the floor is a grid, and a grid item is blockified, which is
   exactly what -webkit-box cannot survive */
.chip.def.clip .t{
  position:relative;overflow:hidden;
  max-height:calc(var(--lines,2) * 1.35em);
}
.chip.def.clip .t::after{
  content:"…";position:absolute;right:0;bottom:0;padding-left:16px;
  background:linear-gradient(90deg,transparent,var(--surface-2) 45%);
}

/* ------------------------------------------------------------
   THE FIGURE — the page beside the prose
   ------------------------------------------------------------ */
.page.graph{display:grid;place-items:center;padding:var(--slide-gutter)}
.figure{
  display:flex;flex-direction:column;
  width:min(var(--unit-frame-w),100%);max-height:100%;
  background:var(--surface);padding:18px 20px 16px;
}
.figure header{
  display:grid;grid-template-columns:auto minmax(0,1fr) auto;
  align-items:center;gap:10px;min-width:0;
}
.figure .back{
  display:inline-flex;align-items:center;gap:7px;min-width:0;padding:5px 10px;
  border:1px solid var(--line-soft);border-radius:var(--chip-r);
  font-size:10.5px;letter-spacing:var(--tr-label);text-transform:var(--tt-label);
  color:var(--ink-3);transition:color .15s,border-color .15s,gap .15s;
}
.figure .back:hover{color:var(--ink);border-color:var(--line);gap:10px}
.figure .back .t{white-space:nowrap}
.figure .ord{flex:none;font-family:var(--f-mono);font-size:10px;color:var(--ink-3)}
/* the strip's two steps, drawn — the swipe leaves no mark on the page it moves,
   and a unit with three figures should not have to be guessed at. Disabled at
   the ends rather than hidden: an arrow that goes grey is telling you where
   you are, and one that disappears is only taking a control away */
.figure .pager{display:flex;align-items:center;gap:8px;flex:none}
.figure .pager .step{
  display:inline-flex;align-items:center;justify-content:center;
  width:24px;height:22px;padding:0;line-height:1;font-size:14px;
  border:1px solid var(--line-soft);border-radius:var(--chip-r);
  color:var(--ink-3);transition:color .15s,border-color .15s;
}
.figure .pager .step:hover:not(:disabled){color:var(--ink);border-color:var(--line)}
.figure .pager .step:disabled{opacity:.32;cursor:default}
.figure .fig-title{
  font-family:var(--f-display);font-weight:var(--w-display);letter-spacing:var(--tr-display);
  font-size:13px;line-height:1.2;margin:0;text-align:center;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ink);
}
.figure .plate{
  flex:1 1 auto;min-height:0;display:grid;place-items:stretch;
  margin-top:12px;aspect-ratio:16/10;width:100%;
}
/* an author's markup is drawn in the theme's ink, so currentColor in it
   follows the colour scheme the way everything else on the page does */
.figure .plate>svg{display:block;width:100%;height:100%;max-height:100%;color:var(--ink)}
.figure .plate>img{display:block;max-width:100%;max-height:100%}
.figure figcaption{
  margin-top:9px;padding-top:8px;border-top:1px solid var(--line-soft);
  font-size:11.5px;color:var(--ink-2);line-height:1.45;
}
/* the page exists before the drawing does — see data/SCHEMA.md */
.plate-empty{
  display:grid;place-items:center;width:100%;min-height:200px;padding:20px;
  border:1px dashed var(--line);border-radius:var(--chip-r);
  background:var(--surface-2);color:var(--ink-3);text-align:center;
  font-size:11px;letter-spacing:var(--tr-label);text-transform:var(--tt-label);
}

.empty-hint{
  position:absolute;left:50%;bottom:14px;transform:translateX(-50%);
  font-size:11px;color:var(--ink-3);z-index:2;pointer-events:none;
  transition:opacity .3s;text-align:center;max-width:92vw;
}
/* the hint speaks for the prose only: it names the doors, and there are
   none on a figure */
#viewUnit[data-open="1"] .empty-hint,
#viewUnit:not([data-page="0"]) .empty-hint{opacity:0}

/* ------------------------------------------------------------
   NARROW — the unit runs from margin to margin
   ------------------------------------------------------------ */
@media (max-width:720px){
  .unit-strip{--slide-gutter:0px}
  .slide,.figure{
    width:100%;padding:22px 18px 18px;
    border-left-width:0;border-right-width:0;border-radius:0;
  }
  .slide .corners,.figure .corners{display:none}
  .empty-hint{font-size:10px;bottom:8px}
}
@media (max-width:420px){
  .slide,.figure{padding:18px 14px 14px}
  /* the titles are the first thing a narrow footing gives up: the arrows still
     say which way the reading goes, and the breadcrumb says where it is */
  .step .lab{display:none}
  .step{flex:none}
  .bullets li{gap:8px}
}
/* ── css/maps.css ── */
/* ============================================================
   LAYERS 2 & 3 — THE WEBS
   A node carries its title and nothing else; the detail lives
   in the tooltip. Node widths come from the layout engine, which
   sizes them against the window (see js/graph.js).
   ============================================================ */
.map{position:absolute;inset:0;overflow:hidden;cursor:grab;touch-action:none}
.map.dragging{cursor:grabbing}
.world{position:absolute;top:0;left:0;transform-origin:0 0;will-change:transform}
.edges{position:absolute;top:0;left:0;overflow:visible;pointer-events:none}

/* the vertical axis, shared by both webs: down is where the reading starts */
.axis{
  position:absolute;left:16px;top:0;bottom:0;width:150px;
  display:flex;flex-direction:column;justify-content:space-between;
  padding:22px 0;pointer-events:none;z-index:2;transition:opacity .3s ease;
}
.axis span{
  font-size:9.5px;letter-spacing:var(--tr-label);text-transform:var(--tt-label);
  color:var(--ink-3);display:flex;align-items:center;gap:7px;
}
.axis .rule{position:absolute;left:5px;top:44px;bottom:44px;width:1px;background:var(--line-soft)}
.axis .up-c{color:var(--up)}
.axis .down-c{color:var(--down)}
.axis .seq-c{color:var(--seq)}
/* when the packer wraps the bands, height is only local — drop the claim */
.view[data-axis="off"] .axis{display:none}
.view[data-axis="off"] .legend{left:16px}

/* ---- unit node: title only ---- */
.node{
  position:absolute;transform:translate(-50%,-50%);
  padding:8px 12px;background:var(--surface);text-align:left;
  border:var(--fw) var(--fstyle) var(--line);border-radius:var(--fr);
  box-shadow:var(--box-shadow);width:var(--node-w,196px);
  transition:border-color .15s,box-shadow .15s,opacity .2s;
}
[data-scaffold="organic"] .node,[data-scaffold="hairline"] .node{border:1px solid var(--line-soft)}
.node .t{display:block;font-size:12.5px;line-height:1.25;color:var(--ink)}
.node:hover{border-color:var(--focus);z-index:6}
.node.moving,.chnode.moving{cursor:grabbing;z-index:8}
.node[data-here="1"]{box-shadow:0 0 0 3px var(--sel),var(--box-shadow);border-color:var(--focus)}
.node[data-dim="1"]{opacity:.22}
.node[data-hot="1"]{border-color:var(--focus);z-index:6}

/* ---- the term a connector was derived from, written on the connector.
   Mono at label size, the same voice a travel chip uses for "via". The
   stroke is painted first and in the background colour, so each word
   arrives inside a halo that cuts the line out from under it — which is
   why a label sitting on its own path needs no plate behind it. Where
   the packing had to give up, the node card paints over the remainder,
   so labels are the last thing added to the edge layer. ---- */
.edge-label{
  font-family:var(--f-mono);font-size:11px;fill:var(--ink-2);
  text-anchor:middle;dominant-baseline:central;
  paint-order:stroke;stroke:var(--bg);stroke-width:4px;stroke-linejoin:round;
  opacity:var(--lo,1);transition:opacity .15s,fill .15s;
  pointer-events:auto;cursor:grab;
}
/* the word being read, and the wide invisible line under it that catches a
   mouse a hairline would miss — see js/views/edge-focus.js */
.edge-label.lit{opacity:1;fill:var(--focus);stroke-width:5px}
.edge-label.moving{cursor:grabbing;fill:var(--focus);opacity:1}
.edge-hit{fill:none;stroke:transparent;stroke-linecap:round;pointer-events:stroke;cursor:pointer}

/* a card at one end of the chord being read. Its own attribute, not the
   `data-hot` a node hover writes: the two say different things and would
   otherwise erase each other. */
.node[data-chord="1"],.chnode[data-chord="1"]{border-color:var(--focus);z-index:6}

/* ---- what the word on a chord means. Fixed to the window rather than parked
   in the world, because the world is scaled and a definition is prose: inside
   it, the sentence would be set in whatever size the zoom happens to be. It
   follows its label instead, frame by frame. ---- */
.kdef{
  position:fixed;z-index:60;max-width:min(290px,78vw);padding:9px 12px 10px;
  pointer-events:none;background:var(--panel);
  border:1px solid var(--line-soft);border-radius:var(--chip-r);
  box-shadow:0 14px 34px -16px var(--shadow);opacity:0;transition:opacity .12s;
}
.kdef.on{opacity:1}
.kdef.pinned{border-color:var(--focus)}
.kdef .w{
  display:block;font-family:var(--f-mono);font-size:11.5px;color:var(--focus);
  letter-spacing:var(--tr-label);margin-bottom:4px;
}
.kdef .d{font-size:11.5px;line-height:1.4;color:var(--ink-2)}
.kdef .d.none{color:var(--ink-3);font-style:italic}
.kdef .hint{
  display:block;margin-top:7px;font-size:8.5px;color:var(--ink-3);
  letter-spacing:var(--tr-label);text-transform:var(--tt-label);
}

.hull{
  position:absolute;border:1px var(--fstyle) var(--line-soft);border-radius:calc(var(--fr) + 12px);
  background:var(--bg-2);opacity:.55;pointer-events:none;
}
.hull-label{
  position:absolute;font-family:var(--f-display);font-size:12px;color:var(--ink-3);
  letter-spacing:var(--tr-label);text-transform:var(--tt-label);pointer-events:none;
}

/* ---- chapter node: title only ---- */
.chnode{
  position:absolute;transform:translate(-50%,-50%);
  width:var(--chnode-w,216px);padding:14px 16px;background:var(--surface);text-align:left;
  border:var(--fw) var(--fstyle) var(--line);border-radius:var(--fr);
  box-shadow:var(--box-shadow);transition:border-color .15s,transform .18s;
}
[data-scaffold="organic"] .chnode,[data-scaffold="hairline"] .chnode{border:1px solid var(--line-soft)}
.chnode:hover{border-color:var(--focus)}
.chnode h3{font-family:var(--f-display);font-weight:var(--w-display);font-size:16px;letter-spacing:var(--tr-display);line-height:1.2}
.chnode[data-here="1"]{border-color:var(--focus);box-shadow:0 0 0 3px var(--sel),var(--box-shadow)}

/* ---- map furniture ---- */
.mapctl{
  position:absolute;right:16px;bottom:16px;display:flex;flex-direction:column;
  gap:6px;z-index:10;transition:opacity .3s ease;
}
.mapctl button{
  width:32px;height:32px;display:grid;place-items:center;background:var(--panel);
  border:1px solid var(--line-soft);border-radius:var(--chip-r);font-size:14px;color:var(--ink-2);
}
.mapctl button:hover{color:var(--ink);border-color:var(--line)}
.legend{
  position:absolute;left:186px;bottom:15px;display:flex;gap:18px;z-index:10;white-space:nowrap;
  font-size:10px;color:var(--ink-3);letter-spacing:var(--tr-label);text-transform:var(--tt-label);
  pointer-events:none;transition:opacity .3s ease;
}
.legend span{display:flex;align-items:center;gap:6px}
.legend i{width:20px;height:0;border-top:2px solid currentColor;display:block}
.legend .l-up{color:var(--up)}
.legend .l-lat{color:var(--lat)}
/* the spine is drawn heavier than the arcs, and the swatch says so */
.legend .l-seq{color:var(--seq);border-top-width:3px}
/* along the top: the bottom edge belongs to the legend, which is wide */
.zoomout-hint{
  position:absolute;left:50%;top:14px;transform:translateX(-50%);
  font-size:10.5px;color:var(--ink-3);z-index:10;pointer-events:none;
  letter-spacing:var(--tr-label);text-transform:var(--tt-label);
  transition:opacity .3s ease;
}

@media (max-width:900px){
  .axis{display:none}
  .legend{left:14px;gap:12px}
  .legend .desk{display:none}
  .zoomout-hint{display:none}
}
@media (max-width:640px){
  .legend{font-size:8.5px;gap:9px;bottom:10px}
  .legend i{width:13px}
  .mapctl{right:10px;bottom:10px}
  .chnode{padding:11px 13px}
  .chnode h3{font-size:14px}
  .node{padding:6px 9px}
  .node .t{font-size:11.5px}
  .edge-label{font-size:10.5px;stroke-width:3.5px}
  /* the tooltip goes away at this width, so the definition is the only detail
     a phone gets — and a tap is the only way to ask for it */
  .kdef{padding:8px 10px 9px}
}