/* ============================================================
   Swiftpoint Hub — Guides & Docs viewer (Obsidian-style).

   Three zones: explorer | article | outline rail.

   Tokens, the reset and the site header come from the shared
   layers (tokens.css, base.css, hdr.css), loaded before this
   file. Only the guide chrome belongs here.
   ============================================================ */

/* Page-specific layout metrics for the three-column guide view.
   Scoped to .d-body (set by routes/docs.js) so they can never
   leak into the rest of the site. --measure comes from tokens. */
/* The guides' own metrics. Sticky offsets use --hdr-total-h, which
   css/hdr.css sets from the real header height (row 1 + tool strip);
   the var(--hdr-h) fallback keeps this sane before js/docs.js measures. */
.d-body {
  --exp-w: 274px;
  --rail-w: 236px;
}

body.d-body {
  background-color: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* ---------- header ---------- */
/* No header overrides here on purpose: css/hdr.css loads after this file
   and owns the header, so the guides view gets the same header as every
   other page. The guide's own tools arrive as the header's second row. */

/* ---------- top action bar ----------
   The guide's tools are the shared header's second row (`.tbar`),
   rendered from routes/docs.js and written in the shared `.tbar-*`
   vocabulary — the same search field, the same buttons and the same
   three-zone layout the builder, the OLED editor and the checker use.
   There is nothing left to style here, and nothing here may set
   position, height, border or width: the strip owns all of that. */

/* ---------- three-zone layout ----------
   As wide as the browser allows. A reference guide is read by scanning
   for a heading and then reading three paragraphs under it; at 1240px
   the middle column was holding a 68ch measure inside a column that was
   already 260px wider than that, so the extra width was spent on empty
   space either side of the text rather than on more text. The measure
   went up with it (--measure in tokens.css) and the padding came down,
   which is the other half: a wide column with 54px of padding on each
   side reads as two columns. */
.d-layout {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: var(--exp-w) minmax(0, 1fr) var(--rail-w);
  align-items: start;
  max-width: 1800px;
  margin: 0 auto;
  min-height: 72vh;
}

/* --- explorer --- */
.d-explorer {
  position: sticky; top: var(--hdr-total-h);
  align-self: start;
  max-height: calc(100vh - var(--hdr-total-h));
  overflow: auto;
  border-right: 1px solid var(--line);
  scrollbar-width: thin;
}
.d-explorer-in { padding: 20px 18px 40px; }

.d-tags { display: flex; flex-wrap: wrap; gap: 6px; padding-bottom: 16px; border-bottom: 1px solid var(--line); margin-bottom: 14px; }
/* What sits above the tree when a tag is filtering it: which one, and
   the way back to everything. It replaces the second copy of the site's
   tag list that used to be here. */
.d-tagfilter {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
  padding: 8px 10px 8px 12px;
  border: 1px solid rgba(198, 245, 66, 0.35);
  border-radius: var(--r-md);
  background: var(--lime-dim);
  font-size: 12.5px;
  color: var(--text-dim);
}
.d-tagfilter b { color: var(--lime); font-family: var(--font-mono); font-weight: 600; }
.d-tagfilter-x {
  flex: 0 0 auto;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text-dim);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  padding: 2px 9px;
  transition: color .15s, border-color .15s;
}
.d-tagfilter-x:hover { color: var(--text); border-color: var(--line-strong); }

/* A guide the filter excluded.
   This rule is the whole reason the search and the tag filter ever
   appeared to do nothing: the script has been setting `is-miss` on
   every non-matching guide since the day it was written, and this is
   the first time anything was styled to react to it. */
.d-tree-file.is-miss { display: none; }
.d-tree-dir.is-empty > .d-tree-dir-h { opacity: .45; }

/* tree */
.d-tree ul { list-style: none; margin: 0; padding: 0; }
.d-tree-root > li { margin-bottom: 2px; }

.d-tree-dir-h {
  display: flex; align-items: center; gap: 8px;
  width: 100%;
  padding: 6px 8px;
  background: none; border: 0; border-radius: 9px;
  color: var(--text-dim); font-size: 12.5px; font-weight: 700; letter-spacing: 0.02em;
  font-family: var(--font-display);
  text-transform: uppercase;
  transition: color .15s, background .15s;
}
.d-tree-dir-h:hover { color: var(--text); background: var(--surface); }
.d-tree-caret {
  flex: none;
  width: 7px; height: 7px;
  border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
  transform: rotate(-45deg);
  transition: transform .18s var(--ease);
  opacity: .7;
}
.d-tree-dir-h.is-open .d-tree-caret { transform: rotate(45deg); }
/* The rail of a nested section.
   The list indents but drew no lines, so depth was something you had to
   count rather than see. `tree` draws a `│` down the left of everything
   below a folder and a short tick into each child; that is all this is,
   and it is what makes a section's contents legible as belonging to it
   once there are folders inside folders. */
.d-tree-dir-b {
  overflow: hidden;
  margin-left: 11px;
  padding-left: 10px;
  border-left: 1px solid var(--line);
}
.d-tree-dir-b.is-collapsed { display: none; }
.d-tree-dir-b > li:last-child > .d-tree-dir-h,
.d-tree-dir-b > li:last-child > .d-tree-file > .d-tree-file-a { border-bottom-left-radius: 9px; }
.d-tree-dir-b > li > .d-tree-file > .d-tree-file-a::before {
  content: '';
  flex: none;
  width: 9px;
  height: 1px;
  margin-left: -2px;
  background: var(--line-strong);
}

/* The position in the section, where every row used to carry a
   decorative square. It took the square's place rather than being
   added next to it: the list is the one column in this layout that has
   to stay narrow, and a second marker would have cost width in the only
   place that cannot afford any. Monospaced and right-aligned, so two
   digits and one digit line up and the eye can count down the column
   instead of re-reading the titles. */
.d-tree-n {
  flex: none;
  width: 18px;
  margin-left: -2px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  line-height: 1;
  text-align: right;
  color: var(--text-faint);
  opacity: .75;
  transition: color .15s, opacity .15s;
}
.d-tree-file-a:hover .d-tree-n { opacity: 1; }
.d-tree-file.is-active .d-tree-n { color: var(--lime); opacity: 1; }

/* How many guides a section holds. Right-aligned and quiet, so a folder
   list can be planned from — which section is the long one — without
   opening every row of it. */
.d-tree-count {
  margin-left: auto;
  padding-left: 8px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 500;
  color: var(--text-faint);
  opacity: .7;
}
.d-tree-dir-h:hover .d-tree-count,
.d-tree-dir-h.is-open .d-tree-count { opacity: 1; }

.d-tree-file-a {
  position: relative;
  display: flex; align-items: center; gap: 8px;
  padding: 5.5px 8px;
  border-radius: 9px;
  font-size: 13.5px; font-weight: 500;
  color: var(--text-dim);
  transition: color .15s, background .15s;
}
.d-tree-file-a:hover { color: var(--text); background: var(--surface); }
.d-tree-file-a:hover::before { background: var(--line-strong); }
.d-tree-file.is-active .d-tree-file-a {
  color: var(--lime);
  background: var(--lime-dim);
}

.d-nomatches { padding: 10px 8px; font-size: 13px; color: var(--text-faint); }

/* A guide that is closed to the public.
   Only rendered to somebody who may read it — a closed guide is not in
   the tree at all otherwise — so the padlock tells the public nothing and
   saves a staff member a click to find out what a section is. Glyph
   rather than a drawn padlock so it matches the one in the admin tree,
   and grey rather than lime so an eye scanning the list of titles does
   not stop on it. */
.d-tree-lock {
  display: inline-flex;
  margin-left: 6px;
  vertical-align: -1px;
}
.d-tree-lock-ic {
  display: block;
  font-size: 10px;
  line-height: 1;
  opacity: .55;
  filter: grayscale(1);
}
/* The glyph itself. It has to be here: `content` only works on a
   pseudo-element, and the span in the markup is empty — a rule that sets
   a font size on an element with no content draws nothing, which is why
   this shipped as a closed guide with no sign of being closed on it. */
.d-tree-lock-ic::before { content: "\1F512"; }
.d-tree-file.is-active .d-tree-lock-ic,
.d-tree-dir-h:hover .d-tree-lock-ic { opacity: .9; }

/* --- main article --- */
.d-main {
  min-width: 0;
  /* Less padding than the column is wide. At 1800px the old clamp(20px,
     5vw, 54px) put 54px against both rails and made a 1500px column
     look like a 900px one with a lot of air in it. */
  padding: 34px clamp(18px, 2.2vw, 34px) 80px;
}

.d-crumb {
  display: flex; align-items: center; gap: 6px;
  margin-bottom: 26px;
  font-size: 12px; font-weight: 600; letter-spacing: 0.04em;
  color: var(--text-faint);
  text-transform: uppercase;
}
.d-crumb-home { color: var(--text-faint); transition: color .15s; }
.d-crumb-home:hover { color: var(--lime); }
.d-crumb-sep { opacity: .5; }
.d-crumb-name { color: var(--text-dim); }

.d-article { max-width: var(--measure); }

.d-article-hd {
  margin-bottom: 32px;
  padding-bottom: 26px;
  border-bottom: 1px solid var(--line);
}
.d-article-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 16px; }
.d-chip {
  font-family: var(--font-mono); font-size: 11.5px; font-weight: 500;
  color: var(--blue); background: var(--blue-dim);
  border: 1px solid rgba(121, 180, 255, 0.25); border-radius: var(--r-pill);
  padding: 3px 11px;
  transition: border-color .15s, background .15s, color .15s;
}
.d-chip:hover { color: var(--lime); background: var(--lime-dim); border-color: rgba(198, 245, 66, 0.35); }

.d-article-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(28px, 4vw, 40px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.12;
  color: var(--text);
}
.d-article-title::first-letter { color: var(--lime); }
.d-article-desc {
  margin: 14px 0 0;
  font-size: 16.5px; line-height: 1.55;
  color: var(--text-dim);
  max-width: 42em;
}
.d-article-meta {
  display: flex; flex-wrap: wrap; gap: 6px 18px;
  margin-top: 18px;
  font-size: 11.5px; color: var(--text-faint);
}
.d-article-meta-n { font-family: var(--font-mono); }
.d-article-fold { color: var(--text-dim); }

/* --- prose --- */
.d-prose { font-size: 15.5px; line-height: 1.78; color: var(--text); }
.d-prose > *:first-child { margin-top: 0; }
.d-prose p { margin: 1.05em 0; }
.d-prose h1, .d-prose h2, .d-prose h3, .d-prose h4 {
  font-family: var(--font-display);
  color: var(--text);
  line-height: 1.25;
  scroll-margin-top: calc(var(--hdr-total-h) + 22px);
}
/* A guide already prints its title as `.d-article-title` above the body,
   so an `# h1` in the body is a second one. It still has to look like it
   was meant to: the group above sets h1's family and colour, and without
   a size here it fell through to the browser's own h1 — Times-adjacent
   weight at 2em with 0.67em margins, wedged in under the real title. */
.d-prose h1 { font-size: clamp(24px, 2.8vw, 32px); font-weight: 700; letter-spacing: -0.015em; margin: 1.6em 0 0.6em; }
.d-prose h2 { font-size: clamp(20px, 2.3vw, 26px); font-weight: 700; letter-spacing: -0.01em; margin: 2em 0 0.7em; padding-bottom: 8px; border-bottom: 1px solid var(--line); }
.d-prose h2::before { content: ''; display: inline-block; width: 14px; height: 3px; background: var(--lime); border-radius: 3px; margin-right: 10px; vertical-align: 0.18em; }
.d-prose h3 { font-size: 19px; font-weight: 600; margin: 1.7em 0 0.6em; }
.d-prose h4 { font-size: 16px; font-weight: 600; margin: 1.5em 0 0.5em; color: var(--text-dim); }
.d-prose a { color: var(--lime); text-decoration: underline; text-decoration-color: rgba(198, 245, 66, 0.35); text-underline-offset: 3px; transition: text-decoration-color .15s, color .15s; }
.d-prose a:hover { color: var(--lime-hi); text-decoration-color: var(--lime-hi); }
.d-prose strong { color: var(--text); font-weight: 650; }
.d-prose em { color: var(--text); }
.d-prose code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 1.5px 6px;
  color: var(--lime);
}
.d-prose pre {
  position: relative;
  margin: 1.4em 0;
  background: var(--bg-raise);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 18px 18px 16px;
  overflow: auto;
  scrollbar-width: thin;
}
.d-prose pre code {
  display: block;
  background: none; border: 0; padding: 0;
  color: var(--text);
  font-size: 13px; line-height: 1.7;
}
.d-copy {
  position: absolute; top: 9px; right: 9px;
  font-size: 11px; font-weight: 600;
  color: var(--text-faint);
  background: var(--surface-2);
  border: 1px solid var(--line); border-radius: 7px;
  padding: 4px 10px;
  opacity: 0; transition: opacity .18s, color .15s, border-color .15s;
}
.d-prose pre:hover .d-copy { opacity: 1; }
.d-copy:hover, .d-copy.d-copied { color: var(--lime); border-color: rgba(198, 245, 66, 0.4); }
.d-copy.d-copied { opacity: 1; }

.d-prose blockquote {
  margin: 1.4em 0;
  padding: 14px 18px;
  border-left: 3px solid var(--lime);
  background: var(--lime-dim);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  color: var(--text-dim);
}
.d-prose blockquote p { margin: 0.4em 0; }
.d-prose blockquote > :first-child { margin-top: 0; }

.d-prose ul, .d-prose ol { margin: 1em 0; padding-left: 22px; }
.d-prose li { margin: 0.35em 0; }
.d-prose ul li::marker { color: var(--lime); }
.d-prose ol li::marker { color: var(--lime); font-family: var(--font-display); font-weight: 600; }

.d-prose table {
  width: 100%;
  margin: 1.4em 0;
  border-collapse: collapse;
  font-size: 13.5px;
  line-height: 1.55;
  border-radius: var(--r-sm);
  overflow: hidden;
}
.d-prose th {
  text-align: left;
  font-family: var(--font-display); font-size: 12px; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--text-dim);
  background: var(--surface);
  padding: 10px 14px;
  border-bottom: 1px solid var(--line-strong);
}
.d-prose td { padding: 10px 14px; border-bottom: 1px solid var(--line); vertical-align: top; }
.d-prose tr:hover td { background: rgba(255, 255, 255, 0.02); }
.d-prose td code { white-space: nowrap; }

.d-prose hr { margin: 2.6em 0; border: 0; border-top: 1px solid var(--line); }

/* images
   These three used to be one line, and the one line was
   `.d-prose img.d-prose img.d-zoom:hover` — a selector the browser
   throws away as invalid, so guides had no image styling at all. A
   3000px screenshot of a DPI value rendered at its natural width and
   pushed the whole page sideways on a phone. */
.d-prose img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 1.5em auto;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--bg-raise);
}
.d-prose img.d-zoom { cursor: zoom-in; transition: border-color .15s, transform .15s; }
.d-prose img.d-zoom:hover { border-color: rgba(198, 245, 66, 0.5); transform: translateY(-1px); }

/* --- callouts --- */
.d-callout {
  margin: 1.5em 0;
  border: 1px solid var(--line-strong);
  border-left-width: 3px;
  border-radius: var(--r-md);
  overflow: hidden;
}
.d-callout-ttl {
  display: flex; align-items: center; gap: 8px;
  padding: 11px 18px 9px;
  font-family: var(--font-display); font-weight: 700; font-size: 13px; letter-spacing: 0.02em;
}
.d-callout-ttl::before {
  content: '';
  width: 9px; height: 9px; border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 12px currentColor;
}
.d-callout-bd { padding: 2px 18px 14px; }
.d-callout-bd p { margin: 0.55em 0; }
.d-callout-bd > :first-child { margin-top: 0; }
.d-callout-bd > :last-child { margin-bottom: 0; }

.d-callout--note    { border-left-color: var(--lime);   background: var(--lime-dim);   } .d-callout--note .d-callout-ttl { color: var(--lime); }
.d-callout--info    { border-left-color: var(--blue);   background: var(--blue-dim);   } .d-callout--info .d-callout-ttl { color: var(--blue); }
.d-callout--tip     { border-left-color: var(--teal);   background: var(--teal-dim);   } .d-callout--tip .d-callout-ttl { color: var(--teal); }
.d-callout--success { border-left-color: var(--teal);   background: var(--teal-dim);   } .d-callout--success .d-callout-ttl { color: var(--teal); }
.d-callout--warning { border-left-color: var(--orange); background: var(--orange-dim); } .d-callout--warning .d-callout-ttl { color: var(--orange); }
.d-callout--danger  { border-left-color: var(--red);    background: var(--red-dim);    } .d-callout--danger .d-callout-ttl { color: var(--red); }
.d-callout--example { border-left-color: var(--violet); background: var(--violet-dim); } .d-callout--example .d-callout-ttl { color: var(--violet); }

/* --- changelog blocks (: : : added / changed / fixed / removed / security) ---
   The same five names the forum and the updates list use, in the same
   colours: a changelog written once should read the same wherever it is
   pasted, and a reader should only have to learn the palette once.

   Loud on purpose — the filled badge, the glyph and the solid bar are the
   whole reason these exist rather than a paragraph with a bold word in it. */
.d-entry {
  --e-colour: var(--text-3);
  --e-tint: var(--surface-2);
  margin: 1.4em 0;
  padding: 15px 18px 15px 20px;
  border: 1px solid color-mix(in srgb, var(--e-colour) 34%, transparent);
  border-left: 4px solid var(--e-colour);
  border-radius: var(--r-md);
  background: var(--e-tint);
}
.d-entry-hd {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 0 0 9px;
}
.d-entry-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1.5;
  /* The page background as the text colour: inverted, so the badge is
     the brightest thing in the block, in both themes. */
  color: var(--bg);
  background: var(--e-colour);
  border-radius: var(--r-pill);
  padding: 3px 11px;
  white-space: nowrap;
}
.d-entry-badge::before { content: var(--e-glyph, ''); font-size: 12px; line-height: 1; opacity: 0.85; }
.d-entry-title { font-family: var(--font-display); font-size: 15px; font-weight: 700; line-height: 1.3; color: var(--text); }
.d-entry-bd > *:last-child { margin-bottom: 0; }
.d-entry-added { --e-colour: var(--teal); --e-tint: var(--teal-dim); --e-glyph: '+'; }
.d-entry-changed { --e-colour: var(--blue); --e-tint: var(--blue-dim); --e-glyph: '~'; }
.d-entry-fixed { --e-colour: var(--lime); --e-tint: var(--lime-dim); --e-glyph: '✓'; }
.d-entry-removed { --e-colour: var(--orange); --e-tint: var(--orange-dim); --e-glyph: '✕'; }
.d-entry-security { --e-colour: var(--red); --e-tint: var(--red-dim); --e-glyph: '!'; }

/* --- video ---
   Same markup the forum gets, so the same measurements: 16/9 from one
   property rather than a padding box, and the caption carries the link
   out for anybody the frame does not reach. */
.d-video { margin: 0 0 1.2em; max-width: 100%; }
.d-video-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  width: var(--video-w, 640px);
  max-width: 100%;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-md, 8px);
  background: var(--bg-raise);
}
.d-video-frame > iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}
.d-video-cap {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  align-items: baseline;
  margin-top: 8px;
  font-size: 13px;
  color: var(--text-3);
}
/* When the embed plays only part of the video — see .fr-video-range in
   ui.css for why the player has to be told from the page. */
.d-video-range {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-dim);
  border: 1px dashed var(--border-soft);
  border-radius: var(--r-pill);
  padding: 1px 8px;
  white-space: nowrap;
}
.d-video-link {
  margin-left: auto;
  font-size: 12px;
  color: var(--text-3);
  text-decoration: none;
  white-space: nowrap;
}
.d-video-link:hover { color: var(--lime-hi); text-decoration: underline; }
.d-video-bad {
  margin: 0 0 1em;
  padding: 12px 14px;
  border: 1px dashed var(--orange);
  border-radius: var(--r-sm);
  color: var(--orange);
}

/* --- video: size and chapters -------------------------------------

   The player is capped rather than filling the column. A 16:9 frame at
   the width of a guide page is taller than the fold, which made a
   `::: video` read as "the whole page is this video" — the width is the
   one thing about an embed that is not content. `--video-w` is the single
   knob: a `size=480` in the markdown sets it inline on the figure, and
   the keywords below set it in css. */

.d-video--small { --video-w: 360px; }
.d-video--medium { --video-w: 640px; }
.d-video--large { --video-w: 100%; }

/* The chapter row. Each chip is a real link to the watch page at that
   second, so with scripting off — or before it has loaded — they are
   still the useful thing they were meant to be. js/video-player.js only
   takes over the click. */
.d-video-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}
.d-video-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 3px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--bg-raise);
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-2);
  text-decoration: none;
}
.d-video-chip:hover {
  border-color: var(--lime-hi);
  color: var(--lime-hi);
  text-decoration: none;
}
.d-video-chip:focus-visible {
  outline: 2px solid var(--lime-hi);
  outline-offset: 2px;
}
.d-video-chip-t {
  font-variant-numeric: tabular-nums;
  color: var(--lime-hi);
  font-weight: 600;
}
.d-video-chip-l { color: var(--text-3); }


/* --- spoiler ---
   A native <details>, so it works with no script at all — which is the
   situation a reader is in when the toggle is the thing they needed. */
.d-spoiler {
  margin: 0 0 1.1em;
  border: 1px solid var(--line);
  border-radius: var(--r-md, 8px);
  background: var(--surface-2);
  overflow: hidden;
}
.d-spoiler > .d-spoiler-t {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  font-family: var(--font-display);
  font-size: 14.5px;
  font-weight: 600;
  color: var(--text-2);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.d-spoiler > .d-spoiler-t::-webkit-details-marker { display: none; }
/* The disclosure is two things, not one: a caret on the left, where a
   disclosure triangle is expected and where the click target is, and a
   word on the right. The caret alone was the whole affordance and it was
   a 9px triangle in the faintest grey at the far edge of a full-width
   row — so a row reading "Full changelog" looked like a heading, and
   nothing on the page said the text under it was there.
   The word is the part that cannot be missed, and it says which state it
   is in, which a caret at rest does not. */
.d-spoiler > .d-spoiler-t::before {
  content: '\25B8';
  flex: none;
  color: var(--text-3);
  font-size: 12px;
  transition: transform var(--t-fast);
}
.d-spoiler[open] > .d-spoiler-t::before { transform: rotate(90deg); }
.d-spoiler > .d-spoiler-t::after {
  content: 'Show';
  margin-left: auto;
  padding: 2px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface-3);
  color: var(--text-3);
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  transition: color var(--t-fast), background var(--t-fast), border-color var(--t-fast);
}
.d-spoiler[open] > .d-spoiler-t::after {
  content: 'Hide';
  color: var(--lime-hi);
  border-color: color-mix(in srgb, var(--lime) 35%, transparent);
  background: var(--lime-dim);
}
.d-spoiler > .d-spoiler-t:hover { background: var(--surface-3); color: var(--lime-hi); }
.d-spoiler-b {
  padding: 12px 14px 14px;
  border-top: 1px solid var(--line);
}
.d-spoiler-b > *:last-child { margin-bottom: 0; }

/* --- tabs --- */
.d-tabs { margin: 1.5em 0; border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden; }
.d-tabs-nav {
  display: flex; flex-wrap: wrap; gap: 4px;
  padding: 9px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}
.d-tab-btn {
  font-family: inherit; font-size: 12.5px; font-weight: 600;
  color: var(--text-dim);
  background: none; border: 1px solid transparent; border-radius: var(--r-pill);
  padding: 6px 14px;
  transition: color .15s, background .15s, border-color .15s;
}
.d-tab-btn:hover { color: var(--text); }
.d-tab-btn.is-active { color: var(--on-accent); background: var(--lime); }
.d-tabs-body { background: var(--bg-raise); }
.d-tab-pane { display: none; }
.d-tab-pane.is-active { display: block; }
.d-tab-pane-in { padding: 18px 20px; }
.d-tab-pane-in > :first-child { margin-top: 0; }
.d-tab-pane-in > :last-child { margin-bottom: 0; }

/* --- recorded walkthrough (: : : steps) --- */
.d-steps {
  margin: 1.6em 0;
  background: var(--bg-raise);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
  font-size: 14px;
}
.d-steps.is-loading, .d-steps.is-error { padding: 22px 24px; color: var(--text-dim); font-size: 13.5px; }
.d-steps.is-error { color: var(--orange); }
.d-steps-hd {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}
.d-steps-ttl {
  font-family: var(--font-display); font-weight: 600; font-size: 14.5px;
  color: var(--text);
}
.d-steps-i { margin-left: auto; font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.d-steps-bd { display: flex; gap: 16px; padding: 18px 20px; }
.d-steps-num {
  flex: none; width: 36px; height: 36px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--lime-dim); border: 1px solid rgba(198, 245, 66, 0.4);
  color: var(--lime); font-family: var(--font-display); font-weight: 700; font-size: 15px;
}
.d-steps-content { min-width: 0; flex: 1; }
.d-steps-title { font-weight: 600; color: var(--text); line-height: 1.45; }
.d-steps-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.d-steps-chip {
  font-family: var(--font-mono); font-size: 11.5px;
  color: var(--lime); background: rgba(198, 245, 66, 0.08);
  border: 1px solid rgba(198, 245, 66, 0.3);
  border-radius: var(--r-pill); padding: 3px 10px;
}
.d-steps-target {
  display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap;
  margin-top: 7px; font-size: 13px;
}
.d-steps-target-k { color: var(--orange); font-weight: 600; }
/* The view the step happened in. "Kind" is a different field in every
   tab, so the label alone does not say where the reader is looking. */
.d-steps-where {
  color: var(--text-3); font-size: 11.5px; text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 1px 6px; border: 1px solid var(--line); border-radius: 5px;
}
.d-steps-shotwrap { margin-bottom: 10px; }
.d-steps-shot {
  display: block; max-width: 100%; height: auto;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--bg-raise);
}
.d-steps-target-v {
  color: var(--text-2); font-family: var(--font-mono); font-size: 12px;
  padding: 1px 6px; border: 1px solid var(--line); border-radius: 5px;
  max-width: 30ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.d-steps-details { margin-top: 10px; font-size: 13px; color: var(--text-dim); white-space: pre-line; }
.d-steps-nav {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px;
  border-top: 1px solid var(--line);
  background: var(--surface);
}
.d-steps-btn {
  font-family: inherit; font-size: 12px; font-weight: 600;
  color: var(--text); background: none; border: 1px solid var(--line-strong);
  border-radius: var(--r-pill); padding: 6px 14px; cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
}
.d-steps-btn:hover:not(:disabled) { color: var(--lime); border-color: rgba(198, 245, 66, 0.45); }
.d-steps-btn:disabled { opacity: 0.35; cursor: default; }
.d-steps-dots { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; flex: 1; }
.d-dot {
  width: 9px; height: 9px; border-radius: 50%;
  border: 1px solid var(--line-strong); background: none; padding: 0; cursor: pointer;
  transition: background .15s, border-color .15s;
}
.d-dot:hover { border-color: rgba(198, 245, 66, 0.5); }
.d-dot.is-cur { background: var(--lime); border-color: var(--lime); }
/* The live launch. It is the whole point of a recorded guide — reading
   about a step and then watching the real editor perform it — so it is a
   full-width call to action, not a quiet link in the corner. */
.d-steps-live {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin: 0 16px 6px; padding: 11px 16px;
  font-size: 13px; font-weight: 600;
  color: var(--lime-ink); background: var(--lime);
  border: 1px solid var(--lime); border-radius: var(--r-sm);
  text-decoration: none;
  transition: background .15s, border-color .15s;
}
.d-steps-live:hover { background: var(--lime-hi); border-color: var(--lime-hi); text-decoration: none; }
.d-steps-live-sub {
  margin: 0 16px 16px; font-size: 12px; color: var(--text-dim); text-align: center;
}
/* Admin-only: authored content, managed elsewhere. */
.d-steps-open {
  display: inline-block; margin: 0 16px 16px;
  font-size: 12px; color: var(--text-dim);
  text-decoration: none; border: 1px solid var(--line);
  border-radius: var(--r-pill); padding: 6px 14px;
}
.d-steps-open:hover { color: var(--lime); border-color: rgba(198, 245, 66, 0.45); }

/* --- prev / next --- */
.d-prevnext { margin-top: 48px; }
.d-prevnext-in { display: flex; gap: 12px; }
.d-prevnext-card {
  flex: 1;
  display: flex; flex-direction: column; gap: 4px;
  padding: 14px 18px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  transition: border-color .18s, transform .18s var(--ease);
}
.d-prevnext-card:hover { border-color: rgba(198, 245, 66, 0.45); transform: translateY(-1px); }
.d-prevnext--next { align-items: flex-end; text-align: right; }
.d-prevnext-k { font-size: 10.5px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-faint); }
.d-prevnext--next .d-prevnext-k { color: var(--lime); }
.d-prevnext-t { font-family: var(--font-display); font-weight: 600; font-size: 14px; color: var(--text); }

/* --- outline rail --- */
.d-rail {
  position: sticky; top: var(--hdr-total-h);
  align-self: start;
  max-height: calc(100vh - var(--hdr-total-h));
  overflow: auto;
  border-left: 1px solid var(--line);
  scrollbar-width: thin;
}
.d-rail-in { padding: 22px 18px 40px; }
.d-rail-sec { margin-bottom: 26px; }
.d-rail-h {
  font-family: var(--font-display);
  font-size: 11px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 10px;
}
.d-rail-h::before { content: ''; display: inline-block; width: 8px; height: 8px; background: var(--lime); border-radius: 2px; margin-right: 8px; }

.d-outline { display: flex; flex-direction: column; gap: 1px; }
.d-outline-a {
  padding: 4px 8px 4px 2px;
  font-size: 12.5px; font-weight: 500;
  color: var(--text-dim);
  border-left: 2px solid transparent;
  line-height: 1.35;
  transition: color .15s, border-color .15s;
}
.d-outline-a:hover { color: var(--text); }
.d-outline-a.is-active { color: var(--lime); border-left-color: var(--lime); }
.d-outline-a--3 { padding-left: 18px; color: var(--text-faint); }
.d-outline-a--4 { padding-left: 32px; color: var(--text-faint); font-size: 12px; }
.d-outline-none { font-size: 12.5px; color: var(--text-faint); }

.d-rail-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.d-rail-tag {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-mono); font-size: 11.5px;
  color: var(--text-dim);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-pill);
  padding: 3px 6px 3px 10px;
  transition: color .15s, border-color .15s;
}
.d-rail-tag:hover { color: var(--lime); border-color: rgba(198, 245, 66, 0.4); }
/* The tag currently filtering the tree, so it is obvious why the list
   is short and where to go back to everything. */
.d-rail-tag.is-active {
  color: var(--lime);
  background: var(--lime-dim);
  border-color: rgba(198, 245, 66, 0.4);
}
.d-rail-tag-n {
  font-size: 10px; font-weight: 600;
  min-width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-pill);
  background: var(--surface-3); color: var(--text-faint);
}
.d-rail-links { display: flex; flex-direction: column; gap: 4px; }
.d-rail-link {
  font-size: 12.5px; font-weight: 500;
  color: var(--text-dim);
  padding: 3px 6px; border-radius: 7px;
  transition: color .15s, background .15s;
}
.d-rail-link:hover { color: var(--lime); background: var(--lime-dim); }
.d-rail-links > span { font-size: 12.5px; color: var(--text-faint); }

/* --- empty state --- */
.d-empty-state { padding: 40px 0; }
.d-empty-state h1 { font-family: var(--font-display); font-size: 30px; margin: 0 0 10px; }
.d-empty-state p { color: var(--text-dim); margin: 0 0 18px; }
.d-empty-btn {
  display: inline-block;
  padding: 10px 18px;
  border-radius: var(--r-pill);
  background: var(--lime); color: var(--on-accent);
  font-weight: 700; font-size: 13px;
}

/* --- footer --- */
.d-foot { border-top: 1px solid var(--line); margin-top: 30px; background: rgba(16, 16, 20, 0.5); }
/* The same width as the columns above it, or the footer reads as a
   narrower page under a wider one. */
.d-foot-in { max-width: 1800px; margin: 0 auto; padding: 26px 24px 34px; }
.d-foot-nav { display: flex; gap: 20px; font-size: 13px; font-weight: 600; }
.d-foot-nav a { color: var(--text-dim); transition: color .15s; }
.d-foot-nav a:hover { color: var(--lime); }
.d-foot p { color: var(--text-faint); font-size: 12.5px; margin: 12px 0 0; }

/* --- graph overlay ---
   Three parts. The list is the fixed left column and the map takes
   whatever is left of the window, so the map can never be drawn under
   the list and the list never depends on the map fitting. */
.d-graph { position: fixed; inset: 0; z-index: 90; }
.d-graph-glow::before {
  content: '';
  position: fixed; inset: 0; z-index: -1;
  background: radial-gradient(900px 560px at 62% 42%, var(--amb-1), transparent 60%), var(--bg);
}
.d-graph canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; cursor: grab; touch-action: none; }
.d-graph canvas:focus-visible { outline: 2px solid var(--lime); outline-offset: -2px; }
.d-graph-panning canvas { cursor: grabbing; }

/* The list: every note, in words, at full size. */
.d-graph-side {
  position: absolute; left: 0; top: 0; bottom: 0;
  width: 384px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 24px 20px;
  background: linear-gradient(to right, var(--bg) 74%, transparent);
  pointer-events: none;
}
.d-graph-side > * { pointer-events: auto; }
.d-graph-head { flex: none; }
.d-graph-title { font-family: var(--font-display); font-weight: 700; font-size: 18px; color: var(--text); margin: 0 0 4px; }
.d-graph-readout {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--lime);
  min-height: 15px;
}
.d-graph-legend { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.d-graph-legend-chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11.5px; font-weight: 600; color: var(--text-dim);
  padding: 3px 9px; border-radius: var(--r-pill);
  border: 1px solid var(--line); background: var(--surface);
}
.d-graph-legend-chip i { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }

.d-graph-list {
  list-style: none;
  flex: 1 1 auto;
  min-height: 0;
  margin: 0;
  padding: 0 6px 0 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  counter-reset: none;
}
.d-graph-row {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 4px 8px;
  padding: 7px 10px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  transition: background .14s ease, border-color .14s ease;
}
.d-graph-row + .d-graph-row { border-top: 1px solid var(--line); border-top-left-radius: 0; border-top-right-radius: 0; }
.d-graph-row.is-focus { background: var(--lime-dim); border-color: var(--lime-glow); border-radius: var(--r-sm); }
.d-graph-row.is-here { border-color: var(--line-strong); }
.d-graph-row-n {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 600;
  text-align: right;
  color: var(--text-faint);
}
.d-graph-row-a {
  font-size: 13px;
  line-height: 1.35;
  color: var(--text-dim);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.d-graph-row-a:hover { color: var(--lime-hi); text-decoration: underline; }
.d-graph-row.is-here .d-graph-row-a { color: var(--text); }
.d-graph-row.is-focus .d-graph-row-a { color: var(--lime-hi); }
.d-graph-row-l {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-faint);
  white-space: nowrap;
}

.d-graph-hint { flex: none; font-size: 11.5px; line-height: 1.5; color: var(--text-faint); margin: 0; }

/* The connections panel: the note under the pointer, in words.
   A fixed height on purpose — the map is fitted to the space this leaves,
   so a panel that grew and shrank would move the map under the pointer
   every time you crossed a note. */
.d-graph-links {
  position: absolute; left: 50%; bottom: 20px;
  transform: translateX(-50%);
  width: min(680px, calc(100% - 460px));
  height: 172px;
  display: flex;
  flex-direction: column;
  padding: 12px 16px;
  background: rgba(11, 11, 13, 0.9);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  backdrop-filter: blur(6px);
}
.d-graph-links-head {
  flex: none;
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding-bottom: 8px; margin-bottom: 8px;
  border-bottom: 1px solid var(--line);
}
.d-graph-links-title { font-family: var(--font-display); font-size: 13.5px; font-weight: 600; color: var(--text); }
.d-graph-links-count { font-family: var(--font-mono); font-size: 10.5px; color: var(--lime); white-space: nowrap; }
.d-graph-links-list {
  list-style: none; margin: 0; padding: 0;
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-wrap: wrap; align-content: flex-start; gap: 4px 6px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.d-graph-link { margin: 0; }
.d-graph-link-a {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 9px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
  font-size: 12px;
  color: var(--text-dim);
  text-decoration: none;
}
.d-graph-link-a:hover { border-color: var(--lime); color: var(--lime-hi); background: var(--lime-dim); text-decoration: none; }
.d-graph-link-dir { font-family: var(--font-mono); font-size: 11px; color: var(--lime); }
.d-graph-link--none { font-size: 12px; color: var(--text-faint); padding: 3px 0; }

/* Close, in the panel's own header.
   It was an absolutely positioned 42px circle with a glyph in it,
   floating over the canvas: a big target in the corner of a drawing,
   with a multiplication sign that never sat quite in the middle of its
   circle and spun on hover. A labelled control next to the title is
   smaller, says what it does, and cannot overlap anything — the corner
   of the map is where a dragged node ends up. */
.d-graph-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px 12px; }
.d-graph-head .d-graph-title { grid-column: 1; grid-row: 1; }
.d-graph-head .d-graph-readout,
.d-graph-head .d-graph-legend { grid-column: 1 / -1; }
.d-graph-close {
  grid-column: 2; grid-row: 1;
  align-self: start;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  padding: 4px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--text-dim);
  cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
}
.d-graph-close:hover { color: var(--text); border-color: var(--line-strong); background: var(--surface-3); }
.d-graph-close:focus-visible { outline: 2px solid var(--lime); outline-offset: 2px; }

@media (max-width: 900px) {
  /* Narrow: the list becomes a strip along the top, the map keeps the
     rest. The map fits itself to whatever is left, so the two can swap
     places without anything being drawn underneath anything. */
  .d-graph-side {
    left: 0; right: 0; top: 0; bottom: auto;
    width: auto;
    max-height: 46vh;
    padding: 16px;
    background: linear-gradient(to bottom, var(--bg) 78%, transparent);
  }
  .d-graph-list { max-height: 24vh; }
  .d-graph-links { width: calc(100% - 32px); left: 16px; transform: none; bottom: 16px; height: 140px; }
}

@media (max-width: 560px) {
  /* On a phone the canvas cannot be drawn legibly at all: eleven cards in
     a ring would sit in about 340×220 pixels, so every title would be
     smaller than the link count next to it. Rather than ship a picture
     of nothing, the list becomes the map — the same notes, in reading
     order, at full width, with their cross-references underneath the one
     you point at. The arcs are the redundant half of this on a screen
     that size, and they are still there from 561 pixels up. */
  .d-graph canvas { display: none; }
  .d-graph-side {
    inset: 0;
    width: auto;
    max-height: none;
    height: 100%;
    padding: 16px;
    background: var(--bg);
  }
  .d-graph-list { flex: 1 1 auto; max-height: none; }
  .d-graph-links {
    position: static;
    width: auto;
    height: 156px;
    margin-top: 10px;
    transform: none;
  }
  .d-graph-hint { display: block; }
}

/* --- lightbox --- */
.d-lightbox {
  position: fixed; inset: 0; z-index: 95;
  background: rgba(6, 6, 8, 0.92);
  backdrop-filter: blur(6px);
}
.d-lightbox:not([hidden]) {
  display: flex; align-items: center; justify-content: center;
}
.d-lightbox-fig { margin: 0; max-width: min(94vw, 1100px); text-align: center; }
.d-lightbox-img { max-width: 100%; max-height: 82vh; border-radius: var(--r-md); border: 1px solid var(--line-strong); box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6); }
.d-lightbox-cap { margin-top: 12px; color: var(--text-dim); font-size: 13px; }
.d-lightbox-close {
  position: absolute; top: 18px; right: 22px;
  width: 42px; height: 42px;
  border: 1px solid var(--line); border-radius: 50%;
  background: var(--surface); color: var(--text-dim);
  font-size: 22px; line-height: 1;
}
.d-lightbox-close:hover { color: var(--on-accent); background: var(--lime); }

/* --- toast --- */
.d-toast {
  position: fixed; bottom: 26px; left: 50%;
  transform: translateX(-50%);
  z-index: 99;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 18px;
  background: var(--lime); color: var(--on-accent);
  border-radius: var(--r-pill);
  font-weight: 700; font-size: 13px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}

/* ================= responsive ================= */
@media (max-width: 1199px) {
  .d-layout { grid-template-columns: var(--exp-w) minmax(0, 1fr); }
  .d-rail { display: none; }
}
/* Above this width the index is a column, not a drawer, and the toolbar
   button that toggles it did nothing at all: `body.d-tree-open` had
   rules only inside the media query below, so on a desktop it set a
   class that styled nothing. A control that is on screen and does
   nothing is worse than no control, so the same class now collapses the
   column and hands the width to the article — which is the thing to
   want when reading, and the reason the button exists. */
@media (min-width: 900px) {
  body.d-tree-open .d-layout { grid-template-columns: minmax(0, 1fr) var(--rail-w); }
  body.d-tree-open .d-explorer { display: none; }
}
@media (max-width: 1199px) {
  body.d-tree-open .d-layout { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 899px) {
  .d-layout { grid-template-columns: minmax(0, 1fr); }
  .d-explorer {
    position: fixed; z-index: 60;
    top: var(--hdr-total-h); left: 0; bottom: 0;
    width: min(300px, 84vw);
    background: var(--bg-raise);
    border-right: 1px solid var(--line-strong);
    box-shadow: 20px 0 60px rgba(0, 0, 0, 0.5);
    transform: translateX(-105%);
    transition: transform .28s var(--ease);
    max-height: none;
  }
  body.d-tree-open .d-explorer { transform: translateX(0); }
  body.d-tree-open::after {
    content: '';
    position: fixed; inset: 0; z-index: 55;
    background: rgba(0, 0, 0, 0.5);
  }
  .d-main { padding: 24px 20px 70px; }
}
@media (max-width: 560px) {
  .d-prevnext-in { flex-direction: column; }
  .d-prevnext--next { align-items: flex-start; text-align: left; }
}

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