/* rookery — default styling, modelled on Forester (www.forester-notes.org).
   Deliberately thin: enough that the fold/permalink interaction reads
   correctly out of the box, and nothing that presumes a page design. No
   fonts, no page colors, no typographic scale — only the affordances.

   Nothing here is load-bearing for FUNCTION: linking, transclusion and the
   <details>/<summary> fold all work via native HTML semantics with zero CSS.
   What the CSS carries is the CLICK BUDGET, and that is worth stating once,
   because it is the whole interaction model:

     - the summary of a `#window` folds and unfolds. That is all it does.
     - `[data-rookery="label"]` — the `[idea:etal]` permalink, on the tab that rules off
       the top of a card (`[data-rookery="tab"]`) — is the ONLY link inside a window, and it
       goes to the note's standalone page. (`[data-rookery="ref"]` is the other anchor
       this package emits, but an author asks for that one by writing
       `@idea:other`; it is prose, not window chrome.)

   Override any of it; the classes are the contract.

   THE THEME. Every colour below is `var(--x, <default>)`. The default is the
   literal in the var() call, right here; the override arrives as an inline
   custom property on `[data-rookery="box"]` / `[data-rookery="window"]` / `[data-rookery="ref"]` / a minted
   page's `<h1>`, set by `#show: rookery.with(theme: (...))` (or its granular
   equivalents), and inherits down. So a document that never configures
   anything emits no properties at all and still gets exactly this, and a
   reader with no custom-property support gets it too:

     --idea-link-color   link-color   hover background on ANY rookery link
     --idea-fold-color   fold-color   hover background on a foldable window block
     --idea-name-color   name-color   the [idea:etal] permalink's text
     --idea-date-color   date-color   an idea's/window's date, where shown
     --idea-border-color border-color the left rule on `[data-rookery="box"]`/`[data-rookery="window"]`,
                                      and the top tab rule (`[data-rookery="tab"]`) that
                                      meets it at the corner
     --idea-pad          pad          how far a card's or a window's content sits
                                      from the rule beside it, and a window's own
                                      right padding: ONE indent (0.5em, halved
                                      under 600px). The tab's offset and the top
                                      rule's stub span the same distance and read
                                      this, so the corner closes at any value
     --idea-rule-width   rule-width   how THICK every one of those is, plus the
                                      floor under a FOLDED window's summary and
                                      `#ideas-outline`'s rule and its row
                                      markers: one thickness for the whole frame
                                      (2px). NOT the separators above a
                                      footnotes/references block — those are
                                      apparatus, not the note's frame
     --idea-external-color            the underline an OUTBOUND link takes on
                                      hover, where an internal one takes the
                                      fill above (defaults to the id colour)
     --idea-label-font   label-font   the face EVERY HAT is set in — a note's
                                      `[idea:etal]` id and `#ideas-outline`'s
                                      "Contents" title, which are the same object
                                      wearing the same rule. A CSS font stack, not
                                      a colour and not a length: it is passed
                                      through unvalidated, and an array of family
                                      names is joined for you. Defaults to
                                      `monospace`, the generic family, so it is
                                      whatever face the reader configured rather
                                      than one this package picked. BODY PROSE IS
                                      UNAFFECTED — this package sets no
                                      `font-family` on writing and must not start
     --idea-label-size   label-size   the size every HAT is set in, alongside
                                      `label-font` above — the same id and
                                      "Contents" title. A LENGTH: pass a Typst
                                      length or a CSS length string, `rem`
                                      preferred (see `[data-rookery="label"]`'s font-size
                                      below for why `rem` and not `em`).
                                      LOAD-BEARING beyond the label itself: the
                                      tab's lift, a window's summary lift, a
                                      folded window's tint offset and the
                                      footer's padding are all `calc()`s against
                                      this same variable, so retheming it keeps
                                      the card's corner shut. Defaults to
                                      `0.57rem`

   `--idea-border-color` defaults to `--idea-link-color` itself (not to a
   separate literal), so the rule and the link hover read as the same colour
   until a theme sets `border-color` on its own.

   The first two are the look. Both are hover BACKGROUNDS, which is what makes
   them comparable: the lighter blue belongs to the fold (a block that only
   opens and closes), the stronger purple to every link (which actually goes
   somewhere). Forester makes the same split with one blue at two alphas; two
   hues survive being read quickly.

   Setting them yourself in your own stylesheet works just as well, and wins
   nothing over the Typst-side route — they are the same properties. */

/* The permalink, on the tab above `#idea`'s heading and above `#window`'s title
   — the same affordance meaning the same thing in both places, and in prose on
   its own where a nested window ran out of depth. Grey and light so it reads as
   an id rather than as body text, and unstyled by any inherited link decoration
   so it does not shout. `white-space: nowrap`
   keeps "[idea:etal]" from breaking mid-id. (`idea:` is the default prefix;
   `#show: rookery.with(prefix: ...)` changes the TEXT, never the class.)

   The padding is on the element, NOT on :hover — a padding that appeared only
   on hover would shift the surrounding line every time the pointer crossed
   it. Only the background changes. */
[data-rookery="label"] {
  margin-left: 0.5em;
  padding: 0 2px;
  white-space: nowrap;
  color: var(--idea-name-color, gray);
  font-weight: 200;
  /* Metadata, not content — explicitly smaller than the title beside it
     (`[data-rookery="window-title"]` and a heading's own text both stay at their
     ambient size). `[data-rookery="date"]` reads this same property, so the date at
     the hat's other end reads as the same kind of thing as the id at this one.

     `rem`, NOT `em` — an id is ONE thing wherever it appears, and `em` would make it
     three different sizes: 0.57 of an `<h2>` in an `#idea` heading, 0.57 of an
     `<h1>` on a minted page, 0.57 of body text in a `#window` summary (smaller again
     inside the footer, which scales down). Root-relative, it comes out identical in
     all of them, at the window's size — the smallest of the three, which is the one
     that reads as machinery rather than as part of the heading it sits in. */
  font-size: var(--idea-label-size, 0.57rem);
  /* THE HAT'S FACE, shared with `#ideas-outline`'s title, which wears the same
     rule and is therefore the same object. `monospace` is the GENERIC family, so
     the default is the face the reader configured rather than one this package
     chose for them — and an id is machine text, which is what a monospace face
     says without a word of explanation. */
  font-family: var(--idea-label-font, monospace);
  text-decoration: none;
  border-radius: 3px;
}

/* `[data-rookery="label"]` is a `<span>` wrapping a bare `<a>` — `link()`'s own
   HTML export carries none of the properties above, so without this rule the
   id would render in the browser's default link blue, underlined, at body
   size, ignoring everything the span just set. Author styles always beat the
   user-agent default regardless of specificity, so a plain `inherit` on each
   property is enough: the anchor becomes visually transparent and the span's
   own rule is the only one a reader can see. */
[data-rookery="label"] > a {
  color: inherit;
  font: inherit;
  text-decoration: inherit;
}

/* The 0.5em separates the permalink from a title it follows — the case for a
   permalink standing in ordinary prose, in a nested window with no depth budget
   left for a tab. */
[data-rookery="label"]:first-child {
  margin-left: 0;
}

/* THE TOP RULE. A note's id is not a word in its heading — it is the card's top
   edge, as wide as the id needs and no wider, with the id straddling it:

     -[idea:etal]-
     |
     | Note title
     | Body text of the note runs on from here...

   The rule is the same `--idea-border-color` as the left rule below, so the two
   meet at the corner and read as one frame.

   Drawn as a zero-height pseudo-element with a `border-top` rather than as a
   background line behind the text: a line that has to break BEHIND the id would
   need to know the page's background colour, which a package cannot know, and
   guessing it puts a grey box around every id on a dark site.
   `align-items: center` puts the rule through the id's middle, which is right HERE
   — on a minted page the tab is a leader beside the id with no border to meet, and
   a dash at the id's baseline would just hang low. Inside a card or a window,
   where the rule IS the box's top edge, it is overridden to `flex-end` beside the
   lift that depends on it (see the two rules below).

   ONE stub, to the LEFT. The rule runs out of the corner and STOPS at the id —
   it does not resume on the far side. The id is where the frame ends, not a word
   set into a line that continues past it.

   `margin-bottom` here rather than on the heading below it, because the tab owns
   the gap under the rule: whatever comes next — a title, a date, or the note's
   first paragraph when it has neither — sits that far below, and no further. See
   the two rules that zero out what would otherwise be added to it. */
/* `#ideas-outline`'s title (`data-rookery="outline-title"`) wears this shape
   too, flagged with the boolean `data-rookery-tab` rather than a second value
   on `data-rookery` itself — one attribute, one role, so the two selector
   lists below are the only place the borrowed shape is declared. */
[data-rookery="tab"],
[data-rookery-tab] {
  display: flex;
  align-items: center;
  gap: 0.3em;
  margin-bottom: 0.25em;
}

[data-rookery="tab"]::before,
[data-rookery-tab]::before {
  content: "";
  flex: 0 0 auto;
  border-top: var(--idea-rule-width, 2px) solid var(--idea-border-color, var(--idea-link-color, rgba(128, 0, 255, 0.12)));
}

/* The stub reaches back across the card's own `padding-left` (from `--idea-pad`)
   plus the width of the left rule (`--idea-rule-width`), so it ends on that
   rule's OUTER edge and the corner has no notch in it. */
[data-rookery="tab"]::before,
[data-rookery-tab]::before {
  width: calc(var(--idea-pad, 0.5em) + var(--idea-rule-width, 2px));
}

/* `[data-rookery="label"]`'s 0.5em separates it from a title it follows. In a tab it
   follows nothing. */
[data-rookery="tab"] [data-rookery="label"] {
  margin-left: 0;
}

/* `[data-rookery="head"]` groups the tab with the heading under it, so the two are real
   siblings whatever Typst's HTML export decides about paragraph grouping (see
   `_head` in lib.typ — loose siblings came out `<p>`-wrapped in some cards and
   not others). It is also the theme container on a minted note page, which has no
   `[data-rookery="box"]` to be one. Structural only: no box, no spacing of its own.

   `flow-root`, NOT `block`, and that is the difference between a closed corner
   and no corner at all. The tab is lifted by a negative `margin-top` (below) to
   straddle the card's top edge. As a plain block this element has no border and
   no padding, so that margin would ADJOIN its own — collapsing out through
   `[data-rookery="head"]`, then out through `[data-rookery="box"]`, which has no top border or
   padding either — so the whole card would shift up by the lift instead of just
   the tab, and the stub would stay exactly as far below the rule's top end as
   without any lift. `flow-root` makes this a block formatting context, which is
   precisely the rule that stops a child's margin escaping its parent. */
[data-rookery="head"] {
  display: flow-root;
}

/* A REAL TOP-LEFT CORNER, and both margins are what make it one.
   `margin-left` cancels the card's `padding-left` so the stub starts exactly on
   the left rule's own line. `margin-top` lifts the tab so it STRADDLES the card's
   top edge — half above, half below — because the stub is drawn at the tab's
   vertical middle, and the top END of the left rule is at the edge. Without the
   lift the stub sat half an id BELOW that end and the rule stuck up past it.

   `line-height: 1` is what makes the arithmetic exact rather than a guess: it
   collapses the tab's box to exactly the id's own height, whatever line-height
   the host page sets, so half of it is half of `--idea-label-size`. The lift is
   expressed against that variable for the same reason the id's size is — move the
   variable and the corner follows.

   `font-size` is deliberately NOT set here: the stub widths and the gap below are
   in `em`, and re-basing them on the id's 0.57rem would shrink the rule and break
   `margin-left`'s cancellation of a padding measured in the card's own em.

   Only inside a card. A minted page's `[data-rookery="head"]` has no border and no padding
   to meet, so its tab stays in flow. */
[data-rookery="box"] > [data-rookery="head"] > [data-rookery="tab"],
[data-rookery="window-summary"] > [data-rookery="tab"] {
  line-height: 1;
  /* The rule sits at the bottom of the line, not through the id's middle, and only
     in the two contexts that have a top border for it to be: a zero-height flex
     item centred would land on the label's own centre line. At `flex-end` the rule
     sits under the label, so the lift below puts the id ON the border. */
  align-items: flex-end;
  /* Half the generic tab's `gap`, so the id reads as sitting on the end of the rule
     rather than floating away from it. Scoped to these two contexts with the rest of
     the treatment: a MINTED page's tab keeps the wider gap, and keeps its id centred
     on the stub, because there the dash is a leader beside the id rather than a
     frame's top edge. */
  gap: 0.15em;
  /* A whole label, not half: with the rule at the line's bottom (above), lifting by
     the label's full height lands that rule on the box's top edge and leaves the id
     entirely above it — half a label would centre the id ON the border instead.
     From the custom property, never a pixel count, so a project retheming
     `--idea-label-size` keeps the alignment. */
  margin-top: calc(-1 * var(--idea-label-size, 0.57rem) - 1px);
  margin-left: calc(-1 * var(--idea-pad, 0.5em) - var(--idea-rule-width, 2px));
}

/* `[data-rookery="window"]` puts 2px of padding above the `<summary>`, so the lift there has
   to clear that too or the corner lands 2px low. */
[data-rookery="window-summary"] > [data-rookery="tab"] {
  margin-top: calc(-1 * var(--idea-label-size, 0.57rem) - 3px);
}

/* ONE PIXEL OF AIR UNDER THE ID, and on the LABEL rather than on the tab. Lifting
   the tab would take the rule with it — `::before` is a flex sibling aligned to the
   same line bottom — and the rule has to stay ON the box's top edge, because it IS
   that edge and the corner closes on the left rule there. With `align-items:
   flex-end` each item's MARGIN box bottom meets the line's bottom, so a
   `margin-bottom` on the label alone raises the label within that line.
   IT MUST BE PAIRED WITH 1px MORE LIFT: the label is the tallest item, so its
   margin box sets the line's height, and the 1px of air under the id therefore has
   to be paid for with 1px more lift, or the tab's bottom moves 1px down into the box
   while the id stays put. So every lift below also carries `- 1px`: the tab's bottom
   returns to the box's top edge, the rule with it, and the id ends up the 1px higher
   that was wanted. */
[data-rookery="box"] > [data-rookery="head"] > [data-rookery="tab"] > [data-rookery="label"],
[data-rookery="window-summary"] > [data-rookery="tab"] > [data-rookery="label"] {
  margin-bottom: 1px;
}

/* FOLDED, the window has given up its 2px `padding-top` (see the folded rule
   below), so the lift must give up the 2px it added to clear it or the tab lands
   2px high and the hat parts company with the wash under it. */
[data-rookery="window-details"]:not([open]) > [data-rookery="window-summary"] > [data-rookery="tab"] {
  margin-top: calc(-1 * var(--idea-label-size, 0.57rem) - 1px);
}

/* The tab carries the space above a note, so the heading under it contributes
   none of its own — neither the site's heading margin nor this package's.
   Structural (a bare sibling heading), not keyed to the heading's own role: a
   minted page's `<h1>` wears no `data-rookery` of its own (see the banner on
   `[data-rookery="head"] > h1` below), and `[data-rookery="tab"]` is emitted
   nowhere except directly before the heading it belongs to. */
[data-rookery="tab"] + h1,
[data-rookery="tab"] + h2,
[data-rookery="tab"] + h3,
[data-rookery="tab"] + h4,
[data-rookery="tab"] + h5,
[data-rookery="tab"] + h6 {
  margin-top: 0;
}

/* A HEADING INSIDE THE HEAD CONTRIBUTES NOTHING BELOW IT EITHER, and this one is
   a consequence of `[data-rookery="head"]` being a block formatting context (see above).
   Margins do not collapse across a BFC boundary, so without this rule a heading's own
   `margin-bottom` would ADD to the first body block's `margin-top` instead of
   collapsing with it — doubling the gap under a note's name on a site whose own
   heading rule sets a non-zero `margin-bottom`. Zeroing it here makes the gap under
   a note's name the first block's to set again, exactly what the heading rule above
   has always said it should be.

   STRUCTURAL, keyed to the PARENT's role rather than the heading's own: a
   minted page's `<h1>` is the one heading this package emits with no
   `data-rookery` of its own (`.marrow.typ` — its identity is already carried
   by the enclosing `[data-rookery="head"]`, and adding a second attribute
   would duplicate that for no reason a selector needs). Every OTHER heading a
   direct child of `[data-rookery="head"]` is, by construction, an idea's own
   — nothing else is ever placed there — so the parent alone is enough to
   scope this. */
[data-rookery="head"] > h1,
[data-rookery="head"] > h2,
[data-rookery="head"] > h3,
[data-rookery="head"] > h4,
[data-rookery="head"] > h5,
[data-rookery="head"] > h6 {
  margin-bottom: 0;
}

/* THE SAME GAP FOR A NOTE WITH NO TITLE. Its heading element survives to carry
   the `id` anchor but is empty and takes no space (below), so the thing under the
   rule is the note's first block — a sibling of `[data-rookery="head"]`, arriving with its
   own margin-top, which is the site's paragraph spacing rather than this gap.
   Zero it and the distance from the rule to the first thing under it is the tab's
   `margin-bottom` in both cases, which is what makes titled and untitled notes
   line up as the same object.

   `:has` rather than a class, because whether the heading is empty is decided by
   the author's arguments (`title:`, `display-date:`) at every call site, not by
   anything the package could put in the markup once. `[data-rookery="page-refs"]:has(li)`
   already relies on the same selector. */
[data-rookery="head"]:has(> :empty) + * {
  margin-top: 0;
}

/* A note whose heading has NO CONTENT keeps the element only to carry the `id`
   anchor every `@idea:etal` fragment link resolves to, and must take no space.

   A titleless note derives a naming LABEL from its body, but this heading does NOT
   print it: a heading sits directly above the note's own body, so printing the
   label there would render the same text twice (see `#idea`'s title-vs-label banner
   in src/idea.typ). The label goes to the places that REFER to the note instead —
   the minted page's `<title>`, an index row, an outline entry.

   Scoped through the PARENT `[data-rookery="head"]`, for the same reason the
   margin-bottom rule above is: it is what lets this reach a minted page's
   `<h1>`, which carries no role of its own. */
[data-rookery="head"] > h1:empty,
[data-rookery="head"] > h2:empty,
[data-rookery="head"] > h3:empty,
[data-rookery="head"] > h4:empty,
[data-rookery="head"] > h5:empty,
[data-rookery="head"] > h6:empty {
  margin: 0;
}

/* THE SAME TRICK ONE ELEMENT UP, for a tab rather than a heading. A tab holds
   three things and every one of them is optional: the permalink
   (`display-name: false` drops it), the tag pills (`display-tags:`, off by default) and
   the date (`display-date:`, off by default). Turn the permalink off on an ordinary
   note and nothing is left, so the tab has to collapse or a note asked for no
   hat still wears an empty band in its place. `#idea`/`#window` keep
   emitting the `<span>` in that state deliberately — it is the element every
   other tab rule in this file is written against, and it comes back the moment a
   pill or a date is turned on.

   `:empty` matches an element with no children AND no text, which is exactly the
   condition — a tab with pills has element children, a tab with a date likewise.
   Placed AFTER every other `[data-rookery="tab"]` rule in this file so it wins on
   source order; none of them sets `display`, so it needs no extra specificity. */
[data-rookery="tab"]:empty {
  display: none;
}

/* THE LINK HOVER — every link rookery is responsible for, which is what makes
   the permalink and an `@idea:other` reference read as the same kind of thing:

     [data-rookery="label"]      the [idea:etal] permalink, in a heading or a window summary;
     [data-rookery="ref"] a      an `@idea:other` reference rendered by `ref-rule`;
     [data-rookery="box"] a      an author's own link inside a note;
     [data-rookery="window"] a   ...and inside a transcluded copy of one;
     [data-rookery="outline"] a  a row of a page's own table of contents.

   Deliberately NOT a bare `a:hover`: this stylesheet is injected into every
   page of a rheo project, and a package has no business restyling a site's
   nav. It reaches the links in rookery's own blocks and nothing else.

   INTERNAL LINKS ONLY. The fill is how a rookery says "this goes somewhere
   else in the rookery" — a permalink, a reference, an outline row, a backlink.
   An outbound link is a different promise, and washing it in the same colour
   claims a continuity that is not there: the reader is leaving. So the fill is
   withheld from anything whose href starts a URL scheme, and those links get
   their own quieter treatment below.
   `[href^="http"]` catches the case that exists in practice. rheo emits every
   in-project link as a depth-relative path, so "has a scheme" and "leaves the
   rookery" are the same set here. A site that writes its OWN pages as absolute
   urls opts them out of the fill; write them relative, which is what rheo's
   own link rewriting produces anyway. */
[data-rookery="label"]:hover,
[data-rookery="ref"] a:not([href^="http"]):hover,
[data-rookery="box"] a:not([href^="http"]):hover,
[data-rookery="window"] a:not([href^="http"]):hover,
[data-rookery="outline"] a:not([href^="http"]):hover,
[data-rookery="footnotes"] a:not([href^="http"]):hover,
[data-rookery="references"] a:not([href^="http"]):hover,
[data-rookery="page-refs"] a:not([href^="http"]):hover,
[data-rookery="footer"] a:not([href^="http"]):hover {
  background-color: var(--idea-link-color, rgba(128, 0, 255, 0.12));
}

/* ...and the outbound half of that pair. A link that leaves gets a change in
   its UNDERLINE rather than a fill behind it: the same information — this is
   live, you are pointing at it — carried by the quieter of the two channels,
   which is the difference a reader should feel between staying and going.
   Only on hover, so outbound links are not permanently marked up in the prose;
   an author who wants that can say so in their own stylesheet.
   `text-decoration-line` is set here because a site whose links are not
   underlined at rest has no underline to recolour otherwise, and a rule that
   did nothing on half of its installs would be worse than one that adds the
   line it needs. `--idea-external-color` defaults to the id grey rather than
   to the link wash: the wash is a translucent fill, and a 2px line drawn in it
   is close to invisible. */
[data-rookery="ref"] a[href^="http"]:hover,
[data-rookery="box"] a[href^="http"]:hover,
[data-rookery="window"] a[href^="http"]:hover,
[data-rookery="outline"] a[href^="http"]:hover,
[data-rookery="footnotes"] a[href^="http"]:hover,
[data-rookery="references"] a[href^="http"]:hover,
[data-rookery="page-refs"] a[href^="http"]:hover,
[data-rookery="footer"] a[href^="http"]:hover {
  text-decoration-line: underline;
  text-decoration-color: var(--idea-external-color, var(--idea-name-color, gray));
  text-decoration-thickness: 2px;
  text-underline-offset: 0.15em;
}

/* Radius only, no padding, on links that are ordinary prose — padding would
   change inline metrics of body text. The permalink pads because it is a
   bracketed id sitting apart from the words around it. */
[data-rookery="ref"] a,
[data-rookery="box"] a,
[data-rookery="window"] a,
[data-rookery="outline"] a,
[data-rookery="footnotes"] a,
[data-rookery="references"] a,
[data-rookery="page-refs"] a,
[data-rookery="footer"] a {
  border-radius: 3px;
}

/* ---- The footer of a minted note page ------------------------------------
   Two sections of equal standing, either of which may be absent:

     data-rookery="context"    the page the note was written in, linked to the
                               note's own anchor there — the way back to the
                               argument it was written inside, which a minted
                               page strips away;
     data-rookery="backlinks"  the notes that point AT this one, as folded windows.

   They share a markup shape (heading first, then content) and every rule
   below, because they are two answers to the same question about where a note
   sits, not one main thing and one afterthought.

   Ruled off and set small: apparatus, not part of the note. The date colour is
   reused rather than given a knob of its own, since both are the same kind of
   thing — muted metadata about the note rather than the note.

   That colour goes on the footer's OWN parts (below), never on this box. A
   `#window` in Backlinks is a NOTE, and has to read as one — the same thing it
   is anywhere else — but an inherited `color` here would reach inside the
   transcluded copy and tint its title in the date colour, where every other
   window's title is the ambient text colour. Per-part is also the only fix that
   stays theme agnostic: a package cannot name the colour a window's title should
   revert TO, only decline to override it. */
[data-rookery="footer"] {
  margin-top: 2em;
  padding-top: 0.5em;
  /* Neutral grey rather than black-at-low-alpha, so the rule stays visible on
     a dark page as well as a light one. */
  border-top: 1px solid rgba(128, 128, 128, 0.3);
  font-size: 0.85em;
}

/* Both sections have the same shape — a heading, then rows flowing down from
   it — so they take the same spacing and need no rules of their own.

   REJECTED: side by side in a grid, which gives a one-line fact half the
   footer's width and a list of unknown length the other half; and Context as a
   tinted banner across the top, which makes the two sections look like
   different kinds of thing when they are the same kind. */
[data-rookery="footer"] > * + * {
  margin-top: 1.5em;
}

/* A row naming a PAGE — the origin under Context, and any page under
   Backlinks. A page cannot be a `#window`, having no note to fold open, so it is
   a plain link wearing the row shape a `#window` gives a note: the same left
   rule and the same indent. Within one list these are peer entries, and a page
   row hanging flush left beside a ruled, indented note row reads as a
   different list rather than the rest of the same one.

   No bullets: the list is structure, not enumeration. */
[data-rookery="page-list"] {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* A ROW THAT IS PART OF THE FRAME, not a piece of metadata beside it. This shape
   serves two things — a page backlink, and the link a `#window` bottoms out at
   when its depth budget runs out — and both name a place the reader can go, in the
   note's own words. So it takes the frame's own bar and the body's own size:

   - `border-left` from `--idea-rule-width` and the `--idea-border-color` chain, so
     it is the SAME bar an idea and a window draw down their left edge, and follows
     a theme the way theirs do.
   - `padding-left` from `--idea-pad`, so the text starts exactly where an open
     idea's or window's text starts, at any screen width.
   - `font-size: 1rem`, NOT `1em`, and no colour of its own. `1em` is a no-op — it
     means "whatever I inherit", which inside `[data-rookery="footer"]` is that
     block's 0.85em, and `--idea-pad`'s `em`-based indent would then read against
     the wrong base font size. Root-relative fixes both at once: the row reads at
     body size and its indent is the same distance every frame uses. */
[data-rookery="page-row"] {
  border-left: var(--idea-rule-width, 2px) solid var(--idea-border-color, var(--idea-link-color, rgba(128, 0, 255, 0.12)));
  padding: 2px var(--idea-pad, 0.5em) 2px var(--idea-pad, 0.5em);
  margin-bottom: 0.4em;
  font-size: 1rem;
}

/* Typst wraps every `#window` in a <figure>, and the browser default for that is
   `margin: 1em 40px` — which would push every backlink row 40px off the label
   above it, and off the page rows beside it, which carry no such margin.
   Scoped to the footer deliberately: on an ordinary page that indent is a
   reasonable way for a transclusion to read, and undoing it there would be a
   look this package has no business choosing. The rows bring their own
   vertical spacing (`[data-rookery="window-details"]`), so the wrapper needs none. */
[data-rookery="footer"] figure {
  margin: 0;
}

/* Sized to the footer, not to the note: these are labels on an index, not
   sections of the document. Light and letterspaced so they read as a label
   for what follows without competing with the folded rows, which are bold.
   The bottom margin is the gap between the label and the rows under it. */
[data-rookery="footer-title"] {
  margin: 0 0 0.5em 0;
  font-size: 1em;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--idea-date-color, gray);
}

/* ---- #idea — a note where it was written ---------------------------------
   A light left rule, blockquote-fashion, so a new note is visible as a note
   without a box, a background or a heading style doing the work. Same neutral
   grey as the footer rule, at 1px: enough to follow down the margin, not
   enough to compete with the text.

   `#idea` wraps itself in a <figure> (the marker element the package uses to
   find notes again), and the BROWSER indents that by 40px on both sides by
   default — nothing here asked for it, and nested notes compound it. Halved
   to 20px, and moved off the figure onto the note itself so the rule sits at
   the text margin with the body indented from it, which is what makes it read
   as a blockquote rather than as an indented box with a stripe.

   `:has()` is what lets a package stylesheet touch ONLY its own figures, with
   no class to hook (Typst emits a bare <figure>). Where it is unsupported —
   some EPUB readers — the figure keeps its 40px and the note sits further in
   than intended, with the rule and everything else intact. A worse indent, not
   a broken layout. */
figure:has(> [data-rookery="box"]),
figure:has(> [data-rookery="window"]),
/* A `#window` that BOTTOMED OUT at its depth budget is wrapped in a figure like any
   other window, but its content is a `ul[data-rookery="page-list"]` rather than an
   `[data-rookery="window"]`, so it matches neither selector above; without this rule
   it would keep the UA's `margin: 1em 40px` and read as a deeply indented aside
   rather than as the frame's own row. */
figure:has(> [data-rookery="page-list"]) {
  margin-left: 0;
  margin-right: 0;
  /* ROOM FOR THE LIFTED ID. The tab is pulled up by a negative margin, which does
     not make this figure any taller, so the id overflows UPWARD out of the box and
     into whatever precedes it, by a whole label's height — the figure owes that
     clearance back or the gap above the note shrinks by it.

     PADDING, NOT MARGIN, and this is the trap: a `margin-top` here COLLAPSES with
     the preceding paragraph's `margin-bottom` (16px by default) and would do
     nothing at all. */
  /* The `+ 1px` matches the extra 1px of lift the tab carries (see the label's
     `margin-bottom`), so raising the id does not quietly spend the clearance
     above it. */
  padding-top: calc(0.5 * var(--idea-label-size, 0.57rem) + 1px);
}

/* WINDOW AFTER WINDOW GETS A GAP, and it has to be said three times because a
   run of windows reaches the page in three different shapes:

     - `#window((<a>, <b>, <c>))`, the array form, emits several `[data-rookery="window"]`
       divs inside ONE figure. They are plain siblings with nothing between them,
       so nothing separates them without this rule.
     - two separate `#window` calls each get their own figure, which takes the
       browser's 1em figure margin — a gap, but a different one than the array
       form gives, so the same sequence would read differently depending on how
       it was written.
     - inside a minted page's footer every figure margin is zeroed (see
       `[data-rookery="footer"] figure` above), which is right for the label above the list
       and wrong between the backlink windows in it.

   One value for all three, so a run of windows looks like a run of windows
   however it was authored.

   `rem`, NOT `em`, because `[data-rookery="footer"]` sets `font-size: 0.85em`: an
   `em`-based gap would read three-quarters the size inside a minted page's
   Backlinks as it does between ordinary cards, and the gap between two stacked
   cards is page rhythm, not text spacing — it should not shrink because the
   text around it did.

   IT ONLY BITES WHERE IT EXCEEDS THE FIGURE MARGIN IT COLLAPSES WITH, which is
   worth knowing before tuning it again. Adjacent figures collapse their vertical
   margins and the larger wins, so this competes with the UA's own
   `figure { margin: 1em }` on the card above: on a site whose body text is larger
   than 16px the UA's own em-based margin can exceed this value and win, and the
   gap becomes the UA's rather than this rule's. The minted page's footer is the
   one place this value always wins outright, because `[data-rookery="footer"] figure`
   zeroes the UA margin it would otherwise compete with.

   It also has to pay for the id. Since a note's id sits ABOVE its box rather
   than through the border, the next card's id overhangs into this gap by a whole
   label's height, and the gap has to be wide enough to still leave visible space
   once that overhang is subtracted. */
[data-rookery="window"] + [data-rookery="window"],
figure:has(> [data-rookery="window"]) + figure:has(> [data-rookery="window"]),
[data-rookery="footer"] figure + figure {
  margin-top: 1rem;
}

/* A CARD AFTER ANY FIGURE, and the gap has to grow with the label, which is the
   whole point of the `calc`.

   The rule above covers window-after-window; card-after-card would otherwise ride
   the UA's own collapsed `figure { margin: 1em }` — 16px, which this stylesheet
   never states and so never adjusts for the id. A card's id overhangs the top of
   its figure by half a label: the tab is lifted by `label + 1px` and the figure
   pays back only `0.5 * label + 1px` (see `figure:has(> [data-rookery="box"])`
   above), so the clearance a reader actually sees is `16px - 0.5 * label` — linear
   in the label size, and negative once the label is wide enough that the id would
   touch the card above. The body's own content has nothing to do with it: an empty
   card's clearance is identical to a full one's at every label size.

   `1rem +` the overhang, so the visible clearance is a constant 1rem whatever
   the project sets the label to — the same "express it against
   `--idea-label-size`, not a pixel count" rule every other height here follows.

   `figure + figure:has(> [data-rookery="box"])` rather than spelling out each pairing: the
   overhang belongs to the FOLLOWING card, so what precedes it does not matter.
   Window-after-window keeps its own flat `1rem` above; a window's summary tab
   has a different lift and its rule is tuned against that. */
figure + figure:has(> [data-rookery="box"]) {
  margin-top: calc(1rem + 0.5 * var(--idea-label-size, 0.57rem));
}

/* A PAGE LIST AFTER A CARD — the fourth shape a run of backlink rows arrives in,
   and the only one that is not a figure, so the rule above cannot reach it. A
   minted page's Backlinks lists notes as figure-wrapped windows and then pages as
   a BARE `ul[data-rookery="page-list"]` (`.marrow.typ`'s `page-list`), so
   `[data-rookery="footer"] figure + figure` misses it, `[data-rookery="page-list"]` zeroes its own
   margins and `[data-rookery="page-row"]` sets `margin-bottom` alone: without this rule
   nothing gives it a top margin at all.

   THE FOOTER IS THE ONLY PLACE THIS BITES. Elsewhere adjacent figures still
   collapse the UA's own `figure { margin: 1em }` into a 16px gap; `[data-rookery="footer"] figure`
   zeroes exactly that — the same reason `[data-rookery="footer"] figure + figure` had to
   exist above.

   `1rem` MINUS HALF A LABEL, not a flat `1rem`, because two gaps are the same gap
   only if they LOOK the same. Between two cards the 1rem above buys 16px of box
   gap and the next card's id is lifted up into it: the figure's `padding-top` and
   the tab's negative `margin-top` put the label's top exactly
   `0.5 * --idea-label-size` above the figure's edge, so the reader sees only
   `1rem - 0.5 * label` of empty space, not the full 1rem. A page row has no id to
   lift, so a flat `1rem` would read as a LARGER gap than the run of cards it ends.
   Reading the same custom property is what keeps the two in step when a project
   retunes `--idea-label-size`.

   No mirrored `[data-rookery="page-list"] + figure`: pages come after notes in Backlinks
   (see `page-rows` in `.marrow.typ`) and Context is a lone list with no figure
   beside it, so the reverse order does not arise. */
[data-rookery="footer"] figure + [data-rookery="page-list"] {
  margin-top: calc(1rem - 0.5 * var(--idea-label-size, 0.57rem));
}

/* A note and a transclusion of one indent identically, from a rule in the same
   place: they are the same kind of thing, and a `#window` that sat further in
   than the note beside it would read as subordinate to it rather than as
   another note. Nesting still shows, because a window inside a note starts from
   the note's own content edge and adds its rule there — one more rule per
   level, rather than one ever-growing indent. */
[data-rookery="box"],
[data-rookery="window"],
[data-rookery="head"] {
  /* ONE VARIABLE FOR THE INDENT, because four rules have to agree about it: this
     padding, the tab's `margin-left` that cancels it, `::before`'s width that
     spans it, and a folded window's wash that reaches back across it. A rule
     with a hardcoded length of its own instead of reading this property would
     drift out of step with the other three the moment the mobile block below —
     or a project's own theme — changes the indent. Declared on `[data-rookery="head"]`
     too, so a MINTED page's tab (not inside a card) inherits it rather than
     falling back to the literal. */
  --idea-pad: 0.5em;
}

/* A CARD'S head must DEFER to the card rather than re-declare the default over it.
   The rule above names `[data-rookery="head"]` for the minted page's sake, but inside a card
   that element sits INSIDE `[data-rookery="box"]` — so without this rule it would
   shadow the value a theme sets as an inline property on the card itself, and the
   stub and tab would size against the package default instead of the card's own
   `--idea-pad`, breaking the corner while a window's stayed closed.
   Two classes beats one, so this wins over both the rule above and the
   narrow-screen override, and what it inherits is whichever of those applies to the
   card itself. */
[data-rookery="box"] > [data-rookery="head"] {
  --idea-pad: inherit;
}

[data-rookery="box"],
[data-rookery="window"] {
  border-left: var(--idea-rule-width, 2px) solid var(--idea-border-color, var(--idea-link-color, rgba(128, 0, 255, 0.12)));
  padding-left: var(--idea-pad, 0.5em);
}

/* An idea's heading is an <h2>–<h6> (`#idea`'s `level:` + 1) and would
   otherwise take the host site's heading margins, which are sized for section
   headings — a note's own name announcing itself as loudly as the section it
   sits in. Half that (0.75em against the ~1.5em a site typically gives a
   heading), which is the gap this package wants above a note.

   No `margin-bottom` here: the space between a note's name and its first line
   is the site's typography to set, and only the space ABOVE the note is about
   how notes sit in a column. Set `--idea-heading-margin-top` in your own
   stylesheet to go back to your site's scale (or anywhere else). */
h2[data-rookery="idea"],
h3[data-rookery="idea"],
h4[data-rookery="idea"],
h5[data-rookery="idea"],
h6[data-rookery="idea"] {
  margin-top: var(--idea-heading-margin-top, 0.75em);
}

/* ---- #window — a transcluded note -----------------------------------------
   The block gets a faint hover tint, which is what signals "this folds"; the
   disclosure marker is hidden (below), so without the tint the summary would
   look inert. Forester does exactly this. */
/* Radius on the RIGHT corners only: the hover tint wants softening, but the
   left edge is now a rule that has to line up with the one on `[data-rookery="box"]`
   above and below it, and a rounded 1px border bows away from that line.
   `padding-left` comes from the shared rule above — only the other three
   sides are set here. */
[data-rookery="window"] {
  border-radius: 0 5px 5px 0;
  padding-top: 2px;
  /* THE SAME `--idea-pad` as the left indent, not a literal of its own: a window's
     content sits the same distance from the rule on its left as from its right
     edge, and one variable is what keeps that true when a project retheme's it. */
  padding-right: var(--idea-pad, 0.5em);
  padding-bottom: 2px;
}

/* `:not([data-rookery-no-bg])` is `#window`'s `display-background: false`. It is a
   switch of its own rather than part of the frame: a slipshow slide asks for
   `display-frame: false` and keeps this tint, because the frame is decoration and
   the tint is the slide answering a pointer. */
[data-rookery="window"]:not([data-rookery-no-bg]):hover {
  background-color: var(--idea-fold-color, rgba(0, 100, 255, 0.05));
}

/* `#idea-body`'s wrapper carries this SECOND ATTRIBUTE to opt out of the box —
   border, padding, radius, hover tint — while keeping every rule above that
   is keyed to `[data-rookery="window"]` alone: link colours, raw/code styling,
   footnote/reference layout. `#idea-body` renders a note's body with no
   chrome by design (see its own doc comment in lib.typ), and a consumer
   embedding that body somewhere with a box of its own — `@rookery/core-
   search`'s preview pane, say — should not also get THIS box nested inside
   it.
   TWO ATTRIBUTES, not a new selector list added to every rule above: this file
   is UNLAYERED throughout, so a plain `[data-rookery-plain] { border: none }`
   written by a downstream package (even inside ITS OWN `@layer`) can never
   beat an unlayered rule here regardless of specificity. Winning
   requires either matching unlayered specificity, or being MORE specific
   within this same unlayered sheet; `[data-rookery="window"][data-rookery-plain]`
   is the latter, and lives here rather than fighting the cascade from outside
   it. */
[data-rookery="window"][data-rookery-plain] {
  border-left: none;
  padding: 0;
  border-radius: 0;
}

[data-rookery="window"][data-rookery-plain]:hover {
  background-color: transparent;
}

/* `#idea(display-frame: false)` / `#window(display-frame: false)`: a note that keeps
   everything it has — its hat, its title, its body, its tags — and gives up only
   the BOX, the left rule and the indent that go with it. A presentation is the
   case this exists for: `@rookery/slipshow` puts each slip in a `<section>` of
   its own, and a card's frame inside that reads as a frame around a frame.

   THE SAME TWO-ATTRIBUTE TRICK as `data-rookery-plain` directly above, for the
   same cascade reason — read that comment for the full argument. In short: this
   file is unlayered throughout, unlayered CSS beats layered CSS regardless of
   specificity, so a project cannot win this from its own stylesheet no matter
   how it writes the selector. Being more specific WITHIN this sheet is the only
   move available, and it has to live here.

   A SEPARATE ATTRIBUTE from `data-rookery-plain` rather than a second value of
   it, because the two are different jobs on different elements: `plain` says
   "`#idea-body` rendered a bare body, do not draw a box around it", and applies
   to a window only; `bare` says "this note was asked not to wear its frame",
   and applies to a card as well. A note can be neither, either, or — for a
   plain window — effectively both.

   `padding-left`, not `padding`: the box's own vertical padding is not part of
   the frame, and zeroing all four sides here would collapse a note against
   whatever sits above it. `data-rookery-plain` above DOES zero all four, and
   that is right there — it is embedding a body inside someone else's box. */
[data-rookery="box"][data-rookery-bare],
[data-rookery="window"][data-rookery-bare] {
  border-left: none;
  padding-left: 0;
}

/* Marker hidden in both spellings — `::marker` for standards-mode browsers,
   `::-webkit-details-marker` for older WebKit, and `list-style: none` on the
   summary itself, which is what actually suppresses it in Safari. The summary
   is the click target as a WHOLE, so a triangle at one end would misdescribe
   where the reader may click.

   `flex` (+ `align-items: baseline`) keeps the row's items on one text baseline
   despite their different weights and sizes. The DATE is not one of them: it
   lives in the hat, which is its own flex container and does the
   `margin-left: auto` push itself (see `[data-rookery="tab"] > [data-rookery="date"]`). This row is
   therefore the tab on its own line above a bold title, and nothing else. */
[data-rookery="window-summary"] {
  display: flex;
  /* The tab is now the first item in this row, and without wrapping it would sit
     on the same line as the title instead of above it. */
  flex-wrap: wrap;
  align-items: baseline;
  list-style: none;
  cursor: pointer;
}

/* A summary row that cannot toggle must not advertise a click. `#window`'s
   `foldable: false` emits `data-rookery-static`, and the row is a plain `div`
   there rather than a `<summary>` — the permalink inside it keeps its own
   pointer, since that is still a real link. */
[data-rookery="window"][data-rookery-static] [data-rookery="window-summary"] {
  cursor: default;
}

/* Its own line above the title, not another item in the title's row. */
[data-rookery="window-summary"] > [data-rookery="tab"] {
  flex: 0 0 100%;
}

/* A FOLDED window says so at rest. Closed, a `#window` is its summary row and
   nothing else, and the only sign a body is behind it was the hover tint — which
   a reader has to find with a pointer, and which does not exist at all on a touch
   device or for someone tabbing past. This rule under the summary, the full width
   of the window's content, is the boundary that says "there is more here".
   Complementary to the tint rather than a replacement: the tint says the row
   RESPONDS, the rule says there is CONTENT.

   IT IS THE FLOOR TO THE TAB'S HAT. The hat (`[data-rookery="tab"]::before` above) reaches
   back across the card's own `padding-left` PLUS the width of the left rule, so it
   lands on that rule's OUTER edge and the corner has no notch in it; the floor is
   the same frame closed at the bottom, and it gets there by being the window's own
   `border-bottom` rather than by reaching:

     -[idea:etal]-        <- the hat, out of the corner, stopping at the id
     |
     | Note title
     +-------------       <- the floor, out of the same corner, full content width

   Two frames' worth of arithmetic in one place, so the two ends cannot drift:
   both use `calc(0.75em + var(--idea-rule-width, 2px))`.

   Keyed to `:not([open])` and not to a class emitted at build time, because
   `folded: true` sets only the INITIAL state — a reader can click it open, and the
   rule has to leave with the disclosure and come back when it shuts. `[open]` is
   present exactly when the body shows, so the browser does that for free, with no
   JS and nothing for `#window` to emit.

   The CHILD combinator is load-bearing: a window nested inside another window's
   body has a summary of its own, and a descendant selector would let a closed
   OUTER window draw a rule under an INNER one's summary too.

   `--idea-rule-width`, THE FRAME'S OWN THICKNESS, because that is what this line
   is. It closes the box the left rule and the tab open, so it is the fourth side
   of a frame rather than a separator laid across content — the distinction the
   header above draws when it excludes the footnotes/references rules from this
   property. One consequence and it is the point: a project setting
   `#show: rookery.with(rule-width: ...)` moves the floor with the left rule, the
   tab and `#ideas-outline`'s markers, so a heavier or lighter frame stays one
   system instead of three-quarters of one.

   TWO PROPERTIES, EACH DOING WHAT IT IS FOR. The floor is `--idea-border-color`,
   the frame colour, because a boundary is what it is. The folded row also takes
   `--idea-fold-color` as a BACKGROUND WASH, which is the colour that already means
   "this folds" — `[data-rookery="window"]:hover` paints exactly it. So a folded window reads
   as pre-hovered: the affordance is on the row at rest instead of waiting for a
   pointer that a touch device does not have.

   THE WASH, NOT A FOLD-COLOURED LINE, and this is measured rather than taste.
   `--idea-fold-color`'s default is `rgba(0, 100, 255, 0.05)` — 5% alpha, tuned for
   a large background — and MEASURED as a 1px border it composites to
   `rgb(242, 247, 255)` on white, a delta of 13/8/0 and about 1.04:1 contrast:
   invisible, which is the opposite of the point. The same 5% across the whole row
   reads fine, because area is what a 5% wash needs. Alpha that is too low for a
   hairline is exactly right for a wash.

   Hovering a folded window therefore paints the wash twice — once here, once from
   `[data-rookery="window"]:hover` behind it — compositing to about 9.75%. That is deliberate:
   hover still does something, it just deepens a tint that is already there.

   Inert everywhere it should be: `[open]` exists only in HTML, so the paged
   target (where `folded` is ignored and the body always shows) never sees this,
   and `#idea-body` emits no `<details>`/`<summary>` at all. */
/* THE FLOOR IS THE WINDOW'S OWN BOTTOM BORDER, not a rule under the summary, and
   that is what makes it line up. Two borders of the SAME box meet at their corner
   by construction — no arithmetic, nothing to drift — so the floor ends exactly
   where the left rule ends, and it inherits the box's `border-radius` at the
   bottom-right for free. A `border-bottom` on the summary instead sits one
   `padding-bottom` short of the frame's bottom, and no amount of reaching sideways
   fixes a vertical mismatch.
   `padding-bottom: 0` while folded closes that gap from the other side, and
   `[data-rookery="window-details"]:not([open])` gives up its own 0.2em `margin-bottom` for the
   same reason: left in place, it sits between the wash and the floor as an
   untinted seam. With both gone the summary's bottom IS the floor's top, so the
   tint runs into the rule. `:has()` is already how this sheet scopes to its own
   figures (see `figure:has(> [data-rookery="window"])` above); where it is unsupported a
   folded window simply keeps today's appearance. */
[data-rookery="window"]:has(> [data-rookery="window-details"]:not([open])) {
  border-bottom: var(--idea-rule-width, 2px) solid var(--idea-border-color, var(--idea-link-color, rgba(128, 0, 255, 0.12)));
  /* NO PADDING EITHER END. `padding-bottom` so the wash meets the floor;
     `padding-top` so it meets the HAT — without it, a 2px gap shows between the
     top of the tint and the rule above it. The paired tab override below drops
     the lift's own +2px, which existed only to clear this padding. */
  padding-top: 0;
  padding-bottom: 0;
  /* SHARP, overriding `[data-rookery="window"]`'s own `0 5px 5px 0`. That radius exists to
     soften a hover tint on an open block; a folded window is a closed box, and a
     rule that turns a corner reads as a curve rather than as the frame meeting
     itself. Folded only — an OPEN window keeps its rounded right corners. */
  border-radius: 0;
}

/* THE WASH stays on the SUMMARY rather than joining the floor on `[data-rookery="window"]`,
   and the reason is `:hover`. `[data-rookery="window"]:hover` paints the same
   `--idea-fold-color` on the window; put the resting wash on that same element and
   the two rules fight over one property — this selector is the more specific, so
   hover would stop doing anything visible at all on a folded window. On separate
   elements they COMPOSITE instead: about 9.75% under the pointer against 5% at
   rest, so hover still deepens what is already there.
   Both ends reach the frame so the wash spans the floor's full length: left to the
   rule's outer edge (the hat's arithmetic, above), right across the window's own
   `padding-right`. No radius on it: the folded window is square (see above), so
   the wash has no rounded corner to stay inside of.

   SUB-PIXEL AT A FRACTIONAL `rule-width`, the same wart `[data-rookery="tab"]`'s
   stub carries, from the same arithmetic: the `calc()` and the browser's own
   rendered border width can round differently, landing the wash's left edge a
   fraction of a pixel outside the frame. Invisible at 5% alpha, and it cannot be
   fixed here without moving the wash onto `[data-rookery="window"]`, which is what the
   hover paragraph above rules out. Whole-pixel widths — including the 2px
   default — line up exactly. */
[data-rookery="window-details"]:not([open]) > [data-rookery="window-summary"] {
  background-color: var(--idea-fold-color, rgba(0, 100, 255, 0.05));
  /* Back to the border's INNER edge, NOT its outer one — the rule's own width is
     deliberately left out of this reach. Pulling the wash under the border would
     let a translucent border composite over the tint, changing the rule's colour
     wherever the wash sits behind it — but two frame pieces that are one colour
     everywhere else on the page must not part company here. Ending at the inner
     edge leaves the border painting on the page, as it does in a card, and the
     tint starts where the content does. */
  margin-left: calc(-1 * var(--idea-pad, 0.5em));
  padding-left: var(--idea-pad, 0.5em);
  margin-right: calc(-1 * var(--idea-pad, 0.5em));
  padding-right: var(--idea-pad, 0.5em);
  /* BALANCE, and it is a different sum now the id sits ABOVE the border. The tab
     is lifted clear of the box, so what remains inside it above the title is the
     tab's own `margin-bottom` (0.25em) and nothing else — the window's 2px
     `padding-top` is absent too (see above). Matching that below gives the title
     equal air on both sides, rather than the bottom-heavy look a flat carry-over
     of the open window's own padding would give it.
     It goes on the SUMMARY, not on `[data-rookery="window"]`'s `padding-bottom`: the wash is
     the summary's background, so padding here is tinted and still meets the floor,
     where window padding would reopen the untinted seam. */
  padding-bottom: 0.25em;
}

[data-rookery="window-summary"]::marker,
[data-rookery="window-summary"]::-webkit-details-marker,
[data-rookery="window-more-summary"]::marker,
[data-rookery="window-more-summary"]::-webkit-details-marker {
  display: none;
}

/* A window showing only its preview — `[data-rookery="window-more"]` present
   and closed — has more behind it exactly as a fully folded window does, so it
   wears the same wash; the wash drops once the inner `<details>` opens.
   `:not([data-rookery-no-bg])` is the same `display-background: false` opt-out
   the hover rule above honours. */
[data-rookery="window"]:not([data-rookery-no-bg]):has([data-rookery="window-more"]:not([open])) {
  background-color: var(--idea-fold-color, rgba(0, 100, 255, 0.05));
}

/* A truncated window's shown blocks sit INSIDE this `<summary>` — the reason a
   click anywhere in them, not only on the ellipsis, unfurls `rest`. `display:
   block` is what a `<summary>` needs for that content to lay out as normal
   paragraphs instead of collapsing onto the disclosure's marker line. */
[data-rookery="window-more-summary"] {
  display: block;
  cursor: pointer;
  list-style: none;
}

[data-rookery="window-ellipsis"] {
  color: gray;
}

/* Nothing is elided once the tail is showing. */
[data-rookery="window-more"][open] [data-rookery="window-ellipsis"] {
  display: none;
}

[data-rookery="window-title"] {
  font-weight: bold;
}

/* A TITLELESS FOLDED WINDOW RESERVES THE LINE ITS TITLE WOULD HAVE OCCUPIED.

   The summary is a wrapping flex row: the tab is the first item and the title
   wraps onto a second line beneath it. `_window-content` emits
   `[data-rookery="window-title"]` only when the note HAS a title, so an untitled note's
   summary has one flex line where a titled one has two, so without this rule a
   closed untitled row sits at less than half a titled row's height — a missing
   line box, not a smaller variant of the same row.

   `1lh`, not a pixel count and not `--idea-label-size`: what is missing here is
   one line of ORDINARY TEXT, so the reservation should track the project's own
   line-height the way the real title does. Tying it to the label variable would
   track the wrong thing and stop matching the moment a project set either one.

   `::after` on the summary rather than always emitting an empty title span:
   this is presentation, and the Typst side has no reason to know about it.
   `:not(:has(...))` so the reservation appears ONLY where the title is absent —
   `:has()` is already load-bearing in this stylesheet (see `figure:has(>
   [data-rookery="box"])`), so it is not a new dependency.

   `:not([data-rookery-no-title-reserve])` is `#window`'s `reserve-title:
   false`, for a window that is READ rather than scanned — a slipshow slide,
   where the reserved line is dead space above the body and there is no row
   beside it to match heights with. A TITLED summary is untouched by the switch,
   since this rule never applied to one. */
[data-rookery="window-summary"]:not([data-rookery-no-title-reserve]):not(:has(> [data-rookery="window-title"]))::after {
  content: "";
  flex-basis: 100%;
  min-block-size: 1lh;
}

/* The note's date, at the FAR RIGHT OF THE HAT — the same object in the same place
   on a card, in a `#window`'s summary, and on a minted note page, so it wears one
   role in one rule: `[data-rookery="date"]`, wherever the date appears.

   Same grey/light treatment as `[data-rookery="label"]`, and the same size through the same
   property: a date and an id are the two ends of one rule, so they have to be one
   size or the pair reads as two kinds of thing. Setting `--idea-label-size` moves
   both together, which is the only sane way for them to move.

   `margin-left: auto` soaks up the hat's remaining space, pushing the date to the
   frame's right edge while the id stays packed against the stub on the left. It is
   scoped `[data-rookery="tab"] > [data-rookery="date"]` rather than left on the role alone, because the hat is
   the flex container that makes `auto` mean anything — a `[data-rookery="date"]` anywhere else
   would simply be a nowrap grey span, which is the right thing to degrade to.

   No property undoes this push for a window's summary specifically: the hat
   is the date's only home now, and a project wanting the date to flow after
   the id instead can say `margin-left: 0.5em` in its own CSS without a
   property to discover.

   The date's right edge lands on the card's own right edge, including on a card
   carrying several tags. The top rule deliberately does NOT resume on the
   date's far side: the hat draws ONE stub, to the left, and stops at the id (see
   `[data-rookery="tab"]`), so a date floating free at the right end of the frame is the
   intended look. */
[data-rookery="date"] {
  color: var(--idea-date-color, gray);
  font-weight: 200;
  font-size: var(--idea-label-size, 0.57rem);
  white-space: nowrap;
}

[data-rookery="tab"] > [data-rookery="date"] {
  margin-left: auto;
}

/* THE HAT PILL — a tag rendered as a visible chip in the hat, opt-in per call
   site (`display-tags: true` on `#idea`/`#window`, off by default, the same
   mechanism as `display-date`), directly after the id and before the date (see
   `_permalink-tab` in lib.typ). An untagged note's tag list is empty either
   way, so it renders no pill regardless of `display-tags`.

   `[data-rookery="tag"]` is this rule's own hook. Each pill also names its tag with
   the same `idea-tag-<tag>` class rookery already puts on the card and
   the heading — and the same class `@rookery/search` puts on its own
   chips (`.rookery-search-tag`, that package's own stylesheet) — so ONE
   project rule, `[class~="idea-tag-draft"] { ... }`, now styles that tag
   everywhere it appears, including this pill. That is the intent, not an
   accident. The class stays styling's hook; `data-rookery-tags` alongside it
   is what a script or a generic selector reads to ask "which tags does this
   element carry" without naming one.

   SHAPE COPIED FROM `.rookery-search-tag` — radius, letter-spacing, colours,
   horizontal padding — so the two packages draw one recognizable object.
   HEIGHT DELIBERATELY DIFFERS, and this is load-bearing, not cosmetic:
   rookery-search's own metrics (a larger font-size and line-height, plus its own
   vertical padding) are taller than this hat's label, and the hat
   (`[data-rookery="tab"]`) is a flex row aligned `flex-end` and lifted by a
   `margin-top` keyed to `--idea-label-size` (see the lift rules above) — a flex
   row is as tall as its tallest item, so anything taller than the label drags
   the whole rule, and the card's corner with it, down into the card. Sizing the
   chip from `--idea-label-size` and sharing `line-height: 1` — the SAME two
   properties the label itself uses, not merely matching values — is what keeps
   the two heights equal even after a project retunes `--idea-label-size`; giving
   the chip its own separate line-height would drift the moment that happened.
   `font-family` also follows `--idea-label-font`: a tag in the hat is the same
   machinery as the id beside it, and mixing faces on one row would say
   otherwise.

   REJECTED: pinning `[data-rookery="tab"]`'s own height to `--idea-label-size`
   directly, rather than sizing the chip itself — it lets the chip overflow
   above the card in every row, tagged or not.

   Scoped to `[data-rookery="tab"] > [data-rookery="tag"]` specifically, not
   `[data-rookery="tab"] *`: `#ideas-outline`'s title also wears
   `[data-rookery="tab"]` via `data-rookery-tab` (see the tab shape above), and it has
   no `[data-rookery="tag"]` children of its own — this rule must not leak into it. */
/* THE CHIP'S SHAPE, DECLARED ONCE for both places a chip appears: the hat pill
   (`[data-rookery="tab"] > [data-rookery="tag"]`) and a badge in a shared row
   (`[data-rookery="row-badges"] > [data-rookery="tag"]`, `#idea-row`). One declaration
   keeps `@rookery/search`'s and `@rookery/timeline`'s own chips visually
   identical to this one, rather than three hand-maintained copies of the same
   object drifting apart.

   THE SIZE IS NOT SHARED, and that is the whole reason this is two rules rather than
   one. The hat's chip is keyed to `--idea-label-size` because `[data-rookery="tab"]` is a flex
   row as tall as its tallest item: anything taller than the label drags the rule —
   and the card's corner — down with it (see above). A row has no such constraint
   and takes an ordinary relative size. */
[data-rookery="tab"] > [data-rookery="tag"],
[data-rookery="row-badges"] > [data-rookery="tag"] {
  line-height: 1;
  font-family: var(--idea-label-font, monospace);
  letter-spacing: 0.03em;
  border-radius: var(--idea-tag-radius, 999px);
  padding: 0 0.5em;
  white-space: nowrap;
  color: var(--idea-tag-color, var(--idea-name-color, gray));
  background-color: var(--idea-tag-bg, rgba(128, 128, 128, 0.18));
  background-color: var(--idea-tag-bg, color-mix(in oklab, currentColor 14%, transparent));
}

[data-rookery="tab"] > [data-rookery="tag"] {
  font-size: var(--idea-tag-size, var(--idea-label-size, 0.57rem));
}

/* SET AS WRITTEN, not uppercased. A chip in a row is the same object as a chip on a
   hat, and the two sit on one page — so core draws them one way and a project that
   wants status labels shouting says so in its own stylesheet. */
[data-rookery="row-badges"] > [data-rookery="tag"] {
  font-size: var(--idea-tag-size, 0.85em);
}

/* ============================================
   `#idea-row` — the shared row (`src/row.typ`)

   A GRID, NOT A FLEX LINE. A flex item's basis is a HYPOTHETICAL size: content wider
   than it renders at its own width and pushes what follows along, so titles land at a
   different x on every row. A grid track is the same width on every row whatever sits
   in it — which is the entire point of a column of dates.

   The row emits an `<li>` and nothing around it: whether the list is a plain `<ul>`,
   a scroll box or a table is the VIEW's business, so no rule here touches the parent.
   ============================================ */
/* THE VERTICAL PADDING IS PUBLISHED as `--idea-row-pad-block`, and it inherits: a cell
   that paints a background has to reach past it, or the wash stops at the text's line
   box and reads as a chip sitting in the row rather than as the row's own band. Such a
   cell stretches and negates this value — @rookery/todos' date band does exactly
   that — which works whatever a view sets the padding to. */
[data-rookery="row"] {
  --idea-row-pad-block: 0.45rem;
  display: grid;
  grid-template-columns: var(--idea-row-gutter, 7.5em) 1fr auto auto;
  gap: 0 0.9rem;
  align-items: baseline;
  padding: var(--idea-row-pad-block) 0;
}

/* HIDING A ROW HAS TO WORK FROM A DOWNSTREAM PACKAGE, and without this rule it does
   not. `[data-rookery="row"]` above sets its own `display`, which beats the UA's `[hidden]`
   rule — and a filtering view lives in another package whose CSS is inside a cascade
   LAYER, so ITS `[hidden]` override loses to this unlayered file however specific it
   is — as @rookery/search found when its own `.panel-row[hidden]` stopped biting
   the moment its rows became `#idea-row`s. So the package that sets `display`
   is the one that has to take it away again.

   `!important` IS NOT USED and must not be: the point is to lose to a project's own
   unlayered rule, which is the whole contract of this stylesheet. */
[data-rookery="row"][hidden] {
  display: none;
}

/* `tabular-nums` is what makes the first column a COLUMN: without it the separators
   in `7.9.26` and `14.10.26` land in different places and there is nothing to scan
   down. `nowrap` because a date that wraps is not a date. */
[data-rookery="row-when"] {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* THE ONE PRESENTATIONAL FACT `soft:` CARRIES: this date answers a different question
   than the list asked — a booked event standing in for a deadline, a rung merely
   expected. It keeps the column, because it answers the same question a reader is
   scanning for, and it has to read as the softer of the two. */
[data-rookery="row-when"].soft {
  color: var(--idea-muted-color, gray);
  font-style: italic;
}

[data-rookery="row-title"] {
  color: inherit;
  text-decoration: none;
}

a[data-rookery="row-title"]:hover {
  text-decoration: underline;
}

/* A caller's own extra column — a host institution, a path. Muted and non-wrapping,
   because it is a fact about the row rather than part of naming it. */
[data-rookery="row-cell"] {
  color: var(--idea-muted-color, gray);
  white-space: nowrap;
}

/* HARD RIGHT, and the strip is one flex line so several chips read as a group rather
   than as separate columns. A badge glued to the end of a variable-length title lands
   somewhere different on every row, which is nothing to scan. */
[data-rookery="row-badges"] {
  display: flex;
  gap: 0.35rem;
  align-items: baseline;
  justify-content: flex-end;
  flex-wrap: wrap;
}

/* NARROW: the date column stops paying for itself — a fixed 7.5em beside a
   title leaves the title two words per line — so the row stops being a
   table and becomes a CARD: the date is a hat at the top-left corner, a
   rule in the hat's colour runs down the left edge under it, the badges
   join the hat on that line, and the title sits below both, indented past
   the rule. The same corner `[data-rookery="tab"]` and `[data-rookery="box"]`
   draw for a note, at the size a row can afford.

   THE FIRST LINE IS BUILT BY PLACEMENT, not by DOM order: the badge strip
   is emitted LAST (see `idea-row-body`) and grid places definitely-positioned
   items before it flows anything, so naming a row and a column for the hat
   and the strip is enough. The title and any `cells:` entry take a column
   span only and flow into the lines beneath, in the order they were written.

   NAMED AREAS WERE REJECTED for the same reason: a template would need a
   third row for `cells:`, and `gap` applies between empty tracks — every
   row without one would gain a phantom line. */
@media (max-width: 768px) {
  [data-rookery="row"] {
    position: relative;
    grid-template-columns: auto 1fr;
    gap: 0.15rem 0.5rem;
    align-items: center;
    padding-left: calc(var(--idea-rule-width, 2px) + var(--idea-pad, 0.5em));
  }

  /* THE HAT. `margin-left` cancels the row's indent and `padding-left`
     pays it back inside the cell, so a cell that paints a background
     paints it right out to the row's left edge — over the rule below,
     which closes the corner — while the date's glyphs stay on the
     content margin the title keeps.

     `position: static` is load-bearing: @rookery/todos makes this cell
     `relative` to anchor its countdown tooltip, and that would make the
     HAT the containing block for the rule below rather than the row. At
     this width the row is the containing block for both. */
  [data-rookery="row-when"] {
    grid-row: 1;
    grid-column: 1;
    position: static;
    margin-left: calc(-1 * (var(--idea-rule-width, 2px) + var(--idea-pad, 0.5em)));
    padding-left: calc(var(--idea-rule-width, 2px) + var(--idea-pad, 0.5em));
  }

  /* THE RULE, drawn from the hat's own pseudo-element and not from a
     `border-left` on the row. The colour is the point: a consumer paints
     the hat on THIS cell, and a custom property reaches descendants only —
     a bar belonging to the row could never read what was set on the cell
     inside it. `--idea-row-rule-color` is read and never set here, so a
     layered stylesheet can re-point it (@rookery/todos runs its urgency
     band down this bar) without losing to this unlayered file. */
  [data-rookery="row-when"]::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: var(--idea-rule-width, 2px);
    background-color: var(
      --idea-row-rule-color,
      var(--idea-border-color, var(--idea-link-color, rgba(128, 0, 255, 0.12)))
    );
  }

  /* Beside the hat, and LEFT-aligned: the strip's `justify-content:
     flex-end` is right when it terminates a wide row and wrong here,
     where it is the second thing on a line rather than the last thing
     in a table. */
  [data-rookery="row-badges"] {
    grid-row: 1;
    grid-column: 2;
    justify-content: flex-start;
    align-items: center;
  }

  [data-rookery="row-title"],
  [data-rookery="row-cell"] {
    grid-column: 1 / -1;
  }

  /* A chip sharing a line with the date has to be smaller than one that
     had a column to itself, or the hat's line is the tallest thing in
     the row and the title reads as its caption. */
  [data-rookery="row-badges"] > [data-rookery="tag"] {
    font-size: var(--idea-tag-size, 0.72em);
  }
}

/* An open window needs more room beneath it than a closed one, or a column of
   folded windows reads as one undifferentiated block. */
[data-rookery="window-details"] {
  margin-bottom: 0.2em;
}

/* FOLDED, the window's own `border-bottom` is the floor and this margin would sit
   between the summary's wash and that rule as an untinted seam — see the folded
   rule above. Nothing needs the gap here: with the body hidden there is nothing
   below the summary to be spaced away from. */
[data-rookery="window-details"]:not([open]) {
  margin-bottom: 0;
}

[data-rookery="window-details"][open] {
  margin-bottom: 0.5em;
}

/* A CONTAINER-START MARGIN, which is nobody's spacing. A transcluded body opens
   with whatever block the note itself opens with, and the user agent gives that
   block a top margin — `1em` for a heading, the same for a paragraph — so a
   note beginning with `== Problem` renders its heading pushed down off the
   window's own top edge, with the gap reading as part of the window rather than
   as space between two blocks.

   `> :first-child` rather than a list of headings: a body opening with a
   paragraph or a list carries the same UA margin and the same argument against
   it, and the block that happens to come first is not the note's business.

   Only the FIRST child, and only its TOP: the margins BETWEEN a body's blocks
   are ordinary text rhythm and stay. The footnote and reference blocks are
   appended after the body, so they are never this selector's match. */
[data-rookery="window-body"] > :first-child {
  margin-top: 0;
}

/* ---- Footnotes — an idea's own, not the page's ----------------------------
   `#footnote` inside an idea numbers against THAT idea and lists its bodies
   here, at the end of the idea, so two notes on one page may each carry a
   footnote 1. The block follows the idea to every surface it appears on — its
   hatch page, every `#window` on it, its minted page — and each of those gets
   its own copy with its own anchors, because a footnote reference is a
   same-page fragment.

   Subordinate, not sectional: smaller than body text, a hairline above to
   separate it from the prose it belongs to, and aligned to the note's TEXT
   margin rather than sitting out at the note's left rule. It is apparatus for
   the note, not a division of the document.

   Nothing here is load-bearing: the reference and its body are an ordinary
   pair of same-page anchors and work with this stylesheet absent. */
[data-rookery="footnotes"] {
  margin-top: 1em;
  padding-top: 0.4em;
  border-top: 1px solid var(--idea-border-color, var(--idea-link-color, rgba(128, 0, 255, 0.12)));
  font-size: 0.85em;
}

/* An <h4> for document-outline correctness, styled so it does not read as one.
   Size and margins are set explicitly rather than left to inherit: a site's own
   heading scale would otherwise make "Footnotes" compete with the note's title
   directly above it.

   NOT A HAT, and deliberately not. `#ideas-outline`'s title is one (see
   `[data-rookery="outline-title"]`) because it labels the FRAME it sits on. This labels a
   LIST inside a note that already has its own frame, and the separator above it
   is an apparatus rule rather than a note's edge. Do not give it a tab to
   match. */
[data-rookery="footnotes-title"] {
  font-size: 1em;
  font-weight: 600;
  margin: 0 0 0.3em;
}

/* The <ol> numbers itself, and each <li> carries the id its reference points
   at — so no ::marker games and no hand-drawn numbers. `padding-left` keeps the
   digits inside the block rather than hanging into the rule at its left. */
[data-rookery="footnote-list"] {
  margin: 0;
  padding-left: 1.5em;
}

[data-rookery="footnote"] {
  margin: 0.15em 0;
}

/* The inline reference. `line-height: 0` stops the superscript from opening up
   the line it sits in, which is the usual defect of a raw <sup> in body text. */
[data-rookery="fn-ref"] {
  line-height: 0;
}

/* The way back up to the reference. Grey and light like the permalink, because
   it is the same kind of thing: a small navigational mark beside content rather
   than part of it. It takes the shared link hover above, so it is not restyled
   here. */
[data-rookery="fn-backlink"] {
  color: var(--idea-name-color, gray);
  text-decoration: none;
  margin-right: 0.3em;
}

/* ---- References — one bibliography per idea, and the page's own -----------
   Typst partitions citations positionally, so rookery emits one bibliography
   per idea (`[data-rookery="references"]`) and, where a page cites outside any idea, one
   for the page (`[data-rookery="page-refs"]`).

   Every rule here is scoped under one of those two classes, never under a bare
   `section[role="doc-bibliography"]`: this stylesheet is injected into every
   page of a rheo project, and a project may have a bibliography of its own that
   this package has no business restyling. Typst renders the <section>, its
   heading and its <ul>, and adds `class="hanging-indent"` under some styles —
   all of that is left alone.

   An idea's block is apparatus for that idea, exactly like its footnotes above:
   smaller than body text, hairline above, aligned to the note's text margin.

   THAT ALIGNMENT IS NOT A RULE HERE, and nothing below should try to make it
   one. `#idea` emits this block as a CHILD of `[data-rookery="box"]` (see the note at the
   `_bracket` call in `idea.typ`), so the card's own `border-left` and
   `padding-left` reach it: the bar runs down beside the entries and they start
   at the note's text margin, for free and by the same declarations the footnotes
   block above it takes. Giving this class a `border-left` or a `padding-left` of its own
   would draw a SECOND bar inside the card's, so do not: emission inside the card
   is what keeps the alignment true, and `[data-rookery="page-refs"]` below stays
   outside on purpose. */
[data-rookery="references"] {
  margin-top: 1em;
  padding-top: 0.4em;
  border-top: 1px solid var(--idea-border-color, var(--idea-link-color, rgba(128, 0, 255, 0.12)));
  font-size: 0.85em;
}

/* Typst emits an <h2>. Sized explicitly so a site's own heading scale cannot
   let "References" compete with the note's title above it — the same reason
   `[data-rookery="footnotes-title"]` is pinned. */
[data-rookery="references"] h2,
[data-rookery="page-refs"] h2 {
  font-size: 1em;
  font-weight: 600;
  margin: 0 0 0.3em;
}

/* `padding-left: 0` because the entries carry no markers (Typst sets
   `list-style-type: none` inline) and an author-date style has no numbers to
   hang — indenting would leave the list floating away from the rule above it. */
[data-rookery="references"] ul,
[data-rookery="page-refs"] ul {
  margin: 0;
  padding-left: 0;
}

[data-rookery="references"] li,
[data-rookery="page-refs"] li {
  margin: 0.15em 0;
}

/* THE SWEEP BLOCK, and why this is `:has(li)`.
   `#idea` emits a title-less `[data-rookery="page-refs"]` BEFORE itself, unconditionally,
   to claim prose citations written above it. It cannot know whether there are
   any — an idea never sees the prose around it, and asking by query is
   circular (see `_sweep-block` in lib.typ). Almost every one is therefore
   empty, and an empty one must occupy NO vertical space, or every idea on the
   page gains a phantom gap above it.

   So the wrapper carries nothing by default and `:has(li)` adds the spacing and
   the rule only when the block lists something. One rule covers all three
   users of the class: an empty sweep (invisible), a sweep that did catch a
   prose citation, and the page's own trailing-citation block.

   Where `:has()` is unsupported the block renders flush and its entries stay
   readable — the same bargain the `figure:has(> [data-rookery="box"])` rule above makes. */
[data-rookery="page-refs"] {
  margin: 0;
  font-size: 0.85em;
}

[data-rookery="page-refs"]:has(li) {
  margin-top: 1.5em;
  padding-top: 0.4em;
  border-top: 1px solid var(--idea-border-color, var(--idea-link-color, rgba(128, 0, 255, 0.12)));
}

/* CLEARANCE FOR THE FOLLOWING CARD'S HAT, and the sweep block is the only place
   in this stylesheet that owes it. `#idea` emits the sweep immediately before its
   card and OUTSIDE it (`_sweep-block` in bib.typ, called from idea.typ and
   transclusion.typ), so a sweep that caught a citation is the one element that can
   sit directly under a lifted id with nothing to give it: the `li` margin is
   0.15em of this block's own 0.85em, about 2px, against an overhang of a whole
   label plus 1px, about 10px at the default. The id lands on the last reference
   line.

   `figure:has(> [data-rookery="box"])`'s `padding-top` cannot pay for it. That padding is at
   the TOP of the figure and the sweep sits between it and the card, so it buys
   space above the references rather than under them.

   THE FOOTNOTES AND REFERENCES BLOCKS NEED NO SUCH RULE: both are emitted INSIDE
   `[data-rookery="box"]`, after the body, so neither is ever the element directly above
   another card's hat.

   `:has(li)` because an empty sweep must go on occupying no space at all — see
   the rule above. `+ [data-rookery="box"]` because the same role is also the page's own
   trailing-citation block, which is followed by nothing and needs no spacing
   under it; and because `_sweep-block` is never emitted before an `[data-rookery="window"]`,
   whose summary tab carries a different lift.

   Expressed against `--idea-label-size` and carrying the same `+ 1px` the lift
   does, so the visible gap stays about 0.5rem whatever a project sets the label
   to. Half of `figure + figure:has(> [data-rookery="box"])`'s 1rem, because a sweep and its
   card are one note's apparatus rather than two stacked cards. */
[data-rookery="page-refs"]:has(li) + [data-rookery="box"] {
  margin-top: calc(0.5rem + var(--idea-label-size, 0.57rem) + 1px);
}

/* ---- #ideas-outline — a page's own notes, as a tree ------------------------
   Wears the same left rule as `[data-rookery="box"]`/`[data-rookery="window"]`, from the same
   custom property, so a page's table of contents reads as part of the same
   apparatus as the notes it lists rather than as a stray browser list above
   them. Nesting works the way it does for notes, too: each level adds its own
   rule at the previous level's content edge — one more rule per level, not
   one ever-growing indent.

   The theme reaches this via inline custom properties on the OUTERMOST <ul>
   only (see `_nest-outline` in lib.typ). An outline is a sibling of the
   notes, not a descendant of one, so it is the only thing this package emits
   that has no themed ancestor to inherit from.

   `padding-left` is larger than `[data-rookery="box"]`'s `.75em` because the row's
   marker lives in it: 0.75em of rule plus 0.5em of gap before the text. */
/* "Contents", above an outline, AND IT IS A HAT — the same object a note's id is,
   drawn the same way: a stub of rule out of the top-left corner with the label
   sitting on its end. The element carries the boolean `data-rookery-tab`
   alongside its own `data-rookery="outline-title"` (see `#ideas-outline` in
   lib.typ), so the flex box and the `::before` stub come from the tab rules
   above, and only the differences live here.

   NOT SIZED LIKE `[data-rookery="footnotes-title"]`, despite both labelling a block of
   apparatus: an outline's title is part of the outline's FRAME, while a footnotes
   title is a label above a list. `[data-rookery="footnotes-title"]` keeps its own `1em`/`600`
   for that reason — see the note there.

   THE REACH-BACK ARITHMETIC DOES NOT ARISE, which is worth stating because the
   card's version of it is delicate. A card's tab sits INSIDE the padded box, so its
   stub needs a negative `margin-left` and a width spanning `--idea-pad` plus the
   rule to reach back out to the corner. This title is a SIBLING of the `<ul>`, above
   it, so its left edge already IS the `<ul>`'s left edge: the stub starts on the
   rule's outer edge with no cancellation at all, and `[data-rookery-tab]::before`'s width is
   simply how long the stub looks.

   THAT IS ALSO WHY `[data-rookery="outline"]`'s `padding-left` STAYS `1.25em` rather than
   adopting `--idea-pad`. The hat does not read it, and the three outline values are
   one geometry — the row marker's `width` plus its `margin-right` IS that padding,
   and its `margin-left` is the negation of it (see the mobile block). Moving it
   would move every row's marker to buy a consistency nothing now depends on.

   THE CORNER CLOSES VERTICALLY BY GIVING UP THE GAP. `align-items: flex-end` puts
   the stub on the line's bottom, which is this element's box bottom, so the rule
   lands exactly on the `<ul>`'s top edge only while nothing separates them — hence
   `margin: 0` here and `margin-top: 0` on the list below. The air below the hat
   lives in the list's own `padding-top` instead, INSIDE the border, where it
   cannot push the rule away from the corner.

   UPPERCASE ASSERTED, NOT INHERITED. `rookery.ohrg.org` sets
   `h1..h6 { font-variant: small-caps }`, and any site with a heading rule may do the
   same, so both halves are stated here on the package's own class — which beats an
   element selector on specificity — rather than left to chance. */
[data-rookery="outline-title"] {
  /* THE LABEL SIZE, the same property an id reads, because they are one object.
     This replaces a `0.85em` that existed to match `[data-rookery="footnotes-title"]`. */
  font-size: var(--idea-label-size, 0.57rem);
  font-family: var(--idea-label-font, monospace);
  font-variant: normal;
  text-transform: uppercase;
  font-weight: 600;
  /* `1`, so the box collapses to the label's own height and the stub sits on the
     label's own line bottom rather than on the host page's leading — the same
     reason, and the same value, as the card's tab. */
  line-height: 1;
  align-items: flex-end;
  /* Half the generic tab's gap, as a card's and a window's hats take: the label
     reads as sitting ON the end of the rule rather than floating past it. */
  gap: 0.15em;
  margin: 0;
}

[data-rookery="outline-title"] + [data-rookery="outline"] {
  margin-top: 0;
  /* The air under the hat, moved off the title — see the corner note above. */
  padding-top: 0.4em;
}

[data-rookery="outline"] {
  list-style: none;
  margin: 0.4em 0;
  padding-left: 1.25em;
  /* 2px, the same weight as `[data-rookery="box"]`/`[data-rookery="window"]`'s rule: an outline is the
     page's notes listed, and its rule is the same object as theirs. Two weights
     on one page read as two different systems. */
  border-left: var(--idea-rule-width, 2px) solid var(--idea-border-color, var(--idea-link-color, rgba(128, 0, 255, 0.12)));
}

/* A nested list is already indented by its parent row; it needs separating
   from the row above it, not from the list as a whole. */
[data-rookery="outline-row"] > [data-rookery="outline"] {
  margin: 0.25em 0 0.4em 0;
}

[data-rookery="outline-row"] {
  margin-bottom: 0.25em;
}

/* THE BULLET, at every level: a short rule rather than a disc, in the same
   colour as the rule it grows out of. The negative `margin-left` is the
   negation of the list's `padding-left`, which puts the hairline's own left
   end exactly ON that rule — so the marker reads as a tick off the rule, the
   two cannot drift apart when the padding changes, and a row that wraps hangs
   its later lines at the text edge rather than under the marker.

   Drawn as a `border-top` on a zero-height box rather than as a glyph in
   `::marker`: the marker is a RULE, matched to the outline's own at 2px
     whatever the font, and `::marker` accepts
   `content` in far fewer readers than `::before`, and a disc scaled down to
   look like a rule is at the mercy of the font's own bullet metrics. Where
   `::before` is unsupported the rows simply lose their markers — the rule and
   the indent still carry the structure.

   IN FLOW, not `position: absolute`, and that is what `vertical-align:
   middle` buys: it puts a zero-height box at the parent's baseline plus half
   its x-height, i.e. exactly through the middle of a lowercase letter, from
   the FONT's metrics rather than from a guessed `top` offset that a host
   page's line-height would throw off.

   THE MARKER IS WHAT A THEMED TAG COLOURS ON THIS ROW, not the row's link text.
   A row wears `idea-tag-<tag>`, and `theme: (tags-color: ..)` publishes
   `--idea-tag-line` on that class, so the tick takes the tag's colour. Colouring
   the LINK instead would read as two dozen different link states down a long
   index; a column of coloured ticks lets the eye group kinds without touching
   the prose. An untagged row, and a row whose tags are not themed, keeps exactly
   the colour it has below: `--idea-tag-line` is only ever set by a generated rule
   for a tag the project themed by name.

   A ROW CARRYING SEVERAL THEMED TAGS takes the LAST generated rule that matches
   — plain cascade, since the generated rules all have equal specificity and are
   emitted in `tags-color` iteration order. That is the defined behaviour, not an
   accident worth a mechanism. */
[data-rookery="outline-row"]::before {
  content: "";
  display: inline-block;
  vertical-align: middle;
  width: 0.75em;
  height: 0;
  margin-left: -1.25em;
  margin-right: 0.5em;
  /* 2px, matching the rule it grows out of. A 1px marker on a 2px rule reads as
     a different, thinner kind of line rather than as a branch off that one. */
  border-top: var(--idea-rule-width, 2px) solid var(--idea-tag-line, var(--idea-border-color, var(--idea-link-color, rgba(128, 0, 255, 0.12))));
}

/* ---- Narrow screens: half the indent ---------------------------------------
   The indent COMPOUNDS. Every nesting level adds another rule and another
   padding at the previous level's content edge, which is what makes structure
   legible on a wide column and what makes it expensive on a phone: a note three
   deep on a 375px screen has given up a visible fraction of its measure to
   apparatus before a word is set. Halving the horizontal spacing buys that back
   without flattening the structure — the rules are all still there, one per
   level, just closer together.

   600px, and not a number invented here: it is the breakpoint
   `rookery.ohrg.org` already uses, and the site that renders this package
   agreeing with the package is worth more than a rounder figure. It also
   leaves 601px and up — tablet and desktop — at exactly today's spacing, which
   is the point: the constraint being answered is phone width, not small
   screens in general.

   THE THREE OUTLINE VALUES ARE ONE GEOMETRY. Do not halve one without the
   others. `::before`'s `margin-left` is the negation of `[data-rookery="outline"]`'s
   `padding-left` (that is what puts the hairline's left end ON the rule), and
   that same `padding-left` is the hairline's `width` plus its `margin-right`
   (that is what makes the text start exactly where the marker stops). Both
   survive halving — 0.375 + 0.25 = 0.625, and -0.625 negates 0.625 — but
   neither survives halving the padding alone, which detaches the bullet from
   the rule and looks, at a glance, merely a bit tighter.

   `border-left` deliberately does NOT halve: the rule is 2px at every width. A
   phone showing a 1px rule would read as a different design rather than a
   tighter one, and the fractional widths that halving invites render
   inconsistently across displays and on some not at all.

   THE INDENT DOES NOT HALVE AT ALL, because halving today's `0.5em` desktop
   value gives `0.25em` — 4px, barely more than the 2px rule beside it plus the
   space a reader reads as separation — which would leave a note's content
   sitting almost on its own rule. `--idea-pad` is therefore `0.5em` at every
   width, and this block does not touch it. Nothing else has to change with it:
   the tab's offset, `::before`'s width and a folded window's wash all READ the
   variable, so they follow whatever it is — the whole reason it is a variable
   rather than a literal; see its declaration. */
@media (max-width: 600px) {
  [data-rookery="outline"] {
    padding-left: 0.625em;
  }

  [data-rookery="outline-row"]::before {
    width: 0.375em;
    margin-left: -0.625em;
    margin-right: 0.25em;
  }
}
