/* =============================================================================
   css/facets.css : structure and theme looks
   =============================================================================
   Tunable numbers (font, timings, opacities) are NOT here: they live in
   content.js and arrive as CSS variables set by js/engine.js at startup:
     --mono      font stack              (tuning.style.font)
     --type-ms   terminal typing time    (tuning.typing.typeMs)
     --line-ms   per-line output delay   (tuning.typing.lineMs)
     --sa        surface opacity         (tuning.style.surfaceAlpha / opts.alpha)
     --acc       facet accent colour     (face.colors[0])
     --c1 --c2   current blended colours (rewritten every frame)
     --sq-w --sq-h --sq-top   text square size and position on a face

   Colour mode: page tokens (--bg, --fg, ...) switch automatically with the
   system or the toggle. <html> carries mode-light or mode-dark (effective
   mode), so any theme may add a variant, e.g.
     .mode-dark .th-zine { ... }
   A theme without such a rule simply keeps its own look in both modes.

   Fonts (self-hosted in fonts/, each with its licence file next to it; never
   from a CDN, the page works fully offline):
     JetBrains Mono  --mono      the main font, everything by default; named
                                 first in tuning.style.font (also used for the
                                 canvas mood words); not preloaded: browsers
                                 refuse font preloads on file://, and swap plus
                                 the engine's refit after loading cover it
     Archivo         --signage   metro, hazard, nameplate, card; its width axis
                                 gives condensed bold for warnings and plates
     Special Elite   --typewriter  zine
   The files are subsets (Latin and Latin Extended-A); characters they lack
   fall back to the system fonts in the stacks. To change a font, replace the
   file, keep the family name or update it here and in tuning.style.font.

   Layering inside .stage (bottom to top):
     canvas#glass   back faces, refracted portrait, front faces (one canvas)
     .ovs           one HTML overlay per facet, projected onto its face with
                    matrix3d and clipped to the face polygon; text stays real
                    text so it is translatable, selectable and indexable
   ========================================================================== */

/* ---------- self-hosted fonts (see header) ---------- */
@font-face{font-family:"JetBrains Mono";src:url("../fonts/jetbrains-mono.woff2") format("woff2");
  font-weight:400 800;font-style:normal;font-display:swap}
@font-face{font-family:"Archivo";src:url("../fonts/archivo.woff2") format("woff2");
  font-weight:400 800;font-stretch:62% 100%;font-style:normal;font-display:swap}
@font-face{font-family:"Special Elite";src:url("../fonts/special-elite.woff2") format("woff2");
  font-weight:400;font-style:normal;font-display:swap}

:root{
  --mono:"JetBrains Mono",ui-monospace,"SF Mono",Menlo,Consolas,"DejaVu Sans Mono","Liberation Mono",monospace;   /* replaced by tuning.style.font at start */
  --signage:"Archivo","Helvetica Neue",Arial,sans-serif;
  --typewriter:"Special Elite","Courier New",Courier,monospace;
  --bg:#ecebf2; --fg:#16151c; --muted:#4f4c5e; --line-back:rgba(22,21,28,.22);
  --chip:rgba(236,235,242,.72);
  --c1:#8b5cf6; --c2:#2e1065;
  box-sizing:border-box;
  /* Phones draw under their status and home bars; keep content clear of them. */
  padding-top:env(safe-area-inset-top,0px);
  padding-bottom:env(safe-area-inset-bottom,0px);
}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){--bg:#0c0b11;--fg:#efeef4;--muted:#a6a3b6;--line-back:rgba(239,238,244,.2);--chip:rgba(12,11,17,.6);color-scheme:dark}
}
:root[data-theme="dark"]{--bg:#0c0b11;--fg:#efeef4;--muted:#a6a3b6;--line-back:rgba(239,238,244,.2);--chip:rgba(12,11,17,.6);color-scheme:dark}
*,*::before,*::after{box-sizing:inherit}
html,body{height:100%;margin:0}
body{
  font-family:var(--mono);color:var(--fg);background-color:var(--bg);overflow:hidden;
  background-image:radial-gradient(circle at 50% 50%,
    color-mix(in oklab,var(--c1) 75%,var(--bg)) 0%,
    color-mix(in oklab,var(--c2) 55%,var(--bg)) 40%,
    var(--bg) 88%);
}
main{position:relative;height:100%}
/* Page backgrounds per facet: stacked full-page layers above the body's colour
   gradient and below the gem; opacity is set by the engine (crossfade). */
.page-bgs{position:absolute;inset:0;pointer-events:none;overflow:hidden}
.page-bg{position:absolute;inset:0;opacity:0;will-change:opacity}

/* ---------- stage ---------- */
.stage{position:absolute;inset:0;container-type:size;overflow:hidden;
  touch-action:none;cursor:grab;user-select:none;-webkit-user-select:none}
.stage.dragging{cursor:grabbing}
/* .wrap is never drawn: it only measures the gem size with container units.
   Larger of short-side and long-side scaling, capped so the front face fits. */
.wrap{position:absolute;left:50%;top:50%;aspect-ratio:1;transform:translate(-50%,-50%);visibility:hidden;
  --scale-long-now:0;   /* long-side scaling only on portrait screens (tall phones), never on wide desktops */
  width:min(max(calc(var(--scale)*100cqmin),calc(var(--scale-long-now)*100cqmax)),calc(var(--max-w)*100cqw))}
@media (orientation:portrait){.wrap{--scale-long-now:var(--scale-long)}}
#glass{position:absolute;inset:0;width:100%;height:100%}
.media{position:absolute;width:1px;height:1px;overflow:hidden;opacity:0;pointer-events:none}

/* ---------- facet overlays: whole face (clip-path), invisible text square ---------- */
.ovs{position:absolute;inset:0;pointer-events:none}
.ov{position:absolute;transform-origin:50% 50%;backface-visibility:hidden;visibility:hidden;line-height:1.45;
  will-change:transform,opacity,clip-path}
.ov .bg{position:absolute;inset:0;background-repeat:no-repeat;opacity:var(--bg-a,1)}
/* .inner is the only element that clips text, so the engine's overflow check
   (scrollHeight vs clientHeight) sees every overflow. */
.ov .inner{position:absolute;left:50%;top:50%;width:var(--sq-w);height:var(--sq-h);transform:translate(-50%,-50%);
  overflow:hidden;padding:.8em .9em}
.ov.live a{pointer-events:auto}
/* Decorations on spare faces (content.js decor): image only, never interactive. */
.decor{position:absolute;transform-origin:50% 50%;backface-visibility:hidden;visibility:hidden;pointer-events:none;will-change:transform,opacity}
/* Quote decoration: one quote, large and bold, centred on its face; the author
   smaller at the bottom right. White with a dark shadow reads over any glass.
   The engine sets font-size so each quote fills its square (fitQuote). */
.decor-quote{display:flex;flex-direction:column;justify-content:center;box-sizing:border-box;padding:3%;overflow:hidden;
  font-family:var(--signage);color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.8),0 0 14px rgba(0,0,0,.5)}
.decor-quote .dq-text{margin:0;font-weight:800;font-stretch:87%;line-height:1.12;text-align:center}
.decor-quote .dq-text::before{content:"\201C";display:block;font-size:1.9em;line-height:.55;opacity:.55}
.decor-quote .dq-author{margin:.55em 0 0;text-align:right;font-size:.5em;font-weight:600;font-style:italic;opacity:.92}
.decor.decor-fill img{object-fit:cover}   /* fill: the image covers the face; clip-path (engine) cuts it to the outline */
.decor img{display:block;width:100%;height:100%;object-fit:contain;-webkit-user-drag:none}
.ov a{color:inherit;text-underline-offset:.2em}

/* ---------- corner chrome ---------- */
.chip{font:inherit;font-size:.78rem;padding:.3rem .6rem;border-radius:.35rem;border:1px solid var(--line-back);
  background:var(--chip);color:var(--fg);cursor:pointer;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}
a.chip{text-decoration:none}   /* the text-version link looks like the other controls */
.chip[aria-pressed="true"]{background:var(--fg);color:var(--bg);border-color:var(--fg)}
.top{position:absolute;top:0;left:0;right:0;z-index:3;padding:.7rem .75rem 0 .9rem;pointer-events:none;
  display:grid;grid-template-columns:1fr auto;grid-template-areas:"brand ctl" "hint ctl";align-items:start;column-gap:.8rem}
.top > *{pointer-events:auto}
.ctl{grid-area:ctl;display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.4rem .6rem}
.ctl>*{flex:none}
.lang{display:flex;gap:.25rem}
#motion[hidden],#mode[hidden]{display:none}
/* Side padding keeps room for the small-print button in the lower right corner. */
nav.facets{position:absolute;left:0;right:0;bottom:.75rem;padding:0 3.2rem;z-index:3}
#nav{display:flex;flex-wrap:wrap;justify-content:center;gap:.35rem}
.chip[aria-selected="true"]{background:var(--fg);color:var(--bg);border-color:var(--fg)}
/* The name, top left: the page's main heading, holding the spin button. */
.brand{grid-area:brand;margin:0;font-size:1rem;font-weight:700;line-height:1.2}
.brand-btn{font:inherit;color:var(--fg);background:none;border:0;padding:.1em .2em;margin:-.1em -.2em;cursor:pointer;border-radius:.2em}
.brand-btn:hover{text-decoration:underline;text-underline-offset:.2em}
.hint{grid-area:hint;margin:.45rem 0 0;font-size:.78rem;color:var(--muted);transition:opacity .6s;pointer-events:none}
.hint.gone{opacity:0}
/* Narrow screens: the controls fill the top row, so the hint moves below them. */
/* Phones: one column, controls first, then the name and the hint below them, in normal flow:
   the name follows the controls however many rows they wrap to. The three text controls
   collapse to their icon; their names stay for screen readers. */
@media (max-width:34rem){
  .top{grid-template-columns:1fr;grid-template-areas:"ctl" "brand" "hint";padding:.6rem .6rem 0;row-gap:.5rem;text-align:center}
  .ctl{justify-content:center}
  .hint{margin-top:0}
  .chip-label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
}
.chip-label{margin-left:.35em}
/* Focus ring in two tones (white inside, black outside) so it stays visible on
   every surface: light page, dark terminal faces, glass, occasion colours. */
:focus-visible{outline:2px solid #fff;outline-offset:1px;box-shadow:0 0 0 5px #000}
.noscript-msg{padding:1rem}
.stage:focus,.stage:focus-visible{outline:none;box-shadow:none}   /* not a Tab stop; keyboard focus goes to the facet menu, which shows the ring */

/* ---------- accessibility helpers ---------- */
/* Visually hidden, still read by screen readers (and shown by text browsers). */
.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
/* Skip link to the text version: hidden until focused with the keyboard. */
.skip{position:absolute;left:.75rem;top:-3rem;z-index:20;padding:.4rem .7rem;border-radius:.35rem;background:var(--fg);color:var(--bg);font-size:.85rem}
.skip:focus{top:.75rem}
/* Facet headings take their size from the theme rules below. */
.ov h2{font:inherit;margin:0}
/* Motion switched off: everything decorative stands still (WCAG 2.2.2). */
.no-motion .cursor,.no-motion .ld-cursor{animation:none}
.no-motion .particles i{animation-play-state:paused}
.no-motion .stage{animation:none!important}

/* ---------- text view (?view=text) and no-JavaScript fallback (<noscript>) ---------- */
.view-text body,.no-js body{overflow:auto;background-image:none}
.no-js main{display:none}   /* without scripts the gem cannot work; the noscript text takes over */
.textview{height:auto;max-width:68ch;margin:0 auto;padding:1.5rem 1.1rem 4rem;font-size:1rem;line-height:1.6}
.textview h1{font-size:1.6rem;margin:.5rem 0 1rem}
.textview h2{font-size:1.2rem;margin:1.6rem 0 .3rem}
.textview h3{font-size:1rem;margin:0 0 .5rem;color:var(--muted)}
.textview a{color:inherit;text-underline-offset:.2em}
.textview hr{border:0;border-top:1px solid var(--line-back);margin:1.5rem 0}

/* ---------- small print: button (lower right) and dialog ----------
   nav.facets keeps room on both sides, so the button never covers the menu. */
.legal-btn{position:absolute;right:.75rem;bottom:.75rem;z-index:3;display:flex;align-items:center}
.legal-i{font-size:1rem;line-height:1}
/* Collapsed to the icon; the label slides out on hover and on keyboard focus
   (so keyboard users get the same). Screen readers always hear the full name
   from aria-label. Touch screens (no hover) keep the icon only. */
.legal-label{display:inline-block;max-width:0;opacity:0;overflow:hidden;white-space:nowrap;vertical-align:middle;
  transition:max-width .25s ease,opacity .2s ease,margin .25s ease}
.legal-btn:hover .legal-label,.legal-btn:focus-visible .legal-label{max-width:14rem;opacity:1;margin-left:.4em}
@media (hover:none){.legal-btn:hover .legal-label{max-width:0;opacity:0;margin-left:0}}
@media (prefers-reduced-motion:reduce){.legal-label{transition:none}}
.legal{width:min(42rem,calc(100vw - 2rem));max-height:min(85dvh,52rem);padding:0;border:1px solid var(--line-back);border-radius:.5rem;
  background:var(--bg);color:var(--fg);font:inherit;font-size:.9rem;line-height:1.55}
.legal::backdrop{background:rgba(0,0,0,.55)}
.legal-head{position:sticky;top:0;display:flex;justify-content:space-between;align-items:center;gap:1rem;
  padding:.8rem 1.1rem;background:var(--bg);border-bottom:1px solid var(--line-back)}
.legal-head h2{margin:0;font-size:1rem}
.legal-body{padding:.4rem 1.1rem 1.2rem}
.legal-body h3{font-size:.95rem;margin:1.2rem 0 .4rem}
.legal-body p{margin:.45rem 0}
.legal-body p.strong{font-weight:700}
.legal-body a{color:inherit;text-underline-offset:.2em}
.legal-body .legal-debug{margin-top:1.4em;font-size:.72rem;opacity:.5}   /* unexplained on purpose: the cut in use */
.legal-body .legal-debug a{text-decoration:none}

/* ---------- the random bug (tuning.bug) ----------
   Moved every frame by the engine (no transform transition). The button is
   explicitly transparent with no native appearance: some browsers (Safari)
   otherwise keep drawing native button styling. The halo is part of the SVG. */
.bug{position:absolute;left:0;top:0;z-index:3;width:var(--bug-size,26px);height:var(--bug-size,26px);padding:0;margin:0;border:0;
  -webkit-appearance:none;appearance:none;background:transparent;box-shadow:none;border-radius:50%;
  cursor:pointer;opacity:0;transition:opacity .35s ease;will-change:transform,opacity;-webkit-tap-highlight-color:transparent}
.bug[hidden]{display:none}
.bug.here{opacity:1}
.bug.gone{opacity:0}
.bug img{display:block;width:100%;height:100%;pointer-events:none}
.bug.squashed img{transform:scale(1.3,.25);filter:brightness(.55);transition:transform .2s ease,filter .2s ease}
.bug-dialog{width:min(26rem,calc(100vw - 2rem))}
.bug-dialog .legal-body .chip{margin-top:.6rem}

/* ---------- loading overlay (index.html #loader; timing in tuning.loader) ----------
   Never shown without JavaScript (.no-js), removed by the engine when ready,
   and faded out by a CSS failsafe after 6 s in case a script fails. */
.loader{position:fixed;inset:0;z-index:10;display:grid;place-items:center;background:var(--bg);color:var(--fg);
  transition:opacity .4s ease;cursor:pointer;animation:loader-failsafe .4s ease 6s forwards}
.loader.out{opacity:0;pointer-events:none}
.no-js .loader{display:none}
.loader-inner{margin:0;font-size:.9rem}
.ld-prompt{color:var(--muted)}
.ld-cursor{margin-left:.15em;animation:blink 1s steps(1) infinite}
@keyframes loader-failsafe{to{opacity:0;visibility:hidden;pointer-events:none}}
@media (prefers-reduced-motion:reduce){.ld-cursor{animation:none}}

/* ---------- diagnostics (config errors always; warnings when tuning.debug.warnings) ---------- */
.diag{position:absolute;left:.75rem;bottom:3.5rem;z-index:5;max-width:min(36rem,calc(100% - 1.5rem));max-height:40%;overflow:auto;
  font-size:.78rem;line-height:1.45;padding:.7rem .85rem;border-radius:.4rem;background:#1b1a22;color:#f3f2f7;border:1px solid #fbbf24}
.diag.fatal{inset:auto;left:50%;top:50%;bottom:auto;transform:translate(-50%,-50%);border-color:#f87171;max-height:80%}
.diag b{color:#fbbf24}.diag.fatal b{color:#f87171}
.diag ul{margin:.4rem 0 0;padding-left:1.1rem}

/* ===================== THEMES =====================
   Surface opacity comes from CONFIG.style.surfaceAlpha (per facet: opts.alpha). */
/* terminal */
.th-terminal{background:rgb(7 6 14 / var(--sa,.66));color:var(--acc)}
/* The terminal's link stands out: bright, bold, on a tinted highlight. */
.th-terminal .inner a{color:#fff;font-weight:700;background:color-mix(in srgb,var(--acc) 34%,transparent);
  padding:.05em .35em;border-radius:.2em;text-decoration-thickness:.12em;text-underline-offset:.2em}
.th-terminal .inner{display:flex;flex-direction:column}
.th-terminal .bar{font-size:.72em;margin-bottom:.4em;color:color-mix(in srgb,var(--acc) 55%,#fff);opacity:.75}
.th-terminal .tbody{white-space:pre-wrap}
.th-terminal .prompt{color:color-mix(in srgb,var(--acc) 55%,#8a8a96)}
.th-terminal .title{font-weight:700;color:#fff}
.th-terminal .typed{display:inline-block;vertical-align:bottom;white-space:pre;overflow:hidden;max-width:calc(var(--n)*1ch)}
.tag-ok{color:#4ade80}.tag-warn{color:#fbbf24}.tag-bad{color:#f87171}.tag-info{color:#60a5fa}
.cursor{animation:blink 1s steps(1) infinite}
@keyframes blink{50%{opacity:0}}
.run .typed{animation:type var(--type-ms) steps(var(--n)) both}
@keyframes type{from{max-width:0}to{max-width:calc(var(--n)*1ch)}}
.run .out{animation:appear 60ms both;animation-delay:calc(var(--type-ms) + 50ms + var(--i)*var(--line-ms))}
@keyframes appear{from{opacity:0}to{opacity:1}}

/* zine */
.th-zine{color:#111;background-color:rgb(244 242 234 / var(--sa,.78));
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .18 0'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E")}
.th-zine{font-family:var(--typewriter)}   /* typewriter face for the photocopied zine */
.th-zine .band{position:absolute;left:0;right:0;top:calc(var(--sq-top) - .4em);height:1.65em;
  background:repeating-conic-gradient(#111 0 25%,#f4f2ea 0 50%) 0 0/1.65em 1.65em}
.th-zine .inner{padding-top:1.85em}   /* clears the 1.5x bar */
.th-zine .ztitle{display:inline-block;margin:0 0 .5em;padding:.12em .35em;font-size:1.5em;line-height:1.1;background:#111;color:#f4f2ea;transform:rotate(-2deg)}
.th-zine .strip{display:table;margin:.35em 0;padding:.08em .4em;background:var(--acc);color:#fff;transform:rotate(1deg)}
.th-zine .strip:nth-child(even){background:#111;transform:rotate(-1.2deg)}
.th-zine a{display:inline-block;margin-top:.5em;padding:.08em .4em;border:2px solid #111;background:#fff;font-weight:700;text-decoration:none;transform:rotate(-1deg)}

/* lead sheet: ruled rows aligned with the text rows inside the square */
/* Ruled lines and text rows share one pitch (--ls-pitch), so the text always sits on the lines.
   1.42em leaves room for a full music facet on small phones. */
.th-leadsheet{--ls-pitch:1.42em;color:#1f1b16;background-color:rgb(251 247 236 / var(--sa,.78));
  background-image:repeating-linear-gradient(to bottom,transparent 0 calc(var(--ls-pitch) - 1px),rgba(31,27,22,.16) calc(var(--ls-pitch) - 1px) var(--ls-pitch));
  background-position:0 calc(var(--sq-top) + var(--ls-pitch) / 2)}
.th-leadsheet .inner{line-height:var(--ls-pitch)}
.th-leadsheet .ls-title{text-align:center;font-size:1.25em;font-weight:700}
.th-leadsheet .ls-meta{display:flex;justify-content:space-between;font-size:.8em;opacity:.75}
.th-leadsheet .chord{color:var(--acc);font-weight:700;margin-right:.45em}

/* blueprint */
.th-blueprint{color:var(--acc);background-color:rgb(8 20 10 / var(--sa,.7));
  background-image:linear-gradient(color-mix(in srgb,var(--acc) 16%,transparent) 1px,transparent 1px),
                   linear-gradient(90deg,color-mix(in srgb,var(--acc) 16%,transparent) 1px,transparent 1px);
  background-size:1.2em 1.2em}
.th-blueprint .inner{display:flex;flex-direction:column;gap:.55em}
.th-blueprint .dim{font-size:.72em;text-align:center;border:1px solid currentColor;border-top:0;height:.9em;line-height:1.6em}
.th-blueprint .notes{flex:1;white-space:pre-wrap}
.th-blueprint .tblock{display:grid;grid-template-columns:auto 1fr;align-self:flex-end;min-width:62%;font-size:.72em;border:1px solid currentColor}
.th-blueprint .tblock span{padding:.15em .45em;border:.5px solid color-mix(in srgb,var(--acc) 50%,transparent)}
.th-blueprint .tblock .v{color:#fff}
.th-blueprint .bp-title{color:#fff;font-weight:700;letter-spacing:.06em}

/* metro: a transit line on a light signage panel. The line colour is the
   facet accent; dots are drawn with ::after, line segments with ::before. */
.th-metro{color:#1b1d24;background:rgb(250 250 247 / var(--sa,.82));font-family:var(--signage)}
.th-metro .metro-head{display:flex;align-items:center;gap:.5em;margin-bottom:.4em}
.th-metro .metro-badge{display:inline-grid;place-items:center;min-width:1.7em;height:1.7em;padding:0 .35em;border-radius:1em;
  background:var(--acc);color:#fff;font-weight:700;font-size:.85em}
.th-metro .metro-badge:empty{min-width:1.1em;height:1.1em;padding:0}
.th-metro .metro-name{margin:0;font-size:1.12em;font-weight:700}
.th-metro .metro-sub{font-size:.8em;opacity:.7;margin:-.25em 0 .5em}
.th-metro .metro-stops{list-style:none;margin:0;padding:0 0 0 1.35em}
.th-metro .stop{position:relative;display:flex;gap:.6em;padding:.12em 0}
.th-metro .stop::before{content:"";position:absolute;left:-.98em;top:0;bottom:0;width:.32em;background:var(--acc)}
.th-metro .stop:first-child::before{top:.75em}
.th-metro .stop.terminus::before{bottom:auto;height:.75em}
.th-metro .stop::after{content:"";position:absolute;left:-1.15em;top:.43em;width:.66em;height:.66em;border-radius:50%;
  background:#fff;border:.15em solid var(--acc);box-sizing:border-box}
.th-metro .stop.inter::after{left:-1.3em;top:.29em;width:.96em;height:.96em;border:.2em solid #1b1d24}
.th-metro .stop.terminus::after{border-radius:.15em;background:var(--acc)}
.th-metro .when{flex:none;min-width:3.1em;font-weight:700}
/* Future stop: dotted approach from the terminus to a dashed ring with a "?" in it.
   The ring has an opaque centre so the approach ends at its edge. */
.th-metro .stop.future{min-height:1.9em}
.th-metro .stop.future::before{background:none;width:0;border-left:.3em dotted var(--acc);left:-.97em;top:-.05em;bottom:auto;height:.5em}
.th-metro .stop.future::after{content:"?";display:flex;align-items:center;justify-content:center;box-sizing:border-box;
  left:-1.42em;top:.42em;width:1.25em;height:1.25em;border:.12em dashed var(--acc);border-radius:50%;
  background:#fafaf7;color:var(--acc);font-weight:800;font-size:1.05em;line-height:1}
.mode-dark .th-metro{color:#eef0f4;background:rgb(22 24 30 / var(--sa,.82))}
.mode-dark .th-metro .stop::after,.mode-dark .th-metro .stop.future::after{background:#16181e}
.mode-dark .th-metro .stop.terminus::after{background:var(--acc)}   /* keep the terminus filled in dark mode */
.mode-dark .th-metro .stop.inter::after{border-color:#eef0f4}

/* hazard: warning-sign yellow with hazard tape; "! " lines are warning bars,
   "1. " lines numbered steps. Signage colours on purpose in both modes. */
.th-hazard{color:#111;background:rgb(255 214 10 / var(--sa,.86));font-family:var(--signage)}
.th-hazard .hz-head,.th-hazard .hz-warn{font-stretch:75%;letter-spacing:.01em}   /* condensed bold, like signage */
.th-hazard .hz-band{position:absolute;left:0;right:0;top:calc(var(--sq-top) - .4em);height:1.65em;   /* full face width, like the zine bar */
  background:repeating-linear-gradient(-45deg,#111 0 .7em,#ffd60a .7em 1.4em)}
.th-hazard .inner{padding-top:1.85em}
.th-hazard .hz-head{margin:0 0 .35em;font-size:1.05em;font-weight:800}
.th-hazard .hz-meta{font-size:.72em;opacity:.7;margin-bottom:.3em}
.th-hazard .hz-warn{background:#111;color:#ffd60a;font-weight:700;padding:.12em .45em;margin:.2em 0}
.th-hazard .hz-warn::before{content:"\26A0  "}
.th-hazard .hz-step{display:flex;gap:.5em;align-items:baseline;margin:.15em 0}
.th-hazard .hz-num{flex:none;display:inline-grid;place-items:center;width:1.45em;height:1.45em;border-radius:50%;
  background:#111;color:#ffd60a;font-weight:700;font-size:.82em}
.th-hazard .hz-note{margin:.25em 0;font-style:italic}
.th-hazard a{color:#111;font-weight:700}

/* nameplate: brushed metal in the facet accent, engraved dark text, rivets.
   The text colour is --ink (opts.ink); keep the accent light for contrast. */
.th-nameplate{font-family:var(--signage);background-color:color-mix(in srgb,var(--acc) calc(var(--sa,.72) * 100%),transparent);
  background-image:repeating-linear-gradient(90deg,rgba(255,255,255,.05) 0 1px,rgba(0,0,0,.02) 1px 3px)}   /* subtle brushing: lets the portrait show through */
.th-nameplate .inner{color:var(--ink,#1d2410);line-height:1.35}   /* a type plate is set denser than running text */
.th-nameplate .np-rivet{position:absolute;width:.7em;height:.7em;border-radius:50%;
  background:radial-gradient(circle at 35% 35%,rgba(255,255,255,.75) 0,rgba(140,140,140,.75) 45%,rgba(40,40,40,.75) 100%)}
.th-nameplate .np-rivet.tl{top:.3em;left:.3em}.th-nameplate .np-rivet.tr{top:.3em;right:.3em}
.th-nameplate .np-rivet.bl{bottom:.3em;left:.3em}.th-nameplate .np-rivet.br{bottom:.3em;right:.3em}
.th-nameplate .np-maker{font-stretch:75%;margin:.1em 0 0;text-align:center;font-size:1em;font-weight:800;letter-spacing:.12em;
  text-transform:uppercase;text-shadow:0 1px 0 rgba(255,255,255,.45)}
.th-nameplate .np-title{text-align:center;font-size:.8em;margin-bottom:.35em}
.th-nameplate .np-fields{display:grid;grid-template-columns:auto 1fr;gap:.04em .6em;margin:.2em .3em 0;padding-top:.25em;
  border-top:1px solid color-mix(in srgb,var(--ink,#1d2410) 40%,transparent);
  border-bottom:1px solid color-mix(in srgb,var(--ink,#1d2410) 40%,transparent);padding-bottom:.3em}
.th-nameplate dt{font-size:.76em;text-transform:uppercase;letter-spacing:.06em;opacity:.8;align-self:center}
.th-nameplate dd{margin:0;font-weight:700;text-shadow:0 1px 0 rgba(255,255,255,.4)}
.th-nameplate .np-line{grid-column:1 / -1}
.th-nameplate .np-serial{text-align:right;font-size:.8em;letter-spacing:.16em;margin:.45em .45em 0;opacity:.85}
.th-nameplate .inner > a{display:block;width:max-content;margin:.45em auto 0;color:inherit;font-weight:700;letter-spacing:.06em}   /* link on its own line, centred */

/* card: an image with caption and short text. */
.th-card{color:#1b1d24;background:rgb(252 252 250 / var(--sa,.8));font-family:var(--signage)}
.th-card .card-fig{margin:0 0 .5em;text-align:center}
.th-card .card-fig img{display:block;max-width:100%;max-height:calc(var(--sq-h) * .55);margin:0 auto;object-fit:contain;border-radius:.3em}
.th-card .card-cap{font-weight:700;margin-top:.35em}
.th-card .card-text{white-space:pre-wrap;font-size:.92em}
.mode-dark .th-card{color:#eef0f4;background:rgb(22 24 30 / var(--sa,.8))}

/* plain text (with an example dark-mode variant below: the pattern for any theme) */
.th-plaintext{color:#26221f;background:rgb(255 255 255 / var(--sa,.74))}
.th-plaintext .inner{white-space:pre-wrap}
.th-plaintext .pt-file{font-size:.72em;opacity:.55;margin-bottom:.8em}
.th-plaintext .pt-h{font-weight:700}
.mode-dark .th-plaintext{color:#e9e6e1;background:rgb(24 22 20 / var(--sa,.74))}

@media (prefers-reduced-motion:reduce){.run .typed,.run .out,.cursor{animation:none}}
