/* IAD Wiki — public site. Monochrome Swiss-typographic direction (ZHdK).
 *
 * The visual language is rules, not boxes: every division is a border of a
 * specific weight (3px = section start, 2px = structural, 1px hairline = row
 * separator). No shadows, no gradients, no border-radius anywhere, and no colour
 * accent — emphasis comes from weight and rule thickness. Links have no colour;
 * hover draws an underline. Keep it that way when extending.
 */

/* --- Tokens --------------------------------------------------------------- */
:root {
  --bg: #ffffff;
  --ink: #000000;          /* text, solid buttons, footer background */
  --soft: #3d3d3d;         /* secondary text */
  --faint: #6f6f6f;        /* tertiary text, mono labels */
  --rule: #000000;         /* all rules and borders */
  --hair: rgba(0, 0, 0, .25);
  --inset: #f2f2f2;        /* code background, hover fills */
  --logo-f: none;          /* header logos */
  --logo-f-inv: invert(1); /* logos on the inverted footer */
  --font: 'Archivo', Helvetica, Arial, sans-serif;
  --mono: ui-monospace, Menlo, Consolas, monospace;
  --measure: 816px;        /* article text column (widened ~20%) */
}

/* Warm near-black, deliberately not pure black. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #1a1a18;
    --ink: #ecebe7;
    --soft: #c2c1bc;
    --faint: #8f8e88;
    --rule: #ecebe7;
    --hair: rgba(236, 235, 231, .28);
    --inset: #262624;
    --logo-f: invert(.93);
    --logo-f-inv: invert(.08);
  }
}
:root[data-theme="dark"] {
  --bg: #1a1a18;
  --ink: #ecebe7;
  --soft: #c2c1bc;
  --faint: #8f8e88;
  --rule: #ecebe7;
  --hair: rgba(236, 235, 231, .28);
  --inset: #262624;
  --logo-f: invert(.93);
  --logo-f-inv: invert(.08);
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

/* A short page — one search hit, a 404, a stub article — would otherwise end
   mid-viewport and leave the footer stranded with blank space beneath it. The
   body fills the screen as a flex column and <main> takes up the slack, so the
   footer sits at the bottom of the viewport or below the content, whichever is
   lower. `flex: 1 0 auto` (not `1`) so main never shrinks below its content. */
body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  font-family: var(--font);
  font-size: 17px;
  line-height: 1.65;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
body > main { flex: 1 0 auto; }
/* The editor's preview renders the page with no header or footer (see layout()),
   so the article would otherwise begin hard against the top of the frame — the
   site's own header is what gives it air on a real page. */
body.bare > main { padding-top: 24px; }
body > .site-header, body > .site-footer { flex: none; }

/* Keyboard focus. The palette has no accent colour, so the ring is the ink
   itself with an offset — visible against both themes without inventing a hue. */
:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Text selection. Same reasoning as focus: no accent hue, so selection inverts
   the ink — a black highlight in light mode, near-white in dark — rather than
   the browser's default blue. */
::selection { background: var(--ink); color: var(--bg); }

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

a { color: inherit; text-decoration: none; }

.skip-link {
  position: absolute; left: -999px; top: 0;
  background: var(--ink); color: var(--bg); padding: .6rem 1rem; z-index: 100;
  font-weight: 700; font-size: 13px;
}
.skip-link:focus { left: 0; }

/* --- Header --------------------------------------------------------------- */
.site-header {
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px; padding: 20px 48px; border-bottom: 2px solid var(--rule);
}
.brand { display: flex; align-items: center; }
.brand img { height: 26px; width: auto; display: block; filter: var(--logo-f); }
.header-tools { display: flex; align-items: center; gap: 14px; }

.search { display: flex; align-items: center; border: 2px solid var(--rule); }
.search input {
  font: inherit; font-size: 13px; font-weight: 500; padding: 8px 12px; width: 210px;
  border: 0; background: transparent; color: var(--ink);
}
.search input::placeholder { color: var(--faint); }
.search input:focus { outline: none; }
.search:focus-within { outline: 2px solid var(--ink); outline-offset: 2px; }
.search button {
  font: inherit; font-size: 15px; font-weight: 700; line-height: 1;
  padding: 8px 12px; border: 0; background: transparent; color: var(--ink); cursor: pointer;
}

.theme-toggle {
  border: 0; background: transparent; color: var(--ink);
  font-size: 17px; line-height: 1; padding: 4px; cursor: pointer;
}

/* --- Homepage ------------------------------------------------------------- */
.hero { padding: 64px 48px 0; }

/* The arrow field sits in the empty column the title leaves: the h1 is capped at
   12ch and the lead at 560px, so the right of the hero was always air.
   The text keeps its own box beside it. It was a grid at first, with the field
   spanning both rows — which let the field's height inflate those rows and push
   the lead 166px below the title. Two flex children cannot do that to each
   other: the text stacks on its own terms and the field sits alongside.
   Faint and fine on purpose: beside 110px of black type, anything with weight
   would be a second voice talking over the first. */
/* No gap between the two: the h1 already carries ~140px of column it never inks
   (it wraps at 10.8ch but its longest line is 602px), and that is the air between
   the title and the field. A flex gap on top of it put them 4 columns further
   apart than they should be. */
.hero:has(.hero-art) { display: flex; align-items: flex-start; gap: 0; }
/* The text takes what the title needs and no more, so the field can have the
   rest. Capped at 11ch rather than the h1's own 12ch: the wrap is identical
   ("Design Wiki" is under both), but 12ch reserved ~180px of column the title
   never inked, and that width belongs to the field. */
.hero-text { flex: 0 1 auto; min-width: 0; }
.hero:has(.hero-art) h1 { max-width: 10.8ch; }
.hero-art {
  /* Takes everything the text leaves; the client then fills it with as many
     columns as fit. justify-content keeps them flush with the right margin when
     the space is not an exact multiple of a cell, and overflow is a seatbelt:
     a field that somehow outgrew its box would be cropped rather than pushing
     the page sideways. */
  flex: 1 1 0; min-width: 0; justify-content: end; overflow: hidden;
  /* One cell ABOVE the title's cap line, which is 22px below the h1's box top
     (the box carries that much leading over the caps). 21 rows of 15.08px then
     put the last one a cell below the lead's final baseline: the field frames
     the block of type beside it by a row at each end. */
  margin: 7px 0 0;
  /* A cell per character, each with its own box. Whatever glyph lands in one is
     centred inside it, so a wide fallback (Consolas has no ↖↗↘↙) cannot shift
     the row — which it did, visibly, when this was a run of preformatted text.
     Sized in `ch` because that IS the advance: square cells in any monospace
     face rather than only in the one this was measured in. */
  --art-cell: 1.83ch;
  display: grid;
  grid-template-columns: repeat(var(--art-cols, 28), var(--art-cell));
  grid-auto-rows: var(--art-cell);
  place-items: center;
  font-family: var(--mono); font-size: 15px; line-height: 1;
  color: var(--faint);
  user-select: none; -webkit-user-select: none;
}
/* Below this what is left of the row is a sliver rather than a field: the title
   keeps its two lines all the way down, but the column beside it falls under
   twenty cells and stops reading as a texture. */
@media (max-width: 1200px) { .hero-art { display: none; } }
@media print { .hero-art { display: none; } }
.hero h1 {
  font-size: 110px; font-weight: 800; letter-spacing: -0.035em; line-height: 1.02;
  margin: 0; max-width: 12ch;
}
.hero .lead {
  font-size: 20px; font-weight: 500; color: var(--soft);
  max-width: 560px; margin: 32px 0 0; line-height: 1.5;
}

.section { margin: 64px 48px 0; border-top: 3px solid var(--rule); }
.section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 24px; padding-top: 18px;
}
.section-title { font-size: 40px; font-weight: 800; letter-spacing: -0.025em; margin: 0; }

/* Pages attached to the header itself, above its sections. Deliberately without
   separators — the only rule in a section is the 3px one at its top. */
.page-list { list-style: none; margin: 14px 0 0; padding: 0; }
.page-list li { margin: 0; }
.page-list a {
  display: flex; gap: 14px; align-items: baseline;
  padding: 6px 0; font-size: 22px; font-weight: 700;
}
.page-list a::before { content: "→"; font-weight: 800; flex: none; }
.page-list a span { border-bottom: 1px solid transparent; transition: border-color .12s ease; }
.page-list a:hover span { border-bottom-color: currentColor; }

/* The homepage type scale steps 17 → 18 → 22 → 40 → 110. The gaps are tuned to
   that: the steps are close enough together that tight spacing would read as one
   undifferentiated block. */
/* The column COUNT follows the width rather than being fixed at three. A section
   spans the whole viewport — there is no max-width up here — so on a wide screen
   three tracks left ~600px of air around a list of short links, and a section
   with five subsections wrapped to a second row with a hole beside it.

   auto-FILL, not auto-fit: fill keeps the empty tracks, so the module is the
   same width in every section and the columns line up down the whole index.
   auto-fit collapses them, which would stretch a two-subsection section across
   the full width and make its columns twice those of the section above it.

   300px is the narrowest column the 3-up layout ever used (at the old 1080px
   breakpoint), so it is a width this type is already known to sit in. The
   min() guards the case where the track is wider than the container. */
.section-children {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
  gap: 40px;
  margin-top: 44px; padding-bottom: 56px;
}
.subsection-title { font-size: 18px; font-weight: 800; margin: 0 0 18px; }
.subsection .page-list { display: flex; flex-direction: column; gap: 16px; margin: 0; }
.subsection .page-list a { padding: 0; font-size: 17px; font-weight: 500; }

.empty { color: var(--faint); }

/* --- Article -------------------------------------------------------------- */
/* Centred reading column. The wrapper owns the width and the horizontal padding
   and centres the whole article, so the prose sits balanced on the page — with
   the Contents list in its left track — instead of the column hugging the left
   edge. */
/* Wide enough to hold the 240px Contents rail + gap + the widened text column. */
.article { max-width: 1208px; margin: 0 auto; padding: 0 48px; }
/* No Contents list (a page with fewer than two H2s): a single reading column,
   still centred, narrowed to the measure. */
.article-narrow { max-width: calc(var(--measure) + 96px); }
.article-head { padding: 28px 0 0; }
.breadcrumb { font-size: 13px; font-weight: 600; color: var(--soft); }
.breadcrumb a { border-bottom: 1px solid var(--hair); }
.breadcrumb a:hover { border-bottom-color: currentColor; }
.breadcrumb [aria-current] { color: var(--ink); }
.article-head h1 {
  font-size: 76px; font-weight: 800; letter-spacing: -0.03em; line-height: 1;
  margin: 40px 0 0;
}
.article-rule { border-top: 3px solid var(--rule); margin-top: 40px; }

/* Two columns: sticky table of contents, then the text column. */
.article-body {
  display: grid; grid-template-columns: 240px minmax(0, 1fr); gap: 56px;
  padding: 40px 0 88px;
}
.article-narrow .article-body { grid-template-columns: minmax(0, 1fr); }
/* A long Contents list would otherwise stick taller than the viewport, leaving its
   last entries permanently below the fold with no way to scroll to them. Cap it to
   the visible height and let it scroll on its own. The thin, monochrome scrollbar
   keeps it inside the design language. */
.toc {
  position: sticky; top: 40px; align-self: start;
  max-height: calc(100vh - 80px); overflow-y: auto;
  scrollbar-width: thin; scrollbar-color: var(--rule) transparent;
  padding-right: 8px;
}
.toc-head {
  font-size: 15px; font-weight: 800; border-bottom: 2px solid var(--rule);
  padding-bottom: 10px; margin: 0 0 4px;
}
.toc ul { list-style: none; margin: 0; padding: 0; }
.toc li { border-bottom: 1px solid var(--hair); }
.toc a {
  display: flex; gap: 10px; align-items: baseline;
  padding: 9px 0; font-size: 14px; font-weight: 600;
}
.toc a::before { content: "→"; font-weight: 800; color: var(--faint); flex: none; }
.toc a:hover { background: var(--inset); }

.page { max-width: var(--measure); min-width: 0; }
/* Only the edit shortcut lives here now (the "Updated" line was removed), so it
   sits on its own at the top-right of the reading column, for logged-in users. */
.page-meta { display: flex; justify-content: flex-end; margin: 0 0 20px; }
.edit-link { display: inline-flex; align-items: center; gap: 6px; color: var(--ink); }
.edit-link span { border-bottom: 1px solid transparent; transition: border-color .12s ease; }
.edit-link:hover span { border-bottom-color: currentColor; }

/* --- Prose ---------------------------------------------------------------- */
/* Break long unbroken strings (e.g. a pasted URL or gibberish) so they wrap
   inside the column instead of spilling past it. Inherited, so it covers
   headings, list items and table cells too; `pre` keeps `white-space: pre` and
   scrolls, so code is unaffected. */
.prose { font-size: 17px; line-height: 1.65; overflow-wrap: break-word; }
.prose > *:first-child { margin-top: 0; }
.prose .lead { font-size: 21px; font-weight: 500; line-height: 1.55; margin: 0 0 28px; }
.prose h1 { font-size: 34px; font-weight: 800; letter-spacing: -0.02em; margin: 52px 0 20px; }
.prose h2 {
  font-size: 30px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.15;
  border-top: 2px solid var(--rule); padding-top: 16px; margin: 52px 0 20px;
}
.prose h3 { font-size: 21px; font-weight: 800; margin: 32px 0 12px; }
.prose h1, .prose h2, .prose h3 { scroll-margin-top: 24px; }
.prose p { margin: 0 0 20px; }
.prose strong { font-weight: 800; }
.prose a { font-weight: 600; border-bottom: 2px solid currentColor; }
.prose mark { background: #fff3a3; color: #000; padding: 0 .12em; }
.prose u { text-underline-offset: 2px; }

/* Lists carry no bullets: an arrow (unordered) or a tabular numeral (ordered)
   in a fixed-width leading column, with hairline separators between rows. */
.prose ul, .prose ol { list-style: none; margin: 0 0 28px; padding: 0; counter-reset: li; }
.prose li {
  display: flex; gap: 14px; align-items: baseline;
  padding: 10px 0; border-top: 1px solid var(--hair); margin: 0;
}
.prose li:last-child { border-bottom: 1px solid var(--hair); }
.prose ul > li::before { content: "→"; font-weight: 800; flex: none; }
.prose ol > li { counter-increment: li; }
.prose ol > li::before {
  content: counter(li); font-weight: 800; font-variant-numeric: tabular-nums;
  min-width: 1.2em; flex: none;
}

.prose code {
  font-family: var(--mono); font-size: 14.5px; background: var(--inset);
  color: var(--ink); padding: 2px 6px;
}
.prose pre {
  background: var(--inset); border-left: 3px solid var(--rule);
  padding: 22px 24px; margin: 0 0 28px; overflow-x: auto;
  font-size: 13.5px; line-height: 1.65;
}
/* Language label above the code, sharing the panel's inset + left rule so the
   two read as one block. The wrapper carries the spacing; the pre's own margin
   is reset so the label sits flush on top. */
.prose .code-block { margin: 0 0 28px; }
.prose .code-block pre { margin: 0; padding-top: 12px; }
.prose .code-label {
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .1em; color: var(--faint);
  background: var(--inset); border-left: 3px solid var(--rule);
  padding: 12px 24px 0;
}
.prose pre code { background: none; padding: 0; font-size: inherit; }

/* Syntax highlighting stays monochrome: weight and italic carry the meaning
   that colour would in a themed editor. */
.tok-comment { color: var(--faint); font-style: italic; }
.tok-keyword { font-weight: 700; }
.tok-string { color: var(--soft); }
.tok-number { color: var(--soft); font-variant-numeric: tabular-nums; }

.prose hr { border: none; border-top: 3px solid var(--rule); margin: 40px 0; }

/* No blanket cap: an image with no explicit width (the "Full width" preset)
   fills the column. Medium/Small set their own max-width inline, which still
   caps and — with margin:auto — centres them. */
.prose figure.image { margin: 28px auto; }
/* Fill the figure (which the width preset sizes), so the image renders at the
   selected width — Full = column, Medium = 640, Small = 320 — not its natural size. */
.prose figure.image img { width: 100%; max-width: 100%; height: auto; display: block; }
/* A standalone image opens the same overlay a gallery thumbnail does; the label
   is the click target that toggles its checkbox. */
.prose figure.image .image-zoom { display: block; cursor: zoom-in; }
.prose figure.image.align-left { margin-left: 0; }
.prose figure.image.align-right { margin-right: 0; }
.prose figcaption {
  font-size: 13px; font-weight: 600; color: var(--faint);
  margin-top: 12px; text-align: center;
}

/* --- Gallery ------------------------------------------------------------- */
.gallery-block { margin: 0 0 28px; }
.gallery { display: grid; gap: 12px; align-items: start; }
.gallery-c1 { grid-template-columns: 1fr; }
.gallery-c2 { grid-template-columns: repeat(2, 1fr); }
.gallery-c3 { grid-template-columns: repeat(3, 1fr); }
.gallery-c4 { grid-template-columns: repeat(4, 1fr); }
.gallery-fig { margin: 0; }
/* Uniform square cells, images cropped to fill for an even grid. The cell is a
   <label> that toggles its lightbox checkbox. */
.gallery-cell { display: block; aspect-ratio: 1 / 1; overflow: hidden; background: var(--inset); cursor: zoom-in; }
.gallery-cell img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .2s ease; }
.gallery-cell:hover img { transform: scale(1.04); }
/* Scoped past `.prose figcaption`, which would otherwise centre it and win. */
.gallery-fig .gallery-cap {
  font-size: 13px; font-weight: 500; color: var(--faint);
  margin-top: 8px; line-height: 1.4; text-align: left;
}

/* CSS-only lightbox via the checkbox-toggle pattern (no anchors/`:target`, which
   break inside the editor's srcdoc preview). The hidden checkbox is revealed by
   its <label> thumbnail; `:checked` shows the adjacent overlay. */
.lightbox-cb { position: absolute; width: 1px; height: 1px; opacity: 0; margin: -1px; overflow: hidden; }
.lightbox { display: none; }
.lightbox-cb:checked + .lightbox {
  display: flex; position: fixed; inset: 0; z-index: 1000;
  align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .9); padding: 4vh 4vw;
}
.lightbox-bg { position: absolute; inset: 0; cursor: zoom-out; }
.lightbox-bg::after {
  content: "\00d7"; position: absolute; top: 16px; right: 28px;
  color: #fff; font-size: 40px; line-height: 1;
}
.lightbox-fig { position: relative; margin: 0; display: flex; flex-direction: column; align-items: center; max-height: 92vh; }
.lightbox-fig img { max-width: 100%; max-height: 84vh; object-fit: contain; }
/* Scoped past `.prose figcaption` so the caption stays white on the dark overlay. */
.lightbox .lightbox-cap { color: #fff; font-size: 14px; font-weight: 500; margin-top: 14px; text-align: center; max-width: 60ch; }

/* Collapse columns on smaller screens. */
@media (max-width: 720px) {
  .gallery-c3, .gallery-c4 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 420px) {
  .gallery-c2, .gallery-c3, .gallery-c4 { grid-template-columns: 1fr; }
}

/* Attachment: a full-width bordered row, download arrow, no path shown. */
.attachment { margin: 0 0 28px; }
.attachment a {
  display: flex; align-items: center; gap: 14px;
  border: 2px solid var(--rule); padding: 16px 20px;
  font-size: 16px; font-weight: 700;
}
.attachment a:hover { background: var(--inset); }
.attachment-icon { font-size: 16px; font-weight: 800; flex: none; }

/* Callouts: one monochrome box, distinguished by icon + label only. */
/* A colour-coded left bar per variant (matching the editor), no surrounding box. */
.callout { border: 0; border-left: 4px solid var(--rule); padding: 14px 20px; margin: 0 0 28px; }
.callout-info { border-left-color: #0b74d0; }
.callout-note { border-left-color: var(--faint); }
.callout-tip, .callout-success { border-left-color: #1a9e5f; }
.callout-warning { border-left-color: #d08b0b; }
.callout-danger { border-left-color: #d0290b; }
.callout-question { border-left-color: #8250df; }
.callout-title {
  display: flex; align-items: center; gap: 10px;
  font-size: 15px; font-weight: 800; margin-bottom: 6px;
}
.callout-title svg { width: 18px; height: 18px; flex: none; }
.callout-body { font-size: 16px; color: var(--soft); }

.table-wrap { overflow-x: auto; margin: 0 0 28px; }
.prose table { border-collapse: collapse; width: 100%; font-size: 15px; }
.prose th {
  text-align: left; font-size: 14px; font-weight: 800;
  border-bottom: 2px solid var(--rule); padding: 12px 14px 12px 0;
}
.prose td { padding: 12px 14px 12px 0; border-bottom: 1px solid var(--hair); }
.prose th:last-child, .prose td:last-child { padding-right: 0; }
.prose td:first-child { font-family: var(--mono); font-size: 13.5px; }

.embed-video {
  position: relative; padding-top: 56.25%; margin: 0 0 28px;
  border: 2px solid var(--rule); background: var(--inset);
}
.embed-video iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* Sub-pages ({{subpages}}): the same arrow-row language as the homepage. */
.prose .subpages { margin: 0 0 28px; }
.prose .subpages ul { margin: 0; padding: 0; }
.prose .subpages li { border-top: 1px solid var(--hair); padding: 0; }
.prose .subpages li:last-child { border-bottom: 1px solid var(--hair); }
.prose .subpages li::before { content: none; }
.prose .subpages a {
  display: flex; gap: 14px; align-items: baseline; padding: 12px 0;
  font-size: 17px; font-weight: 700; border-bottom: 0;
}
.prose .subpages a::before { content: "→"; font-weight: 800; flex: none; }

/* --- Search results ------------------------------------------------------- */
.search-page { padding: 40px 48px 88px; max-width: 900px; }
.search-head h1 { font-size: 56px; font-weight: 800; letter-spacing: -0.03em; margin: 0; }
.search-head .lead { font-size: 17px; font-weight: 500; color: var(--soft); margin: 12px 0 0; }
.search-results { list-style: none; margin: 32px 0 0; padding: 0; border-top: 2px solid var(--rule); }
.search-results li { border-bottom: 1px solid var(--hair); padding: 18px 0; }
.search-results a { display: flex; gap: 14px; align-items: baseline; }
.search-results a::before { content: "→"; font-weight: 800; flex: none; }
.result-title { font-size: 17px; font-weight: 700; border-bottom: 1px solid transparent; transition: border-color .12s ease; }
.search-results a:hover .result-title { border-bottom-color: currentColor; }
.result-section {
  display: inline-block; margin-left: 12px; font-size: 10.5px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--faint);
}
.result-snippet { margin: 6px 0 0 28px; color: var(--soft); font-size: 15px; }
.result-snippet mark { background: #fff3a3; color: #000; }

/* --- Footer --------------------------------------------------------------- */
.site-footer {
  background: var(--ink); color: var(--bg);
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 24px; padding: 32px 48px; margin-top: 64px;
}
.footer-logos { display: flex; align-items: center; gap: 24px; }
.footer-logos img { width: auto; display: block; filter: var(--logo-f-inv); }
.footer-zhdk { height: 34px; }
.footer-iad { height: 22px; }
.footer-links { display: flex; gap: 28px; font-size: 13px; font-weight: 600; }
.footer-links a { border-bottom: 1px solid transparent; transition: border-color .12s ease; }
.footer-links a:hover { border-bottom-color: currentColor; }

/* --- Error pages ---------------------------------------------------------- */
.error-page main { padding: 96px 48px; }
.error-page h1 { font-size: 76px; font-weight: 800; letter-spacing: -0.03em; margin: 0 0 12px; }

/* --- Responsive ----------------------------------------------------------- */
@media (max-width: 1080px) {
  .article-body { grid-template-columns: 1fr; gap: 32px; }
  /* Stacked above the article at this width, so it flows with the page — the
     viewport cap and inner scroll from the sidebar layout must not apply. */
  .toc { position: static; max-height: none; overflow-y: visible; padding-right: 0; }
  /* The two-column step used to live here. The track size drops it to two on its
     own at ~1020px, and to one below ~680px — a breakpoint would now only fight
     that. The phone rule at 720px still overrides, deliberately. */
}
@media (max-width: 720px) {
  body { font-size: 16px; }
  /* Search and the theme toggle are one group and stay on one line: the toggle
     dropping to a row of its own left the header three rows tall on a phone, for
     one 22px button. So the tools never wrap internally — the FIELD gives
     instead, down to whatever is left beside the wordmark. If even that is too
     little, the header's own wrap carries the pair down together. */
  .site-header { padding: 16px 20px; flex-wrap: wrap; gap: 12px 16px; }
  /* basis 0, not auto: a flex container decides its LINES from the items'
     hypothetical sizes and only then shrinks what is on them — so tools sized by
     their content wrap to a row of their own before they ever give a pixel. From
     zero they always fit beside the wordmark, then grow into what is left. */
  .header-tools { flex: 1 1 0; flex-wrap: nowrap; justify-content: flex-end; min-width: 0; gap: 10px; }
  .search { flex: 1 1 auto; min-width: 0; }
  .search input { width: 100%; min-width: 0; }

  /* The internal wiki puts more up here — an Admin link, who you are, a way out —
     and it will not all share a phone row. Two rules keep that from going wrong
     in the two ways it can:

     `flex: auto` asks for the group's real width, so when it cannot fit beside
     the wordmark the WHOLE group moves down together. Sized from zero it would
     stay on the line and split instead, leaving the theme toggle stranded on a
     row of its own under a full-width account block — which is what happened at
     375px with a long enough address.

     And the account block itself never shrinks: it is three separate controls,
     and squeezing them is how "View site ↗" ended up 29px wide in the admin. */
  .header-tools:has(.site-account) { flex: 1 1 auto; flex-wrap: wrap; row-gap: 12px; }
  .header-tools:has(.site-account) .site-account { flex: none; }
  /* The field takes the row beneath, which is where a phone puts one anyway. */
  .header-tools:has(.site-account) .search { flex: 1 1 100%; order: 1; }
  .hero { padding: 40px 20px 0; }
  .hero h1 { font-size: 46px; letter-spacing: -0.025em; }
  .hero .lead { font-size: 17px; margin-top: 20px; }
  .section { margin: 40px 20px 0; }
  .section-title { font-size: 28px; }
  .section-children { grid-template-columns: 1fr; gap: 32px; padding-bottom: 32px; }
  .article { padding: 0 20px; }
  .article-head { padding-top: 20px; }
  .article-head h1 { font-size: 36px; margin-top: 24px; }
  .article-rule { margin-top: 24px; }
  .article-body { padding: 24px 0 56px; }
  .prose h2 { font-size: 24px; }
  .search-page { padding: 24px 20px 56px; }
  .search-head h1 { font-size: 36px; }
  .site-footer { flex-direction: column; align-items: stretch; gap: 20px; padding: 24px 20px; }
  /* Once the footer stacks, the links have the whole width to themselves — so
     they use it, evenly, first flush left and last flush right. Bunched at a
     fixed 18px they left two thirds of the row empty and read as one long word.
     The gap stays as the floor for when they have to wrap. */
  .footer-links { flex-wrap: wrap; gap: 12px 18px; justify-content: space-between; }
  /* The logo pair keeps to the left; only the links spread. */
  .footer-logos, .footer-mark { align-self: flex-start; }
}

/* --- Page + theme transitions --------------------------------------------- */
/* Two animations share the View Transitions API here:
     1. Moving between pages — a quick cross-fade with the incoming article
        rising a few pixels into place.
     2. The theme toggle — a circular reveal from the click point.
   Both are pure CSS (bar a class the toggle sets); a browser without the API
   just navigates/swaps instantly, and nothing here touches the CSP. */

/* 1. Page navigation. Opting in on both documents is all it takes for same-origin
      link clicks, form GETs and back/forward to animate. */
@view-transition { navigation: auto; }

/* The article is captured as its own section, so the identical header and footer
   stay put while only the content transitions. Every public page has one #main. */
#main { view-transition-name: main-content; }
/* ...but not while a zoomed image is open.
 *
 * A named element is a containing block for its fixed-position descendants —
 * Safari applies that at all times, not only mid-transition. The lightbox is
 * `position: fixed; inset: 0`, so inside #main it stops meaning "the viewport"
 * and starts meaning "the whole article": on a long page the overlay lands
 * hundreds of pixels above the fold and you have to scroll up to find the image.
 * Dropping the name for as long as an overlay is open hands the viewport back,
 * and costs nothing — you cannot navigate with the overlay covering the page. */
#main:has(.lightbox-cb:checked) { view-transition-name: none; }
::view-transition-old(main-content) { animation: nav-out .14s cubic-bezier(.4, 0, .2, 1) both; }
::view-transition-new(main-content) { animation: nav-in .30s cubic-bezier(.4, 0, .2, 1) both; }
/* Everything else (header, footer, background) just cross-fades. Being identical
   page to page, that reads as "holds still". Only the duration is tuned; the
   browser's default fade keyframes stay. */
::view-transition-old(root),
::view-transition-new(root) { animation-duration: .18s; }
@keyframes nav-out { to { opacity: 0; } }
@keyframes nav-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Fallback for browsers without cross-document view transitions (Firefox today):
   there is no outgoing snapshot to cross-fade, so the incoming content simply
   animates itself in on load — the same fade-and-rise, minus the held header.
   Gated on @supports so browsers that DO run the real transition above never
   play both. `styles.css` is render-blocking, so the opacity:0 start applies
   before first paint (no flash), and reduced-motion opts out entirely. */
@supports not (view-transition-name: none) {
  @media (prefers-reduced-motion: no-preference) {
    #main { animation: nav-in .30s cubic-bezier(.4, 0, .2, 1) both; }
  }
}

/* 2. Theme toggle. The toggle adds .vt-theme to <html> for the duration (see
      theme.js), which both selects the circular reveal and drops the article's
      own section — so the page reveals as a single image, not a content fade. */
.vt-theme #main { view-transition-name: none; }
.vt-theme::view-transition-old(root) { animation: none; }
.vt-theme::view-transition-new(root) { animation: theme-reveal .6s cubic-bezier(.4, 0, .2, 1); }
@keyframes theme-reveal {
  from { clip-path: circle(0px at var(--tx, 50%) var(--ty, 50%)); }
  to   { clip-path: circle(var(--tr, 150vmax) at var(--tx, 50%) var(--ty, 50%)); }
}

/* Honour a reduced-motion preference: swap instantly, no fade or movement. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* --- Print -----------------------------------------------------------------
 *
 * Also the PDF: "Download PDF" in the editor opens /admin/…/print, which is this
 * page with the chrome off and the print dialog already up, and Save as PDF is
 * the printer. So these rules are not a courtesy for the rare person at a
 * printer — they are the design of every PDF this wiki produces.
 *
 * The provenance line and the page-break rules below matter more on paper than
 * anything on screen: a sheet has no URL bar to say where it came from, and no
 * scrollbar to explain why a table stops halfway. */
@page { margin: 18mm 16mm; }

@media print {
  .site-header, .site-footer, .breadcrumb, .theme-toggle, .search, .skip-link,
  .edit-link, .toc { display: none !important; }
  body { font-size: 11pt; color: #000; background: #fff; }
  .article { max-width: 100%; padding: 0; }
  .article-body { display: block; padding: 0; }
  .page { max-width: 100%; }
  a { color: #000; }
  .prose a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .8em; color: #555; }

  /* Nothing that reads as one thing may be split across two sheets, and no
     heading may be left stranded at the foot of a page above nothing. */
  h1, h2, h3, h4 { break-after: avoid; break-inside: avoid; }
  figure, table, pre, blockquote, .callout, .gallery, .attachment,
  .page-list li, .prose li { break-inside: avoid; }
  thead { display: table-header-group; }  /* a long table repeats its header */
  tr, img { break-inside: avoid; }
  p { orphans: 3; widows: 3; }

  /* Photographs are laid out for a screen a thousand pixels wide; on paper they
     have to come back inside the type area, and never push a page sideways. */
  img, svg, video { max-width: 100% !important; height: auto !important; }
  .gallery { display: block; }
  .gallery figure { margin: 0 0 8mm; }

  /* Colour: printers do not honour backgrounds by default, and a callout that
     depends on its tint would come out as unmarked text. Its rule does the work
     on paper, so the meaning survives a black-and-white printer too. */
  .callout { background: transparent !important; border-left: 2pt solid #000; padding-left: 4mm; }
  .callout-title svg { width: 12pt; height: 12pt; }
}

/* Where this sheet came from. Only the print view emits it, and it is a quiet
   line on screen — but on paper it is the only thing left that can answer which
   page this is, from which wiki, and when it was taken. */
.print-meta {
  margin: 48px 0 0; padding-top: 12px; border-top: 1px solid var(--hair);
  font-family: var(--mono); font-size: 11px; color: var(--faint);
}
@media print {
  .print-meta { margin-top: 10mm; color: #555; font-size: 8pt; }
}

/* --- Old-version banner ----------------------------------------------------
   Only the admin's "view this version" page emits this: the version is rendered
   through the public layout, because the question it answers is "what did this
   page look like", and the answer has to look like the page. The bar states
   plainly that you are not on the live page. */
.version-banner {
  border-bottom: 2px solid var(--rule); background: var(--inset);
  font-family: var(--font);
}
.version-banner-in {
  max-width: 1180px; margin: 0 auto; padding: 12px 24px;
  display: flex; flex-wrap: wrap; gap: 12px 24px; align-items: center;
  font-size: 13px;
}
.version-banner-do { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; margin-left: auto; }
.version-banner-do a, .version-banner-do button {
  font: inherit; font-weight: 700; background: none; border: 0; padding: 0;
  color: inherit; cursor: pointer; border-bottom: 1px solid var(--hair);
}
.version-banner-do a:hover, .version-banner-do button:hover { border-bottom-color: currentColor; }

/* Signed-in identity and the way out. Only the internal wiki renders this — the
   public site looks the same to everyone — and it is the only sign-out a reader
   has, since the admin chrome is closed to them. */
.site-account { display: inline-flex; align-items: center; gap: 10px; }
.site-whoami { font-size: 12px; color: var(--faint, #6f6f6f); }
.site-logout { display: inline; margin: 0; }
.site-logout button {
  font: inherit; font-size: 12px; font-weight: 600; padding: 0;
  border: 0; border-bottom: 1px solid transparent; background: transparent;
  color: inherit; cursor: pointer;
}
.site-logout button:hover { border-bottom-color: currentColor; }

/* The account box shares the header with the search field, and the search field
   was the one that gave — it shrank to nothing and left a box with only its
   arrow in it. The field keeps its width; the header wraps instead.

   Above the phone breakpoint only: down there the header has room for one row of
   tools and no more, so the trade is reversed and the field is what gives (see
   the 720px block above). */
.site-header { flex-wrap: wrap; }
@media (min-width: 721px) {
  .header-tools { flex-wrap: wrap; justify-content: flex-end; }
  .search { flex: none; }
  .search input { min-width: 10rem; }
}

/* --- The internal wiki's register ----------------------------------------- *
 * Same typeface, same rules-not-boxes, same arrows. What changes is the octave.
 *
 * The public site is a front page: 110px cover type, a 380px hero, one link above
 * the fold. The internal wiki is looked up rather than read — people arrive
 * knowing the word they want — so the whole scale steps down about a third and
 * the vertical rhythm tightens with it. The proportions between the steps are
 * kept, which is what stops this reading as "the public site, shrunk".
 *
 * Public scale   17 → 18 → 22 → 40 → 110      article h1 76
 * Internal       15 → 16 → 17 → 22 →  44      article h1 34
 * ------------------------------------------------------------------------- */

/* Index head: a title and a field, not a cover. */
body.internal .hero-index { padding: 44px 48px 0; }
body.internal .hero-index h1 {
  font-size: 44px; letter-spacing: -0.025em; line-height: 1.05; max-width: none;
}
body.internal .hero-index .lead {
  font-size: 16px; margin: 14px 0 0; max-width: 62ch;
}

/* Search leads the page. Full width of the measure, and the only one on screen —
   the header's copy is suppressed here rather than shown twice. */
body.internal .search-lead {
  margin-top: 28px; max-width: 640px; border-width: 3px;
}
body.internal .search-lead input { width: 100%; font-size: 16px; padding: 14px 16px; }
body.internal .search-lead button { font-size: 18px; padding: 0 18px; }

/* Sections: the 3px rule stays — it is the one piece of structure on the index —
   but the titles come down to something you scan rather than read. */
body.internal .section { margin: 40px 48px 0; }
body.internal .section-head { padding-top: 12px; }
body.internal .section-title { font-size: 22px; letter-spacing: -0.015em; }
body.internal .subsection-title { font-size: 15px; }
body.internal .page-list { margin: 6px 0 0; }
body.internal .page-list a { font-size: 17px; gap: 10px; padding: 3px 0; }

/* Article: a reference document. The oversized display h1 becomes a heading. */
body.internal .article-head { padding: 20px 0 0; }
body.internal .article-head h1 {
  font-size: 34px; letter-spacing: -0.02em; line-height: 1.1; margin: 18px 0 0;
}
body.internal .article-rule { margin-top: 22px; }
body.internal .article-body { padding: 26px 0 64px; gap: 44px; }

body.internal .prose { font-size: 16px; line-height: 1.6; }
body.internal .prose h1 { font-size: 26px; margin: 36px 0 14px; }
body.internal .prose h2 { font-size: 21px; padding-top: 12px; margin: 34px 0 14px; }
body.internal .prose h3 { font-size: 17px; margin: 22px 0 8px; }
body.internal .prose p { margin: 0 0 14px; }
body.internal .prose ul, body.internal .prose ol { margin: 0 0 20px; }
body.internal .prose .lead { font-size: 18px; margin: 0 0 20px; }

/* Tables are the point of half these pages, so they get the room: tighter cells,
   and the first column loses the mono treatment that was sized for a wider page. */
body.internal .prose table { font-size: 14.5px; }
body.internal .prose th { font-size: 12px; padding: 8px 12px 8px 0; letter-spacing: .04em; text-transform: uppercase; }
body.internal .prose td { padding: 9px 12px 9px 0; }
body.internal .prose td:first-child { font-family: inherit; font-size: 14.5px; font-weight: 600; }

/* Callouts carry a lot of the meaning here — keep them, tighten them. */
body.internal .prose blockquote, body.internal .callout { margin: 0 0 18px; }

/* Footer: no institutional logos, no Impressum — this is not a public face. */
.site-footer-internal { padding: 22px 48px; margin-top: 48px; }
.footer-mark {
  font-family: var(--mono); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; opacity: .75;
}
@media (max-width: 900px) {
  body.internal .hero-index { padding: 28px 20px 0; }
  body.internal .section { margin: 32px 20px 0; }
  .site-footer-internal { padding: 20px 20px; }
}

/* The account row sat on three different baselines: the email box was 20px tall,
   the Log out button 21 and the toggle 25, so centring the BOXES left the email
   3px above the button. Give the two text items one line-height and they centre
   onto the same line; the glyph keeps its own size. */
.site-account { line-height: 1; }
.site-whoami, .site-logout, .site-logout button, .site-admin { line-height: 1; }
.site-logout button { display: block; }

/* Into the admin, from the wiki you are in. Same weight as Log out — they are
   the two things you do to the session, not navigation. */
.site-admin {
  font-size: 12px; font-weight: 600;
  border-bottom: 1px solid transparent; transition: border-color .12s ease;
}
.site-admin:hover { border-bottom-color: currentColor; }

/* --- One bar across all three surfaces ------------------------------------ *
 * The public site, the internal wiki, the admin and the editor each had their
 * own header, and they only looked alike: measured, the bars stood 83 / 78 / 77
 * tall and the wordmark sat at y 28 / 25 / 25, so it jumped three pixels as you
 * moved between them. The cause was that each bar was sized by whatever its
 * tallest control happened to be — a 41px search field here, a 25px toggle there.
 *
 * A stated height fixes both at once: the bar is the same everywhere and the logo
 * is centred in it. min-height rather than height so a bar that wraps on a narrow
 * window can still grow. The same three values are declared in admin.css, which
 * serves the admin and the editor — the two stylesheets never load together, so
 * each has to stand alone. Change one, change the other.
 * ------------------------------------------------------------------------- */
:root { --bar-h: 84px; --bar-pad-x: 48px; --bar-gap: 24px; }

.site-header {
  min-height: var(--bar-h);
  padding: 12px var(--bar-pad-x);
  gap: var(--bar-gap);
}
/* The wordmark never shrinks, on any of the three. */
.brand { flex: none; }
/* Utility text — who you are, the way out, the way in — is one size everywhere. */
.site-whoami, .site-admin, .site-logout button { font-size: 13px; }

@media (max-width: 900px) {
  :root { --bar-h: 72px; --bar-pad-x: 20px; }
  .site-header { padding: 10px var(--bar-pad-x); }
}
