/* components/course-chrome.css
   the course roadmap sidebar every page of this repo carries. Not product.

   Reads: --bevel-hair, --chrome-accent, --chrome-bg, --chrome-border, --chrome-muted,
          --chrome-pressed, --chrome-text, --focus-ring, --scrim, --swatch-dark, --swatch-light
   Stand:  every page of the kit, because this is the panel they are read through and not a specimen on one of them
   Stands on: 106 ui-visual screens, which is ALL of them, and it is the only file here that
     does. Every product component stops at 105 and the one it misses is the same each time,
     overview.html: a contact sheet carrying this sidebar and its own tk-* markup and no
     product chrome at all. So 105 is the right number everywhere else, and backlog 52's
     premise held for exactly one file. Re-counted 2026-08-10 from the rendered DOM.
   Colour goes through a role, geometry straight from a primitive. No em dash. */

/* .wf-screen > a.planned::after was here and is gone (step 7e). .wf-screen is
   the grey tree's screen drawer, 2392 uses there and none in the painted tree,
   the vitrine or a stand: the sidebar this file DOES style marks a planned stage
   with .sidebar-page-link.planned, a few lines down. It survived the colour sweep only
   because that gate was counting wireframes/*.html as markup. */
/* THIS BUTTON LEFT THE BRAND ROW ON 2026-08-10, and it left because it was drawn
   over the product's logo in every screenshot ever taken of this repository.
   It sat at top 12 left 12, 36 square, so it occupied x 12 to 48 on every one of the
   160 documents below the dock. The header row's padding-left computes 14 on a phone,
   so the logo starts at x=14: measured at five widths in both themes, the button
   covered 34px of the mark and the word on 88 pages, a control on 89 more at the top
   RIGHT if it had gone there, and text on 55. `header.css` had an answer written into
   its mobile block, `.app-header .row{padding-left:var(--space-56)}`, and that
   declaration had never once applied, because the same selector is set again at equal
   specificity thirty-four lines below it.
   THE CHROME MOVES AND THE PRODUCT DOES NOT, which is the same decision the dock took
   on the same day: an indent would have pushed the brand 42px off the column every
   line under it aligns to, in the review build only, for a tool. So the question was
   which corner the product does not use, and it was measured rather than guessed, all
   160 documents at 390, 700, 1280, five candidate boxes, classifying what sits under
   each: **bottom right is empty on 147 of 160 pages** above the desk rung, against
   top-left covering the brand on 88 and top-right a control on 89. It is also the
   corner a left drawer never reaches, so the button stays clickable with the panel
   open, which at top-left it was not: it drew ON the panel it had opened.
   BELOW THE DESK RUNG IT LIFTS BY 132, because that is where the product stacks two
   things on this edge and the number is their sum. `.bottom-nav` is
   `position:sticky;bottom:0` and measures 56 tall on the 39 screens that carry one;
   `.bet-dock` in betpanel.css is `position:sticky;bottom:52px` and measures **68 tall
   in all 48 readings of it**, so its top sits **exactly 120 from the bottom edge**.
   132 is that 120 and the same 12 gap this button uses everywhere else. From the rung
   up, 12 is enough and the lift is dropped: the nav is gone there, and the dock
   reserves 52px under itself for a nav that is not there, so the bottom strip is free.
   THE FIRST NUMBER HERE WAS 68 AND IT WAS WRONG, which is worth keeping. It was 56
   plus 12, correct arithmetic for one of the two bars and blind to the other, and it
   was blind because the first measurement read every page at scroll 0, where a sticky
   dock has not stuck yet. Re-read at top, middle and end of every page: a lift of 68
   lands on the bet dock in 32 readings of 960 and a lift of 12 on a nav item in 63.
   **Reading a sticky element without scrolling is reading the source again.**
   IT STILL CANNOT BE PERFECT. A sticky nav sits at the viewport's bottom on a long
   page and at the CONTENT's bottom on a short one, so no fixed offset tracks it, and
   about ten readings of 960 land on a nav item that stopped short of the edge. What is
   NOT done about that is tune the number until those go: 120 measured slightly better
   than 132 and is refused, because it is a height fitted to today's content, which is
   the mistake this same pass closed at 520 in position.css an hour earlier.
   A modal dialog is in the top layer, so it covers this button wherever it stands and
   the page behind it is inert either way: on the 17 screens that ship with a sheet
   open, this button was visible at the top left and is behind the sheet now, and on
   none of them was it ever clickable. */
/* AND IT IS 44 SINCE 2026-08-10, WHICH THE CORNER PAID FOR. `docs/backlog.md` 44 filed
   this at 36 beside a `.theme-switch` twelve lines down that declares 44, and it was filed
   rather than fixed for one reason: the button stood over the TOP LEFT of every screen, so
   8px more of it moved onto a header laid out around 36. It stands in the bottom right now,
   in a corner measured empty on 147 of 160 documents, so the 8px lands on nothing. It is
   the only way to open this route below the dock, which makes it the one control on the
   page a person has no alternative to, and it was the last control in the repository under
   the project's own floor. */
/* THE LIFT IS COMPUTED FROM THE FURNITURE IT CLEARS SINCE 2026-08-16, AND THAT DELETED BOTH THE
   RUNG AND THE OVER-PROVISION BELOW. Everything argued below is still the reason this control
   stands where it does; what changed is that the two bars it clears now have names,
   `--bottom-nav-h` and `--dock-h`, each zeroed by the rung block that hides its own bar. So this
   one declaration is 56+68+12 below DESK, 0+68+12 between DESK and DETAIL where the nav is gone,
   and 0+0+12 from DETAIL up where both are, which is the plain `--space-12` this rule always had.
   The width query that used to carry the lift is gone with it: a number that tracks the thing it
   clears does not need a rung to be cut at, and this file's own paragraph below about a boundary
   borrowed in the wrong unit is exactly the defect that removes.
   The 12 is exact again, too. It had quietly become 8: the dock's own offset was `52px` against a
   nav measuring 56, so the dock's top sat 120 from the edge while the 132 here was arithmetic for
   124. Two files, three numbers, and only the third one was ever re-read. */
.rm-toggle{position:fixed;bottom:calc(var(--bottom-nav-h) + var(--dock-h) + var(--space-12));right:var(--space-12);z-index:var(--z-chrome-top);background:var(--chrome-bg);border:var(--hairline) solid var(--chrome-border);border-radius:var(--radius-6);width:var(--control-44);height:var(--control-44);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-4);cursor:pointer}
/* rung: DETAIL 760, and this is BELOW it, because that is where the bet dock exists:
   betpanel.css docks the panel as a sidebar at 760 and the dock goes with it. It was
   written at the DESK rung first and that was 8px too optimistic - at 36 the button fitted
   inside the 52px strip the dock reserves under itself, at 44 it does not, so it crossed
   the dock's bottom edge by 4px in 16 readings of 960 the moment the floor was applied.
   Growing a control is a reason to re-read the clearance it was written against.
   The ladder is in components/tokens.css, page frame; the 132 is argued above.

   THE RUNG AND THE LIFT WENT TO `rem` ON 2026-08-14, AND THE PX VERSION WAS A REAL
   DEFECT AT EVERY BROWSER DEFAULT BUT THE ONE EVERYBODY TESTS. This block was cut at
   `759.98px` while the thing it is cut FOR, `betpanel.css`'s dock, is cut at
   `47.5rem`, and the two were written on the same day to be the same rung. They are
   the same number only at a 16px root. **`Page.setFontSizes` on event-detail.html,
   seven widths, three defaults: at 16 the toggle clears the dock everywhere; at 20 it
   crosses it by 4px at 760, 860 and 900; at 24 by 4px from 760 to 1000 and by 5px at
   640.** The chrome is 220 PHYSICAL pixels wide whatever the reader's font is and that
   argument still holds for the sidebar, but it was never an argument about a rung that
   exists to mirror a product rung: **a boundary borrowed from another file has to be
   borrowed in that file's unit, or the two only agree at the default.**
   The 132 became `8.25rem` for the second half of the same defect: it clears two
   pieces of product furniture whose height is set by the type inside them, so at a
   24px root the stack is taller and a fixed 132 is short. That is the 5px at 640.
   Re-measured after, all three defaults, seven widths: 0 overlap everywhere. */
/* AND THE BLOCK THAT STOOD HERE IS GONE, 2026-08-16. It was
   `@media(max-width:47.49875rem){.rm-toggle{bottom:8.25rem}}`, and everything above is the record
   of what it cost to get one number right in a file that could not see the number it depended on.
   The rem conversion of 2026-08-14 was correct and it was still a copy: `8.25rem` is 132, which is
   the sum of two other files' heights plus a gap, written here, re-measured here, and wrong again
   the moment either of those files moved. The clearance is `calc(var(--bottom-nav-h) +
   var(--dock-h) + var(--space-12))` on the base rule now, so this file states the RELATION and the
   two components state their own sizes. One width query fewer, and the borrowed-boundary trap this
   file documents cannot be re-entered here. */
.rm-toggle span{display:block;width:var(--icon-16);height:1.5px;background:var(--chrome-text);border-radius:var(--radius-2)}
.rm-overlay{display:none;position:fixed;inset:0;background:var(--scrim);z-index:var(--z-chrome-scrim);backdrop-filter:blur(2px)}
.rm-overlay.open{display:block}
/* THE PANEL NAMES ITS OWN TYPE. On a painted screen and in the vitrine it
   inherited the body font and that was the product's; on the 28 course pages,
   which have their own stylesheet and their own --font, it would inherit Inter
   and be a different panel on every second page. A component that changes with
   the page it stands on is not a component. */
/* THE FIFTH `vh`, AND THE CENSUS THAT CLOSED BACKLOG 124 COULD NOT SEE IT. That row named
   `betpanel.css`, `toc.css` and `catnav.css` as the three files that had written the `svh`
   argument, and `base.css` as the one full-page shell that never had; it counted WHO HAD
   WRITTEN THE ARGUMENT and then the shell, and never counted WHO USES THE UNIT. This file is
   neither of those, so it stayed at `height:100vh` for two days after the row was closed. Taken
   from the comment-stripped source on 2026-08-15: seven viewport-height sites in `components/`,
   six of them `svh` or a pair, and this one.
   AND IT IS A HARDER CASE THAN THE SHELL WAS, because `min-height` only ever ADDS room while
   `height` on a fixed SCROLL CONTAINER puts the container's own bottom edge off screen. `vh` is
   the LARGE viewport, the one with the browser's bar retracted, so the panel is taller than the
   window until the reader scrolls the document, and a fixed panel is not what scrolling the
   document moves. Measured in both engines at 390x640 with the drawer open and its transition
   killed, the panel forced to 700 to stand in for a 60px bar, scrolled to its own maximum:
   **the last link in the roadmap, `ui-kit` in the closing note, sits 38px below the visible edge
   and there is no scroll left to reach it.** At 700 one link is cut and at 640 none is, in
   Chromium 151 and WebKit 26.5 alike, which is the reading moving when the input moved.
   ONE DECLARATION, NOT A PAIR, AND IT WAS A PAIR FOR ONE DAY. Backlog 155 was decided on
   2026-08-15: `tokens.css` had dropped the `vh` fallback beside `--sheet-cap` on 2026-08-14 and
   the pair turned out to be unwritable for a token at all, so the system could only be made to
   say one thing by dropping it everywhere. Five sites, one sentence. The argument and the cost
   are in `base.css` beside `.device`. */
.sidebar{font-family:var(--font-body);position:fixed;top:0;left:0;width:220px;height:100svh;background:var(--chrome-bg);border-right:var(--hairline) solid var(--chrome-border);display:flex;flex-direction:column;overflow-y:auto;scrollbar-width:none;z-index:var(--z-chrome);transform:translateX(-100%);transition:transform var(--dur-slow) var(--ease-enter)}
.sidebar.open{transform:translateX(0)}
.sidebar::-webkit-scrollbar{display:none}
.sidebar-brand{padding:var(--space-20) var(--space-16) var(--space-16);border-bottom:var(--hairline) solid var(--chrome-border)}
.sidebar-project-name{font-size:var(--text-11);font-weight:var(--weight-bold);letter-spacing:var(--track-caps);text-transform:uppercase;color:var(--chrome-accent);line-height:var(--leading-base)}
.sidebar-nav{padding:var(--space-12) 0;flex:1}
.sidebar-page-link{display:flex;align-items:center;justify-content:space-between;font-size:var(--text-13);font-weight:var(--weight-medium);color:var(--chrome-text);text-decoration:none;padding:var(--space-8) var(--space-16);cursor:pointer}
.sidebar-page-link:hover:not(.planned){background:var(--bevel-hair)}
/* The right-hand slot of the row, which is what the space-between above is for.
   It says what is BEHIND the row before it is clicked (52 glyphs, 14 molecules),
   and that is the one thing a list of links cannot say on its own. Normal rather
   than italic because <i> is the shortest element that survives an innerHTML
   round trip, not a claim about the text; muted because it is the second thing
   read, and muted is the ROLE, never an opacity, for the reason above. */
.sidebar-page-link i{font-style:normal;font-size:var(--text-10);font-weight:var(--weight-regular);letter-spacing:var(--track-label);color:var(--chrome-muted);flex:0 0 auto;padding-left:var(--space-8)}
.sidebar-page-link.active{color:var(--chrome-accent)}
/* Same lesson as the labels below: a planned stage was dimmed to opacity .6,
   which took --chrome-muted from 5.03:1 to 2.69:1. It is the muted colour, and
   what separates it from the next stage up is the badge, which is what the badge
   is for. */
.sidebar-page-link.planned{color:var(--chrome-muted);cursor:default}
.sidebar-page-link.planned::after{content:'Soon';font-size:var(--text-10);font-weight:var(--weight-bold);letter-spacing:var(--track-caps);text-transform:uppercase;background:var(--bevel-hair);color:var(--chrome-muted);padding:var(--space-2) var(--space-4);border-radius:var(--radius-6);border:var(--hairline) solid var(--chrome-border)}
.sidebar-page-link.planned.next{color:var(--chrome-text)}
.sidebar-page-link.planned.next::after{content:'Next';color:var(--chrome-accent);border-color:var(--chrome-accent)}
.sidebar-sub{padding:var(--space-2) 0 var(--space-8)}
.sidebar-sub-link{display:block;font-size:var(--text-12);color:var(--chrome-muted);text-decoration:none;padding:var(--space-4) var(--space-16) var(--space-4) var(--space-28);position:relative}
.sidebar-sub-link:hover{color:var(--chrome-text)}
/* ONE MARK FOR THE PAGE YOU ARE ON, at either level. It used to be brass on a
   top-level row and grey-plus-a-dot on a nested one, so the two panels this file
   paints answered "where am I" two different ways: in the vitrine the page you
   were on was the brass row, in the screens tree the brass row was the family
   and the page you were on was grey. The dot stays as the nested-level tick,
   which is why it is only on the indented row: it has the room. */
.sidebar-sub-link.active{color:var(--chrome-accent)}
.sidebar-sub-link.active::before{content:'';position:absolute;left:17px;top:50%;transform:translateY(-50%);width:var(--size-4);height:var(--size-4);border-radius:50%;background:var(--chrome-accent)}
.sidebar-back{display:flex;align-items:center;gap:var(--space-8);font-size:var(--text-12);font-weight:var(--weight-semibold);color:var(--chrome-muted);text-decoration:none;padding:var(--space-12) var(--space-16);border-bottom:var(--hairline) solid var(--chrome-border)}
.sidebar-back:hover{color:var(--chrome-accent);background:var(--bevel-hair)}
.sidebar-back .bk-arrow{font-size:var(--text-14);line-height:var(--leading-none)}
/* THE LABEL OVER A RUN OF ROWS, one class for both panels. A group of components
   in the vitrine and a family of screens in the painted tree are the same thing
   said twice: rows that belong together, under a name that opens nothing. There
   were two classes for it, .sidebar-divider here and .sidebar-sub-head in the
   screens tree, and the nested one was drawn HEAVIER than the one above it
   (opacity .7 over .55, bold over semibold), so depth read backwards.

   QUIET IS A COLOUR, NOT AN OPACITY, which is what this pass had to learn twice.
   Both labels used to be dimmed with opacity, and opacity fades the text INTO
   its background: --chrome-muted is 5.03:1 on the panel and the same value at
   opacity .55 is 2.37:1. Every contrast sweep this repo has run read
   getComputedStyle().color, which does not carry opacity, so none of them could
   see it. Depth is the colour role now, the outer label taking the text colour
   and the nested one the muted, and both clear AA because the value is the thing
   being checked. */
.sidebar-divider{font-size:var(--text-10);font-weight:var(--weight-bold);letter-spacing:var(--track-caps-loud);text-transform:uppercase;color:var(--chrome-text);padding:var(--space-16) var(--space-16) var(--space-8)}
.sidebar-divider.sub{font-weight:var(--weight-semibold);letter-spacing:var(--track-caps);color:var(--chrome-muted);padding:var(--space-8) var(--space-16) var(--space-4) var(--space-28)}
/* the group you are IN, which is not the page you are ON. The label takes the
   brass, the row takes the brass, and the row is four sizes bigger. */
.sidebar-divider.active,.sidebar-divider.sub.active{color:var(--chrome-accent)}
.sidebar-note{font-size:var(--text-11);line-height:var(--leading-base);color:var(--chrome-muted);padding:var(--space-8) var(--space-16)}
/* A LINK IN THE NOTE IS THE PANEL'S OWN, not the vitrine's. It lived in
   the kit's own stylesheet as .ck-note-link, so the same note rendered in the browser's
   blue anywhere the vitrine's stylesheet was not loaded, which is every specimen
   of this component and every screen in ui-visual. The reason it was written
   there is still true and now belongs here: the panel keeps one dark palette in
   both grounds, so nothing inside it may read a themed role. --chrome-accent,
   never --text-brass, which in daylight is the dark brass for a pale surface. */
.sidebar-note a{color:var(--chrome-accent);text-decoration:underline;text-decoration-color:var(--chrome-border);text-underline-offset:2px}
.sidebar-note a:hover{text-decoration-color:currentColor}
/* the theme switch. It lives in the chrome and is drawn in chrome colour on
   purpose: it is the control that moves the product, so it must not move with
   it. The two swatches are the two grounds, side by side, and the one in play
   is ringed, so the button says what it will do without a word of label. */
.theme-switch{display:flex;align-items:center;gap:var(--space-8);width:100%;box-sizing:border-box;margin:0;padding:var(--space-12) var(--space-16);background:none;border:0;border-bottom:var(--hairline) solid var(--chrome-border);font-family:inherit;font-size:var(--text-12);font-weight:var(--weight-semibold);letter-spacing:var(--track-caps);color:var(--chrome-muted);text-align:left;cursor:pointer;min-height:var(--control-44)}
.theme-switch:hover{color:var(--chrome-text);background:var(--bevel-hair)}
.theme-switch .ts-swatches{display:flex;flex:0 0 auto;gap:var(--space-4)}
.theme-switch .ts-sw{width:var(--size-16);height:var(--size-16);border-radius:var(--radius-6);border:var(--hairline) solid var(--chrome-border);box-shadow:none}
.theme-switch .ts-sw.ts-dark{background:var(--swatch-dark)}
.theme-switch .ts-sw.ts-light{background:var(--swatch-light)}
.theme-switch[aria-pressed="false"] .ts-sw.ts-dark,
.theme-switch[aria-pressed="true"] .ts-sw.ts-light{border-color:var(--chrome-accent);box-shadow:0 0 0 1px var(--chrome-accent)}
.theme-switch .ts-label{flex:1 1 auto;min-width:0}
/* THE PANEL KEEPS ITS OWN RING, and this is not a preference. --focus-ring is a
   PRODUCT role, so it flips with the theme: brass-400 on graphite, brass-700 in
   daylight. This panel does not flip. It is the course frame, section 2 of
   tokens.css deliberately leaves the chrome out of the light theme, and the
   sidebar stays graphite whichever theme the page is in. So in daylight the
   product's ring landed on a ground it was never measured against and came out at
   2.39:1 against a 3:1 floor, measured on .sidebar-back. Every row of a panel that
   stands on every page, invisible to anyone arriving by tab, in one theme only,
   which is the theme nobody checks.
   --chrome-accent is the fix because it is the role that already answers this
   question: it is not overridden in section 3, so it is the same brass on the same
   graphite in both themes. The theme switch has used it since step 9; the rows
   around it fell through to the default in base.css because nobody asked what the
   default was standing on.
   :not(.theme-switch) so the switch keeps its INSET ring, which is one of the
   three exceptions to base.css: it sits flush against the panel edge and an
   outset ring would be clipped.
   MEASURED after, by real Tab rather than by a scripted focus, on a painted
   screen and on a vitrine page: brass-400 on --chrome-bg is 8.71:1 in the Vault
   AND 8.71:1 in daylight, because neither the ring nor the ground moves now.
   Before: 8.71:1 and 2.39:1, and only one of those two numbers had ever been
   taken. */
.sidebar :is(a,button,summary,[tabindex]):not(.theme-switch):focus-visible{outline:var(--ring) solid var(--chrome-accent);outline-offset:var(--ring)}
.theme-switch:focus-visible{outline:var(--ring) solid var(--chrome-accent);outline-offset:calc(var(--ring) * -1)}
/* `.theme-switch-inline` AND ITS RING STOOD HERE AND BOTH ARE GONE, 2026-08-15. It was the theme
   switch lifted out of the panel and laid in a row on a vitrine page, and **0 elements wear it**,
   in either tree, in the DOM.
   THE RULE IT CARRIED IS THE REASON THIS HEADSTONE IS LONG: **A RING ANSWERS TO WHAT IT STANDS ON,
   NOT TO THE COMPONENT IT BELONGS TO.** The panel takes `--chrome-accent` because the panel does
   not flip with the theme; this variant stood on a page that does, so the fixed brass that is right
   in the panel landed on chalk at 2.03:1 against a 3:1 floor, and it took `--focus-ring` instead,
   measured at 8.98:1 in the Vault and 7.46:1 in daylight. That sentence is the one thing worth
   carrying forward, and the day the switch is put on a page again it is the rule to write with it.
   The variant is deleted rather than kept for that day, on this folder's own account of
   `account.css`: a face with no placement is a name, not a component. */

/* PRESS, and it reads a chrome role for the same reason the ring above does.
   Every row of this panel already had a hover and none of them had a press, so a
   tap on a touch screen left the hover wash stuck on the row a person had just
   left. --chrome-pressed and not the product's --bg-pressed: section 3 overrides
   the product role and does not touch the chrome one, so a shared role would
   press a graphite row to chalk on a daylight page.
   .rm-toggle had neither state: it is the drawer button, so it is the one control
   here that only exists below the dock width, which is exactly where a stuck
   hover shows. */
.sidebar-page-link:active:not(.planned),.sidebar-back:active,.theme-switch:active{background:var(--chrome-pressed)}
.sidebar-sub-link:active{background:var(--chrome-pressed);color:var(--chrome-text)}
.rm-toggle:hover{border-color:var(--chrome-accent)}
.rm-toggle:active{background:var(--chrome-pressed)}

/* THE DOCK. The width is argued in components/base.css, beside the body inset it
   has to agree with, because the two are one decision written in two files: this
   panel stops being a drawer and the body gains 220px of padding at the same
   pixel. 1140 is 900 + 220 + 20, and it was 860 until 2026-08-10, which docked
   the review chrome while the product still needed the width. Change both or
   neither. */
@media(min-width:1140px){
  .rm-toggle,.rm-overlay{display:none!important}
  .sidebar{transform:none;transition:none}
}
