/* One system for every page: tokens on :root, redefined for dark mode. */
:root {
  --bg: #f7f6f2; --surface: #ffffff; --ink: #1d1f24; --muted: #5b6070; --line: #d9d7cf;
  --accent: #245bd1; --accent-ink: #ffffff; --ok: #1f7a4d; --warn: #b4620e; --bad: #b3261e;
  --chip: #ebe9e1; --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* Elevation: the only raised surface on a page uses these. A shadow never carries the cue
     alone: the raised surface is lighter in dark mode and always has a border. */
  --surface-raised: #ffffff; --elev-2: 0 1px 2px rgb(0 0 0 / .08), 0 8px 24px rgb(0 0 0 / .16);
  /* Syntax colors, each at least 4.5:1 on --surface; strings are also italic. */
  --syn-kw: #8a1fa3; --syn-str: #1b6a3e; --syn-num: #9a4500; --syn-fn: #1f4fbf; --syn-com: #5b6070;
  color-scheme: light;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #15171c; --surface: #1e2129; --ink: #e8e8e4; --muted: #a2a7b4; --line: #343846;
    --accent: #7ea2ff; --accent-ink: #0d1324; --ok: #5fcf8d; --warn: #f0a24a; --bad: #ff7b72;
    --chip: #2a2e3a; color-scheme: dark;
    --surface-raised: #282c36; --elev-2: 0 1px 2px rgb(0 0 0 / .5), 0 8px 28px rgb(0 0 0 / .6);
    --syn-kw: #e0a8ff; --syn-str: #93d9a9; --syn-num: #f5b072; --syn-fn: #9dbcff; --syn-com: #a2a7b4;
  }
}
:root[data-theme="dark"] {
  --bg: #15171c; --surface: #1e2129; --ink: #e8e8e4; --muted: #a2a7b4; --line: #343846;
  --accent: #7ea2ff; --accent-ink: #0d1324; --ok: #5fcf8d; --warn: #f0a24a; --bad: #ff7b72;
  --chip: #2a2e3a; color-scheme: dark;
  --surface-raised: #282c36; --elev-2: 0 1px 2px rgb(0 0 0 / .5), 0 8px 28px rgb(0 0 0 / .6);
  --syn-kw: #e0a8ff; --syn-str: #93d9a9; --syn-num: #f5b072; --syn-fn: #9dbcff; --syn-com: #a2a7b4;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body { margin: 0; background: var(--bg); color: var(--ink); font: 16px/1.55 var(--sans); }
main { max-width: 860px; margin: 0 auto; padding: 24px 16px 64px; }
h1 { font-size: 1.7rem; line-height: 1.2; margin: 0 0 8px; }
h2 { font-size: 1.25rem; margin: 40px 0 8px; }
h3 { font-size: 1.05rem; margin: 24px 0 6px; }
/* Headings and labels wrap into even lines, never leaving one word alone (learnweb/wrap_check.js). */
h1, h2, h3, legend, button { text-wrap: balance; }
p, li { max-width: 70ch; }
a { color: var(--accent); }
code, pre { font-family: var(--mono); font-size: 0.9em; }
/* A long path or command in running text breaks rather than pushing the page sideways on a phone. */
code { overflow-wrap: anywhere; }
pre { background: var(--surface); border: 1px solid var(--line); border-radius: 8px; padding: 12px; overflow-x: auto; }
.lede { color: var(--muted); font-size: 1.05rem; margin-top: 0; }
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; padding: 0; list-style: none; }
.chip { background: var(--chip); border-radius: 999px; padding: 2px 10px; font-size: 0.8rem; color: var(--muted); }
.card { background: var(--surface); border: 1px solid var(--line); border-radius: 10px; padding: 16px; margin: 12px 0; }
.grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.bar { display: block; height: 10px; background: var(--accent); border-radius: 3px; min-width: 2px; }
.bar-row { display: grid; grid-template-columns: 9em 1fr 4em; gap: 8px; align-items: center; font-size: 0.9rem; margin: 4px 0; }
.bar-row .n { text-align: right; font-variant-numeric: tabular-nums; }
.controls { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); margin: 12px 0; }
.controls label { display: block; font-size: 0.9rem; color: var(--muted); }
.controls output { color: var(--ink); font-weight: 600; margin-left: 6px; }
input[type=range] { width: 100%; }
button { font: inherit; background: var(--accent); color: var(--accent-ink); border: 0; border-radius: 8px; padding: 8px 14px; cursor: pointer; }
button.quiet { background: var(--chip); color: var(--ink); }
select { max-width: 100%; font: inherit; padding: 4px 8px; border-radius: 6px; border: 1px solid var(--line); background: var(--surface); color: var(--ink); }
.cite { font-size: 0.85em; }
.cite a { text-decoration: none; border-bottom: 1px dotted var(--accent); white-space: nowrap; }
.ok { color: var(--ok); }
/* A status chip sits on the chip fill, where --ok alone falls under 4.5:1; darken it toward the ink. */
.chip.ok { color: color-mix(in srgb, var(--ok) 70%, var(--ink)); } .warn { color: var(--warn); } .bad { color: var(--bad); }
.hidden { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.level-note { font-size: 0.9rem; color: var(--muted); }
/* Scroll a wide table inside .scroll, never by changing the table's own display: thead and
   tbody with a display of their own each size their columns alone, and headers drift. */
.scroll { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; font-size: 0.92rem; }
th, td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--line); vertical-align: top; }
/* Headers sit centered over their columns; short values (counts, inputs) center under them,
   while longer text stays left-aligned so it reads as prose. */
th { text-align: center; vertical-align: bottom; }
td.center { text-align: center; }
td input[type=number] { width: 4.5em; font: inherit; padding: 2px 4px; }
td code { white-space: nowrap; overflow-wrap: normal; }
.timeline { display: grid; gap: 6px; margin: 12px 0; }
.turn { display: grid; grid-template-columns: 2.2em 1fr; gap: 8px; align-items: center; font-size: 0.85rem; }
.turn .req { height: 18px; background: var(--chip); border-radius: 4px; position: relative; min-width: 6px; }
.turn .req span { position: absolute; left: 100%; margin-left: 6px; top: -2px; white-space: nowrap; color: var(--muted); font-size: 0.75rem; }
.turn > div { min-width: 0; }
.turn .calls { display: flex; gap: 3px; flex-wrap: wrap; }
.turn .call { background: var(--accent); color: var(--accent-ink); border-radius: 3px; padding: 0 5px; font-family: var(--mono); font-size: 0.75rem; }
.turn .final { color: var(--ok); font-weight: 600; }
.codelist { list-style: none; margin: 0; padding: 0; counter-reset: ln; border: 1px solid var(--line); border-radius: 8px; overflow-x: auto; background: var(--surface); }
.codelist li { counter-increment: ln; font-family: var(--mono); font-size: 0.82rem; white-space: pre; padding: 2px 10px 2px 0; cursor: pointer; display: grid; grid-template-columns: 3em max-content; }
.codelist li::before { content: counter(ln); color: var(--muted); text-align: right; padding-right: 10px; }
.codelist li:hover { background: var(--chip); }
.codelist li.hit { background: color-mix(in srgb, var(--bad) 18%, transparent); }
.codelist li.miss { background: color-mix(in srgb, var(--warn) 18%, transparent); }
.codelist li.found { background: color-mix(in srgb, var(--ok) 18%, transparent); }
.note { border-left: 3px solid var(--accent); padding: 6px 12px; margin: 10px 0; background: var(--surface); font-size: 0.95rem; }
footer { margin-top: 48px; font-size: 0.85rem; color: var(--muted); }
/* Figures: HTML, not SVG, so they reflow at phone width and the text stays readable. */
.fig { margin: 16px 0; }
.fig figcaption { font-size: 0.88rem; color: var(--muted); margin-top: 8px; max-width: 70ch; }
.flow { display: grid; grid-template-columns: auto auto auto auto auto; gap: 6px 8px; align-items: center; justify-content: start; font-size: 0.9rem; }
.flow .arrow { color: var(--muted); }
.flow .head { font-size: 0.8rem; color: var(--muted); }
.step { border: 1px solid var(--line); border-left: 4px solid var(--line); border-radius: 6px; padding: 2px 8px; background: var(--surface); }
.step.s1 { border-left-color: var(--accent); }
.step.s2 { border-left-color: var(--warn); }
.step.done { border-left-color: var(--ok); }
.step.stop { border-left-color: var(--bad); }
.lane { display: grid; grid-template-columns: 2.4em 1fr; gap: 8px; align-items: start; margin: 6px 0; }
.lane > code { padding-top: 3px; }
.trips { display: flex; flex-wrap: wrap; gap: 4px; list-style: none; margin: 0; padding: 0; }
.trips li { font-size: 0.82rem; max-width: none; }
.worked { border-top: 1px solid var(--line); margin-top: 16px; padding-top: 4px; }
.calls { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 2px; }
.call { display: inline-block; border: 1px solid var(--line); border-radius: 4px; padding: 0 6px; background: var(--chip); font-size: 0.82rem; margin: 1px 2px 1px 0; }
.call b { color: var(--accent); margin-right: 2px; }
.call { white-space: nowrap; }
.worked-steps { list-style: none; padding: 0; margin: 8px 0; }
.worked-steps > li { border-left: 3px solid var(--line); padding: 2px 0 2px 12px; margin: 0 0 14px; max-width: none; }
.worked-steps p { margin: 4px 0; }
.worked-steps .label { display: block; font-size: 0.8rem; color: var(--muted); }
.worked-steps .total { font-weight: 600; }
/* Deeper material sits in <details>, closed by default, so the page stays skimmable. */
details { border: 1px solid var(--line); border-radius: 8px; padding: 6px 12px; margin: 10px 0; background: var(--surface); }
details > summary { cursor: pointer; font-weight: 600; color: var(--accent); }
details[open] > summary { margin-bottom: 6px; }
.plain { border-left: 3px solid var(--ok); padding-left: 10px; }
.myth { border-left: 3px solid var(--bad); padding-left: 10px; }
.loop { list-style: none; padding: 0; margin: 8px 0; display: grid; gap: 4px; max-width: 34rem; }
.loop li { max-width: none; }
.loop .node { border: 1px solid var(--line); border-radius: 8px; padding: 6px 10px; background: var(--surface); }
.loop .node b { display: block; }
.loop .arrow { color: var(--muted); font-size: 0.85rem; padding-left: 18px; }
.answer { margin: 16px 0; }
.answer h3 { margin-top: 0; }
.card > h2:first-child { margin-top: 0; }
/* The trace figure (learnweb/figures.py draws it). Wide on purpose: it scrolls sideways in its
   box on a phone and offers full screen, instead of shrinking its text below legibility.
   Every meaning has a shape, line or glyph; the hues below only repeat what those say. */
/* Wider than the text column when the screen allows, centered on it. */
.tr-fig { width: min(1200px, 100vw - 32px); margin-left: calc((100% - min(1200px, 100vw - 32px)) / 2); }
.tr-frame { border: 1px solid var(--line); border-radius: 8px; background: var(--surface); }
.tr-frame .tr-controls { margin: 0; padding: 8px; border-bottom: 1px solid var(--line); }
.tr-fig .scroll { background: var(--surface); border-radius: 0 0 8px 8px; }
/* The worked section breaks out as one column, heading and figure aligned. */
.worked { width: min(1200px, 100vw - 32px); margin-left: calc((100% - min(1200px, 100vw - 32px)) / 2); }
.worked .tr-fig { width: auto; margin-left: 0; }
.tr-svg { display: block; width: 100%; height: auto; color: var(--ink); font-family: var(--sans); }
.tr-svg text { fill: currentColor; }
.tr-svg .tr-strong { font-weight: 600; }
.tr-svg .tr-muted { fill: var(--muted); }
.tr-svg .tr-role { fill: var(--muted); font-style: italic; }
.tr-life { stroke: var(--line); stroke-width: 1.5; }
.tr-head { fill: var(--chip); stroke: var(--line); }
.tr-wire { stroke: currentColor; stroke-width: 1.5; }
/* A message keeps its fill wherever it appears: a user card is chip-colored, an assistant card is
   accent-tinted, and a message sent again is the same card drawn smaller with a muted outline. */
.tr-new rect { stroke: currentColor; stroke-width: 2; }
.tr-user rect { fill: var(--chip); }
.tr-assistant rect { fill: color-mix(in srgb, var(--accent) 16%, var(--surface)); }
.tr-carried rect { stroke: var(--muted); stroke-width: 1; }
.tr-carried text { fill: var(--muted); }
.tr-bracket { fill: none; stroke: var(--muted); stroke-width: 1; }
.tr-chip rect { fill: var(--surface); stroke: var(--accent); stroke-width: 1.5; }
.tr-chip.tr-s2 rect { stroke: var(--warn); stroke-dasharray: 5 3; }
.tr-chip.tr-both rect { stroke: var(--accent); stroke-width: 3.5; }
/* A hatched rect carries its fill as an attribute; CSS must not paint over it. */
.tr-result rect { stroke: var(--ok); stroke-width: 1.5; }
.tr-result rect:not([fill]) { fill: var(--surface); }
.tr-result.tr-flagged rect, .tr-result .tr-fail { color: var(--bad); stroke: var(--bad); stroke-width: 2.5; }
.tr-result .tr-fail[fill] { stroke: none; }
.tr-result.tr-unflagged rect { stroke: var(--bad); stroke-dasharray: 2 3; stroke-width: 2.5; }
.tr-answer rect { fill: none; stroke: var(--ok); stroke-width: 1.5; }
.tr-answer.tr-wrong rect { stroke: var(--bad); }
.tr-legends { display: grid; gap: 8px 20px; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); margin: 8px 0; }
.tr-legend-title { margin: 0 0 4px; font-size: 0.85rem; font-weight: 600; }
.tr-guide { padding-left: 20px; }
.tr-guide li { margin: 2px 0; }
.tr-legend { list-style: none; padding: 0; margin: 0; display: grid; gap: 4px; font-size: 0.85rem; color: var(--muted); }
.tr-legend li { display: grid; grid-template-columns: 40px 1fr; gap: 8px; align-items: center; max-width: none; }
.tr-legend svg { color: var(--ink); display: block; }
/* The exploration controls: the question and the loop, each a labeled group, above the tabs. */
.tr-explore { border-top: 1px solid var(--line); margin-top: 12px; padding-top: 8px; }
.tr-sets { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); }
.tr-set { border: 1px solid var(--line); border-radius: 8px; padding: 8px 12px 10px; margin: 0; min-width: 0; background: var(--surface); }
.tr-set legend { font-weight: 600; padding: 0 4px; }
.tr-set label { display: block; font-size: 0.9rem; margin: 6px 0; }
.tr-set select { margin-left: 4px; max-width: 100%; }
.tr-set p { margin: 4px 0 6px; }
.tr-orders { display: flex; flex-wrap: wrap; gap: 4px 12px; }
.tr-orders label { margin: 2px 0; }
.tr-actions { margin: 8px 0 4px; }
.tr-controls { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
.tr-controls button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }
.tr-fits { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.tr-fits button { border-radius: 0; }
.tr-rung { margin: 8px 0; position: relative; }
/* A box that scrolls says so: a fade at the right edge, and keyboard focus can scroll it. */
.tr-rung.can-scroll::after { content: ""; position: absolute; top: 0; border-radius: 0 0 8px 0; right: 0; bottom: 0; width: 32px; pointer-events: none; border-radius: 0 8px 8px 0; background: linear-gradient(to left, var(--surface), transparent); }
.tr-legend-box { margin: 8px 0; }
.tr-legend-box > summary { font-weight: 600; }
.tr-fig .scroll:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
.tr-jump { font-size: 0.85rem; padding: 2px 8px; }
.worked > h3 { margin-top: 12px; }
/* Fit to width shows the whole shape at once; the reader zooms in for the words. */
.tr-fig.tr-fit .tr-svg { min-width: 0 !important; }
.tr-fig:fullscreen { background: var(--bg); padding: 16px; overflow: auto; }
.tr-fig:fullscreen .tr-svg { min-width: 1100px; }
/* Who does what (learnweb/actors.py draws it): three lanes, the person, the server code, the model.
   Line and shape carry every meaning; stroke widths are presentation attributes from the run, so
   no rule here sets stroke-width on a wire. */
.ac-svg .ac-strong { font-weight: 600; }
.ac-svg .ac-muted { fill: var(--muted); }
.ac-wire { stroke: currentColor; }
.ac-gap { stroke: var(--surface); }
.ac-life { stroke: var(--line); stroke-width: 1.5; }
.ac-head { fill: var(--chip); stroke: var(--line); }
.ac-side rect { fill: color-mix(in srgb, var(--accent) 7%, var(--surface)); stroke: var(--line); stroke-dasharray: 4 3; }
.ac-work { fill: var(--surface); stroke: currentColor; stroke-width: 1.5; }
.ac-bracket { fill: none; stroke: currentColor; stroke-width: 1.5; }
.ac-hatch { color: var(--muted); }
.ac-glyph rect { fill: var(--surface); stroke: currentColor; stroke-width: 1.2; }
.ac-strike { stroke: currentColor; stroke-width: 2.5; }
.ac-door { stroke: currentColor; }
/* A word on the Four words card lights up the marks that are it: a heavier outline, not only a hue. */
/* A request is one arrow whose shaft is a segment per part, each in the pattern it had when
   first sent; history persists in the mark, so no rule here changes a segment's width or dash.
   The stem is the hairline from the lifeline to where the tail begins. */
.ac-seg { stroke: currentColor; }
.ac-stem { stroke: var(--muted); }
.ac-svg .ac-lit rect, .ac-svg .ac-lit path { stroke: var(--accent); stroke-width: 3.5; }
.ac-svg .ac-lit line:not(.ac-gap) { stroke: var(--accent); }
.ac-svg .ac-lit text { fill: var(--accent); text-decoration: underline; }
.ac-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
/* Two drawings of the same run: the wide one scrolls on a desktop, the narrow one fits a phone. */
.ac-narrow-box { display: none; padding: 4px; }
.ac-n { width: 100%; }
@media (max-width: 640px) {
  .ac-wide-box, .ac-fig .tr-fits, .ac-fig .tr-phone-note { display: none !important; }
  .ac-narrow-box { display: block; }
}
.ac-fig .ac-trip.ac-off, .ac-fig.ac-hide-trips .ac-trips { visibility: hidden; }
.ac-key li { grid-template-columns: 40px 1fr; }
.ac-key svg { color: var(--ink); }
.ac-steps li.ac-now { font-weight: 600; }
.ac-pos { margin: 0; padding: 0 8px; min-height: 0; }
.ac-pos:not(:empty) { padding: 8px; border-bottom: 1px solid var(--line); }
/* One figure, three layers, taken away by the script and given back by a press: the counts
   (SVG labels, the key's rule, the counted sentence) show only with "Now the cost"; without JS
   everything shows. :has() picks the counted sentence over the plain one in a step. */
.ac-fig.ac-layered:not(.ac-numbers) .ac-count { display: none; }
.ac-fig.ac-numbers .ac-steps li:has(.ac-count) .ac-plain { display: none; }
.ac-fig.ac-layered .ac-steps li { cursor: pointer; }
.ac-fig.ac-layered .ac-mark { cursor: pointer; }
.ac-details { margin: 8px 0; }
.ac-detail { margin: 4px 0; }
.ac-detail > summary { font-size: 0.9rem; cursor: pointer; }
.ac-detail pre { margin: 4px 0 8px; font-size: 0.8rem; white-space: pre-wrap; }
/* What a request is made of: hovering it marks every earlier message a segment of it carries. */
.ac-svg .ac-made rect, .ac-svg .ac-made path { stroke: var(--accent); stroke-width: 3; }
.ac-svg .ac-made line:not(.ac-gap) { stroke: var(--accent); }
.ac-svg .ac-made text { fill: var(--accent); }
/* A customer picked in the scenario's table lights its count wherever the page uses it. */
#scenario-data tr[data-customer] { cursor: pointer; }
#scenario-data tr.ac-lit td, [data-customer].ac-lit { outline: 2px solid var(--accent); outline-offset: 1px; font-weight: 600; }
@media (prefers-reduced-motion: no-preference) {
  .ac-fig .ac-mark { transition: opacity 0.25s ease; }
  .ac-svg .ac-seg { transition: stroke 0.25s ease; }
}
/* Step mode: the current message at full strength, the ones before it faint, the rest fainter. */
.ac-fig.ac-stepping .ac-mark { opacity: 0.28; }
.ac-fig.ac-stepping .ac-mark.ac-done { opacity: 0.45; }
.ac-fig.ac-stepping .ac-mark.ac-now { opacity: 1; }
/* The challenge: brackets hidden until checked; each pick is a numbered pin in the margin. */
.ac-fig.ac-quiz .ac-mark { cursor: pointer; }
.ac-fig.ac-quiz .ac-mark:focus { outline: none; }
.ac-fig.ac-quiz .ac-mark:focus-visible text { text-decoration: underline; }
.ac-pin circle { fill: var(--surface); stroke: currentColor; stroke-width: 2; }
.ac-pin.ac-wrong circle { stroke-dasharray: 3 2; }
.ac-pin text { font-weight: 700; }
/* Result chips in the worked example: the same marks as the trace figure, in HTML. */
.results { display: flex; flex-wrap: wrap; gap: 4px; margin: 4px 0; }
.res { display: inline-block; border: 1.5px solid var(--ok); border-radius: 3px; padding: 0 6px; background: var(--surface); font-size: 0.82rem; }
.res .n { color: var(--accent); font-weight: 700; }
.res.bad { border: 2.5px solid var(--bad); position: relative; padding-right: 24px; }
/* The hatch is a band beside the text, never under it. */
.res.bad::after { content: ""; position: absolute; top: 0; bottom: 0; right: 0; width: 16px; background: repeating-linear-gradient(45deg, transparent 0 3px, var(--bad) 3px 4.5px); }
/* A clicked line in spot-the-bug says what it is with a mark, not only a tint. */
.codelist li.found::after { content: " ¹3 bug"; color: var(--ok); font-weight: 600; }
.codelist li.miss::after { content: " ¹7 not one"; color: var(--warn); font-weight: 600; }
/* Task 4's alignment figure (web/tasks/04-modern-python/alignment.js): solid cards and lines for
   a pair, dashed for a side with no row; the residual sits on the line, NaN in a dashed pill. */
.al-fig { display: block; width: 100%; max-width: 520px; height: auto; }
.al-card, .al-res { fill: var(--surface); stroke: var(--ink); stroke-width: 1.5; }
.al-gap { fill: none; stroke: var(--muted); stroke-width: 1.5; stroke-dasharray: 4 3; }
.al-nan { fill: var(--bg); stroke: var(--muted); stroke-width: 1.5; stroke-dasharray: 4 3; }
.al-pair { stroke: var(--ink); stroke-width: 2.5; }
.al-none { stroke: var(--muted); stroke-width: 1.5; stroke-dasharray: 2 4; }
.al-text { fill: var(--ink); font: 12px var(--mono); }
.al-head { fill: var(--muted); font: 700 12px var(--sans); }
