/* ==========================================================================
   denied.systems — single site stylesheet
   Built against docs/CONTRACT.md §5 (markup) and §7 (tokens).
   No imports, no external fonts, no images. One file for every page.
   ========================================================================== */

/* ---- 1. Tokens (CONTRACT §7) -------------------------------------------- */

:root {
  --bg: #070706;        /* page background — true warm black   */
  --surface: #0d0c0a;   /* panels, code blocks                 */
  --surface-2: #15120e; /* hover, inline code                  */
  --line: #29241e;      /* hairline borders, warm              */
  --text: #d6cfc4;      /* body text, warm gray                */
  --dim: #948b7e;       /* secondary text                      */
  --faint: #5f584e;     /* metadata, hints                     */
  --accent: #d4643a;    /* burnt signal orange — links, brand  */
  --accent-hi: #e58753; /* lighter orange — hovers, code kw    */
  --accent-2: #8296ad;  /* cool steel counterpoint             */
  /* tag palette, in assignment order: */
  --tag-1: #d4643a;  --tag-2: #8296ad;  --tag-3: #96ad82;
  --tag-4: #c8a565;  --tag-5: #a08cb0;  --tag-6: #948b7e;
  --mono: ui-monospace, "DejaVu Sans Mono", "Liberation Mono", Menlo, Consolas, monospace;
  /* body reading font — mono stays for chrome/headings/code (set explicitly).
     System sans stack, no web fonts (zero external requests). */
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  /* faint blueprint grid, used behind the landing hero and the graph */
  --grid: linear-gradient(rgba(214, 207, 196, .035) 1px, transparent 1px),
          linear-gradient(90deg, rgba(214, 207, 196, .035) 1px, transparent 1px);
}

/* ---- 2. Reset / base ----------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  color-scheme: dark;
  /* reserve the scrollbar's width on both sides so the header's left (brand)
     and right (nav) keep equal margins whether or not a scrollbar is present */
  scrollbar-gutter: stable both-edges;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);   /* monospace everywhere by default (chrome, landing, wiki) */
  font-size: 15.5px;
  line-height: 1.7;
}

[hidden] { display: none !important; }

img, svg { max-width: 100%; }

a { color: var(--accent); text-decoration: none; }

:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}

::selection { background: rgba(212, 100, 58, .3); }

/* ---- 3. Shared header ----------------------------------------------------- */

/* Constant-height bar on every page: min-height governs (tallest row content
   is the search input, ≈2.17rem outer worst case — see the height budget in
   the §4 comment above #search-in, the single source of truth; only ~4.3px
   headroom remains under 3.3rem minus the .8rem vertical padding), so pages
   with and without the search widget render the same header height. Wrapped
   rows on narrow screens may grow taller — that is expected. */
.site-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .35rem 1.4rem;
  min-height: 3.3rem;
  padding: .4rem 1.25rem;
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: .85rem;
  /* solid, so the blueprint grid on landing/wiki never shows behind the
     header — the header reads all-black on every page, like the graph view */
  background: var(--bg);
}

.brand {
  color: var(--text);
  font-weight: 600;
  letter-spacing: .01em;
  white-space: nowrap;
}
.brand:hover { color: var(--text); }  /* brand text never shifts color; only .brand-tld is orange */
.brand-tld { color: var(--accent); font-weight: 400; }

.site-nav {
  margin-left: auto;
  display: flex;
  gap: 1.1rem;
}
.site-nav a { color: var(--dim); }
.site-nav a:hover { color: var(--text); }
/* current view (wiki link on wiki.html, graph link on graph.html) — placed
   after :hover so the active link stays accent-colored under the cursor */
.site-nav a.active { color: var(--accent); }

/* ---- 4. Search (header widget on wiki + graph) --------------------------- */

.search { position: relative; }

/* compact: the input inherits body line-height 1.7, so worst-case outer
   height ≈ .85rem×1.7 text + 2×.3rem padding + 2×1px border ≈ 2.17rem
   (~34.7px) — with the header's own padding that needs ~48.5px, inside the
   3.3rem (52.8px) .site-head min-height with only ~4.3px headroom. Recheck
   before adding vertical padding/border here or in .site-head. */
#search-in {
  width: 15rem;
  max-width: 100%;
  padding: .3rem .7rem;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 5px;
  font-family: var(--mono);
  font-size: .85rem;
}
#search-in::placeholder { color: var(--faint); }
#search-in:focus {
  outline: none;
  border-color: var(--accent);
}
/* kill UA extras on type=search */
#search-in::-webkit-search-decoration,
#search-in::-webkit-search-cancel-button { -webkit-appearance: none; }

.search-out {
  position: absolute;
  top: calc(100% + .4rem);
  right: 0;
  z-index: 50;
  width: 24rem;
  max-width: calc(100vw - 2rem);
  max-height: 60vh;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .55);
}

/* Result rows — structural selectors so any sane markup from js/search.js
   (divs or anchors, one element per result) gets styled. */
.search-out > * {
  display: block;
  padding: .55rem .8rem;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
  color: var(--text);
}
.search-out > *:last-child { border-bottom: 0; }
.search-out > *:hover { background: var(--surface-2); }
.search-out > .sel,
.search-out > *.sel {
  background: var(--surface-2);
  box-shadow: inset 2px 0 0 var(--accent);
}
/* first inline element in a row = title, following = summary */
.search-out > * > :first-child {
  display: block;
  font-family: var(--mono);
  font-size: .85rem;
  color: var(--text);
}
.search-out > .sel > :first-child { color: var(--accent); }
.search-out > * > :not(:first-child) {
  display: block;
  font-family: var(--sans);   /* preview text reads in the proportional font */
  font-size: .82rem;
  color: var(--dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* no empty-state rules: js/search.js hides the dropdown (`hidden`) when the
   result list is empty and never emits a "no results" row — if it ever does,
   style it here as a muted, cursor:default `.search-out > *` variant. */

/* ---- 5. Landing page ------------------------------------------------------ */

/* faint blueprint grid behind the content of the landing and wiki pages
   (the .site-head paints solid over it, so the grid only reads below the
   header — the same look as the graph view's .graph-wrap) */
body.page-landing,
body.page-wiki {
  background-image: var(--grid);
  background-size: 52px 52px;
}

.landing {
  max-width: 42rem;
  margin: 0 auto;
  padding: 4.5rem 1.25rem 5rem;
}

/* hero kicker spacing lives in §7, AFTER the shared kicker group — an
   override here would lose to the group's later `.landing .kicker` at equal
   specificity (0,2,0). */

.site-title {
  margin: 0;
  font-family: var(--mono);
  font-size: clamp(2.1rem, 7vw, 3.1rem);
  font-weight: 600;
  letter-spacing: -.02em;
  line-height: 1.15;
  color: var(--text);
}
/* the terminal dot, same gesture as the brand mark */
.site-title .dot { color: var(--accent); }

.site-sub {
  margin: 1rem 0 0;
  font-family: var(--mono);
  font-size: .95rem;
  line-height: 1.75;
  color: var(--dim);
  max-width: 36rem;
}

.cta-row {
  margin: 1.6rem 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: .7rem;
}

.cta-row .cta {
  font-family: var(--mono);
  font-size: .85rem;
  color: var(--accent);
  border: 1px solid var(--line);
  border-bottom: 1px solid var(--line); /* override .landing a underline */
  border-radius: 4px;
  padding: .5rem .9rem;
  background: var(--surface);
}

.cta-row .cta:hover {
  border-color: var(--accent);
  background: var(--surface-2);
}

.landing section { margin-top: 3rem; }

.landing h2 {
  font-family: var(--mono);
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--text);
  margin: 0 0 .7rem;
  padding-bottom: .35rem;
  border-bottom: 1px solid var(--line);
}

.landing p { color: var(--text); }
.landing a { border-bottom: 1px solid rgba(212, 100, 58, .45); }
.landing a:hover { border-bottom-color: var(--accent); }

/* ---- 6. Topic page layout ------------------------------------------------- */

.layout {
  display: flex;
  gap: 2.75rem;
  max-width: 70rem;
  margin: 0 auto;
  padding: 2rem 1.25rem 4rem;
}

/* -- sidebar (rendered by js/app.js; empty on fetch failure → collapses) -- */

.sidebar {
  flex: 0 0 15rem;
  align-self: flex-start;
  position: sticky;
  top: 1.25rem;
  max-height: calc(100dvh - 2.5rem);
  overflow-y: auto;
  font-family: var(--mono);
  font-size: .8rem;
  scrollbar-width: thin;
}
/* The sidebar reserves its width while empty so its async JS fill does not
   shift the article rightward on load (that was a visible flash). js/app.js
   sets the `hidden` attribute if the fetch fails or returns nothing, which
   collapses it. Deliberate trade: with JS fully disabled the fill never
   happens and an empty gutter remains — acceptable, the article is still
   fully readable and the far more common enhanced path no longer jumps. */

.sidebar input.sidebar-filter {
  width: 100%;
  margin-bottom: 1rem;
  padding: .3rem .6rem;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 4px;
  font-family: var(--mono);
  font-size: .8rem;
}
.sidebar input.sidebar-filter::placeholder { color: var(--faint); }
.sidebar input.sidebar-filter:focus { outline: none; border-color: var(--accent); }

.sidebar h2, .sidebar h3, .sidebar .tag-head {
  margin: 1.1rem 0 .35rem;
  font-size: .7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--faint);
  border: 0;
  padding: 0;
}

.sidebar ul {
  list-style: none;
  margin: 0 0 .6rem;
  padding: 0;
}
.sidebar li { margin: 0; }

.sidebar a {
  display: block;
  padding: .22rem .5rem;
  margin-left: -.5rem;
  border-radius: 4px;
  color: var(--dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sidebar a:hover { color: var(--text); background: var(--surface); }
.sidebar a.current, .sidebar .current > a {
  color: var(--accent);
  background: var(--surface);
  box-shadow: inset 2px 0 0 var(--accent);
}

/* -- article column -- */

.prose {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 42rem;
  /* the NOTE text itself reads in the proportional font; headings, code,
     kicker, title and meta inside .prose set mono explicitly and win */
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.65;
}

/* ---- 7. Prose furniture: kicker / title / lede / meta / tags -------------- */

/* `.landing .kicker` (0,2,0) is grouped in so the accent survives ancestor
   paragraph color rules like `.landing p` (0,1,1), which outrank a bare
   `.kicker` (0,1,0). Topic pages (.prose) and wiki (.wiki-index) have no such
   competing p color rule, but p.kicker (0,1,1) guards them anyway. */
.kicker,
p.kicker,
.landing .kicker {
  margin: 0 0 .6rem;
  font-family: var(--mono);
  font-size: .78rem;
  font-weight: 500;
  text-transform: lowercase;
  letter-spacing: .06em;
  color: var(--accent);
}
/* the // sigil and any / separators sit in text color; the words stay orange */
.kicker::before { content: "// "; color: var(--text); }
.kicker .sep { color: var(--text); }

/* per-view kicker spacing — must sit AFTER the group above: same specificity
   (0,2,0) as `.landing .kicker`, so only source order lets these win over the
   group's .6rem default (`.wiki-index .kicker` in §11 relies on the same).
   `.prose .kicker` is the third per-view case: it restates the group's .6rem
   at (0,2,0) because the later `.prose p` in §8 (0,1,1) would otherwise beat
   `p.kicker` (0,1,1) on source order and stretch the gap to 1.1rem. */
.landing > .kicker { margin-bottom: 1rem; }
.prose .kicker { margin-bottom: .6rem; }

.page-title {
  margin: 0 0 .6rem;
  font-family: var(--mono);
  font-size: clamp(1.5rem, 4.5vw, 2.1rem);
  font-weight: 600;
  letter-spacing: -.015em;
  line-height: 1.2;
  color: var(--text);
}

.lede {
  margin: 0 0 1rem;
  font-size: 1.08rem;
  line-height: 1.55;
  color: var(--dim);
}

.page-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .45rem .6rem;
  margin: 0 0 2.25rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: .75rem;
  color: var(--faint);
}

/* tag chips — neutral chip, calm per-tag dot from the palette */
.tag {
  display: inline-flex;
  align-items: center;
  gap: .4em;
  padding: .12rem .55rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--dim);
  font-family: var(--mono);
  font-size: .72rem;
  white-space: nowrap;
}
.tag::before {
  content: "";
  width: .45em;
  height: .45em;
  border-radius: 50%;
  background: var(--faint);
  flex: none;
}
.tag:nth-of-type(6n+1)::before { background: var(--tag-1); }
.tag:nth-of-type(6n+2)::before { background: var(--tag-2); }
.tag:nth-of-type(6n+3)::before { background: var(--tag-3); }
.tag:nth-of-type(6n+4)::before { background: var(--tag-4); }
.tag:nth-of-type(6n+5)::before { background: var(--tag-5); }
.tag:nth-of-type(6n+6)::before { background: var(--tag-6); }

/* ---- 8. Prose typography (pandoc body output) ------------------------------ */

.prose h2, .prose h3, .prose h4 {
  font-family: var(--mono);
  font-weight: 600;
  color: var(--text);
  line-height: 1.3;
  margin: 2.4rem 0 .8rem;
}
.prose h2 {
  font-size: 1.3rem;
  padding-bottom: .35rem;
  border-bottom: 1px solid var(--line);
}
.prose h3 { font-size: 1.05rem; }
.prose h4 {
  font-size: .85rem;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--dim);
}

.prose p { margin: 0 0 1.1rem; }

.prose a {
  border-bottom: 1px solid rgba(212, 100, 58, .45);
}
.prose a:hover { border-bottom-color: var(--accent); }

.prose ul, .prose ol {
  margin: 0 0 1.1rem;
  padding-left: 1.5rem;
}
.prose li { margin: .3rem 0; }
.prose li::marker { color: var(--faint); }

.prose blockquote {
  margin: 1.4rem 0;
  padding: .2rem 0 .2rem 1.1rem;
  border-left: 2px solid var(--accent);
  color: var(--dim);
}
.prose blockquote p:last-child { margin-bottom: 0; }

.prose hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 2.5rem 0;
}

.prose table {
  display: block;            /* enables horizontal scroll on narrow screens */
  max-width: 100%;
  overflow-x: auto;
  border-collapse: collapse;
  margin: 1.4rem 0;
  font-size: .9rem;
}
.prose th, .prose td {
  padding: .45rem .8rem;
  border: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}
.prose th {
  font-family: var(--mono);
  font-size: .72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--dim);
  background: var(--surface);
}
.prose tbody tr:nth-child(even) td { background: var(--surface); }
.prose caption {
  caption-side: bottom;
  padding-top: .5rem;
  font-family: var(--mono);
  font-size: .75rem;
  color: var(--faint);
  text-align: center;
}

.prose figure { margin: 1.6rem 0; text-align: center; }
.prose figure img { max-width: 100%; height: auto; }
.prose figcaption {
  margin-top: .5rem;
  font-family: var(--mono);
  font-size: .75rem;
  color: var(--faint);
}

/* wikilinks (from filters/wikilinks.lua) */
a.wikilink {
  border-bottom: 1px solid rgba(212, 100, 58, .45);
}
a.wikilink:hover { border-bottom-color: var(--accent); }
.wikilink.broken {
  color: var(--dim);
  border-bottom: 1px dashed var(--faint);
  cursor: help;
}

/* ---- 9. Code — inline, blocks, skylighting tokens -------------------------- */

code, kbd, samp {
  font-family: var(--mono);
  font-size: .875em;
}

/* inline code (not inside a pre) */
:not(pre) > code {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: .08em .35em;
  color: var(--text);
  overflow-wrap: break-word;
}

pre {
  margin: 1.4rem 0;
  padding: .9rem 1.1rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  overflow-x: auto;
  line-height: 1.55;
  font-size: .85rem;
}
pre code { background: none; border: 0; padding: 0; font-size: inherit; }

/* pandoc skylighting wrapper: div.sourceCode > pre > code.sourceCode */
div.sourceCode {
  margin: 1.4rem 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  overflow: hidden;
}
div.sourceCode pre {
  margin: 0;
  border: 0;
  border-radius: 0;
  background: none;
}
code.sourceCode > span { display: inline; }

/* numbered listings (pandoc pre.numberSource) */
pre.numberSource { padding-left: 2.8rem; position: relative; }
pre.numberSource code > span { position: relative; }
pre.numberSource code > span > a:first-child::before {
  content: counter(source-line);
  position: absolute;
  left: -2.2rem;
  width: 1.6rem;
  text-align: right;
  color: var(--faint);
  -webkit-user-select: none;
  user-select: none;
}
pre.numberSource code > span { counter-increment: source-line; }
pre.numberSource code { counter-reset: source-line 0; }

/* token scheme — dark, harmonious with §7 (signal-orange keywords, sage
   strings, steel types, muted italic comments) */
code span.kw { color: var(--accent-hi); }                     /* keyword    */
code span.cf { color: var(--accent-hi); font-weight: 600; }   /* ctrl flow  */
code span.im { color: var(--accent-hi); font-style: italic; } /* import     */
code span.dt { color: var(--accent-2); }                  /* data type      */
code span.dv { color: var(--tag-4); }                     /* decimal value  */
code span.bn { color: var(--tag-4); }                     /* base-N value   */
code span.fl { color: var(--tag-4); }                     /* float          */
code span.st { color: var(--tag-3); }                     /* string         */
code span.sc { color: var(--tag-3); font-weight: 600; }   /* special char   */
code span.ss { color: var(--tag-3); font-style: italic; } /* special string */
code span.vs { color: var(--tag-3); }                     /* verbatim str   */
code span.ch { color: var(--tag-3); }                     /* char           */
code span.co { color: var(--faint); font-style: italic; }  /* comment  */
code span.do { color: var(--faint); font-style: italic; }  /* doc      */
code span.an { color: var(--faint); font-style: italic; font-weight: 600; } /* annotation */
code span.cv { color: var(--faint); font-style: italic; }  /* commentVar */
code span.in { color: var(--faint); font-style: italic; }  /* information */
code span.fu { color: var(--tag-5); }                     /* function       */
code span.op { color: var(--dim); }                       /* operator       */
code span.va { color: var(--text); }                      /* variable       */
code span.at { color: var(--tag-6); }                     /* attribute      */
code span.ot { color: var(--tag-6); }                     /* other          */
code span.pp { color: var(--tag-4); font-style: italic; } /* preprocessor   */
code span.cn { color: var(--tag-4); }                     /* constant       */
code span.bu { color: var(--accent-2); }                  /* builtin        */
code span.ex { color: var(--accent-2); }                  /* extension      */
code span.al { color: var(--accent); font-weight: 600; }   /* alert         */
code span.er { color: var(--accent); text-decoration: underline wavy; } /* error */
code span.wa { color: var(--accent-hi); font-style: italic; } /* warning    */

/* ---- 10. Math (native MathML via pandoc --mathml) --------------------------- */

math {
  font-family: "Latin Modern Math", "STIX Two Math", "Cambria Math", math, serif;
  color: var(--text);
}
/* inline math sits in the text flow, slightly larger for legibility */
math { font-size: 1.06em; }

math[display="block"] {
  display: block math;
  margin: 1.5rem auto;
  font-size: 1.15em;
  text-align: center;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  padding: .15rem 0;
}

/* ---- 11. Wiki index page ---------------------------------------------------- */

.wiki-index {
  max-width: 52rem;
  margin: 0 auto;
  padding: 2.25rem 1.25rem 4.5rem;
}

/* wiki.html wraps the listing in the same .layout as topic pages (sidebar +
   content); the layout provides padding and width, the listing just flexes */
.layout > .wiki-index {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 46rem;
  margin: 0;
  padding: 0;
}

.wiki-index .kicker { margin-bottom: 1.6rem; }

.tag-group { margin: 0 0 2.4rem; }

.tag-head {
  margin: 0 0 .2rem;
  padding-bottom: .35rem;
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: .8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--dim);
}

.page-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.page-list li { border-bottom: 1px solid var(--line); }
.page-list li:last-child { border-bottom: 0; }

.page-list a {
  display: block;
  padding: .8rem .6rem;
  margin: 0 -.6rem;
  border-radius: 5px;
  color: var(--text);
}
.page-list a:hover { background: var(--surface); }

.page-list .pl-title {
  display: block;
  font-family: var(--mono);
  font-size: .95rem;
  font-weight: 600;
  color: var(--text);
}
.page-list a:hover .pl-title { color: var(--accent); }

.page-list .pl-summary {
  display: block;
  margin-top: .2rem;
  font-family: var(--sans);   /* preview text reads in the proportional font */
  font-size: .95rem;
  line-height: 1.5;
  color: var(--dim);
}

.page-list .pl-meta {
  display: block;
  margin-top: .3rem;
  font-family: var(--mono);
  font-size: .72rem;
  color: var(--faint);
}

/* ---- 12. Graph page ---------------------------------------------------------- */

body.page-graph {
  height: 100dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
body.page-graph .site-head { flex: none; }

.graph-wrap {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  /* fallback sizing if the body.page-graph flex context is absent;
     inside the flex column this is just the basis and flexes to fit */
  height: calc(100dvh - 4rem);
  background: var(--bg);
  background-image: var(--grid);
  background-size: 52px 52px;
}

svg#graph {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  cursor: grab;
}
svg#graph:active { cursor: grabbing; }

#graph text {
  font-family: var(--mono);
  font-size: 11px;
  fill: var(--dim);
  pointer-events: none;
  user-select: none;
}

.graph-legend {
  position: absolute;
  left: 1rem;
  bottom: 3rem;
  z-index: 10;
  display: flex;
  flex-direction: column;
  gap: .25rem;
  padding: .6rem .8rem;
  background: rgba(13, 12, 10, .9);
  border: 1px solid var(--line);
  border-radius: 6px;
  font-family: var(--mono);
  font-size: .72rem;
  color: var(--dim);
  max-width: 14rem;
  pointer-events: none;
}
.graph-legend:empty { display: none; }
.graph-legend > * {
  display: flex;
  align-items: center;
  gap: .5em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* color swatch emitted by js/graph.js — needs explicit size, the span is
   otherwise empty (background-only) and would render at zero size */
.graph-legend .legend-dot {
  width: .6em;
  height: .6em;
  border-radius: 50%;
  flex: none;
}

/* graceful-degradation message emitted by js/graph.js when d3/fetch/graph.json
   are unavailable */
.graph-error {
  position: absolute;
  inset: 40% 1rem auto;
  z-index: 10;
  margin: 0;
  text-align: center;
  font-family: var(--mono);
  font-size: .85rem;
  color: var(--dim);
}

.graph-hint {
  position: absolute;
  left: 0;
  right: 0;
  bottom: .6rem;
  z-index: 10;
  margin: 0;
  text-align: center;
  font-family: var(--mono);
  font-size: .72rem;
  color: var(--faint);
  pointer-events: none;
}

/* ---- 13. Footer --------------------------------------------------------------- */

.page-foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: .4rem 1.5rem;
  max-width: 70rem;
  margin: 0 auto;
  padding: 1.1rem 1.25rem 1.6rem;
  border-top: 1px solid var(--line);
  font-family: var(--mono);
  font-size: .75rem;
  color: var(--faint);
}
.page-foot a { color: var(--dim); }
.page-foot a:hover { color: var(--accent); }

/* ---- 14. Assistant stub (hidden in v1; keep a sane shape if ever unhidden) ---- */

.assistant-panel {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 60;
  width: 22rem;
  max-width: calc(100vw - 2rem);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 1rem;
}

/* ---- 15. Mobile nav: hamburger + off-canvas drawer (js/nav.js) ---------------- */
/* Desktop: none of this shows — search sits in the header, the sidebar is a
   fixed column. js/nav.js injects the button + drawer and, at ≤900px, moves
   the page's .search and #sidebar into the drawer. */
.nav-toggle { display: none; }
.drawer, .nav-backdrop { display: none; }

/* ---- 16. Responsive ------------------------------------------------------------ */

@media (max-width: 900px) {
  .layout { display: block; padding-top: 1.5rem; }
  .sidebar { display: none; }           /* sidebar collapses out of the column… */
  .prose { max-width: 46rem; margin: 0 auto; }

  /* tighter column gap on mobile so the hamburger sits close to the brand
     (the wiki/graph links are pushed right by .site-nav's margin-left:auto) */
  .site-head { gap: .35rem .55rem; }

  /* hamburger, left of the brand */
  .nav-toggle {
    order: -1;                           /* precede the brand regardless of DOM order */
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
    width: 1.55rem;
    height: 1.9rem;
    margin: 0;
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
  }
  .nav-toggle span {
    display: block;
    width: 1.15rem;
    height: 1.5px;
    background: var(--text);
    transition: transform .2s ease, opacity .2s ease;
  }
  /* hamburger morphs into an × while the drawer is open */
  body.nav-open .nav-toggle span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
  body.nav-open .nav-toggle span:nth-child(2) { opacity: 0; }
  body.nav-open .nav-toggle span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }

  /* search is hidden while still in the header (pre-JS / no-JS); once nav.js
     moves it into the drawer, `.drawer .search` (below) shows it again */
  .site-head .search { display: none; }

  .nav-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 90;
    background: rgba(0, 0, 0, .55);
    opacity: 0;
    pointer-events: none;
    transition: opacity .2s ease;
  }
  body.nav-open .nav-backdrop { opacity: 1; pointer-events: auto; }
  body.nav-open { overflow: hidden; }    /* lock the page behind the drawer */

  .drawer {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    z-index: 100;
    width: 82%;
    max-width: 20rem;
    height: 100dvh;
    padding: 1.15rem 1.15rem calc(1.15rem + env(safe-area-inset-bottom));
    background: var(--bg);
    border-right: 1px solid var(--line);
    overflow-y: auto;
    transform: translateX(-100%);
    transition: transform .22s ease;
    -webkit-overflow-scrolling: touch;
  }
  body.nav-open .drawer { transform: translateX(0); }

  /* …but it IS shown when nav.js has moved it into the drawer; the relocated
     nodes drop their desktop positioning here */
  .drawer .search { display: block; position: relative; margin: 0 0 1rem; }
  .drawer #search-in { width: 100%; }
  .drawer .search-out {
    position: static;
    width: auto;
    max-width: none;
    margin-top: .5rem;
    box-shadow: none;
  }
  .drawer .sidebar {
    display: block;
    position: static;
    flex: none;
    width: auto;
    max-height: none;
    top: auto;
    overflow: visible;
  }
  /* when the drawer already has the full search box, the sidebar's own
     "filter pages…" input is redundant — hide it. On topic pages there is no
     .search in the drawer, so the filter (their only search) stays. */
  .drawer .search ~ .sidebar .sidebar-filter { display: none; }
}

@media (max-width: 640px) {
  body { font-size: 16px; }
  .landing { padding-top: 2.25rem; }
  /* the gesture hint names desktop actions (scroll/drag/click) and wrapped
     onto the legend on phones — drop it on touch-width screens; the legend
     sits alone at the bottom-left */
  .graph-hint { display: none; }
  .graph-legend { left: .6rem; bottom: 1rem; padding: .45rem .6rem; }
  pre { font-size: .8rem; }
}

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