/*======================================================================*
 * <mxw-toc> — the page's own table of contents.
 *
 * Knobs, all --mxw-toc-*, so a site retunes it without reaching into
 * these selectors:
 *
 *   --mxw-toc-label-color    the summary's ink        (default #565656)
 *   --mxw-toc-label-font     its family               (inherits by default)
 *   --mxw-toc-label-rule     under it, when open      (default 2px solid #c9c9c9)
 *   --mxw-toc-indent         one nesting level        (default 1.25em)
 *   --mxw-toc-gap            space around the box     (default 1em)
 *   --mxw-toc-column         narrowest column         (default 16rem)
 *   --mxw-toc-column-gap     between columns          (default 1.75em)
 *   --mxw-toc-column-rule    the line between them    (default 1px solid #dcdcdc)
 *   --mxw-toc-number-color   the entry numbers        (default #6a6a6a)
 *   --mxw-toc-border         the box                  (default 1px solid #d6d6d6)
 *   --mxw-toc-background     inside it                (default transparent)
 *   --mxw-toc-padding        inside it                (default 0.55em 0.9em)
 *   --mxw-toc-radius         its corners              (default 2px)
 *
 * Set --mxw-toc-column to 100% for a single column: it is a column
 * *width*, so one that cannot fit twice never divides.
 *
 * The indent is a margin, not padding, and the numbering is a counter on
 * a pseudo-element rather than a list marker, both for the same reason:
 * a base sheet that resets list padding — most do — would otherwise
 * flatten the nesting and take the markers with it. The indent says
 * which section a subsection is under, and where a column break
 * separates the two, the number is the only thing left that does.
 *
 * Copyright (c) 1999-2026 Stéphane D'Alu — http://www.sdalu.com/
 *======================================================================*/

@layer components {
  mxw-toc {
    display: block;
    margin-block: var(--mxw-toc-gap, 1em);
  }

  /* A box, so a collapsed contents list reads as something to open
     rather than as a stray line of small caps above the prose. The
     border is the whole of it by default and the fill is left
     transparent: this ships to sites whose pages are paper, tinted mat
     board and near-black, and a background that suits one of those is
     wrong on the other two. --mxw-toc-background is there for the site
     that knows which it is. */
  .mxw-toc-box {
    padding: var(--mxw-toc-padding, 0.55em 0.9em);
    background: var(--mxw-toc-background, transparent);
    border: var(--mxw-toc-border, 1px solid #d6d6d6);
    border-radius: var(--mxw-toc-radius, 2px);

    > summary {
      /* The one control here, so it carries a hit area: 24px is the
         floor, and the marker alone is about half that.

         content-box, against the host's reset. Every page this lands on
         sets box-sizing: border-box on everything, and under that the
         1.5rem is a budget the padding and the rule below spend out of:
         open, they take 8.5px of it, so the floor only holds while the
         label's own line is tall enough to make up the difference. It is
         at 13px; a site setting --mxw-toc-label-font to something
         smaller, or a reader shrinking text, would quietly bring the
         band under 24px with nothing to show for it. Measured against
         the content box the 1.5rem means the label's own band, the
         padding and the rule add to it rather than eat it, and the floor
         holds in both states whatever the label is set in. */
      box-sizing: content-box;
      display: flex;
      align-items: center;
      gap: 0.4em;
      min-block-size: 1.5rem;
      font-family: var(--mxw-toc-label-font, inherit);
      font-size: 0.8125rem;
      font-weight: 700;
      letter-spacing: 0.06em;
      text-transform: uppercase;
      color: var(--mxw-toc-label-color, #565656);
      cursor: pointer;

      /* The disclosure mark, drawn rather than native: display: flex on
         a summary suppresses the marker the browser would have given it,
         and the gap above was already reserving room for something to
         stand in its place. Clipped from a square so it takes the
         label's colour and size without a file or a glyph, and turned
         a quarter with the box rather than swapped for a second shape.

         It is decoration: <details> tells assistive technology whether
         it is open, and the summary text is the name. Nothing here is
         the only way to know either. */
      &::before {
        content: "";
        inline-size: 0.55em;
        block-size: 0.55em;
        flex: none;
        background: currentColor;
        clip-path: polygon(20% 0%, 20% 100%, 90% 50%);
        transition: rotate 140ms ease-out;
      }

      &:focus-visible {
        outline: 2px solid currentColor;
        outline-offset: 2px;
      }
    }

    /* Open, the label stops being the whole box and becomes its heading,
       so it takes a rule to sit on. Only open: closed, the box is the
       summary and nothing else, and a rule under it would be a second
       line a few pixels above the box's own bottom edge. The padding and
       margin are what keep the rule off the letters above it and off the
       first entry below. */
    &[open] > summary {
      padding-block-end: 0.5em;
      margin-block-end: 0.35em;
      border-block-end: var(--mxw-toc-label-rule, 2px solid #c9c9c9);
    }

    &[open] > summary::before {
      rotate: 90deg;
    }

    /* The turn is the only motion here, and it is not what carries the
       state — the list appearing under it is. */
    @media (prefers-reduced-motion: reduce) {
      > summary::before { transition: none; }
    }

    /* Forced colours replace background-color, and the mark is painted
       with one, so it would go. A system colour is kept. */
    @media (forced-colors: active) {
      > summary::before { background: CanvasText; }
    }

    ol {
      margin-block: 0.25em;
      padding: 0;
      list-style: none;
      counter-reset: mxw-toc;
    }

    /* The list divides into as many columns as fit. Nothing here is
       marked unbreakable at the section level, which is the whole point:
       a page whose sections are one of eleven and two of none — and
       there is one — puts an unbreakable section in a column of its own
       and leaves the next nearly empty. Left to flow, the same entries
       balance on any shape of page.

       What normally makes that unacceptable is a child carried to the
       top of a column with its parent left behind on the one before:
       an indented line with nothing above it to belong to. Numbered, it
       still says 1.7, so it carries its parent with it. The ordered list
       is what makes the columns safe. */
    > nav > ol {
      columns: var(--mxw-toc-column, 16rem);
      column-gap: var(--mxw-toc-column-gap, 1.75em);
      /* A hairline in the gap. With entries set flush left in each
         column and nothing but white space between them, two columns of
         short labels read as one ragged block; the rule is what says
         where one column ends. It draws only where a column break
         actually happened, so a list narrow enough for one column gets
         no stray line. */
      column-rule: var(--mxw-toc-column-rule, 1px solid #dcdcdc);
    }

    /* Only the nested lists indent; the outer one sits at the margin. */
    ol ol {
      margin-inline-start: var(--mxw-toc-indent, 1.25em);
      margin-block-start: 0.15em;
    }

    li {
      counter-increment: mxw-toc;
    }

    /* The number in a gutter of its own, so a label too long for the
       column wraps against itself and not underneath its own number.
       This is also the unit that never splits: the entry is one line or
       several, but it is never divided by a column break. */
    .mxw-toc-row {
      display: grid;
      grid-template-columns: auto 1fr;
      gap: 0.15em 0.5em;
      align-items: baseline;
      break-inside: avoid;

      &::before {
        content: counters(mxw-toc, ".") ".";
        color: var(--mxw-toc-number-color, #6a6a6a);
        font-variant-numeric: tabular-nums;
      }
    }

    /* A section title should not be left at the foot of a column with
       its first child overleaf. Polish rather than structure: engine
       support for break-after inside a multicol is patchy, and where it
       is ignored the numbering already covers what it would have. */
    > nav > ol > li > .mxw-toc-row {
      break-after: avoid;
    }

    /* inline-block, not the inline-flex this was: a flex box with no
       text of its own to take a baseline from is given one at its bottom
       edge, which lifts it off the line its number sits on. The floor is
       here rather than on the row because the link is the target.

       justify-self because a grid item otherwise stretches to its cell:
       measured, the link around "Directories and files" was 259px wide
       in a 259px column, and the base sheet underlines a:hover — so the
       rule ran the width of the column and the click landed a long way
       from anything that looked like a link. Started, it is as wide as
       its label and still wraps inside the column. */
    a {
      display: inline-block;
      justify-self: start;
      min-block-size: 1.5rem;
    }
  }
}
