/* =============================================================================
   THE SHARED SHELL. ONE HEADER, ONE STYLESHEET, LOADED BY BOTH SHELLS.
   =============================================================================

   The PI, 2026-08-21, with two URLs: "You told me logo and header have become
   uniform. / and /filing/... still look different."

   He was right, and every earlier answer had matched VALUES between two
   implementations rather than deleting one of them: the diamond on 2026-08-18,
   the wordmark size on 2026-08-19, the lockup on 2026-08-20. Each left two
   implementations, and two of anything drift. Measured on production before
   this file existed, at 1440:

       /               header  82px   6px nameplate rule   4 nav links, 14px sentence case
       /filing/...     header 132px   no rule              0 nav links, a 5-item uppercase
                                                           folio, a solid uppercase SEARCH

   `templates/_siteheader.html` made the MARKUP one thing. This file is the
   other half, because markup with no rules is not a shared component: the
   shared header first rendered on catalog pages as 21px serif links with no
   rule in a 226px bar, since `.site-header`, `.primary-nav`, `.nav-link`,
   `.btn--secondary` and `.account-menu` all lived in `platform.css`, which the
   catalog shell does not load.

   WHY A THIRD FILE RATHER THAN EITHER OF THE TWO THAT EXIST.

   Loading `platform.css` on catalog pages was tried first and measured, which
   is why it was not kept. On `/filing/...` at 1440 it restyled the catalog's
   own content: `.ident` 13px to 15px, every section `h2` 40px to 32px, and
   `.coverage` from 1088px wide to 576px, rewrapping it. Platform rules are
   authored to sit on top of the catalog's and they do exactly that.

   Moving these rules into `catalog.css` was the other candidate, and it is a
   large edit to a file two other lanes are working in, leaving `platform.css`
   owning half a component.

   So the shell gets its own sheet, the way the lockup got its own template.
   Both shells load it, in the same position relative to `catalog.css`, and
   neither owns it.

   ORDER: catalog.css, then this, then platform.css where there is one. That is
   the position these rules occupied inside platform.css, so nothing that used
   to override them stops doing so.

   WHAT IS NOT HERE YET, and it is the same shape: the FOOTER. The platform
   draws `.site-footer` and the catalog draws `.colophon`, two implementations
   of one thing, and the PI has already objected to the platform's. It is next.
   ========================================================================== */

/* -----------------------------------------------------------------------------
   2. THE AUTHENTICATED SHELL
   -----------------------------------------------------------------------------
   Bible 13.1. The catalog nameplate says what the publication is. The platform
   nameplate additionally says who is reading, as a byline rather than an
   avatar, in the same slot the catalog uses for its
   "PUBLIC · NO ACCOUNT · MACHINE-READABLE" line. The two shells therefore have
   the same silhouette and differ by one line of type.

   THE SESSION PREDICATE IS SINGLE-SOURCED. base.html computes _signed_in from
   _user.get("id") and nothing else may re-derive it. BUG-20260729T0049Z was a
   signed-in shell rendered to anonymous visitors, and nothing in this file may
   make .byline--account or .account visible outside that branch.
   -------------------------------------------------------------------------- */
/* EVERY CLASS NAME BELOW IS THE ONE ALREADY IN templates/base.html, and none of
   them is renamed. Two reasons, and the second is the binding one:

   1. base.html's own script queries `.account-menu` directly to close the
      disclosure on outside click. A rename breaks that with no test failure,
      no server error, and nothing visibly wrong until someone tries to dismiss
      the menu (WRK-20260808T1132Z section 4).
   2. Restyling should not need a markup edit. The shell already uses a
      <details> disclosure, which is exactly what bible 13.1 specifies, so the
      existing markup was right and only its clothes were wrong. */

.site-header { border-block-end: var(--rule-nameplate); }
.site-header__inner {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-3) var(--sp-5);
  padding-block: var(--sp-4);
}
/* The nameplate. The mark keeps its diamond, which is the same rotated square
   the system uses for "this one" on a radio and on an interval's point
   estimate, so the brand mark and the data vocabulary agree. */
/* .logo and .logo__mark are RETIRED: the lockup is .brand, declared once in
   catalog.css with its markup in templates/_brand.html. What survives of the
   platform's version is one layout fact, below: in this header the brand
   pushes the nav away. */
.site-header .brand { margin-inline-end: auto; }


/* Who is reading, set as a byline under the nameplate rather than as an avatar,
   in the slot the catalog uses for PUBLIC · NO ACCOUNT · MACHINE-READABLE. */
.byline--account {
  font-family: var(--face-ui);
  font-size: var(--t-micro); letter-spacing: var(--tr-caps);
  text-transform: uppercase; color: var(--c-ink-muted);
}

.primary-nav ul { display: flex; flex-wrap: wrap; gap: var(--sp-5); list-style: none; margin: 0; padding: 0; }
/* THE PAGE'S OPENING CLAIM IS NOT ITS SMALLEST TYPE.
   "OPEN · PEER REVIEWED · FREE FOR ACADEMIA" is the first thing the front page
   says and it was set at --t-micro, 11px, the size this system reserves for
   LABELS: smaller than every other word on the page including the provenance
   stamp. The PI, of the phone screenshot: "The academic tier is the tiniest
   font size too."

   He is right on the merits and on the law. Section 3's ladder makes --t-micro
   a label size, and a standing claim about what the product IS is not a label,
   it is the deck's sibling. --t-mini keeps the strip thin and stops it being
   the quietest thing on a page it opens.

   The `.kicker` label size elsewhere is untouched: where the class genuinely
   labels a section it is doing its job. */
.hero .kicker--plain { font-size: var(--t-mini); }

/* TWO ROWS ON A PHONE, NOT FOUR.
   Measured at 390: four rows and 266px of masthead, 32% of the screen spent
   before a word of the page, and the wordmark sat alone on a row of its own
   because it and the controls could not fit one line at 48px. The PI sent a
   photograph of it from his phone and I let it sit for hours.

   Wordmark and controls share the first line; the nav takes the second. That
   is the ordinary phone masthead. (An earlier version of this comment claimed
   it is "what the catalog shell already does"; it is not: the catalog
   masthead deliberately STACKS below 64rem, because squeezing it would cost
   the 44px floor on every control, and that decision is recorded there. The
   two shells solve the same width differently for measured reasons, and a
   comment asserting parity that does not exist is how two audit findings
   burned their first hour. WRK-20260819T1401Z.)

   The wordmark steps down from --t-title to --t-sub, the token the catalog's
   compact nameplate reads, because "a reader arrives on a page, not on a
   cover". (This sentence used to say 34px "the size the catalog's own compact
   nameplate has used all along": the catalog measured 30px on the day that
   was written, so the claim was false at birth. One token in both shells now
   makes the claim true by construction rather than by assertion.)

   The nav is NOT hidden behind a hamburger. Three words are not a menu, and a
   disclosure would cost a tap, a script dependency and a focus trap to conceal
   them, which is the argument the primary nav's own comment already makes. */
@media (max-width: 48rem) {
  .site-header__inner {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: center;
    gap: var(--sp-2) var(--sp-4);
  }
  /* Placement only. This class used to carry a font-size too, twice wrong
     (2.125rem "matching" a catalog that measured 30px, then a token repeated
     from another file); every visual property of the lockup now lives in
     .brand, declared once in catalog.css, and a size written here would be
     the two-implementations defect starting again. */
  .site-header__brand { grid-column: 1; grid-row: 1; }
  .site-header__inner > .row { grid-column: 2; grid-row: 1; justify-self: end; }
  .primary-nav { grid-column: 1 / -1; grid-row: 2; }
  /* ONE LINE THAT NEVER WRAPS, superseding the two-even-column grid that
     stood here. The grid was itself a fix for a ragged 2+1 wrap the PI
     photographed, and it traded the rag for a different spillage: an
     orphaned third item under an empty cell, 44px rows stacked 40px apart,
     which he then photographed too ("the three menu items look like someone
     spewed them there"). The lesson is that BOTH failures are the same
     failure: a nav that wraps at all reads as scattered on a phone.

     So it takes the strip grammar the catalog folio uses on the same widths:
     the folio's own type step, middway tracking, one 44px line, and when a
     width cannot hold it the strip SCROLLS, the tabs convention every phone
     user knows, with the half-visible item at the edge as its own
     affordance. At the folio step the three items fit 390 outright; the
     scroll is for narrower phones. One grammar, two shells.
     WRK-20260819T1401Z. */
  .primary-nav ul {
    display: flex; flex-wrap: nowrap;
    gap: var(--sp-5);
    overflow-x: auto;
    scrollbar-width: none;
    /* The strip is a drawn row of the bar, not text floating between the
       wordmark and the first band: a hairline above ties it to the identity
       row exactly as the catalog strip is tied to its nameplate. */
    border-block-start: var(--rule-hair);
    padding-block: var(--sp-1);
  }
  .primary-nav ul::-webkit-scrollbar { display: none; }
  .primary-nav li { flex: none; }
  .primary-nav .nav-link { white-space: nowrap; font-size: var(--t-micro); }
}

/* The 2.75rem floor is carried in the BOX rather than as a ::before extension,
   because these sit in a wrapping flex row and an invisible halo on a 33px box
   would overlap its neighbour above and below: two targets contesting one
   point, which section 8 forbids outright. The header does not grow, because
   the logo beside them is already 48px (measured: 86px header, unchanged).
   Before this rule the drawn box was 33px and the real hit target, measured
   with elementFromPoint rather than getBoundingClientRect, was 34x81
   (BUG-20260808T2330Z). */
/* SENTENCE CASE, AND THAT IS WHERE THE SIZE WAS (WRK-20260820T1321Z). The PI,
   2026-08-20, for the fourth time on this control: "the top nav still too
   large font, make it more similar to Apple or Stripe." The pixels were 15;
   Stripe's are 15 too and Apple's are 12. What neither of them does is set the
   nav in CAPITALS at 0.08em of tracking, and that is what was being read as
   size: uppercase raises every glyph to cap height and the tracking spends
   about a fifth more width on the same four words. So the case goes, the
   tracking goes, the weight rises to carry the smaller form, and the ink drops
   to muted so the bar reads as chrome beside the wordmark rather than as a
   rival to it. 14px, which is between the two references.

   The 2.75rem box stays. It is the tap target and it is not negotiable. */
.nav-link {
  display: inline-flex; align-items: center;
  min-block-size: 2.75rem;
  font-family: var(--face-ui); font-size: 0.875rem; font-weight: var(--w-medium);
  letter-spacing: -0.005em; text-transform: none;
  color: var(--c-ink-muted); text-decoration: none;
  border-block-end: 2px solid transparent;
}
.nav-link:hover { color: var(--c-ink-strong); }
/* aria-current is the source of truth for "you are here", so the style keys to
   the attribute rather than to a class a template has to remember to add. It is
   an underline rather than a colour change, so it survives greyscale (law 2). */
.nav-link[aria-current="page"] { border-block-end-color: var(--c-ink); color: var(--c-ink-strong); }
/* The header's own controls follow the nav out of capitals, or the bar reads
   in two voices: four sentence-case words beside a shouting button. */
.site-header .btn { text-transform: none; letter-spacing: 0; }

.row { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; }
.row--tight { gap: var(--sp-2); }

/* Three real buttons in a ruled set, the same construction as the catalog's
   PAPER / AUTO / INK setter. The labels differ and the stored value does not:
   DEC-20260808T1149Z makes both shells write `ebomi-theme`. */
/* The header's controls are a ruled set and must share one height. The theme
   setter is 46px, a 44px button inside a 1px rule on each side, and .btn--sm
   drew at 31px, so the two sat centred on one line with visibly different top
   and bottom edges: misaligned to the eye even though the centres agreed.
   catalog.css:2022 sets the same precedent for .entity__kicker, and the reason
   is the same: a small button is small on purpose EXCEPT beside a full-height
   control (BUG-20260810T0012Z).

   `stretch` on the cluster rather than a height on the button, because the
   number that has to match is the setter's OUTER height and no rule can name
   it: 44px of button plus 2px of rule. Stretch makes the row's own height the
   contract, so the two edges line up whatever the setter's border becomes. */
.site-header .row { align-items: stretch; }
.site-header .btn--sm { min-block-size: 2.75rem; }
.site-header .btn--sm::before { content: none; }
/* The bar's controls are chrome beside the brand, never rivals of it: the PI,
   of the signed-out header, "that sign in rectangle is larger than the logo.
   Bad." Exactly the floor, a quiet label size, padding that fits inside the
   44px box. Applies to any .btn the bar carries, so the next control cannot
   re-grow past the wordmark either. */
.site-header .btn {
  min-block-size: 2.75rem;
  padding: 0 var(--sp-4);
  font-size: var(--t-mini);
}

/* THE THEME CONTROL IS A MENU, NOT A SEGMENTED BAR.
   The PI sent Render's own picker as the model: a small trigger, and behind it
   a vertical list with an icon per row and a tick on the one in force. Three
   ruled segments sitting permanently in the masthead were the loudest element
   in it, which is law 1 inverted, and he had said so twice before sending a
   picture.

   The list is achromatic. The current row is marked by a TICK and by ink
   weight, never by a fill: law 2 says the meaning has to survive greyscale, and
   a tick survives it better than any colour could. The old control marked the
   current segment with `--c-mark`, a filled block, which is the vocabulary this
   system reserves for data. */
.theme-menu { display: grid; }
.theme-menu__item {
  display: grid;
  grid-template-columns: 1rem 1fr 0.9rem;
  align-items: center; gap: var(--sp-3);
  inline-size: 100%; min-block-size: 2.5rem;
  padding-inline: var(--sp-4);
  font-family: var(--face-ui); font-size: var(--t-mini);
  text-align: start;
  background: none; border: 0; color: var(--c-ink-muted); cursor: pointer;
}
.theme-menu__item:hover { background: var(--c-hover-wash); color: var(--c-ink-strong); }
.theme-menu__item[aria-pressed="true"] { color: var(--c-ink-strong); font-weight: var(--w-semi); }
.theme-menu__tick { visibility: hidden; }
.theme-menu__item[aria-pressed="true"] .theme-menu__tick { visibility: visible; }
.theme-menu__item svg { display: block; }

/* The trigger. One control at the size of its neighbours, which is the whole
   point: what used to be three boxes is now one. */
.theme-picker { position: relative; }
.theme-picker > summary {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-block-size: 2.75rem; padding-inline: var(--sp-3);
  font-family: var(--face-ui); font-size: var(--t-mini);
  letter-spacing: var(--tr-caps-snug); text-transform: uppercase;
  color: var(--c-ink-muted);
  border: var(--rule-control); cursor: pointer; list-style: none;
}
.theme-picker > summary::-webkit-details-marker { display: none; }
.theme-picker > summary::after { content: "\25BE"; font-size: var(--t-micro); }
.theme-picker[open] > summary::after { content: "\25B4"; }
.theme-picker__icon { display: inline-flex; }
/* THE TRIGGER IS A SQUARE THAT REPORTS THE STATE.
   113x44 with the word THEME taking 51 of it, for a control that names all
   three of its options the moment it opens. Law 1: the chrome is subordinate.
   44x44 now, which is the floor and therefore the smallest it is allowed to be.

   All three icons ship and CSS picks the live one, so the trigger tracks the
   state with NO new script. The state already lives in `aria-pressed`, which
   the existing paintToggle() maintains, so this reads the same source the menu
   draws its tick from and cannot disagree with it. */
.theme-picker > summary {
  inline-size: 2.75rem; min-inline-size: 0; padding-inline: 0;
  justify-content: center; gap: 0;
}
.theme-picker > summary::after { content: none; }
.theme-picker__icon { display: none; }
/* The fallback is System, which is also the default, so a trigger rendered
   before any script has run shows the state the page is actually in. */
.theme-picker__icon--system { display: inline-flex; }
.theme-picker:has([data-theme-choice="light"][aria-pressed="true"]) .theme-picker__icon--system,
.theme-picker:has([data-theme-choice="dark"][aria-pressed="true"])  .theme-picker__icon--system { display: none; }
.theme-picker:has([data-theme-choice="light"][aria-pressed="true"]) .theme-picker__icon--light { display: inline-flex; }
.theme-picker:has([data-theme-choice="dark"][aria-pressed="true"])  .theme-picker__icon--dark  { display: inline-flex; }
/* A CLOSED DISCLOSURE MUST NOT PAINT ITS PANEL, STATED EXPLICITLY.
   Measured at 390px on the front page: `details.open` was FALSE and the panel
   still computed `display: block` with a real 176x138 box, so the theme menu
   stood permanently under the masthead and the header measured 266px in seven
   rows. The browser's own hiding of a closed `<details>`' content did not reach
   an absolutely positioned child here, which is the sort of thing that differs
   by engine and is not worth relying on either way.

   Written for BOTH panels rather than the one that was caught, for the same
   reason the dismiss handler is bound to `header details` rather than to a
   class: a rule a pattern needs belongs on the pattern. */
.theme-picker:not([open]) .theme-picker__panel,
.account-menu:not([open]) .account-menu__panel,
.folio-account:not([open]) .folio-account__panel { display: none; }

.theme-picker__panel {
  position: absolute; inset-inline-end: 0; inset-block-start: calc(100% + var(--sp-2));
  z-index: 5; min-inline-size: 11rem;
  background: var(--c-surface); border: var(--rule-control);
  padding-block: var(--sp-2);
}

/* The account affordance. Already a <details> disclosure in the markup, which
   is what bible 13.1 argues for: it works with scripting disabled, needs no
   focus trap, and the broadsheet has no vocabulary for a floating card.
   No shadow and no radius (law 7). */
.account-menu { position: relative; }
.account-menu > summary {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-block-size: 2.75rem; padding-inline: var(--sp-3);
  font-family: var(--face-ui); font-size: var(--t-mini);
  letter-spacing: var(--tr-caps-snug); text-transform: uppercase;
  border: var(--rule-control); cursor: pointer; list-style: none;
}
.account-menu > summary::-webkit-details-marker { display: none; }
.account-menu > summary::after { content: "\25BE"; font-size: var(--t-micro); }
.account-menu[open] > summary::after { content: "\25B4"; }
.account-menu__panel {
  position: absolute; inset-inline-end: 0; inset-block-start: calc(100% + var(--sp-2));
  z-index: 5; min-inline-size: 14rem;
  background: var(--c-surface); border: var(--rule-control);
  display: grid;
}
/* THE SUMMARY CARRIES AN ADDRESS NOW, not the word "Account", so it can no
   longer be uppercased or allowed to set the header's width. Lowercase because
   an email address is a string to be READ and matched, not a label; truncated
   because a long institutional address would otherwise push the nav off a
   laptop; and the full value is in the title attribute for anyone who needs it.
   PI, 2026-08-19, asking for the Render-shaped cluster. */
.account-menu > summary {
  text-transform: none; letter-spacing: var(--tr-flat);
  max-inline-size: 16rem; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.account-menu__meta {
  font-family: var(--face-ui); font-size: var(--t-micro);
  letter-spacing: var(--tr-caps); text-transform: uppercase;
  color: var(--c-ink-muted);
  padding: var(--sp-3) var(--sp-4) 0;
}
/* The address in full inside the panel, where there is room for it and where a
   reader on a shared machine goes to check who they are. Set in the identifier
   face, because that is what it is. */
.account-menu__email {
  font-family: var(--face-ident); font-size: var(--t-mini);
  color: var(--c-ink-strong);
  padding: 0 var(--sp-4) var(--sp-3);
  overflow-wrap: anywhere;
}
/* THE THEME CONTROL IN THE PANEL IS CHROME AND SITS AT ITS OWN SIZE. My first
   version stretched it to `repeat(3, 1fr)` across the panel, which made three
   large blocks and gave a preference more weight than Sign out directly below
   it. The PI called it "way too large, looks ugly" and asked whether it was in
   line with the bible; it was not (2026-08-19).

   Law 1 is that the chrome is achromatic and subordinate, and the density
   ladder's own review rule is that a gear louder than its surface is a defect
   rather than a taste. A theme preference is the quietest thing in this menu
   and now looks it: intrinsic width, the panel's own type scale, and the 44px
   floor met by the row rather than by three stretched boxes.

   `min-block-size: 0` on the buttons because the row supplies the target: the
   whole control is 2.25rem and each button spans it, which is a hit area of
   36px by its own width. Kept above the floor by the invisible extension the
   rest of this stylesheet uses, rather than by growing the box. */
.account-menu__panel .theme-menu { margin-block-end: var(--sp-2); }
.account-menu__panel hr {
  margin: 0; border: 0; border-block-start: var(--rule-hair);
}
.account-menu__item {
  min-block-size: 2.75rem; display: flex; align-items: center;
  padding: var(--sp-2) var(--sp-4);
  font-family: var(--face-ui); font-size: var(--t-small);
  color: var(--c-ink); text-decoration: none;
  border-block-end: var(--rule-hair);
}
.account-menu__item:last-child { border-block-end: 0; }
.account-menu__item:hover { background: var(--c-hover-wash); }
/* THE SIGN-OUT ROW. It is a <button> in a <form>, because a plain form is what
   makes signing out work with scripting off, and a button needs three
   declarations before it looks like the anchors either side of it: the UA
   gives it a border, a background and a shrink-to-fit width. The form is
   `display: contents` so it does not add a box between the panel and the row.
   `text-align: start` because a button centres its label and every other row
   in this menu begins at the same left edge. */
.account-menu__signout { display: contents; }
button.account-menu__item {
  inline-size: 100%;
  border: 0; border-block-end: var(--rule-hair);
  background: none;
  font: inherit; font-family: var(--face-ui); font-size: var(--t-small);
  text-align: start; cursor: pointer;
}
.account-menu__panel > .account-menu__signout:last-child button.account-menu__item {
  border-block-end: 0;
}
.account-menu hr { border: 0; border-block-start: var(--rule-thin); margin: 0; }

/* -----------------------------------------------------------------------------
   THE PAGE FRAME THE HEADER SITS IN
   -----------------------------------------------------------------------------
   The header draws `<div class="page">` in both shells, and `.page` was in
   platform.css. Without it the header's contents on a catalog page sat at the
   viewport's left edge while the identical markup on `/` was centred: brand
   left 0 against left 176 at 1440. It is the shell's frame, so it lives with
   the shell.
   -------------------------------------------------------------------------- */
.page {
  inline-size: min(100% - (2 * var(--gutter)), calc(var(--measure) + (2 * var(--flank))));
  margin-inline: auto;
}
.page > .wide { inline-size: 100%; }
.page > .full {
  inline-size: 100vw;
  margin-inline: calc(50% - 50vw);
}

/* -----------------------------------------------------------------------------
   THE TWO BUTTON MODIFIERS THE SHELL WEARS
   -----------------------------------------------------------------------------
   `.btn` itself is in catalog.css and both shells already had it. These two
   were in platform.css, so the header's Sign in control was a bare `.btn` on
   every catalog page. They move whole rather than being copied: a second copy
   is the thing this whole change exists to stop.
   -------------------------------------------------------------------------- */
.btn--secondary:hover  { background: var(--c-hover-wash); --btn-rule: var(--c-mark); }
.btn--secondary:active { translate: 0 1px; }
.btn--ghost {
  --btn-rule: transparent; padding-inline: var(--sp-2);
  text-decoration: underline; text-decoration-thickness: 1px;
  text-underline-offset: 0.35em; text-decoration-color: var(--c-rule);
}
.btn--ghost:hover { text-decoration-thickness: 2px; text-decoration-color: currentColor; background: var(--c-hover-wash); }
/* --t-mini, not --t-micro: bible 8.3 reserves the micro step for table and
   card FEET, and the platform wears btn--sm on real controls, including the
   dashboard's whole action set. One rung up keeps them compact without
   labelling working buttons at folio size, which is the PI's "tiny font"
   complaint in its most literal form. WRK-20260819T1401Z decision 1's tail. */
.btn--sm { padding: var(--sp-2) var(--sp-3); font-size: var(--t-mini); min-block-size: 0; }
/* The small button's invisible hit extension, carried from catalog.css:913: it
   grows the pointer target to the 44px floor on any axis where the drawn box is
   smaller, and insets to nothing where it is not. */
.btn--sm::before {
  content: ""; position: absolute;
  inset-block: min(0px, calc((100% - 2.75rem) / 2));
  inset-inline: min(0px, calc((100% - 2.75rem) / 2));
}
.btn-group--nowrap { flex-wrap: nowrap; }

/* --- Tables. .table maps onto the broadsheet's ledger: no zebra, no cell
       borders, no container box. Hairlines between rows, a 2px rule under the


/* -----------------------------------------------------------------------------
   THE FOOTER
   -----------------------------------------------------------------------------
   `templates/_sitefooter.html` draws it in both shells, so its rules live with
   the shell rather than in platform.css. Moved whole from there on 2026-08-21.

   The catalog appends its colophon through the macro's `{% call %}` slot and
   `.colophon` stays in catalog.css, because a licence statement, a coverage
   line and a dataset citation belong to one half of the site.
   -------------------------------------------------------------------------- */
.site-footer { border-block-start: var(--rule-mid); padding-block: var(--sp-8); color: var(--c-ink-muted); }
.site-footer > .page > * { font-size: var(--t-mini); }

/* THE FOOTER IS GROUPED, and until 2026-08-21 it was one wrapping row of eight
   links. The PI: "footer placement links seem just thrown into the page not
   nicely aligned. See how Apple and stripe do it." Both halves of that are
   right. Eight links in a flex row break at whatever width the viewport
   happens to be, so the wrap fell between whichever two links it landed on and
   the last one was orphaned on a line of its own; and nothing said which links
   were legal and which were product, so finding the privacy policy meant
   reading all eight.

   Three columns with a heading each is what Apple and Stripe both do, and the
   grouping is INFORMATION rather than decoration: it answers "where would that
   be" before a single link has been read. `auto-fit` with a 11rem floor makes
   it one column on a phone without a second breakpoint to keep in step. */
.footer-inner { display: grid; gap: var(--sp-7); }
/* ONE COLUMN ON A PHONE, THREE ABOVE 40rem, and stated rather than left to
   `auto-fit`. `auto-fit` with an 11rem floor gave 390px exactly two columns,
   which put Product and Project side by side and left Legal alone underneath:
   a three-group footer wearing a two-column grid reads as a mistake, and it is
   the same rag this replaced arriving one level up. Two breakpointed states,
   both of which are a shape somebody chose. */
.footer-cols {
  display: grid; gap: var(--sp-6) var(--sp-7);
  grid-template-columns: 1fr;
  max-inline-size: 48rem;
}
@media (min-width: 40rem) {
  .footer-cols { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.footer-col { display: grid; gap: var(--sp-2); align-content: start; }
.footer-col__head {
  font-family: var(--face-ui); font-size: var(--t-micro);
  letter-spacing: var(--tr-caps); text-transform: uppercase;
  color: var(--c-ink-faint); font-weight: var(--w-medium);
  margin: 0;
}
/* The bottom bar. These are statements about the site rather than
   destinations, so they sit under a rule instead of among the links. */
.footer-bar {
  display: grid; gap: var(--sp-2);
  padding-block-start: var(--sp-5);
  border-block-start: var(--rule-hair);
}
/* Columns, so the list is a list. The old rule made every ul a wrapping flex
   row, which is what produced the rag this replaced. */
.site-footer ul { display: grid; gap: 0; list-style: none; margin: 0; padding: 0; }
/* Seven links at a drawn height of 17px, which missed section 8's 44px floor
   and also WCAG 2.2 SC 2.5.8's 24px minimum; a footer link in a list does not
   take that criterion's inline-in-a-sentence exception. Real height rather than
   a hit extension for the same reason as .nav-link: these wrap, and a halo
   would have neighbours contesting one point.

   `display: flex` on the LI was wrong and is removed. One footer item is
   "Questions? <a>ping@ebomi.com</a>", so the plain text became an anonymous
   flex item beside the link and the pair stopped reading as a sentence
   (BUG-20260810T0012Z). inline-flex on the anchor alone gives the anchor its
   floor and leaves the text flowing around it. */
.site-footer nav a {
  display: inline-flex; align-items: center;
  min-block-size: 2.75rem;
}
.site-footer a { color: var(--c-ink); }
/* The one sentence in the footer that is news rather than furniture: the
   product is now free. It takes the body size back off the rule above and the
   full ink colour, because a notice nobody can read is not a notice. It is the
   only channel to the 925 registered accounts that never completed a login and
   are deliberately not being emailed. */
.footer-notice {
  font-size: var(--t-small) !important;
  color: var(--c-ink-strong);
  font-weight: var(--w-medium);
}


/* -----------------------------------------------------------------------------
   THE SWITCH
   -----------------------------------------------------------------------------
   Moved out of platform.css on 2026-08-22, when the company page gained the
   watch controls: both shells draw this now, and platform.css is loaded by only
   one of them, so it rendered as a native checkbox on /issuer/{cik}. Same
   lesson as the header, one component later.
   -------------------------------------------------------------------------- */
/* `gap` on the switch itself, so any caller that gives it a visible word gets
   the spacing without knowing about it. The dashboard's rows carry no word;
   the company page's single control does, because a switch alone on a page has
   nothing else to take its meaning from. */
.switch {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  min-block-size: 2.75rem; cursor: pointer;
}
.switch__label { font-family: var(--face-ui); font-size: var(--t-small); color: var(--c-ink); }
/* The control IS a real checkbox: dashboard.html's renderRow builds
   <label class="switch"><input type="checkbox"><span class="switch__track">.
   The input is hidden from paint and kept in the accessibility tree and the
   tab order, so the state is driven from :checked rather than from an
   aria-checked attribute nothing sets. Styling this as if it were a button
   left the raw checkbox drawn next to the track, which is what shipped in the
   first capture. */
.switch input[type="checkbox"] {
  position: absolute; opacity: 0; inline-size: 0; block-size: 0; margin: 0;
}
.switch input:checked + .switch__track { background: var(--c-mark); }
.switch input:checked + .switch__track::after { translate: 1.15rem 0; background: var(--c-on-mark); }
.switch input:focus-visible + .switch__track { outline: 2px solid var(--c-focus); outline-offset: 2px; }
.switch__track {
  inline-size: 2.75rem; block-size: 1.5rem;
  border: var(--rule-control); border-radius: var(--radius);
  position: relative; display: block;
  transition: background-color var(--dur-fast) var(--ease-out);
}
.switch__track::after {
  content: ""; position: absolute;
  inset-block: 2px; inset-inline-start: 2px;
  inline-size: 1.1rem; background: var(--c-ink);
  transition: translate var(--dur-fast) var(--ease-out);
}
