/**
 * TOC: a table of contents with scroll spy (spec _docs/plans/base-v2.md §13)
 * @layer components
 *
 * The server writes the list from the article's own headings (H2 top level,
 * H3 nested; assets/js/toc-build.mjs), so crawlers, AI fetchers and no-JS
 * readers get plain links. assets/js/toc.js adds the spy and the dock.
 * Built to its own file, dist/fluid-toc.min.css (package export "./toc"):
 * link it after core on article pages. Gate: scripts/toc-check.mjs.
 *
 *   <div class="article__body">
 *     <nav class="toc" data-toc="rail dock" aria-labelledby="toc-label">
 *       <details class="toc__fold" open>
 *         <summary class="toc__label" id="toc-label">On this page</summary>
 *         <ol><li><a href="#cost">What it costs</a>
 *               <ol><li><a href="#labour">Labour</a></li></ol></li></ol>
 *       </details>
 *     </nav>
 *     <div class="prose content-grid">…</div>
 *   </div>
 *
 * data-toc words, one per layer:
 *   placement  inline (in the flow) · rail (sticky side column at 70rem and
 *              up, inline below) · dock (pinned two-row bar; needs toc.js) ·
 *              "rail dock" (rail when wide, dock when narrow)
 *   side       end puts the rail after the article (default: inline start)
 * data-toc-depth="2" lists H2s only. toc.js sets data-toc-mode
 * (inline | rail | dock) and aria-current="location" on the active link.
 *
 * Look hooks, read with fallbacks and never declared (set them on :root in
 * a child theme): --toc-font, --toc-dock-font, --toc-dock-bg, --toc-size,
 * --toc-sub-size (H3 entries, default 0.93em), --toc-color (resting entries; the active
 * entry and its section's H2 rise to the text colour), --toc-active,
 * --toc-rule, --toc-marker (a counter style for H2 entries, e.g.
 * decimal-leading-zero).
 *
 * TOC PRESETS: each is markup words + a :root snippet. Layer 1 placement
 * (vertical rail / inline list / horizontal dock), layer 2 behaviour
 * (static: no toc.js; dynamic: toc.js spy, centring, the dock), layer 3
 * motion (glide; a jump under reduced motion or data-motion="reduced"),
 * layer 4 the look (the hooks).
 *   editorial (the default)  data-toc="rail dock"   no snippet
 *   numbered                 data-toc="rail dock"   :root { --toc-marker: decimal-leading-zero; }
 *   serif                    data-toc="rail dock"   :root { --toc-font: var(--font-heading); --toc-size: var(--step-0); }
 *   swiss                    data-toc="rail dock"   :root { --toc-marker: decimal-leading-zero; --toc-dock-font: var(--font-body); --toc-rule: 3px; }
 *   gallery                  data-toc="dock"        :root { --toc-font: var(--font-body); --toc-dock-font: var(--font-display); }
 *   quiet (static list)      data-toc="inline"      no toc.js; :root { --toc-sub-size: 0.875em; }
 *   end rail                 data-toc="rail end"    the rail after the article
 *
 * The dock shows one section's subsections at a time (row two): the current
 * section's, or those of the section that holds keyboard focus. The others
 * stay in the accessibility tree and the tab order, clipped until reached.
 */

@layer components {
  .toc { font-variant-numeric: lining-nums proportional-nums; }

  /* The type sits on the fold, not the nav: the nav's own ch (its --measure
     width below) then resolves at body size and lines up with the prose. */
  .toc__fold {
    margin: 0;
    font: var(--toc-size, var(--step--1)) / 1.3 var(--toc-font, var(--font-body));
  }

  .toc ol { list-style: none; margin: 0; padding: 0; counter-reset: toc; }
  .toc ol ol { margin-inline-start: 1em; }
  .toc li { margin: 0; counter-increment: toc; }

  /* Rows 44px under a coarse pointer, 32px with a mouse. The rule sits on
     the link, so the active mark moves with its (indented) text. */
  .toc { --_toc-row: 2.75rem; }
  @media (pointer: fine) { .toc { --_toc-row: 2rem; } }

  .toc a {
    display: block;
    padding-block: calc((var(--_toc-row) - 1lh) / 2);
    padding-inline: var(--space-s) 0;
    border-inline-start: var(--toc-rule, var(--border-width-thick)) solid transparent;
    color: var(--toc-color, var(--color-text-muted));
    text-decoration-line: none;
  }

  /* The outline reads at a glance: sections in a heavier weight,
     subsections smaller. At rest both are muted, so the current place
     (the active entry and its section) stands out in the text colour. */
  .toc__fold > ol > li > a { font-weight: calc(var(--weight-medium) + var(--_dark-weight)); }
  .toc__fold > ol > li + li { margin-block-start: var(--space-1); }
  .toc li li a { font-size: var(--toc-sub-size, 0.93em); }

  .toc a:hover { color: var(--color-text); text-decoration-line: underline; }
  .toc a:focus-visible { outline-offset: -2px; }

  /* The H2 that holds the active H3 is lit without the rule. */
  .toc li:has(a[aria-current]) > a,
  .toc a[aria-current] { color: var(--color-text); }
  .toc a[aria-current] { border-inline-start-color: var(--toc-active, var(--accent-ink)); }

  /* Forced colours turn the transparent resting rule into a LinkText bar
     on every entry; only the active one keeps a mark. */
  @media (forced-colors: active) {
    .toc a { border-inline-start-color: Canvas; }
    .toc a[aria-current] { border-inline-start-color: Highlight; text-decoration-line: underline; }
  }

  /* Numbering: a leading space collapses when the counter style is none. */
  .toc__fold > ol > li > a::before {
    content: counter(toc, var(--toc-marker, none)) " ";
    word-spacing: 0.5em;
  }

  .article__body > .toc {
    inline-size: min(100% - 2 * var(--page-edge), var(--measure));
    margin: 0 auto var(--space-l);
  }

  /* RAIL: two columns, the nav sticky in the first (or the second, "end").
     Full bleed stops at the article column on these pages. toc.js keeps the
     active entry in view when the list is taller than the rail. */
  @media (width >= 70rem) {
    .article__body:has(> .toc[data-toc~="rail"]) {
      display: grid;
      grid-template-columns: minmax(12rem, 16rem) minmax(0, 1fr);
      column-gap: var(--space-l);
      padding-inline: max(var(--page-edge), (100% - var(--container-content)) / 2);
    }

    .article__body:has(> .toc[data-toc~="rail"][data-toc~="end"]) { grid-template-columns: minmax(0, 1fr) minmax(12rem, 16rem); }
    .article__body:has(> .toc[data-toc~="rail"]) > .content-grid { --_edge: 0rem; }

    .article__body > .toc[data-toc~="rail"] {
      position: sticky;
      inset-block-start: var(--space-xl);
      align-self: start;
      inline-size: auto;
      max-block-size: calc(100dvh - 2 * var(--space-xl));
      margin: 0;
      overflow-y: auto;
    }

    .article__body > .toc[data-toc~="end"] { grid-area: 1 / 2; }
  }

  /* DOCK: a bar pinned to the bottom. Row one is the sections, the current
     one centred with its neighbours either side; row two is the current
     section's subsections. toc.js centres both. Off screen it is only
     translated away: it stays in the accessibility tree and the tab order,
     and slides in when a link inside it takes focus. */
  .toc[data-toc-mode="dock"] {
    position: fixed;
    inset: auto 0 0;
    z-index: var(--z-sticky);
    inline-size: auto;
    margin: 0;
    padding-block-end: env(safe-area-inset-bottom, 0px);
    /* A hint off the page, so it reads as part of the content: the surface
       tint, one step stronger in dark (the plain surface is 1.07:1 there). */
    --_dock-bg: var(--toc-dock-bg, light-dark(var(--color-surface), var(--color-surface-raised)));
    background-color: var(--_dock-bg);
    border-block-start: var(--border-width) solid var(--color-border);
    translate: 0 100%;
    transition: translate var(--duration-normal) var(--ease-settle);
  }

  .toc[data-toc-mode="dock"]:is([data-toc-visible], :has(:focus-visible)) { translate: none; }
  .toc[data-toc-instant] { transition: none; }
  .toc[data-toc-mode="dock"] .toc__fold { border: 0; }
  .toc[data-toc-mode="dock"] .toc__label { display: none; }

  .toc[data-toc-mode="dock"] ol {
    display: flex;
    margin: 0;
    overflow-x: auto;
    scrollbar-width: none;
    overscroll-behavior-inline: contain;
    padding-inline: 50%;
  }

  /* The neighbours fade only at the very edge (1.5rem), so a neighbour's
     label keeps its contrast; an overlay, not a mask, because a mask on row
     one would also hide row two, which sits inside it. */
  .toc[data-toc-mode="dock"]::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(90deg, var(--_dock-bg), transparent 1.5rem calc(100% - 1.5rem), var(--_dock-bg));
  }

  /* No scroll snap: toc.js centres the rows, and snapping rows cancelled
     the page's own scroll to a #fragment while it loaded (Chromium). */
  .toc[data-toc-mode="dock"] li { flex: none; }
  .toc[data-toc-mode="dock"] li + li { margin: 0; }
  .toc[data-toc-mode="dock"] a {
    --_toc-row: 2.75rem;
    white-space: nowrap;
    padding-inline: var(--space-s);
    border: 0;
  }

  .toc[data-toc-mode="dock"] .toc__fold > ol > li > a {
    font: calc(var(--weight-display) + var(--_dark-weight)) var(--step-0) / 1.2 var(--toc-dock-font, var(--font-heading));
  }

  /* On a phone a neighbour is cut short with an ellipsis, so both read as
     words either side of the current section (which is never cut).
     toc.js writes --_toc-side, the room left beside the centred entry,
     so a neighbour ends with its ellipsis on screen, never cut at the edge. */
  @media (width < 48rem) {
    .toc[data-toc-mode="dock"] .toc__fold > ol > li:not(:has(a[aria-current])) > a {
      max-inline-size: var(--_toc-side, 34vw);
      overflow: hidden;
      text-overflow: ellipsis;
    }
  }

  .toc[data-toc-mode="dock"] li > ol {
    position: absolute;
    inset: auto 0 env(safe-area-inset-bottom, 0px);
  }

  /* Row two: the current section's subsections, or those of the section
     that holds keyboard focus (a link a pointer pressed keeps its focus
     but does not count). The others are clipped, not removed. */
  .toc[data-toc-mode="dock"] li:not(:has(a[aria-current])) > ol,
  .toc[data-toc-mode="dock"] .toc__fold:has(a:focus-visible) > ol > li:not(:has(a:focus-visible)) > ol {
    clip-path: inset(50%);
    opacity: 0;
    pointer-events: none;
  }

  .toc[data-toc-mode="dock"] .toc__fold > ol > li:has(a:focus-visible) > ol {
    clip-path: none;
    opacity: 1;
    pointer-events: auto;
  }

  /* Row two's space is kept whenever any section has subsections, so the
     bar never grows and shrinks between sections. */
  .toc[data-toc-mode="dock"]:has(li > ol) { padding-block-end: calc(2.75rem + env(safe-area-inset-bottom, 0px)); }

  .toc[data-toc-mode="dock"] :is(li:has(a[aria-current]) > a, a[aria-current]) {
    text-decoration: underline var(--toc-rule, var(--border-width-thick)) var(--toc-active, var(--accent-ink));
    text-underline-offset: 0.35em;
  }

  /* The article ends with the section rhythm in every mode (the rail left
     its last line 18px above the next band). */
  .article__body { padding-block-end: var(--space-section); }
  .article__body:has(> .toc[data-toc-mode="dock"]) { padding-block-end: max(var(--space-section), var(--_toc-dock-size, 6rem) + var(--space-m)); }
  /* toc.js writes the dock's measured height; 6rem until it does. */
  :root:has(.toc[data-toc-mode="dock"]) { scroll-padding-block-end: calc(var(--_toc-dock-size, 6rem) + var(--space-s)); }

  @media print { .toc[data-toc-mode="dock"] { display: none; } }
}
