/* ---------------------------------------------------------------------------
   Canonical palettes. Every page declares its family on <html> with
   data-dhf-palette, and these blocks are the single source of truth for the
   values. The attribute selector outscores each page's own :root, so the
   inline values in the 21 pages are now inert defaults kept only as a
   no-JavaScript fallback.

   Why this exists: a site-wide audit found seven near-identical background
   values, four greens (including #137960, one hex digit from #147a62 and
   present on exactly one page while its Chinese twin used the standard), and
   three almost-blacks. None of those were decisions; they were what happens
   when each page is hand-authored from a copy of the last one. Five of ten
   bilingual twin pairs had diverged, two of them across colour families.

   The two families are deliberate and stay: warm for documents, cool for
   diagram pages. Twins always share a family.
   --------------------------------------------------------------------------- */
html[data-dhf-palette="warm"] {
  --bg: #f7f4ec;
  --surface: #fffdf8;
  --surface2: #ebe6da;
  --border: rgba(51, 42, 33, 0.14);
  --text: #231f1a;
  --text-dim: #665f55;
  --green: #147a62;
  --green-dim: rgba(20, 122, 98, 0.11);
  --amber: #a15f22;
  --amber-dim: rgba(161, 95, 34, 0.12);
  --rose: #a9475b;
  --rose-dim: rgba(169, 71, 91, 0.10);
  --blue: #2f6f92;
  --blue-dim: rgba(47, 111, 146, 0.10);
}

html[data-dhf-palette="cool"] {
  --bg: #f7faf7;
  --surface: #ffffff;
  --surface2: #eef7f1;
  --border: rgba(22, 45, 37, 0.12);
  --text: #17241f;
  --text-dim: #62736b;
  --green: #0f9b8e;
  --green-dim: rgba(15, 155, 142, 0.10);
  --purple: #8a4fff;
  --purple-dim: rgba(138, 79, 255, 0.10);
  --amber: #b26a00;
  --amber-dim: rgba(178, 106, 0, 0.12);
}

@media (prefers-color-scheme: dark) {
  html[data-dhf-palette="warm"] {
    --bg: #15120f;
    --surface: #211c18;
    --surface2: #2b251f;
    --border: rgba(255, 239, 218, 0.13);
    --text: #f6eee3;
    --text-dim: #b8aa9a;
    --green: #5cd0b2;
    --green-dim: rgba(92, 208, 178, 0.13);
    --amber: #f3b36b;
    --amber-dim: rgba(243, 179, 107, 0.13);
    --rose: #f095a5;
    --rose-dim: rgba(240, 149, 165, 0.12);
    --blue: #91c8e6;
    --blue-dim: rgba(145, 200, 230, 0.12);
  }

  html[data-dhf-palette="cool"] {
    --bg: #101613;
    --surface: #171f1b;
    --surface2: #1e2924;
    --border: rgba(222, 245, 235, 0.12);
    --text: #e7f4ee;
    --text-dim: #9db2a8;
    --green: #35d5c6;
    --green-dim: rgba(53, 213, 198, 0.12);
    --purple: #b99cff;
    --purple-dim: rgba(185, 156, 255, 0.13);
    --amber: #ffc15a;
    --amber-dim: rgba(255, 193, 90, 0.12);
  }
}

/* The two status pages alias the same colours under older local names. */
html[data-dhf-palette] {
  --ink: var(--text);
  --muted: var(--text-dim);
  --line: var(--border);
}

:root {
  --dhf-status-bg: #17241f;
  --dhf-status-text: #f4fbf7;
  --dhf-status-muted: #bed4c9;
  --dhf-status-link: #7ce2ca;
  --dhf-status-border: rgba(124, 226, 202, 0.3);
}

.dhf-status-bar {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 0.75rem 1rem;
  align-items: center;
  margin: 0 0 1rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--dhf-status-border);
  border-radius: 14px;
  background: var(--dhf-status-bg);
  color: var(--dhf-status-text);
  font: 500 0.92rem/1.45 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  box-shadow: 0 10px 28px rgba(10, 28, 22, 0.16);
}

.dhf-status-bar strong {
  color: var(--dhf-status-text);
  white-space: nowrap;
}

.dhf-status-bar span {
  color: var(--dhf-status-muted);
}

.dhf-status-bar a {
  color: var(--dhf-status-link);
  font-weight: 700;
  white-space: nowrap;
  text-underline-offset: 0.2em;
}

.dhf-status-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1rem;
}

.dhf-status-card {
  padding: 1rem;
  border: 1px solid var(--dhf-status-border);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.035);
}

.dhf-status-card > :first-child { margin-top: 0; }
.dhf-status-card > :last-child { margin-bottom: 0; }

@media (max-width: 760px) {
  .dhf-status-bar { grid-template-columns: 1fr; }
  .dhf-status-bar strong,
  .dhf-status-bar a { white-space: normal; }
}

/* The bar is a true page header: flush to the top edge and full-bleed on every
   page, matching the status-style skill map. Two page shapes exist — six pages
   put their padding on an inner .wrap and leave <body> at zero, the other
   fifteen pad <body> itself — so the bleed cannot assume a single value.
   The bar is made full-width structurally rather than by bleeding a pseudo
   element sideways. An earlier attempt used inset: 0 -100vmax with
   overflow-x: clip on <html> to contain it; that does not work, because
   overflow on the root element propagates to the viewport and the root itself
   computes back to visible, so nothing clips. Measured result was 1830px of
   horizontal scroll into empty space. Instead: zero the body's horizontal
   padding, move that padding onto the nav and the content wrapper, and the bar
   is viewport-wide with no overflow at all. */
body { padding: 0 0 clamp(1.5rem, 4vw, 2.4rem) !important; }
.wrap { padding-inline: clamp(1rem, 3vw, 2.4rem); }

.dhf-nav {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 clamp(1.25rem, 3vw, 2.25rem);
  padding-inline: clamp(1rem, 3vw, 2.4rem);
  color: var(--text, CanvasText);
  font: 400 0.875rem/1.2 var(--font-body, system-ui), system-ui, sans-serif;
}

/* The glass is painted by a bleeding pseudo-element so the bar reaches the
   viewport edges while its links stay aligned with the page content.
   Inherit each page's own palette; token names are not uniform across the 21
   pages, so every custom property here is written with a fallback.
   75% opacity is the floor: the dark status bar scrolls under this bar and the
   scrim alone carries the text contrast, because backdrop blur smears pixels
   without lightening them. Below 75% link text drops under 4.5:1. */
.dhf-nav::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: color-mix(in srgb, var(--surface, Canvas) 75%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--border, CanvasText) 55%, transparent);
  backdrop-filter: saturate(1.7) blur(16px);
  -webkit-backdrop-filter: saturate(1.7) blur(16px);
}

.dhf-nav-home {
  flex: none;
  align-self: center;
  padding: 0 0.5rem 0 0;
  margin-right: 0.25rem;
  border-right: 1px solid var(--border, color-mix(in srgb, CanvasText 16%, transparent));
  color: var(--text, CanvasText);
  font-family: var(--font-display, var(--font-body, inherit));
  font-weight: 500;
  letter-spacing: 0.02em;
  text-decoration: none;
  line-height: 44px;
}

.dhf-nav-links {
  display: flex;
  flex: 1 1 auto;
  gap: 0.25rem;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

.dhf-nav-links::-webkit-scrollbar { height: 4px; }
.dhf-nav-links::-webkit-scrollbar-thumb { background: var(--dhf-status-border); border-radius: 4px; }

.dhf-nav a {
  display: inline-flex;
  flex: none;
  align-items: center;
  /* 2.5.5 — 44px min target on the primary bar. */
  min-height: 44px;
  padding: 0 0.6rem;
  /* Underline the active item instead of filling it: no accent colour has to
     be invented, and it stays legible on every page theme. */
  border-bottom: 2px solid transparent;
  background: none;
  /* Full-contrast, not muted. Muted nav text measures 3.4:1 against the dark
     status bar passing under a 75% scrim; the page's own --text measures 9.7:1.
     Hierarchy therefore comes from weight and the underline, not from colour. */
  color: var(--text, CanvasText);
  font-weight: 400;
  text-decoration: none;
  white-space: nowrap;
}

.dhf-nav a:hover {
  border-bottom-color: color-mix(in srgb, currentColor 35%, transparent);
}

.dhf-nav a[aria-current="page"] {
  border-bottom-color: currentColor;
  font-weight: 500;
}

.dhf-nav-lang {
  flex: none;
  margin-left: 0.25rem;
  padding: 0 0.6rem !important;
  min-height: 30px !important;
  align-self: center;
  border: 1px solid color-mix(in srgb, currentColor 30%, transparent);
  border-radius: 999px;
  font-size: 0.8125rem;
}

.dhf-nav-lang:hover {
  border-color: color-mix(in srgb, currentColor 60%, transparent);
}

/* Cross-language link badge: never change a reader's language without warning. */
.dhf-xlang::after {
  content: attr(data-lang);
  margin-left: 0.35rem;
  padding: 0.08em 0.32em;
  border: 1px solid currentColor;
  border-radius: 4px;
  opacity: 0.75;
  font-size: 0.72em;
  font-weight: 700;
  letter-spacing: 0.04em;
}

@media (max-width: 760px) {
  .dhf-nav { gap: 0.4rem; padding: 0.4rem 0.6rem; }
  .dhf-nav-home { padding: 0.4rem; }
}

@media print {
  .dhf-nav, .dhf-skip { display: none; }
}

/* The global nav is sticky, so the six pages with a sidebar TOC must park it
   below the bar rather than letting it slide underneath. */
.toc {
  top: 3.9rem;
  max-height: calc(100dvh - 5.5rem);
}

/* ---------------------------------------------------------------------------
   Sidebar contents, for long pages only. Applied where the page has enough
   sections to be worth indexing: the written spec (12 sections), the English
   beginner guide (7, matching its Chinese twin), and the PM/FDE guide (6).
   Short pages and the two landing directories deliberately do not get one --
   a permanent sidebar taller than the content it indexes is noise.
   --------------------------------------------------------------------------- */
/* Six pages shipped their own sidebar before this component existed. Rather
   than rewrite their markup, give both implementations the same measurements
   so the rail reads identically across all 19 non-landing pages. */
.toc-title,
.dhf-toc-title {
  margin-bottom: 0.6rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--border, color-mix(in srgb, CanvasText 16%, transparent));
  color: var(--text-dim, GrayText);
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.toc a,
.dhf-toc a {
  display: block;
  padding: 0.45rem 0.6rem;
  border-left: 2px solid transparent;
  /* Rule between entries. The six pages that shipped their own rail already had
     this and the newer ones did not, so the sidebar read as two different
     components across the site. */
  border-bottom: 1px solid color-mix(in srgb, var(--green, CanvasText) 34%, transparent);
  border-radius: 0 6px 6px 0;
  color: var(--text-dim, GrayText);
  font-size: 13px;
  line-height: 1.35;
  text-decoration: none;
}

.toc a:last-of-type,
.dhf-toc a:last-of-type { border-bottom: 0; }

.toc a:hover,
.toc a.active,
.dhf-toc a:hover,
.dhf-toc a.is-active {
  background: var(--surface2, color-mix(in srgb, CanvasText 6%, transparent));
  border-left-color: var(--green, var(--blue, LinkText));
  color: var(--text, CanvasText);
}

.dhf-toc a.is-active { font-weight: 500; }

/* The page content is wrapped in one column element so the rail and the content
   are the grid's only two children, in a single row. Two earlier attempts to
   let the rail span the rows instead both failed: grid-row: 1 / -1 collapses to
   one row because these wrappers declare no explicit rows, and grid-row:
   1 / span 9999 created 9994 empty implicit tracks whose 24px row gaps added
   239,856px of blank page below the content. With one row there is nothing to
   span and nothing to leave empty. */
.dhf-col {
  display: grid;
  gap: inherit;
  min-width: 0;
  align-content: start;
}

.dhf-col > * { min-width: 0; }

@media (min-width: 1100px) {
  .dhf-col { grid-column: 2; }
  .dhf-has-toc {
    display: grid;
    grid-template-columns: 190px minmax(0, 1fr);
    column-gap: 2rem;
    align-items: start;
    /* Each page's own max-width was sized for a single column. Left alone, the
       rail eats 190px + 32px out of it and the reading column gets NARROWER
       than before the sidebar existed (1180 -> 958 on the written spec). Widen
       the container by exactly the rail cost so the content column keeps the
       measure it had. The six pages that shipped their own .toc already sized
       for it (1480px) and are deliberately not touched. */
    max-width: 1479px;
  }
  .dhf-toc {
    grid-column: 1;
    position: sticky;
    top: 3.9rem;
    align-self: start;
    max-height: calc(100dvh - 5.5rem);
    overflow-y: auto;
    padding: 0.25rem 0;
  }
}

/* Below the two-column breakpoint the top nav already covers wayfinding. */
@media (max-width: 1099px) {
  .dhf-toc { display: none; }
}

/* 2.5.5 — landing-page quick-link chips were 34px tall. */
.tag { min-height: 44px !important; }

/* CJK label typography. The landing pages style .eyebrow/.label/.meta/.tag with
   a mono face, uppercase, and 0.11em tracking. On Chinese labels uppercase is a
   no-op, the mono fallback is inconsistent, and the tracking visibly loosens the
   characters. Scope those three properties back for Chinese text only. */
:lang(zh) :is(.eyebrow, .label, .meta, .tag),
:lang(zh-CN) :is(.eyebrow, .label, .meta, .tag) {
  font-family: inherit;
  letter-spacing: 0.02em;
  text-transform: none;
}

/* WCAG 2.4.7 — visible focus indicator.
   Every public page links this stylesheet last, and no page defines its own
   focus style, so this is the single site-wide keyboard-visibility rule.
   currentColor is used deliberately: it inherits each link's own colour, which
   already clears 4.5:1 against its background on both the light landing theme
   and the dark flow-map theme, so one rule stays compliant on both. */
:where(a, button, summary, [tabindex]:not([tabindex="-1"]), input, select, textarea):focus-visible {
  outline: 3px solid currentColor;
  outline-offset: 2px;
  border-radius: 6px;
  box-shadow: 0 0 0 6px rgba(128, 128, 128, 0.32);
}

/* Skip link. Add `<a class="dhf-skip" href="#dhf-main">…</a>` as the first
   child of <body>, and `id="dhf-main"` on <main>. */
.dhf-skip {
  position: absolute;
  left: -9999px;
  z-index: 999;
  padding: 0.7rem 1.1rem;
  border-radius: 10px;
  background: var(--dhf-status-bg);
  color: var(--dhf-status-link);
  font: 700 0.95rem/1.2 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  text-decoration: none;
}

.dhf-skip:focus {
  left: 1rem;
  top: 1rem;
}

/* WCAG 2.3.3 — several pages stagger section entry with a --i delay and the
   flow maps animate on load. Honour a reduced-motion preference. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

@media print {
  .dhf-status-bar {
    background: #fff;
    color: #111;
    border-color: #777;
    box-shadow: none;
  }
  .dhf-status-bar strong,
  .dhf-status-bar span,
  .dhf-status-bar a { color: #111; }
}
