@import url('fonts.css');

/* Teamium — shared styles.
   A light, quiet interface: white surfaces, grey structure, one green for
   action. The constellation's own colours are the only saturated thing on the
   page, so the data reads as the subject and the chrome stays out of its way.

   Typography and neutrals follow the reference the brief named
   (sociomapping.com: #4C4C57 body text, #858A9F muted, #F6F6F6 panels, Poppins).
   The font is a system stack rather than a webfont: the app is served from a
   single container with no network access, so a CDN import would simply fail
   and fall back anyway.

   The green is deliberately *not* theirs. #03DA99 is a bright mint that would
   fight the height view's greens, which sit at hue ~110 and are matte by
   design. This one is a deep emerald at hue ~162 — the same family as the
   reference, far enough from the data colours that a button is never mistaken
   for a score, and dark enough to carry white text at 5.1:1. */

/* **The palettes are askable by any element, not only the root** (2026-09-05).
   Both blocks are keyed on the `data-theme` attribute rather than on `:root`
   carrying it, so a *part* of a page can wear the other one — which is how
   the cockpit's own light/dark switch reaches the matrix and the readings
   under it rather than only the dials (`applyCockpitTheme` in cockpit.js).
   There is no duplication and no second palette to keep in step: the dress
   asks for the same values the page does.

   Specificity is not a contest here, because the two rules match different
   *elements* — `:root` matches `<html>` and the attribute rule matches
   whatever carries it. Where both match the same element (the root itself,
   which always carries the attribute) the dark block wins by coming later,
   which is the behaviour it always had. */
:root, [data-theme='light'] {
  /* Two colours are chosen in Settings — `surface_color` and `ink_color` — and
     every neutral and every weight of type is a fixed rung of a lightness
     ladder that `js/theme.js` puts them on. The values below are those rungs
     for the defaults, written out so a page is never briefly the wrong colour
     while the settings are being fetched; api.js recomputes them all the moment
     they arrive.

     The ladder reaches into the constellation too. Its plinth, wedge, ruled
     field, box plot and lights are the same rungs, read straight off this
     document, which is what stops the 3D view from staying grey when the page
     turns to sand. */
  --surface: #e8ebe3;
  --ink: #17302a;
  --bg: #e8ebe3;
  /* The panel the 3D canvas sits in. It is the scene's own backdrop rung, so
     the frame and the space inside it are the same colour rather than a grey
     border around a themed picture. */
  --scene-bg: #fbfbfa;
  /* Cards were pure white, and 100% lightness is white whatever hue you ask
     for, so a white card can never take the theme. Two points down is enough
     for the tint to arrive and not enough to read as grey. */
  --panel: #fbfaf5;
  /* The bar and the rail. The same as `--panel` in the light theme, where a
     frame lighter than its page is right; its own rung in the dark one, where
     the frame sinks below the page instead. */
  --chrome: #fbfaf5;
  --panel-2: #eef0e9;
  --line: #d5dbd2;
  --text: #17302a;
  --muted: #68766f;
  /* One colour, chosen in Settings (`brand_color`), and four values worked out
     from it. These are the defaults for #6EAA00; api.js recomputes all six the
     moment the settings arrive, so a page never flashes the wrong brand.

     Two of them are legibility rather than taste, and are why the family is
     derived instead of hand-picked. --accent-ink is what goes *on* the colour:
     #6EAA00 is still bright enough that white on it is 3.3:1 and short of AA, so
     the ink stays near-black. --accent-text is the colour used *as* text on
     the page's card, darkened until it clears AA — the same
     brightness that makes a good highlight makes an illegible link. */
  --accent: #6eaa00;
  --accent-hover: #5e9100;
  --accent-soft: #f2f9e0;
  --accent-ink: #0d1b17;
  --accent-text: #577c00;
  --accent-bright: #6eaa00;
  /* The wordmark figure and the browser-tab icon are the brand colour itself:
     they sit side by side on screen and two nearly-equal greens read as a
     mistake. The icon is a file, so it is repainted by
     tools/recolour_icons.py rather than by a variable. */
  --brand-green: #6eaa00;
  /* Deepened from #c43d2f, which came to 4.14:1 on `--panel-2` — the inset the
     Delete buttons on Survey and Settings sit on. This clears there at 4.51
     and is *more* saturated, not less (0.88 against 0.76): the old value was
     lightened toward the page rather than deepened, which costs contrast and
     colour at once. The dark dress has its own, and for the opposite reason;
     see `--danger` in the `[data-theme='dark']` block. */
  --danger: #c32e18;
  --ok: #577c00;
  /* What the interface builds with, all the ink's hue on fixed rungs (see
     `groundTones` in js/theme.js): the primary button and the rail's icons
     are moss, the hero bands of the redesigned pages are forest. */
  --moss: #3f6b53;
  --forest: #123f37;
  --forest-deep: #092d2d;
  --on-moss: #ffffff;
  --radius: 10px;
  --radius-card: 18px;
  --shadow: 0 18px 44px rgba(26, 55, 45, 0.09);
  --card-shadow: 0 10px 30px rgba(35, 56, 48, 0.055);
  /* Three faces, shipped with the app (css/fonts.css): Source Sans 3 for the
     interface, Barlow Semi Condensed for headings, Crimson Pro for the one
     big title and the motto. Each with a real fallback, in case. */
  --font: "Source Sans 3", "Segoe UI", system-ui, sans-serif;
  --font-display: "Barlow Semi Condensed", Bahnschrift, "Arial Narrow", "Segoe UI", sans-serif;
  --font-serif: "Crimson Pro", "Palatino Linotype", Palatino, Georgia, serif;
}

/* The dark theme's own defaults, written out for the same reason the light
   ones are: api.js recomputes every one of these the moment the settings
   arrive, and until they do a page should not be the wrong colour. Matched on
   the attribute rather than the media query alone, because a viewer who has
   chosen light on a dark machine must get light.

   The shadow is the exception that has to be rethought rather than retuned. A
   drop shadow is the absence of light and there is none to take away from a
   near-black page; what reads as raised there is a lighter edge, so the card's
   own border does the work and the shadow all but goes. */
[data-theme='dark'] {
  --surface: #17302a;
  --ink: #e8ebe3;
  --bg: #10201d;
  --scene-bg: #13241f;
  --chrome: #0d1b18;
  --panel: #172d28;
  --panel-2: #213933;
  --line: #2a453e;
  --text: #eef3ec;
  --muted: #9db0a6;
  --moss: #7fb597;
  --forest: #1b4b42;
  --forest-deep: #0e302b;
  --on-moss: #0f1f1c;
  --card-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
  --accent: #6eaa00;
  --accent-hover: #a1d21a;
  --accent-soft: #22300a;
  --accent-ink: #0d1b17;
  --accent-text: #a8dc2b;
  --accent-bright: #6eaa00;
  --brand-green: #6eaa00;
  --wordmark-ink: #f3f2ef;
  /* Red carries little luminance, so a red that reads on white does not read
     on a dark card, and the dark dress was a dress behind: #e4705f came to
     3.31:1 on `--panel-2`, which is where the Delete buttons on Survey and
     Settings actually sit, against the light dress's own 4.51 for the same
     token. Raised until it matches that profile — 5.22 / 4.53 / 6.20 on panel,
     panel-2 and the page — and chosen by walking the *hue* up in luminance
     rather than mixing toward white, which would have cost a third of the
     saturation (0.41 against 0.54) and turned a warning into a salmon. */
  --danger: #ff8c75;
  --ok: #a8dc2b;
  --shadow: 0 18px 44px rgba(0, 0, 0, 0.35);
}


* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: 15px;
  background: var(--bg);
  color: var(--text);
  line-height: 1.5;
}

a { color: var(--accent-text); }

/* --- the shell -------------------------------------------------------------

   A bar across the top and a rail down the left, laid out as a grid so the
   sidebar can be taken away by changing one track width rather than by moving
   anything. `.framed` is set by api.js on every page that gets the rail; the
   survey does not, because the person answering it has nowhere to navigate to
   and the bar is there only to say whose survey it is. */

body.framed {
  display: grid;
  /* `minmax(0, 1fr)` rather than `1fr`. A grid track's minimum is its content
     by default, so one wide thing — the response matrix for a thirty-person
     team — stretches the column, and the page grows a horizontal scrollbar.
     The bar and the rail are stuck to the top of the *window*, not to that
     scroll, so scrolling across to read the table carried both off the screen.
     Floored at zero, the column keeps its share, and anything too wide for it
     scrolls inside its own box instead. */
  grid-template-columns: var(--rail) minmax(0, 1fr);
  grid-template-rows: auto 1fr;
  grid-template-areas: 'bar bar' 'nav main';
  min-height: 100vh;
  /* The rail's width when it is showing, and the width of the bar's own left
     cluster whether it is showing or not — so hiding the rail does not move
     the wordmark sideways. `--rail` is the grid column and goes to zero;
     `--rail-open` is the measurement. */
  --rail-open: 13.5rem;
  --rail: var(--rail-open);
  /* The bar's height, needed in two places that cannot measure it: what the
     rail hangs below, and how tall the rail is.

     Two variables, not one, and the difference is load-bearing. `--bar-row`
     is how tall *one* band of the bar is and is a constant; `--bar-h` is how
     tall the bar has actually come out, which is two bands once the crumbs
     wrap onto a line of their own (below), and is written by the
     `ResizeObserver` in api.js. Reading the second into the bar's own
     `min-height` would be a feedback loop — the bar could grow and never
     shrink back — so the bar is floored by the first and the rail hangs
     below the second. */
  --bar-row: 4rem;
  --bar-h: var(--bar-row);
}
body.framed.nav-collapsed { --rail: 0rem; }

header.topbar {
  grid-area: bar;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  /* No padding on the left: the brand cluster below carries it, so that the
     cluster's own width can be exactly one rail. */
  padding: 0 1.4rem 0 0;
  min-height: var(--bar-row);
  box-sizing: border-box;
  background: var(--chrome);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 20;
}

/* The hamburger, the wordmark and the hairline, in a box exactly one rail
   wide (Marc, 2026-09-05). The hairline used to sit about ten pixels right of
   the rail's own border, so two vertical lines ran down the top of the page a
   hand's breadth apart where there should be one. Measuring the *box* rather
   than the wordmark is what keeps them together: the artwork takes whatever
   width is left inside it, so it comes out a little smaller and stays right
   if the rail's width or the bar's padding is ever changed.

   It is `--rail-open` rather than `--rail`, so the wordmark does not slide
   sideways when the rail is hidden. */
.bar-brand {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  flex: 0 0 var(--rail-open);
  width: var(--rail-open);
  box-sizing: border-box;
  padding-left: 1.4rem;
  align-self: stretch;
  min-width: 0;
}
/* Pinned to the cluster's right edge, which is the rail's border line. */
.bar-brand .bar-rule { margin-left: auto; margin-right: 0; }

/* The wordmark is one piece of artwork, letters and all, so nothing is
   typeset here: the spacing between the figure and the word is drawn into the
   file, which is what the `cap`-unit fitting that used to live here
   reproduced. It is sized by *width* now rather than by height — the width is
   what has to come out right for the hairline to land on the rail's border —
   with the old 1.4rem left as a ceiling for a bar wider than the artwork
   wants to be. */
.brand { display: inline-flex; align-items: center; text-decoration: none; min-width: 0; flex: 1 1 auto; }
.brand svg { display: block; width: 100%; height: auto; max-height: 1.4rem; }
/* The wordmark's own navy. Part of the drawing rather than a weight of type, so
   it does not follow the ink setting — a variable only so a dark surface has
   somewhere to override it. */
:root { --wordmark-ink: #172237; }

/* The hairline between the mark and the team, which is what tells you the team
   is a caption on the application rather than part of its name. */
.bar-rule {
  width: 1px;
  align-self: stretch;
  margin: 0.15rem 0.1rem;
  background: var(--line);
}
/* The breadcrumb: which team, and on a page that is about one, which
   animation. This is where identity lives now — the bar names the thing once
   and the page gives its height to the work, which is what every editor from
   Figma to Docs does and what this did not: Direct had the bar, an `<h1>`
   repeating the team's name, and the animation's own head, three bands deep
   before any of the editor showed. */
.bar-crumbs { display: flex; align-items: center; gap: 0.35rem; min-width: 0; }
.bar-crumb-slot { display: inline-flex; align-items: center; gap: 0.35rem; min-width: 0; }
.bar-crumb-slot:empty { display: none; }
.bar-crumbs .crumb-sep { color: var(--muted); opacity: 0.7; }
.bar-tools { display: inline-flex; align-items: center; gap: 0.4rem; }
.bar-tools:empty { display: none; }

/* **The breadcrumb takes a band of its own once the row cannot hold it**
   (Marc, 2026-09-09). The team, the animation and the chapter are three menus
   of names nobody chose for their length, and in one band they were the only
   elastic thing in it: every other item is a fixed width, so all the squeeze
   landed here. Measured on the live bar with the rail open, the three come to
   581px and shrink together — at 760px of window the animation's menu is 44px
   wide and at 640px it is 13, which is what "they overlap" describes.

   The number is where the brand cluster (one rail, 216px), a breadcrumb worth
   reading (26rem) and the fixed chrome on the right stop fitting side by
   side, and it is a *window* query rather than a container one because every
   term in that sum is the window's: the cluster is one rail whether the rail
   is showing or not.

   Two bands, so `--bar-h` is no longer 4rem — but it is not written here
   either, since a band's height is its content's. The `ResizeObserver` in
   api.js measures the bar and writes it, which is also what keeps the rail
   hanging off the right place on a bar that has wrapped for some other
   reason. */
@media (max-width: 930px) {
  header.topbar { flex-wrap: wrap; padding-bottom: 0.3rem; }
  /* Last in the row, so the wrap puts it *under* the chrome rather than
     pushing the account off the end — the account and the page's own tools
     belong beside the wordmark, and the breadcrumb reads as a caption under
     the whole band. `flex-basis: 100%` is what makes it a band rather than a
     tail. */
  .bar-crumbs { order: 5; flex: 1 0 100%; padding-left: 1.4rem; }
  /* The team's name is the crumb with the most to say and the band is now as
     wide as the window, so the narrow-screen ceiling on it goes. */
  .bar-crumbs .bar-switch { max-width: none; }
}
/* Under about 520px the brand cluster and the state pill stop fitting side by
   side and the bar comes out in three bands. That is left as it is rather than
   answered with a fourth rule: a flex line breaks *before* an item that does
   not fit rather than shrinking it, so keeping the chrome on one band would
   mean giving it a box of its own, and three readable bands on a phone are
   better than two crushed ones — which is the fault this whole query is here
   to cure. `--bar-h` follows either way. */

.nav-toggle,
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  cursor: pointer;
  padding: 0;
  font: inherit;
}
.nav-toggle {
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
}
.nav-toggle:hover { background: var(--panel-2); color: var(--text); }

/* The account button. Its letter is the role's initial, so the thing you are
   signed in as is legible without opening anything. */
.account { position: relative; }
.avatar {
  width: 2.2rem;
  height: 2.2rem;
  border-radius: 50%;
  /* The page's two ends, swapped. It was the wordmark's navy with white on it,
     which in the dark theme is a light disc with white letters on it — the
     wordmark's ink is the one colour that has to become light there. */
  background: var(--text);
  color: var(--bg);
  font-weight: 600;
  font-size: 0.9rem;
  letter-spacing: 0.01em;
}
.account-menu,
.action-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 0.45rem);
  min-width: 11rem;
  padding: 0.4rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  z-index: 30;
}
.account-menu .menu-title,
.action-menu .menu-title {
  margin: 0.2rem 0.55rem 0.35rem;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.account-menu .menu-item,
.action-menu .menu-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.42rem 0.55rem;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--text);
  font: inherit;
  cursor: pointer;
}
.account-menu .menu-item:hover,
.action-menu .menu-item:hover { background: var(--panel-2); }
.account-menu .menu-item.on { background: var(--accent-soft); color: var(--accent-text); font-weight: 600; }
/* The account switcher hangs off the right edge of the avatar; a page-action
   menu hangs off the left edge of its button, and a greyed item is an option
   that exists but is not built yet — visible so the shape of the feature is,
   dead so nothing pretends. */
.menu-anchor { position: relative; display: inline-block; }
.action-menu { right: auto; left: 0; }
.action-menu .menu-item:disabled { color: var(--muted); cursor: default; }
.action-menu .menu-item:disabled:hover { background: transparent; }

/* --- the rail -------------------------------------------------------------- */

/* The rail stays where it is while the page scrolls under it. Navigation that
   scrolls away is navigation you have to scroll back for, and on a long page —
   Settings is several screens — that was most of the way to the top.

   Sticky rather than fixed, so it stays a grid column and nothing has to know
   the rail's width twice. It is exactly the viewport less the bar, which keeps
   its panel and its right-hand rule filling the side of the window at every
   scroll position, and it scrolls inside itself if it is ever taller than that
   — a short window, or a large text size, and the rail is a rail rather than a
   list with its foot cut off. `dvh` for the phones whose address bar is part of
   `vh`; the `vh` line above it is what browsers without `dvh` use.

   **The `z-index` here is what puts the team menu over the page**, and it has
   to be on the rail rather than on the menu. `position: sticky` makes a
   stacking context, so the menu's own z-index only ever counted *inside* the
   rail: against the page it was the rail's own level, which is `auto`, and
   the rail comes before `main` in the document — so anything positioned in
   the page won. The menu opened behind the survey hero and behind Direct's
   own popovers, and no number on the menu could have fixed it.

   40 is above every layer the page draws (the deepest is 30, the popovers)
   and below the two that are meant to cover everything: the projected join
   window at 60 and the toasts at 100. The bar's 20 is not a contest — the
   two never overlap, the rail beginning where the bar ends. */
.sidenav {
  grid-area: nav;
  display: flex;
  flex-direction: column;
  padding: 1.4rem 0.75rem;
  background: var(--chrome);
  border-right: 1px solid var(--line);
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  position: sticky;
  top: var(--bar-h);
  z-index: 40;
  align-self: start;
  height: calc(100vh - var(--bar-h));
  height: calc(100dvh - var(--bar-h));
}
.nav-group + .nav-group {
  margin-top: 1.4rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--line);
}
.nav-title {
  margin: 0 0 0.5rem 0.75rem;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.sidenav .nav-item {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  min-height: 2.6rem;
  padding: 0 0.75rem;
  margin-bottom: 0.2rem;
  border-radius: 10px;
  text-decoration: none;
  color: var(--text);
  font-size: 0.95rem;
  white-space: nowrap;
}
/* **The marks are moss on the light rail and the brand's green on the dark
   one** (Marc, 2026-09-09), which is one rule and one override rather than
   one colour, because the two rails are not the same problem.

   On the **dark** chrome moss is #7fb597, a sage sitting at about the weight
   of the words beside it, so a column of seven read as grey shapes rather
   than as the marks of anything; `--accent-text` is #8bc400 there, 8.4:1
   against the chrome, and it pops. On the **light** rail moss is #3f6b53, a
   deep sage against near-white — it already has all the contrast a mark
   needs, and the brand green (#5c8200 as ink) puts a second, louder green
   beside the active row's own, which is the one thing on the rail that is
   supposed to be the brand.

   It is `--accent-text` rather than a green written here, so what pops
   follows the colour chosen in Settings. */
.sidenav .nav-item .icon { color: var(--moss); display: inline-flex; }
[data-theme='dark'] .sidenav .nav-item .icon { color: var(--accent-text); }
.sidenav .nav-item .icon svg { width: 20px; height: 20px; }
.sidenav a.nav-item:hover { background: var(--panel-2); }
/* A view of a team, with no team to view. Kept in place and readable enough to
   name itself, but plainly not a thing you can press. */
.sidenav .nav-item.disabled { color: var(--muted); opacity: 0.45; cursor: default; }
/* The active view, on the brand's own pale wash with the readable variant of
   the brand for the mark and the word. Both come from the one colour chosen in
   Settings, so the highlight follows a change of brand rather than pinning a
   green of its own. */
.sidenav .nav-item.active {
  background: var(--accent-soft);
  color: var(--accent-text);
  font-weight: 700;
  box-shadow: inset 3px 0 var(--accent);
}

/* The switcher: the team the rail is about, and the way to any other. */
/* In the bar rather than the rail, and shaped like a crumb: no border, no
   panel, just the name and a chevron — a control that reads as *where you
   are* rather than as a form field. It grows a ground on hover, which is
   what says it can be pressed.

   **A crumb is never wider than its slot.** The slots shrink when the bar is
   short of room (`min-width: 0`), and a button inside one used to stay as wide
   as its whole name, so its chevron was drawn over the `/` after it: measured
   at 1280px in Dutch, 27px past its slot on the team and 19px on the
   animation. Held to `100%` of the slot, the name takes the ellipsis instead
   — the same cut it already took against the fixed ceiling. */
.bar-switch { position: relative; min-width: 0; }
.nav-switcher {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  max-width: min(20rem, 100%);
  min-height: 2rem;
  padding: 0 0.45rem;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 0.95rem;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}
/* The fill is the hover; the line the three crumbs share is settled beside
   `button:hover`, which is the rule that was reaching one of them. */
.nav-switcher:hover { background: var(--panel-2); }
.nav-switcher:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* The name is two spans (`fillName` in api.js): the tail — the last word or
   two — is kept whole, and the head takes the ellipsis when the line is too
   short, so a name that differs from another only at its end still tells
   them apart on the button. When the whole name fits, nothing is cut. */
.nav-switcher .name { flex: 1; min-width: 0; display: flex; white-space: nowrap; }
.nav-switcher .name .head { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.nav-switcher .name .tail { flex: none; white-space: pre; }
.nav-switcher .chev { display: inline-flex; color: var(--muted); }
.nav-switcher .chev svg { width: 16px; height: 16px; }

/* Starting a team, under the switcher and in the page's primary colour: a
   coordinator makes teams, and it should never be behind a menu. The two are
   one control read twice, so the switcher pulls its own margin in when this
   is there — which is why the gap is written as a `:has` rather than as a
   number on both. */
.nav-switch:has(+ .nav-new) { margin-bottom: 0.5rem; }
.nav-new {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 2.6rem;
  margin-bottom: 1.1rem;
  padding: 0 0.75rem;
  border: 1px solid var(--moss);
  border-radius: 10px;
  background: var(--moss);
  color: var(--on-moss);
  font-size: 0.95rem;
  font-weight: 700;
  text-decoration: none;
}
.nav-new:hover { filter: brightness(1.08); }
.nav-new .icon { display: inline-flex; flex: none; }
.nav-new .icon svg { width: 18px; height: 18px; }
/* Fixed to the viewport and placed by measurement (`placeMenu` in api.js):
   the rail clips what overflows it, and a menu held to the rail's width cut
   every long name to its first words. It is as wide as its longest name,
   between the button's width and a cap, and a name longer than the cap
   wraps below rather than being cut. */
/* A popover, so the platform owns the layer and the dismissing
   (`wireBarMenu`). Its own defaults centre it in the viewport and give it a
   border and padding of their own, so `inset` and `margin` are taken back
   here — measured: with these two, `placeMenu`'s coordinates land exactly. */
.nav-menu {
  inset: auto;
  margin: 0;
  position: fixed;
  width: max-content;
  max-width: 27rem;
  /* `placeMenu` sets the height it may have — the room under the button —
     and a longer list scrolls inside it rather than off the screen. */
  overflow-y: auto;
  overscroll-behavior: contain;
  /* **And it has to *show* that it scrolls** (Marc, 2026-09-09: the chapter
     menu "does not have a scroll bar... we cannot go everywhere"). It always
     did scroll — measured on the Harry Potter animation, 2087px of list in
     836 — but Chrome on Windows 11 now draws overlay scrollbars, which are
     invisible until something is already scrolling, and a menu of 31 chapters
     that shows no bar reads as a menu with 20 chapters in it. Styling
     `::-webkit-scrollbar` is also what opts an element *out* of the overlay
     kind, so the two rules below are one decision: a bar that is there before
     it is touched. `scrollbar-gutter` keeps the list from shifting sideways
     when it appears. */
  scrollbar-gutter: stable;
  scrollbar-width: auto;
  scrollbar-color: var(--moss) var(--panel-2);
  /* And a cap of its own, so the menu can never be taller than the window
     even if `placeMenu` has not run — an inline `max-height` from it is more
     precise and wins where it is there, but nothing should depend on that for
     a list to be reachable. */
  max-height: min(72vh, 34rem);
  padding: 0.4rem;
  background: var(--panel);
  /* A popover's own default is `color: CanvasText`, which is black whatever
     the dress: anything inside that sets no colour of its own — the account
     menu's name — came out black on the dark panel at about 1.3:1. */
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: var(--shadow);
  z-index: 30;
}
/* A rule between a menu's groups. Bare, an `<hr>` keeps the browser's inset
   grey border — and in the bottom sheet, a flex column, it shrank to its
   2px of border and sat in the middle as a dot. */
.nav-menu .menu-rule {
  align-self: stretch;
  width: auto;
  margin: 0.3rem 0;
  border: 0;
  border-top: 1px solid var(--line);
}
.nav-menu::-webkit-scrollbar { width: 14px; }
.nav-menu::-webkit-scrollbar-track { background: var(--panel-2); border-radius: 999px; }
.nav-menu::-webkit-scrollbar-thumb {
  background: var(--moss);
  border: 3px solid transparent;
  border-radius: 999px;
  background-clip: content-box;
}
.nav-menu::-webkit-scrollbar-thumb:hover { background: var(--accent-text); background-clip: content-box; }
/* The list is what scrolls, not the whole menu: the head and the filter stay
   put while it does, which is what makes the box worth having. A menu with no
   filter keeps its own `overflow-y`. */
.nav-menu .menu-list {
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
  scrollbar-width: auto;
  scrollbar-color: var(--moss) var(--panel-2);
}
.nav-menu .menu-list::-webkit-scrollbar { width: 14px; }
.nav-menu .menu-list::-webkit-scrollbar-track { background: var(--panel-2); border-radius: 999px; }
.nav-menu .menu-list::-webkit-scrollbar-thumb {
  background: var(--moss); border: 3px solid transparent; border-radius: 999px; background-clip: content-box;
}
/* **A menu with a filter is a flex column, and only while it is open.**

   This rule sets `display`, and a class selector outranks the one-attribute
   rule that hides a closed menu — `[hidden] { display: none }` yesterday,
   `[popover]:not(:popover-open) { display: none }` now. Written without the
   `:popover-open`, it left the two menus long enough to grow a search box —
   the team's 26 rows and the chapter's 58 — closable by nothing at all, while
   the animation's two rows had no filter and worked. That is the third time
   in this file after the rewind's canvas and `.fs-chapter`, and moving to a
   popover does *not* retire the trap: the guard has to be on the selector. */
.nav-menu:popover-open:has(.menu-filter) { overflow-y: hidden; display: flex; flex-direction: column; }
.nav-menu:has(.menu-filter) .menu-list { flex: 1; min-height: 0; }
/* The menu's own title, above the box; a group heading scrolls with its rows. */
.nav-menu .menu-title.menu-head { flex: none; }
.nav-menu .menu-filter {
  width: 100%;
  margin: 0.1rem 0 0.35rem;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 0.88rem;
  box-sizing: border-box;
}
.nav-menu .menu-filter:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
/* The row the arrows are standing on. Not `:hover`'s ground, or the two
   disagree about what Enter is about to take — a pointer moving over the list
   sets the highlight instead, so there is only ever one answer. */
.nav-menu .menu-item.highlight { background: var(--panel-2); }
.nav-menu .menu-item.on.highlight { background: var(--accent-soft); box-shadow: inset 2px 0 var(--accent); }

/* **A menu is a bottom sheet on a narrow screen** (Marc, 2026-09-09). A
   dropdown hung under its button cannot work when the button is 40px from the
   left edge of a 380px window and the list is 58 rows: it is the shape a
   phone expects that is wanted, not a smaller dropdown. `placeMenu` takes its
   inline coordinates back below this width, or they would beat these rules.

   The grip is the menu's own top edge drawn as a pseudo-element rather than a
   node, so there is nothing to add when the menu is built and nothing to lose
   when it is filled again on the next open. */
@media (max-width: 600px) {
  .nav-menu:popover-open {
    inset: auto 0 0 0;
    width: 100%;
    max-width: none;
    max-height: 78vh;
    padding: 0.9rem 0.6rem 1.1rem;
    border-radius: 18px 18px 0 0;
    border-bottom: 0;
    display: flex;
    flex-direction: column;
    transition: transform 0.12s ease-out;
  }
  .nav-menu:popover-open::before {
    content: "";
    position: absolute;
    top: 0.45rem;
    left: 50%;
    width: 2.4rem;
    height: 4px;
    margin-left: -1.2rem;
    border-radius: 999px;
    background: var(--line);
  }
  /* The page behind it dims, which is what says the sheet has the floor. */
  .nav-menu:popover-open::backdrop { background: rgba(9, 45, 45, 0.35); }
  /* `flex: 1` is `flex-basis: 0`, and in a column whose own height comes from
     its content that resolves to nothing: the sheet came out 33px tall with
     the whole list inside it collapsed. It sizes to its rows and stops at the
     sheet's ceiling instead. */
  .nav-menu .menu-list { flex: 0 1 auto; min-height: 0; }
  /* A row is a target for a finger rather than a pointer. */
  .nav-menu .menu-item { padding: 0.62rem 0.7rem; }
  .nav-menu .menu-filter { font-size: 1rem; padding: 0.55rem 0.7rem; }
}

/* **Three menus cannot share a narrow band by shrinking.** They already take
   a band of their own under 930px; under this they stop being squeezed into
   it and the band scrolls sideways instead, each keeping enough width to be
   read and pressed. Measured before: at 380px a click on the animation's
   button landed on its neighbour. */
@media (max-width: 620px) {
  /* Wrapped rather than scrolled sideways. Scrolling was the first answer and
     put the *chapter* — the crumb the cockpit's pages are about — off the
     right edge, reachable only by dragging a band nothing says can be
     dragged. Two short lines show all three. The bar's height is measured by
     the `ResizeObserver` in api.js, so the rail follows it down. */
  .bar-crumbs { flex-wrap: wrap; row-gap: 0.15rem; }
  .bar-crumbs > *, .bar-crumb-slot > * { flex: none; }
  /* Three classes, so the `min-width: 0` the film and chapter buttons take
     to shrink inside their slots (below) cannot outrank this from later in
     the file. */
  .bar-crumbs .bar-switch .nav-switcher,
  .bar-crumbs .bar-film .film-button,
  .bar-crumbs .bar-chapter .chapter-button { min-width: 5.5rem; }
}
.nav-menu .menu-empty { margin: 0.5rem 0.6rem; color: var(--muted); font-size: 0.85rem; }
.nav-menu .menu-title {
  margin: 0.3rem 0.6rem 0.25rem;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
/* **Inside a menu, `hidden` wins.** Filtering hides rows by the attribute, and
   every rule below that sets `display` — the rows, their wrappers — is a
   two-class selector that outranks `[hidden] { display: none }` on its own.
   Without this the search box set the attribute on 54 of 58 rows and every
   one of them stayed on the screen: it had never filtered anything.

   Fourth time in this file, after the rewind's canvas, `.fs-chapter` and the
   menu's own flex column. One blanket rule here rather than a counterpart per
   selector, because the next row type added would need one too. */
.nav-menu [hidden] { display: none !important; }
.nav-menu .menu-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 0.6rem;
  border-radius: 8px;
  color: var(--text);
  text-decoration: none;
  font-size: 0.9rem;
}
.nav-menu .menu-item .label { flex: 1; min-width: 0; white-space: normal; overflow-wrap: anywhere; line-height: 1.3; }
.nav-menu .menu-item:hover { background: var(--panel-2); }
/* A row that is there to be read and not chosen: the chapters this page has
   no matrix for. Dimmed rather than left out, so the animation's own
   numbering stays whole and nothing reads as cut off. */
.nav-menu .menu-item[aria-disabled='true'] { opacity: 0.55; cursor: default; }
.nav-menu .menu-item[aria-disabled='true']:hover { background: transparent; }
.nav-menu .menu-item.on { background: var(--accent-soft); color: var(--accent-text); font-weight: 700; }
.nav-menu .menu-state { font-size: 0.72rem; color: var(--muted); white-space: nowrap; flex: none; }
/* A team's row in the switcher, with its bin. The bin is only there on hover
   or keyboard focus: this menu is how people move between teams all day, and
   a destructive control sitting on every row invites the mis-click. */
.nav-menu .menu-row { position: relative; display: flex; align-items: stretch; }
.nav-menu .menu-row .menu-item { flex: 1; min-width: 0; padding-right: 2.1rem; }
.nav-menu .menu-bin {
  position: absolute;
  right: 0.25rem;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  opacity: 0;
  cursor: pointer;
}
.nav-menu .menu-bin .icon svg { width: 16px; height: 16px; }
.nav-menu .menu-row:hover .menu-bin,
.nav-menu .menu-row:focus-within .menu-bin { opacity: 1; }
.nav-menu .menu-bin:hover { background: var(--panel-2); color: var(--danger); }
.nav-menu .menu-bin:focus-visible { opacity: 1; outline: 2px solid var(--accent); }
.nav-menu .menu-item.add.quiet { color: var(--muted); font-weight: 400; }
/* A second heading inside a menu, for a group after the first: it needs the
   rule above it that the first one does not, since the first is the menu's
   own title. */
.nav-menu .menu-title.later {
  margin-top: 0.45rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--line);
}

.nav-menu .menu-item.add { margin-top: 0.3rem; padding-top: 0.6rem; border-top: 1px solid var(--line); border-radius: 0; color: var(--accent-text); font-weight: 700; }
.nav-menu .menu-item.add .icon { display: inline-flex; color: var(--moss); }
.nav-menu .menu-item.add .icon svg { width: 16px; height: 16px; }

/* State in the rail: the live count while a survey is open, the round count
   otherwise; and the same in the bar. */
.nav-badge {
  margin-left: auto;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.nav-badge.live { padding: 0.05rem 0.5rem; border-radius: 999px; background: var(--accent); color: var(--accent-ink); }
.nav-foot { margin: auto 0 0; padding: 1rem 0.75rem 0; font-size: 0.75rem; color: var(--muted); white-space: nowrap; }
.bar-state {
  display: inline-flex;
  align-items: center;
  padding: 0.15rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}
.bar-state:empty { display: none; }
.bar-state .label { white-space: nowrap; }
/* The track inside the chip. Thin, under the words, and the same green the
   rest of the interface uses for "this is happening". */
.bar-state .bar-progress {
  display: block;
  width: 3.5rem;
  height: 3px;
  margin-left: 0.5rem;
  border-radius: 2px;
  background: var(--line);
  overflow: hidden;
}
.bar-state .bar-progress > span {
  display: block;
  height: 100%;
  border-radius: 2px;
  background: var(--accent);
  transition: width 0.4s ease;
}
/* No percentage yet — the engine reports its stage before it can report a
   fraction — so the stripe says "working" without claiming a number. */
.bar-state .bar-progress.indeterminate > span {
  width: 40%;
  animation: bar-progress-slide 1.1s ease-in-out infinite;
}
@keyframes bar-progress-slide {
  0% { transform: translateX(-110%); }
  100% { transform: translateX(260%); }
}
@media (prefers-reduced-motion: reduce) {
  .bar-state .bar-progress.indeterminate > span { animation: none; width: 100%; opacity: 0.6; }
}
/* While something is computing the pill is a button: it opens the whole
   panel below (js/compute.js). Only then — a pill that merely says "film
   ready" is a label and must not look pressable. */
.bar-state.opens { cursor: pointer; }
.bar-state.opens:hover { filter: brightness(0.97); }
.bar-state.opens::after { content: ' ▾'; opacity: 0.7; }

/* The deleted teams — a coordinator's own, an administrator's every one. A
   row is what was deleted and the verbs: Restore always, and on a laptop an
   administrator's permanent deletion once the month is up. */
.deleted-row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex-wrap: wrap;
  padding: 0.8rem 0;
  border-bottom: 1px solid var(--line);
}
.deleted-row:last-child { border-bottom: 0; }
.deleted-row .what { min-width: 12rem; }
.deleted-row .what p { margin: 0.15rem 0 0; }
.deleted-row .spacer { flex: 1 1 0; min-width: 0; }
.deleted-row .tag {
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: var(--panel-2);
  color: var(--muted);
  font-size: 0.72rem;
  white-space: nowrap;
}
.deleted-row .tag.due { background: var(--accent-soft); color: var(--accent-text); }

/* A card whose heading is its first line, on the two account pages: the h2's
   own top margin is for a heading between two blocks, and here there is
   nothing above it but the card's padding. */
.card > .card-title { margin-top: 0; }

/* The people (people.html), shaped like the deleted teams above: what the
   account is on the left, its verbs on the right, and — on their own lines
   under it — the link that could not be mailed and the teams it owns. Every
   part wraps, so a phone gets the same row stacked rather than a scroller. */
main.people { max-width: 64rem; }
.people > .auth-message { margin: 0 0 1rem; }
.invite-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0 1rem;
  align-items: end;
  margin-top: 0.8rem;
}
.invite-form .invite-send { margin-bottom: 0.75rem; }
.person-row {
  display: flex;
  align-items: center;
  gap: 0.5rem 0.7rem;
  flex-wrap: wrap;
  padding: 0.85rem 0;
  border-bottom: 1px solid var(--line);
}
.person-row:last-child { border-bottom: 0; }
.person-row .what { min-width: 12rem; flex: 0 1 auto; }
.person-row .what p { margin: 0.15rem 0 0; overflow-wrap: anywhere; }
.person-row .who { display: flex; align-items: center; gap: 0.25rem 0.4rem; flex-wrap: wrap; }
.person-row .who strong { margin-right: 0.15rem; }
.person-row .spacer { flex: 1 1 0; min-width: 0; }
.person-row .verbs { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.person-row.off .what strong { color: var(--muted); }
.tag.status-active { color: var(--ok); border-color: var(--ok); }
.tag.status-disabled { color: var(--danger); border-color: var(--danger); }
.person-teams {
  flex-basis: 100%;
  list-style: none;
  margin: 0.2rem 0 0;
  padding: 0 0 0 0.9rem;
  border-left: 2px solid var(--line);
  display: grid;
  gap: 0.35rem;
}
.person-team { display: flex; align-items: center; gap: 0.4rem 0.8rem; flex-wrap: wrap; font-size: 0.9rem; }
.person-team a { color: var(--text); overflow-wrap: anywhere; }
.person-team select { width: auto; max-width: 100%; padding: 0.25rem 0.5rem; font-size: 0.82rem; }
/* The link shown once. The accent's soft wash rather than a warning colour:
   nothing is wrong, there is just one more thing for the administrator to do. */
.handover {
  flex-basis: 100%;
  padding: 0.75rem 0.85rem;
  border-radius: var(--radius);
  background: var(--accent-soft);
  color: var(--text);
}
.handover p { margin: 0 0 0.55rem; font-size: 0.9rem; line-height: 1.45; }
.handover-link { display: flex; gap: 0.4rem; flex-wrap: wrap; align-items: center; }
.handover-link input {
  flex: 1 1 16rem;
  min-width: 0;
  font-family: 'Consolas', 'DejaVu Sans Mono', monospace;
  font-size: 0.8rem;
}
.people-dialog fieldset.transfer {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin: 0.4rem 0 0.9rem;
  padding: 0.5rem 0.85rem 0.7rem;
}
.people-dialog fieldset.transfer[hidden] { display: none; }
.people-dialog legend { font-weight: 700; font-size: 0.9rem; padding: 0 0.3rem; }
/* A radio with its sentence, as one clickable line. A `label` everywhere else
   is a field's small capitals heading, which is the wrong voice for a choice. */
label.choice-line {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
  margin: 0.4rem 0 0;
  font-size: 0.9rem;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text);
  cursor: pointer;
}
label.choice-line input[type="radio"] { width: auto; margin: 0; }
/* The sentence wraps beside its radio rather than under it: as a flex item of
   its own width it went to the next line whole on a phone, leaving the radio
   a loose dot above a paragraph. Not on the line that also carries a select,
   which is short and should keep the select beside it where there is room. */
label.choice-line:not(:has(select)) > span { flex: 1 1 0; min-width: 0; }
label.choice-line select { width: auto; max-width: 100%; padding: 0.25rem 0.5rem; font-size: 0.85rem; }
.dialog-actions { display: flex; justify-content: flex-end; gap: 0.5rem; flex-wrap: wrap; }
/* The verb that cannot be taken back with one click carries the danger's
   line as well as its ink — the danger-zone card's own dress. */
.people-dialog button.danger { border-color: var(--danger); }

/* Your own account (account.html): three cards of one column, the width of a
   form rather than of the window. */
main.account-page { max-width: 44rem; }
.account-page .token-shown {
  margin: 0.6rem 0;
  padding: 0.75rem 0.85rem;
  border-radius: var(--radius);
  background: var(--accent-soft);
}
.account-page .token-shown[hidden] { display: none; }
.account-page .token-shown p { margin: 0 0 0.55rem; font-size: 0.9rem; line-height: 1.45; }
.account-page .form-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; margin-top: 0.4rem; }
.account-page .auth-fixed { margin: 0; }
.auth-message.ok { color: var(--ok); }

/* The Mail card in Settings: the port and the security share a column, being
   two halves of one fact — how the connection is made. */
.mail-pair { display: grid; grid-template-columns: minmax(5rem, 0.8fr) 1.2fr; gap: 0 1rem; }
.mail-grid { gap: 0 1rem; }
#mail-card .choice-line { margin-top: 0.45rem; font-size: 0.85rem; color: var(--muted); }
#mail-card .choice-line input[type="checkbox"] { width: auto; margin: 0; }
#mail-card .mail-actions { align-items: center; margin-top: 0.6rem; }

/* A page whose tools are not this account's to use: a coordinator on a demo
   team opening Survey, Edit animation or Diagnose (`showReadOnly` in api.js).
   The page's own `main` keeps nothing of its workspace dress, so this is a
   card where a page head would be, as wide as a sentence wants. */
main.read-only { max-width: 46rem; }
.read-only-card h1 { margin: 0 0 0.4rem; }
.read-only-card .lede { margin: 0 0 1rem; }
/* Two links, not two fields: `.row > *` grows every child to fill the row. */
.read-only-card .row > .button { flex: 0 0 auto; }

/* --- focus mode ------------------------------------------------------------
   Our chrome goes, not the browser's — so the URL stays, which is most of
   why this is here rather than an application full-screen button. The bar is
   not removed but lifted out of the way, so it can come back the moment the
   pointer reaches for it (`focus-peek`) without the grid reflowing under the
   work. */
body.focus { --rail: 0rem; }
body.focus .sidenav { display: none; }
/* The bar is *taken out* rather than slid out of the way, and it is worth
   saying why: it is a grid item with `position: sticky; top: 0` of its own,
   and both ways of moving it — a percentage transform and a negative `top`
   — lost to that, computing back to the base rule's zero. `display` cannot
   be argued with. What it costs is the slide, which was a nicety; the bar
   still comes back the moment the pointer reaches the top of the window. */
body.focus header.topbar { display: none; }
body.focus.focus-peek header.topbar {
  display: flex;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 95;
  box-shadow: var(--shadow);
}
body.focus main { padding-top: 0.6rem; }
.bar-icon.on { background: var(--accent-soft); color: var(--accent-text); }

/* --- the command palette ---------------------------------------------------
   Ctrl+K. One box that goes anywhere and does what the page can do — and the
   reason there is no need to keep adding buttons, since a thing three letters
   away does not need a place on screen. */
.palette {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: 12vh 1rem 1rem;
  background: rgba(9, 22, 19, 0.32);
}
.palette[hidden] { display: none; }
.palette-box {
  width: min(34rem, 100%);
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.palette-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.7rem 0.9rem;
  border-bottom: 1px solid var(--line);
  color: var(--muted);
}
.palette-head .icon svg { width: 18px; height: 18px; }
.palette-input {
  flex: 1;
  border: 0;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 1rem;
  outline: none;
  padding: 0;
}
.palette-list { max-height: 22rem; overflow: auto; padding: 0.35rem; }
.palette-group {
  margin: 0.5rem 0.6rem 0.25rem;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.palette-item {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 0.9rem;
  text-align: left;
  cursor: pointer;
}
.palette-item .label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.palette-item .hint { font-size: 0.75rem; color: var(--muted); white-space: nowrap; }
.palette-item.on { background: var(--accent-soft); color: var(--accent-text); }
.palette-empty { margin: 0.9rem 0.6rem; font-size: 0.85rem; color: var(--muted); }
.palette-foot {
  margin: 0;
  padding: 0.5rem 0.9rem;
  border-top: 1px solid var(--line);
  font-size: 0.72rem;
  color: var(--muted);
}
.palette-foot kbd {
  margin-right: 0.25rem;
  padding: 0 0.3rem;
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  border-radius: 4px;
  background: var(--panel-2);
  font-size: 0.68rem;
  color: var(--text);
}

/* The computation, watched from the bar. A popover rather than a place: it
   is a *view* of a job that carries on whether or not it is open, so closing
   it loses nothing and it never earns a page of its own. Above the rail
   (z 40) and below the toasts (100), like every other menu hung off the
   bar. */
.compute-panel {
  position: fixed;
  z-index: 90;
  width: min(24rem, calc(100vw - 1rem));
  padding: 0.9rem 1rem 1rem;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel);
  color: var(--text);
  box-shadow: var(--shadow);
}
.compute-panel[hidden] { display: none; }
.compute-panel .row { display: flex; align-items: center; gap: 0.5rem; }
.compute-panel .row .spacer { flex: 1; }
.compute-panel p { margin: 0; }
.compute-panel .mono { font-variant-numeric: tabular-nums; }
.bar-state.live { border-color: var(--accent-soft); background: var(--accent-soft); color: var(--accent-text); }

/* Swatch and hex side by side, each driving the other. The field is sized to
   the seven characters it holds so it reads as a hex code rather than as a
   general-purpose text box. */
.hex-row { align-items: center; gap: 0.5rem; }
input.hex {
  /* Seven characters of the mono face, plus the padding either side. Sized in
     `ch` so it follows the font rather than a pixel guess, and `border-box` so
     the padding is inside the width instead of pushing the text out of it. */
  box-sizing: content-box;
  width: 7ch;
  flex: 0 0 auto;
  padding: 0.35rem 0.5rem;
  text-transform: uppercase;
}
/* Only while what is typed is not yet a colour. Nothing is applied in that
   state, and a field that looks fine while doing nothing is the worse of the
   two failures. */
input.hex.bad { border-color: var(--danger); }

/* Sits beside Save. Deliberately quiet — it is a note about the page's state,
   not a warning about a mistake. */
.unsaved { color: var(--muted); align-self: center; margin-right: 0.35rem; }

.icon { display: inline-flex; flex: 0 0 auto; }
.icon svg { display: block; width: 20px; height: 20px; }

/* **Collapsed means gone, and it slides back when the pointer reaches the
   left edge** (Marc, 2026-09-05).

   It used to become a 4.25rem strip of icons, which is the other of the two
   patterns in use and the wrong one here: Gmail, Drive and Jira keep an icon
   rail; Notion, Linear, Slack's newer shell and Craft hide the sidebar
   outright and reveal it on hover. Which is right turns on whether the icons
   read without their labels, and ours do not — a meter for Analyse and a
   graph for Diagnose are one glyph apart, and Watch, Survey and Edit
   animation are a triangle, a clipboard and a clapperboard. A strip of those
   spends 68px of every page saying something nobody can read.

   Two things about the reveal are decisions rather than styling.

   It **overlays** rather than reflowing: the rail is taken out of the grid
   (`--rail` is zero) and put back as a fixed panel over the page. A layout
   that reflows on *hover* makes the text jump sideways as the pointer drifts
   near the edge, which is seasick to use and is the version of this everybody
   builds once.

   And it **slides**, unlike the bar in focus mode, which is taken out with
   `display` because a sticky grid item cannot be moved. The rail is fixed
   while it is hidden, so a transform works on it and the movement says which
   way it went. */
.nav-collapsed .sidenav {
  position: fixed;
  left: 0;
  top: var(--bar-h);
  width: var(--rail-open);
  box-sizing: border-box;
  transform: translateX(-100%);
  transition: transform 0.18s ease, box-shadow 0.18s ease;
  will-change: transform;
}
.nav-collapsed.nav-peek .sidenav,
.nav-collapsed .sidenav:focus-within {
  transform: none;
  box-shadow: 0 14px 36px rgba(9, 45, 45, 0.22);
}
@media (prefers-reduced-motion: reduce) {
  .nav-collapsed .sidenav { transition: none; }
}

.spacer { flex: 1; }

main { grid-area: main; padding: 1.25rem; max-width: 1500px; margin: 0 auto; width: 100%; box-sizing: border-box; }

/* Narrow screens: the rail would eat the page, so it takes itself out and is
   opened with the hamburger, which is the same thing the collapse does and is
   what a phone means by a drawer. It used to shrink to icons here; the
   argument against that is the one above — unreadable glyphs are not a
   navigation, and on a narrow screen they cost proportionally more.

   `--rail-open` is narrowed rather than the rail being made a full-width
   sheet: the peek is an overlay, so it does not have to fit beside anything,
   and a drawer the width of the window hides the page it is being used to
   navigate. */
@media (max-width: 860px) {
  body.framed { --rail: 0rem; --rail-open: 15rem; }
  body.framed .sidenav {
    position: fixed;
    left: 0;
    top: var(--bar-h);
    width: var(--rail-open);
    box-sizing: border-box;
    transform: translateX(-100%);
    transition: transform 0.18s ease, box-shadow 0.18s ease;
  }
  /* No hover on a phone, so the hamburger is the whole of it: not collapsed
     means open, and open means over the page. */
  body.framed:not(.nav-collapsed) .sidenav {
    transform: none;
    box-shadow: 0 14px 36px rgba(9, 45, 45, 0.22);
  }
  /* And the page dims behind it, which is the half that says the drawer is
     over the page rather than part of it — and the half that says tapping
     away will close it. The tap itself is caught on the document, not here,
     so the first tap outside also does whatever it was aimed at. */
  body.framed:not(.nav-collapsed)::before {
    content: '';
    position: fixed;
    inset: var(--bar-h) 0 0;
    background: rgba(9, 45, 45, 0.28);
    z-index: 39;
  }
  /* The bar's left cluster keeps the hamburger and the wordmark side by side;
     the hairline goes, there being no rail edge for it to line up with. */
  .bar-brand { flex: 0 0 auto; width: auto; }
  .bar-brand .bar-rule { display: none; }
  .brand svg { width: auto; height: 1.3rem; }
  .bar-team { max-width: 10rem; }
  /* The bar is a breadcrumb and a breadcrumb's last crumb is the one you are
     standing on, so on a narrow window the chapter keeps its place and the
     ones before it give up the room. */
  .bar-film .film-button, .bar-film-name { max-width: min(7rem, 100%); }
  .bar-chapter .chapter-button, .bar-chapter-name { max-width: min(9rem, 100%); }
}

h1, h2, h3 { font-family: var(--font-display); font-weight: 600; letter-spacing: -0.01em; }
h1 { font-size: 1.7rem; margin: 0 0 0.25rem; }
h2 { font-size: 1.3rem; margin: 1.5rem 0 0.6rem; }
h3 { font-size: 1.05rem; margin: 1rem 0 0.4rem; color: var(--text); }
p.lede { color: var(--muted); margin-top: 0; }

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: 1.25rem 1.4rem;
  margin-bottom: 1rem;
  box-shadow: var(--card-shadow);
}

.grid { display: grid; gap: 1rem; }
/* `min()` so a column can be narrower than 320px where the card itself is:
   on a phone the card's content box is under 300px, and a fixed minimum ran
   every field in it 23px past the card's edge. */
.grid.two { grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); }
/* Two cards of unequal appetite: the rounds table carries six columns ending in
   three buttons and needs the room, the members table does not. Only once there
   is room for both — below this the auto-fit rule above stacks them anyway. */
@media (min-width: 900px) {
  .grid.two.uneven { grid-template-columns: 0.85fr 1.15fr; }
}
.grid.three { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }

button, .button {
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  padding: 0.5rem 1rem;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text);
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
/* ``inline-flex`` above would otherwise beat the ``hidden`` attribute, and a
   hidden button (the join window's publish, once published) would stand
   there anyway. */
button[hidden], .button[hidden] { display: none; }
button:hover, .button:hover { border-color: var(--moss); }
/* **The bar's three crumbs fill on hover and draw no line** (Marc, 2026-09-09,
   seeing the team crumb outlined in green where the animation and the chapter
   were not). It was never a decision either way — it was specificity. The rule
   above is for the app's *bordered* buttons, and at (0,1,1) it beats a bare
   `.nav-switcher` (0,1,0) while losing to `.bar-film .film-button` (0,2,0). One
   crumb happened to be styled by one class and the other two by two, so the
   same global rule reached exactly one of the three.

   Which way to settle it is an accessibility answer rather than a taste one:
   the accent is **focus's** colour (`.nav-switcher:focus-visible`), and it has
   to stay unmistakable. A hover that draws the same green line makes the two
   states look alike, and the state that loses is the one somebody on a
   keyboard depends on. So hover is the fill and focus is the ring — which is
   also what GitHub, Linear, Notion, Figma and VS Code do with a toolbar crumb.

   It sits *here*, straight after the rule it is answering, because that is the
   whole of how it wins: same specificity, later in the file. Moved up beside
   `.nav-switcher` it would lose again, silently. */
.nav-switcher:hover,
.film-button:hover,
.chapter-button:hover { border-color: transparent; }
button:disabled { opacity: 0.45; cursor: not-allowed; }
button.primary, .button.primary {
  background: var(--moss);
  color: var(--on-moss);
  border-color: var(--moss);
  font-weight: 700;
}
button.danger { color: var(--danger); }
button.small { padding: 0.2rem 0.5rem; font-size: 0.8rem; }

/* The members box, with a number against every line.
   The gutter is a sibling rather than text in the box, so what is typed is
   exactly what is posted. The two only stay in step while one line is one row,
   so the box does not wrap: a name long enough to wrap would take two rows and
   put every number under it against the wrong line. */
.numbered {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg);
  overflow: hidden;
}
.numbered .gutter {
  flex: 0 0 auto;
  padding: 0.55rem 0.5rem 0.55rem 0.6rem;
  text-align: right;
  color: var(--muted);
  background: var(--panel-2);
  border-right: 1px solid var(--line);
  overflow: hidden;
  user-select: none;
}
.numbered .gutter span { display: block; }
/* The box and its gutter have to agree about a line, exactly, or the numbers
   drift down a long list. One font, one size, one line height, stated here for
   both rather than left to the two elements' defaults. */
.numbered .gutter,
.numbered textarea {
  font: inherit;
  font-size: 0.9rem;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
}
.numbered textarea {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  /* Vertical only. Dragging it wider pulled it out over the card beside it,
     and there is nothing to be gained from a wider box: the lines are names. */
  resize: vertical;
  white-space: pre;
  overflow-x: auto;
  background: var(--bg);
  padding: 0.55rem 0.6rem;
}
.numbered textarea:focus { outline: none; }
.numbered:focus-within { border-color: var(--accent); }
/* The line under the box, when what it has to say is a complaint rather than a
   count. Same place, so there is one thing to look at either way. */
.small.warn { color: var(--danger); }

input, select, textarea {
  font: inherit;
  font-size: 0.9rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--text);
  width: 100%;
}
/* A field that is deliberately not editable, rather than one that is
   temporarily unavailable — the colormap's band boundaries are the case. Muted
   and borderless so it reads as a printed value the row happens to carry, which
   is what it is; `opacity` is left alone, since a half-faded number beside six
   editable ones reads as broken rather than as fixed. */
input:disabled, select:disabled {
  background: transparent;
  border-color: transparent;
  color: var(--muted);
  cursor: default;
}
label { display: block; font-size: 0.7rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); margin-bottom: 0.35rem; }
.field { margin-bottom: 0.75rem; }
/* A voice list and the ▶ that plays the voice it shows (settings.html). */
.voice-pick { display: flex; gap: 0.4rem; align-items: center; }
.voice-pick select { flex: 1; min-width: 0; }
.voice-pick .listen { flex: none; min-width: 2.2rem; }
/* Under the help, a sentence of its own: a stored voice not offered here and
   the one speaking in its place. */
.field .voice-state { margin-top: 0.35rem; }
.row { display: flex; gap: 0.5rem; align-items: flex-end; flex-wrap: wrap; }
.row > * { flex: 1; }
.row > button { flex: 0 0 auto; }

table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
th, td { text-align: left; padding: 0.4rem 0.5rem; border-bottom: 1px solid var(--line); }
th { color: var(--muted); font-weight: 600; }
/* The rounds table earns its width back by keeping each round on one line:
   the action buttons stay in a row and the narrow columns never wrap. */
/* Keeping each round on one line means the table can no longer shrink, so on a
   narrow screen it scrolls inside its own card rather than widening the page. */
#rounds { overflow-x: auto; }
#rounds th, #rounds td { white-space: nowrap; padding-left: 0.3rem; padding-right: 0.3rem; }
#rounds td:last-child { text-align: right; }
#rounds td:last-child button, #rounds td:last-child .button { margin-left: 0.15rem; }
tbody tr:hover { background: var(--panel-2); }
/* The team being worked on, in the list of all of them. The selection lives in
   the browser rather than in this page's address — Teams is the list, not a
   view of one team — so it is said here instead, where somebody checking would
   look. */
tr.current-team { background: var(--accent-soft); }
tr.current-team:hover { background: var(--accent-soft); }
.tag.current {
  margin-left: 0.5rem;
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-text);
}

/* A name edited in place — a round's title, a member's name: it has to read as
   text in the table until you mean to change it, so the box only appears on
   hover and focus. Where there is a placeholder it carries the fallback that
   would be shown instead, so an untitled round shows what it will be called
   rather than an empty gap. */
input.cell-name {
  width: 100%;
  min-width: 11ch;
  padding: 0.15rem 0.3rem;
  font-size: 0.88rem;
  border-color: transparent;
  background: transparent;
}
input.cell-name::placeholder { color: var(--muted); opacity: 0.75; }
input.cell-name:hover { border-color: var(--line); background: var(--bg); }
input.cell-name:focus { border-color: var(--accent); background: var(--panel); }

/* A column header you can order the table by. The arrow holds its width whether
   or not it is showing one, so the headings do not shift as the sort moves. */
th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
th.sortable:hover, th.sortable:focus-visible { color: var(--text); }
th.sortable.sorted { color: var(--text); }
th.sortable .sort-arrow {
  display: inline-block;
  width: 0.9em;
  margin-left: 0.15rem;
  font-size: 0.7em;
  color: var(--accent-text);
}

.tag {
  display: inline-block;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--muted);
}
.tag.open { color: var(--ok); border-color: var(--ok); }
.tag.computed { color: var(--accent-text); border-color: var(--accent); }
.tag.failed { color: var(--danger); border-color: var(--danger); }
.tag.cancelled { color: var(--danger); border-color: var(--danger); }
/* The system corrected something on its own — worth noticing without reading
   as a failure, which is what the log's only other warm colour means.

   **These three are the only colours in the application with no dress**, and
   each of them happened to be picked against one. Measured on `--panel`:

       computing  #7ec8ff   light 1.64   dark 6.53
       stage      #b39ddb   light 2.17   dark 4.94
       realigned  #b8730a   light 3.46   dark 3.10

   A pale blue and a lilac are bright, so they carry on a dark card and vanish
   on a white one — 1.64:1 is the worst text contrast in either dress. The deep
   amber is the other way round and short in both. Every other colour on these
   pages is a token that swaps with the theme; these were hard-coded, which is
   the whole of why nobody noticed.

   So each has two values now, and the rule is the same one everywhere: the
   value that already worked stays where it worked, and the other end is the
   same hue moved until it clears AA on that dress's own card. The cool pair's
   existing hexes are therefore the *dark* ones. */
.tag.computing { color: #466f8f; border-color: #466f8f; }
.tag.stage { color: #776690; border-color: #776690; }
.tag.realigned { color: #9b6208; border-color: #9b6208; }
[data-theme='dark'] .tag.computing { color: #7ec8ff; border-color: #7ec8ff; }
[data-theme='dark'] .tag.stage { color: #b39ddb; border-color: #b39ddb; }
[data-theme='dark'] .tag.realigned { color: #feaf0a; border-color: #feaf0a; }

/* An open round's tag doubles as the way to its session window (QR + PIN +
   progress), so it is a real button wearing the tag's clothes — and says so
   on hover. */
button.tag-button {
  background: none;
  font: inherit;
  font-size: 0.72rem;
  cursor: pointer;
}
button.tag-button:hover, button.tag-button:focus-visible {
  background: var(--accent-soft);
}
/* While a round is collecting answers, OPEN is the coordinator's next step —
   so it is the filled control in its row, not the quietest. */
button.tag-button.open {
  background: var(--ok);
  border-color: var(--ok);
  color: #fff;
  font-weight: 600;
}
button.tag-button.open:hover, button.tag-button.open:focus-visible {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}
/* The answer count, pressable for the session view behind the number. */
button.count-button { font-variant-numeric: tabular-nums; }
/* A primary action whose moment has not come: greyed like disabled, but a
   press still explains what it is waiting for. */
button.looks-disabled { opacity: 0.45; cursor: not-allowed; }
button.looks-disabled:hover { border-color: var(--accent); }

/* A checkbox with its sentence beside it, as one clickable line. */
.check-line {
  display: flex;
  gap: 0.45rem;
  align-items: flex-start;
  margin-top: 0.6rem;
  color: var(--muted);
  cursor: pointer;
}
.check-line input[type="checkbox"] { width: auto; margin-top: 0.15rem; }

/* --- the session window: QR, PIN and everyone's progress --------------- */

/* Nearly the whole screen — this is what a coordinator projects, and it is
   read and scanned from where people sit. */
.join-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgba(12, 16, 20, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem;

  /* The survey app's own palette, pinned rather than inherited. This window
     is the last thing the room sees before their phones open the survey, and
     the two should look like one product — so it stays in the participants'
     light theme whatever theme the coordinator works in, and whatever colours
     the settings have put on the rest of this page. Custom properties set
     here beat the inherited ones from :root for everything inside. */
  --bg: #f3f2ef;
  --surface: #f3f2ef;
  --panel: #f9f8f6;
  --panel-2: #efeeeb;
  --chrome: #f9f8f6;
  --line: #e0dfdc;
  --text: #2b3138;
  --ink: #2b3138;
  --muted: #71757a;
  --accent: #6eaa00;
  --accent-hover: #5e9100;
  --accent-soft: #f2f9e0;
  --accent-ink: #0d1b17;
  --accent-text: #577c00;
  --brand-green: #6eaa00;
  --wordmark-ink: #172237;
  --danger: #c43d2f;
  --ok: #577c00;
  color: var(--text);
}
/* ``display: flex`` above would otherwise beat the ``hidden`` attribute the
   page opens and closes the window with. */
.join-overlay[hidden] { display: none; }
.join-card {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.75rem 2.25rem 2rem;
  width: min(97vw, 1250px);
  max-height: 95vh;
  overflow: auto;
}
.join-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem 2.75rem;
}
/* Beside each other only when there is genuinely room; below that the
   progress list follows under the code. */
@media (min-width: 920px) {
  .join-grid { grid-template-columns: 7fr 5fr; align-items: start; }
}
.join-main { text-align: center; }
/* The QR is pixel art: scaled up smoothly its modules blur and a phone across
   the room stops reading it, so the scaling is told to stay crisp. The white
   mat is part of the code — scanners want a quiet zone whatever the theme. */
.join-card img {
  display: block;
  margin: 0 auto;
  width: min(78vw, 56vh, 30rem);
  height: auto;
  image-rendering: pixelated;
  background: #fff;
  padding: 0.9rem;
  border-radius: 10px;
  border: 1px solid var(--line);
}
/* The wordmark over the code: the wall and the phone that scans it should
   look like one product. */
.join-mark { display: block; margin-bottom: 0.4rem; }
.join-mark svg { height: 30px; width: auto; }
.join-main h2 { margin: 0 0 0.9rem; }

/* The address, then the laptop's way in on its own row — "open …/join and
   type PIN …" — with the PIN the loudest thing in it: it is what people type
   last and get wrong most. All are links, so the facilitator can open the
   survey from here to show it. */
.join-address {
  margin: 1.1rem 0 0;
  font-size: clamp(1.05rem, 2.4vw, 1.55rem);
  font-weight: 500;
  overflow-wrap: anywhere;
}
.join-address a { color: var(--text); text-decoration: none; }
.join-address a:hover { color: var(--accent-text); text-decoration: underline; }
.join-pin-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 0.5rem;
  margin: 0.35rem 0 0;
  font-size: clamp(0.95rem, 1.9vw, 1.3rem);
  font-weight: 500;
}
#join-pin-text { text-align: center; }
.join-pin-line a {
  color: var(--text);
  text-decoration: none;
  letter-spacing: 0.04em;
}
.join-pin-line a.join-pin-value {
  font-size: clamp(1.6rem, 4.5vw, 2.8rem);
  font-weight: 700;
  letter-spacing: 0.06em;
  white-space: nowrap;
}
.join-pin-line a:hover { color: var(--accent-text); text-decoration: underline; }
/* The PIN alone, for a chat window. Icon only — beside a PIN that size,
   a word would be a second thing to read. */
.pin-copy {
  padding: 0.25rem 0.35rem;
  line-height: 0;
  border-radius: 8px;
  align-self: center;
}
.pin-copy svg { width: 18px; height: 18px; }
.join-buttons { justify-content: center; margin-top: 0.9rem; }

/* Who is where, at a glance: name, state, how far, how long quiet, agreed —
   and the way out for somebody who will not answer. Smaller than the code
   beside it, because the room reads the code and only the coordinator reads
   this. */
.join-side { font-size: 0.85rem; }
.join-side h3 { margin-top: 0.4rem; }
/* Every column a fixed share: each row is its own grid, so an ``auto``
   column would be measured per row and the table would not line up. */
.progress-row {
  display: grid;
  grid-template-columns:
    minmax(4rem, 1.1fr) minmax(5.5rem, 1.3fr) 3rem 2.6rem 3rem 4.6rem;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 0.2rem;
  border-bottom: 1px solid var(--line);
}
.progress-row .num { text-align: right; font-variant-numeric: tabular-nums; }
.progress-row.head {
  color: var(--muted);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.progress-state { color: var(--muted); }
.progress-row.done .progress-state { color: var(--ok); font-weight: 600; }
.progress-row.waiting .progress-state { color: var(--danger); }
.progress-row .agreed { color: var(--ok); font-weight: 700; }
.join-close {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  border: none;
  background: none;
  font-size: 1.4rem;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
  padding: 0.3rem 0.5rem;
}
.join-close:hover { color: var(--text); }
/* The join window's verdict on where its code leads: the published state is
   the one worth a colour. */
.ok-note { color: var(--ok); font-weight: 600; }
/* The listener, while it is on. */
#listen.on {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: var(--accent);
}

/* The computation's progress bar. A run is minutes long — six for a 26-member
   team — so the coordinator gets the stage, its own detail, and a bar rather
   than a spinner. */
.progress {
  height: 0.5rem;
  border-radius: 999px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  overflow: hidden;
}
.progress > span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  border-radius: inherit;
  transition: width 0.4s ease;
}
.progress.indeterminate > span {
  width: 30%;
  animation: progress-sweep 1.4s ease-in-out infinite;
}
.progress.failed > span { background: var(--danger); }
@keyframes progress-sweep {
  0% { margin-left: -30%; }
  100% { margin-left: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .progress > span { transition: none; }
  .progress.indeterminate > span { animation: none; margin-left: 0; width: 100%; opacity: 0.4; }
}

.muted { color: var(--muted); }
.small { font-size: 0.85rem; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.85em; }

.toast {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  padding: 0.6rem 0.9rem;
  border-radius: var(--radius);
  max-width: 420px;
  z-index: 100;
  background: var(--panel);
  box-shadow: var(--shadow);
}
.toast.error { border-left-color: var(--danger); }
.toast.ok { border-left-color: var(--ok); }

.empty {
  padding: 2rem;
  text-align: center;
  color: var(--muted);
  border: 1px dashed var(--line);
  border-radius: var(--radius);
}

/* --- activity log ----------------------------------------------------- */

/* One team's entries, folded. A computation writes an entry per stage, so a
   single afternoon of two teams runs to a hundred rows that never say whose
   round they are; the summary carries the team, and the fold keeps the rest
   of the page reachable. */
.log-group { border: 1px solid var(--line); border-radius: var(--radius); margin-bottom: 0.5rem; }
.log-group > summary {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.7rem;
  cursor: pointer;
  border-radius: inherit;
  list-style: none; /* Firefox draws its own marker on top of ours */
}
.log-group > summary::-webkit-details-marker { display: none; }
.log-group > summary::before {
  content: '▸';
  display: inline-block;
  color: var(--muted);
  transition: transform 0.15s ease;
}
.log-group[open] > summary::before { transform: rotate(90deg); }
.log-group > summary:hover { background: var(--panel-2); }
.log-group[open] > summary {
  border-bottom: 1px solid var(--line);
  border-radius: var(--radius) var(--radius) 0 0;
}
.log-group .group-name { font-weight: 600; }
/* The last row's border would double up with the group's own. */
.log-group tbody tr:last-child td { border-bottom: 0; }
@media (prefers-reduced-motion: reduce) {
  .log-group > summary::before { transition: none; }
}

/* The brand colour's own preview: the three places it lands that behave
   differently — a filled surface, text on white, and a bordered mark. */
.brand-preview {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-top: 0.75rem;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg);
}

/* --- viewer ---------------------------------------------------------- */

.viewer-wrap {
  position: relative;
  background: var(--scene-bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  /* A size container, so the transport can shed its text the way YouTube's
     player sheds controls when the frame gets narrow — measured against the
     picture itself rather than the window, which the sidebar makes a liar. */
  container-type: inline-size;
}
/* The board takes touch gestures itself rather than scrolling the page under
   them. It used to be an inline style set by OrbitControls' own `connect()`;
   the gestures are `js/navigation.js`'s now and the library is disconnected,
   so it is written here — where it can be found by somebody wondering why a
   swipe does not scroll. */
.viewer-wrap canvas { display: block; width: 100%; height: 100%; touch-action: none; }

/* The mark that flashes in the middle when the space bar starts or stops
   playback. Big, faint and brief: it is a receipt for a key press, not a
   control — hence no pointer events and no place in the tab order. */
.play-flash {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 3;
  display: grid;
  place-items: center;
  width: 5.5rem;
  height: 5.5rem;
  border-radius: 50%;
  background: rgba(24, 26, 28, 0.42);
  color: #fff;
  opacity: 0;
  pointer-events: none;
}
.play-flash svg { width: 2.6rem; height: 2.6rem; }
/* The same mark, standing rather than flashing: lit in the middle from the
   moment the page opens until the first play, the way a player says "this
   plays, press here". The only state in which it takes the pointer. */
.play-flash.idle {
  opacity: 1;
  pointer-events: auto;
  cursor: pointer;
}
/* Straight in, then out over the rest: the mark has to be readable before it
   starts leaving, or it reads as a flicker rather than as an answer. Length
   and curve are YouTube's own bezel, read off its computed style: 1 s on
   cubic-bezier(0.05, 0, 0, 1) — nearly all of the leaving happens early and
   the last of it lingers. */
.play-flash.show { animation: play-flash 1s cubic-bezier(0.05, 0, 0, 1) forwards; }
@keyframes play-flash {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0.82); }
  12% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.22); }
}
@media (prefers-reduced-motion: reduce) {
  .play-flash.show { animation: play-flash-still 1s steps(1, end) forwards; }
  @keyframes play-flash-still { 0%, 70% { opacity: 1; } 100% { opacity: 0; } }
}
/* The canvas is given focus so the arrow keys reach the camera, and the browser
   draws its own ring round anything focused once a key has been pressed — a
   black rectangle round the whole picture, and it comes straight back on the
   next visit because the ring follows the last thing focused. There is nobody
   for it to inform: the canvas is `tabIndex = -1`, so it is never arrived at by
   tabbing, only ever by the pointer being over it. */
.viewer-wrap canvas:focus,
.viewer-wrap canvas:focus-visible { outline: none; }
#stage { height: min(72vh, 760px); }
.viewer-wrap:fullscreen { border: 0; border-radius: 0; }
.viewer-wrap:fullscreen #stage { height: 100vh; }

/* Transport, YouTube's 2025 player control for control: no box, no blur, no
   gradient. The bar rides bare on the picture and the buttons sit in
   translucent pills, every size and alpha below read off youtube.com's
   computed styles rather than remembered — rgba(0,0,0,.3) pills, #eee ink,
   rgba(255,255,255,.1) hover and .2 pressed, both on 0.2 s
   cubic-bezier(.05,0,0,1). The whole chrome fades as one. */
.transport {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 0;
  display: flex;
  flex-direction: column;
  color: #eee;
  opacity: 1;
  /* YouTube's two fades, read off its computed styles: the state being
     entered carries the transition, so showing decelerates over 0.25 s and
     hiding (below) accelerates away in 0.1 s. */
  transition: opacity 0.25s cubic-bezier(0, 0, 0.2, 1);
}
/* 40px pills in a 56px band: 8px under the bar, 8px to the bottom edge —
   YouTube's own spacing, which is what keeps the row from crowding either. */
.transport-row {
  display: flex;
  align-items: center;
  height: 40px;
  margin-bottom: 8px;
}
.transport.hidden {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.1s cubic-bezier(0.4, 0, 1, 1);
}

/* Skip intro, as a streaming player draws it: a squared, translucent dark
   button with a white rule round it, standing clear of the transport band at
   the bottom right of the picture. It does not hide with the chrome — the
   whole point is that it is there while the viewer is waiting. The rule along
   its foot is the intro's remaining time, draining as it runs. */
.skip-intro {
  position: absolute;
  right: 20px;
  bottom: 76px;
  z-index: 4;
  display: inline-flex;
  align-items: center;
  height: 40px;
  padding: 0 18px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.7);
  border-radius: 4px;
  background: rgba(20, 24, 28, 0.55);
  color: #fff;
  font: inherit;
  font-size: 0.9rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
}
.skip-intro:hover { background: rgba(255, 255, 255, 0.92); color: #17302a; }
.skip-intro:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.skip-intro .countdown {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: rgba(255, 255, 255, 0.18);
}
.skip-intro .countdown i { display: block; height: 100%; width: 100%; background: #fff; }
.skip-intro:hover .countdown i { background: var(--accent); }
.viewer-wrap:fullscreen .skip-intro { right: 32px; bottom: 88px; }
/* The translucent lozenges the buttons sit in. Radius past half the height,
   so the ends stay fully round whatever the pill holds. */
.transport .pill {
  position: relative;
  display: flex;
  align-items: center;
  height: 40px;
  border-radius: 28px;
  background: rgba(0, 0, 0, 0.3);
  flex: 0 0 auto;
}
.transport .step-pill { margin-left: 8px; padding: 0 4px; }
.transport .right-pill { padding: 0 4px; }

/* The scrubber is drawn rather than left native, because the chapter lines
   have to line up exactly with the thumb. A native thumb's width is the
   browser's business and there is no way to measure it, and its width is
   precisely what decides how far the thumb is inset at each end — so a chapter
   boundary placed at a plain percentage of the bar drifts from the thumb,
   furthest apart at the two ends. With the thumb a known size, the fill, the
   thumb and every boundary share one formula: half a thumb in, then the
   fraction of what is left. That also puts the thumb's left edge flush with the
   track's rounded end at zero. --f is the played fraction, written by the
   viewer as it plays.

   The wrap is 20px of hit area with the 4px line through its lower half, so
   the line's centre sits 10px above the pills' band — where YouTube keeps
   its own. Hovering anywhere on it (or dragging) grows the line to 6px and
   the playhead dot to 20px, both on YouTube's clock. */
.transport .scrub-wrap {
  width: 100%;
  --thumb: 12px;
  --f: 0;
  position: relative;
  flex: 1;
  min-width: 120px;
  height: 20px;
  display: flex;
  align-items: center;
}
.transport .scrub {
  -webkit-appearance: none;
  appearance: none;
  flex: 1;
  min-width: 0;
  height: 100%;
  margin: 0;
  background: transparent;
  cursor: pointer;
  /* The shared form rule gives every input a border and 0.55rem of side
     padding. Both had to go: the border was the pale outline around the bar,
     and the padding held the track 8.8 px in from each end while the dot and
     the chapter lines were placed against the full width — which is why the
     fill started short of the first chapter and stopped short of the last. */
  border: 0;
  border-radius: 0;
  padding: 0;
}
.transport .scrub:focus { outline: none; }
/* The input keeps every pointer event and draws nothing: the bar lives in
   `.scrub-bar` below, because a range input's track is one piece and cannot be
   cut into a line per chapter. The invisible thumb stays the size the placement
   formula assumes. */
.transport .scrub { position: relative; z-index: 2; }
.transport .scrub::-webkit-slider-runnable-track {
  height: 100%;
  margin: 0 calc(var(--thumb) / 2);
  background: transparent;
}
.transport .scrub::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: var(--thumb);
  height: var(--thumb);
  margin-top: calc((20px - var(--thumb)) / 2);
  border: 0;
  background: transparent;
}
.transport .scrub::-moz-range-track {
  height: 100%;
  margin: 0 calc(var(--thumb) / 2);
  background: transparent;
}
.transport .scrub::-moz-range-thumb {
  width: var(--thumb);
  height: var(--thumb);
  border: 0;
  background: transparent;
}
/* The bar: one line per chapter, laid end to end with a gap between them and
   filled to --f each by their own rule (see `renderChapters`). Ahead of the
   playhead the track is YouTube's own grey — its rgba(40,40,40,.6), which also
   reads on this light scene where a white alpha vanished — and behind it the
   fill is the brand green, still broken at every chapter. At rest the whole
   drawing stands at two thirds height, exactly YouTube's scaleY, and grows
   under the pointer. */
.transport .scrub-bar {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  transform: scaleY(0.667);
  transition: transform 0.2s cubic-bezier(0.05, 0, 0, 1);
}
/* The bar grows under the pointer, and under a drag regardless — it is then
   the thing in hand. There is no longer anything on it to point at *instead*:
   pointing at a chapter dot used to grow that dot alone and leave the line at
   its resting weight, which is what the `on-mark` gate was for. Every pixel of
   the bar is inside a chapter now, so pointing at the bar and pointing at a
   chapter are the same act. */
.transport .scrub-wrap:hover .scrub-bar,
.transport .scrub-wrap.dragging .scrub-bar { transform: scaleY(1); }
/* One line per chapter. `left` and `width` are written in pixels by
   `refreshBarGaps` — the gaps either side depend on the playhead's rendered
   size, which is a transition, so they cannot be a static calc(). */
.transport .scrub-bar .seg {
  position: absolute;
  top: 50%;
  height: 6px;
  margin-top: -3px;
  background: rgba(40, 40, 40, 0.6);
  overflow: hidden;
  transition: transform 0.2s cubic-bezier(0.05, 0, 0, 1);
}
/* The chapter the pointer is on, standing proud of the already-thickened bar:
   this chapter, in a bar of chapters. */
.transport .scrub-bar .seg.seg-hover { transform: scaleY(1.5); }
/* Its tag: the chapter's time, its title, and what was written about the round.
   This is what the chapter dots' own tooltip used to carry, and it is dressed
   the same, because it is the same answer to the same question — it is only
   that the thing being pointed at is now a stretch of bar rather than a dot at
   the end of one. */
.transport .seg-label {
  position: absolute;
  bottom: 1.35rem;
  width: max-content;
  max-width: min(26rem, 90vw);
  /* Sized to hold a paragraph rather than a phrase, and bounded rather than
     scrollable: nothing the pointer must stay off can be scrolled. The caption
     under the picture carries the same words in full for anyone who wants to
     read rather than glance. */
  max-height: 40vh;
  overflow: hidden;
  padding: 0.45rem 0.6rem;
  border-radius: var(--radius);
  background: rgba(24, 32, 38, 0.96);
  color: #fff;
  font-size: 0.8rem;
  line-height: 1.4;
  text-align: left;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
  pointer-events: none;
  z-index: 4;
}
/* The time the chapter starts at, over the title in the muted weight a video
   player gives it: it is how to *get* to the chapter rather than what the
   chapter is, so it does not compete with the name. */
.transport .seg-label .seg-time {
  display: block;
  font-variant-numeric: tabular-nums;
  font-size: 0.72rem;
  opacity: 0.62;
}
.transport .seg-label .seg-title { display: block; font-weight: 700; }
.transport .seg-label .seg-note {
  display: block;
  margin-top: 0.35rem;
  color: rgba(255, 255, 255, 0.82);
  white-space: pre-wrap;
}
/* Width in pixels, written by `refreshBarGaps`: the fill runs to the
   playhead's own position, not to a fraction of the chapter's shifting box. */
.transport .scrub-bar .seg-fill {
  height: 100%;
  width: 0;
  background: var(--accent-bright);
}
/* The playhead dot — the red dot every video player's bar carries, in the brand
   green. Always on show at 12px, growing to 20px wherever the bar is hovered or
   held.

   It grows by its own width rather than by `transform: scale`, because
   `refreshBarGaps` measures its rendered width to work out how far the chapter
   it does *not* belong to has to stand clear of it, and a scaled box reports
   its unscaled width.

   It carries no ring any more. The ring was the chapter dot's, handed to the
   dot to paint while the two stood on one another — and with no dots there is
   nothing to ring. What it was saying, that the playhead is standing exactly on
   a chapter boundary, the gap on one side now says instead. */
.transport .scrub-dot {
  position: absolute;
  top: 50%;
  left: calc(var(--thumb) / 2 + var(--f) * (100% - var(--thumb)));
  width: var(--thumb);
  height: var(--thumb);
  margin-left: calc(var(--thumb) / -2);
  margin-top: calc(var(--thumb) / -2);
  transition:
    width 0.2s cubic-bezier(0.05, 0, 0, 1),
    height 0.2s cubic-bezier(0.05, 0, 0, 1),
    margin 0.2s cubic-bezier(0.05, 0, 0, 1);
  border-radius: 50%;
  background: var(--accent-bright);
  z-index: 3;
  pointer-events: none;
}
.transport .scrub-wrap:hover .scrub-dot,
.transport .scrub-wrap.dragging .scrub-dot {
  width: calc(var(--thumb) * 5 / 3);
  height: calc(var(--thumb) * 5 / 3);
  margin-left: calc(var(--thumb) * -5 / 6);
  margin-top: calc(var(--thumb) * -5 / 6);
}

/* The time and the chapter's title share one translucent pill, like every
   other control — bare type disappeared into the pale plinth. The clock is
   14px medium in #eee with YouTube's 2px shadow; the title sits beside it
   past a spacer, allowed to grow but not to eat the row — past its share it
   ellipsises and the full text stays on the hover title. The pill rides
   right as the volume lozenge opens, since it is the next thing in the row. */
.transport .text-pill {
  margin-left: 12px;
  padding: 0 14px;
  /* Free to give its width back: the other pills are fixed-size, so the
     title is what yields when the frame is short of room. Wider than the
     frame, the row would overflow the wrap — which `overflow: hidden` does
     not merely crop but silently *scrolls* the moment a control past the
     edge takes focus, shunting the whole panel sideways. */
  flex: 0 1 auto;
  min-width: 0;
}
.transport .clock {
  font-variant-numeric: tabular-nums;
  font-size: 14px;
  font-weight: 500;
  color: #eee;
  text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
  white-space: nowrap;
}
.transport .stamp {
  font-size: 14px;
  font-weight: 500;
  color: #eee;
  text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
  margin-left: 10px;
  min-width: 0;
  max-width: 44ch;
  flex: 0 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Narrower than the row can pay for, the text goes the way YouTube's own
   xsmall player sheds its time display — title first, then the whole pill.
   The thresholds leave room for the volume pill's 71px of hover growth, so
   opening the slider never pushes the right pill past the edge. */
@container (max-width: 720px) {
  .transport .stamp { display: none; }
}
@container (max-width: 620px) {
  .transport .text-pill { display: none; }
}

/* The same note, under the picture rather than over it: what is playing right
   now, for a reader rather than a room. */
.round-note-shown h2 {
  margin: 0.9rem 0 0.3rem;
  font-size: 1rem;
}
.round-note-shown p {
  margin: 0;
  max-width: 62ch;
  white-space: pre-wrap;
  color: var(--muted);
}

/* The two notes under the player: what the team is, and what the round the
   play head is on was. Both are written at length and both have to stay
   readable, so they sit side by side rather than one above the other — and
   because either can be long, each scrolls within its own panel instead of
   pushing its neighbour down the page.

   `auto-fit` does the responsive work without a class to toggle: two panels
   share the row where there is room for both, drop to one column when there
   is not, and a team with nothing written about it leaves the round's note
   the full width, since a hidden panel is no longer a grid item at all. */
.notes {
  display: grid;
  gap: 1.1rem;
  /* 20rem is the narrowest a column of prose is worth reading at, ~46
     characters, and it puts the two-column threshold at a content width of
     about 660px — a laptop window rather than a wide desktop one. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  align-items: start;
  margin-top: 0.9rem;
}
.notes .note {
  max-height: clamp(9rem, 30vh, 22rem);
  overflow-y: auto;
  /* Room for the scrollbar, so it never sits on top of the last word. */
  padding-right: 0.7rem;
}
.notes .note h2 {
  margin: 0 0 0.35rem;
  font-size: 1rem;
}
.notes .note p {
  margin: 0;
  max-width: 62ch;
  white-space: pre-wrap;
  color: var(--muted);
}

/* The description a coordinator writes, on the Team page. */
.round-note {
  width: 100%;
  min-height: 2.4rem;
  resize: vertical;
  font: inherit;
  font-size: 0.86rem;
  line-height: 1.5;
  padding: 0.4rem 0.55rem;
}
tr.round-note-row td { padding-top: 0; border-top: 0; }
/* A chapter that has been taken out of the animation. Still there, still
   holding its responses and its result and still a link in the alignment
   chain — only not in the story — so it is stood back rather than struck
   through or greyed to unreadable. Its own buttons stay at full strength,
   because putting it back is the thing most likely to be wanted next. */
tr.chapter-out > td { opacity: 0.5; }
tr.chapter-out > td:last-child { opacity: 1; }
/* Buttons in the pills: YouTube's 48x40 cells with 24px glyphs, packed edge
   to edge. Their hover is a pseudo-element — a 48x32 lozenge (a 32px disc in
   the play circle) — so the shape lights, not the cell. */
.transport .icon-button {
  width: 48px;
  height: 40px;
  background: transparent;
  border: 0;
  border-radius: 0;
  color: #eee;
  position: relative;
}
.transport .icon-button svg { width: 24px; height: 24px; }
.transport .icon-button::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 48px;
  height: 32px;
  border-radius: 40px;
  background-color: transparent;
  transition: background-color 0.2s cubic-bezier(0.05, 0, 0, 1);
  pointer-events: none;
}
.transport .icon-button:hover::before { background-color: rgba(255, 255, 255, 0.1); }
.transport .icon-button:active::before { background-color: rgba(255, 255, 255, 0.2); }
/* The glyph rides over its own highlight. */
.transport .icon-button .icon { position: relative; }

/* Play: a circle holding the button that stops at the next chapter, becoming a
   lozenge on hover as the button that plays to the end slides out of it — the
   volume pill's own growth, on the same widths and easings, because it is the
   same gesture and should read as one. It opens over the ⏸ as readily as over
   the ▶: reaching for the pause is the moment somebody decides they would
   rather be taken somewhere else, and the second button offers the way of
   playing that is not being used — see `syncPlayMark` — without stopping
   first.

   The 28px radius is past half the height, so at 40px wide the browser clamps
   it back to a circle and the shut pill is exactly the disc it was. Each
   button keeps its own highlight — the circle inset 4px, YouTube's own drawing
   of it — so an open pill says which of the two is about to be pressed. */
.transport .play-pill .icon-button { width: 40px; }
.transport .play-pill .icon-button::before {
  width: auto;
  height: auto;
  inset: 4px;
  top: 4px;
  left: 4px;
  transform: none;
  border-radius: 50%;
}
/* **The pill stands open** (product decision, 2026-09-03). It used to open
   on hover and shut again, the way the volume pill does, and the second
   button appearing and vanishing as the pointer crossed the transport was
   the distraction — a control that moves while you are reaching for it. Both
   ways of playing are always on show instead, which is what they are for.

   The panel keeps its structure and its transition: it is still the thing
   with a width, so restoring the old behaviour is putting the `0` back and
   the `:hover` rule with it. */
.transport .play-panel {
  transition: width 0.2s cubic-bezier(0.05, 0, 0, 1);
  width: 40px;
  height: 40px;
  overflow: hidden;
  position: relative;
}
/* Absolute inside the panel for the reason the volume slider is: the panel is
   the thing whose width animates, and a button laid out in the flow would be
   squeezed by it rather than clipped. */
.transport .play-panel .icon-button { position: absolute; top: 0; left: 0; }

/* Toggles say "on" the way YouTube's CC button does: the icon swaps contrast
   — the enclosing shape fills with the ink and the glyph is cut out of it,
   transparent, so the pill shows through where the lines were. Each such icon
   carries both renderings (`when-off`, `when-on` in icons.js) and this picks
   one; nothing is ever painted black. */
.transport .icon-button .when-on { display: none; }
.transport .icon-button.on .when-on { display: inline; }
.transport .icon-button.on .when-off { display: none; }
/* The page-wide `.icon-button.on` paints an accent block; here "on" is the
   glyph's own contrast swap, so the block must not win the cascade. */
.transport .icon-button.on {
  background: transparent;
  border: 0;
  color: #eee;
}

/* The sliders icon answers the settings menu the way YouTube's gear takes a
   quarter turn: while the menu is open the knobs slide to new positions —
   each knob and the hole it cuts in its rail travel together (see icons.js). */
#gear .knob-a, #gear .knob-b { transition: transform 0.25s cubic-bezier(0.05, 0, 0, 1); }
#gear.open .knob-a { transform: translateX(-6.5px); }
#gear.open .knob-b { transform: translateX(6.5px); }

/* The volume pill: a circle holding the speaker, becoming a lozenge on hover
   as the slider slides out of it — widths, margins and both easings read off
   YouTube (grow decelerates, collapse accelerates away). `--vol` is the kept
   level; the handle travels the slider's width less its own. */
.transport .volume-pill {
  margin-left: 12px;
  --vol: 1;
}
.transport .volume-pill::after {
  content: '';
  position: absolute;
  inset: 4px;
  border-radius: 28px;
  background-color: transparent;
  transition: background-color 0.2s cubic-bezier(0.05, 0, 0, 1);
  pointer-events: none;
}
.transport .volume-pill:hover::after { background-color: rgba(255, 255, 255, 0.1); }
.transport .volume-pill #mute { width: 40px; }
.transport .volume-pill #mute::before { display: none; }
.transport .volume-pill,
.transport .volume-pill .volume-panel {
  transition:
    padding 0.2s cubic-bezier(0.4, 0, 1, 1),
    width 0.2s cubic-bezier(0.4, 0, 1, 1),
    margin 0.2s cubic-bezier(0.4, 0, 1, 1);
}
.transport .volume-panel {
  width: 0;
  height: 40px;
  overflow: hidden;
  position: relative;
}
.transport .volume-pill:hover,
.transport .volume-pill:focus-within,
.transport .volume-pill.dragging { padding-right: 16px; }
.transport .volume-pill:hover .volume-panel,
.transport .volume-pill:focus-within .volume-panel,
.transport .volume-pill.dragging .volume-panel {
  width: 52px;
  margin-right: 3px;
  transition:
    width 0.2s cubic-bezier(0.05, 0, 0, 1),
    margin 0.2s cubic-bezier(0.05, 0, 0, 1);
}
.transport .volume-slider {
  position: absolute;
  top: 0;
  left: 0;
  width: 52px;
  height: 40px;
  cursor: pointer;
  touch-action: none;
}
.transport .volume-slider::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 3px;
  margin-top: -1.5px;
  border-radius: 1.5px;
  background: linear-gradient(
    to right,
    #fff calc(var(--vol) * 40px + 6px),
    rgba(255, 255, 255, 0.2) 0);
}
.transport .volume-handle {
  position: absolute;
  top: 50%;
  left: calc(var(--vol) * 40px);
  width: 12px;
  height: 12px;
  margin-top: -6px;
  border-radius: 50%;
  background: #fff;
  pointer-events: none;
}

/* Full screen is YouTube's big mode: the same chrome a size up — 48px pills
   in a 72px band, 16px time, wider step cells — every value read off the
   fullscreen player the way the windowed ones were. */
.viewer-wrap:fullscreen .transport-row { height: 48px; margin-bottom: 12px; }
.viewer-wrap:fullscreen .transport .pill { height: 48px; border-radius: 28px; }
.viewer-wrap:fullscreen .transport .icon-button { width: 48px; height: 48px; }
.viewer-wrap:fullscreen .transport .icon-button::before { height: 40px; }
.viewer-wrap:fullscreen .transport .step-pill .icon-button { width: 54px; }
.viewer-wrap:fullscreen .transport .play-panel { height: 48px; width: 48px; }
/* The band's rule gives every button's highlight a fixed 40px height; the play
   buttons' is a disc inset from the cell, and has to stay one. */
.viewer-wrap:fullscreen .transport .play-pill .icon-button::before { height: auto; }
.viewer-wrap:fullscreen .transport .volume-pill #mute { width: 48px; }
.viewer-wrap:fullscreen .transport .volume-panel,
.viewer-wrap:fullscreen .transport .volume-slider { height: 48px; }
.viewer-wrap:fullscreen .transport .clock,
.viewer-wrap:fullscreen .transport .stamp { font-size: 16px; }
.viewer-wrap:fullscreen .transport .clock { padding: 0 12px; }
.viewer-wrap:fullscreen .transport .scrub-wrap { height: 28px; }

.icon-button {
  position: relative;
  width: 34px;
  height: 34px;
  justify-content: center;
  padding: 0;
  font-size: 1rem;
  flex: 0 0 auto;
}
.icon-button.on { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
/* "Stop at next chapter" drawn on the button it governs: the bar a player puts
   at the tip of the play arrow for "run as far as the marker". A pseudo-element
   offset from the button's middle rather than a second character, because the
   glyph is centred text — appending a bar to it would shunt the arrow off
   centre every time the setting changed. */

/* The heading under the constellation: title and subtitle on the left, and
   whatever labels the team on the right. */
.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin: 1.1rem 0 0.25rem;
}
.page-head h1 { margin: 0 0 0.2rem; }
.page-head .lede { margin: 0; }
/* The text column takes the width the row leaves it rather than shrinking to
   its own longest line. The notes below it are laid out in columns, and a
   width that came from how much prose happened to be in them would decide
   whether they sit side by side by accident. */
.page-head > div { flex: 1; min-width: 0; }
.tag.sample {
  background: var(--accent-soft);
  color: var(--accent-text);
  border-color: transparent;
  flex: 0 0 auto;
}

/* The three colours share a card, separated by a rule rather than by three
   headings floating in space — they are one decision made three times. */
.palette-row + .palette-row {
  margin-top: 1.1rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--line);
}
.palette-row h3 {
  margin: 0 0 .35rem;
  font-size: .95rem;
}

/* The gear's pop-up panel, anchored above it. */
/* What the line under the pointer says. Positioned from script, so only the
   look is here — and no pointer events, or reaching the box would move the
   pointer off the line that opened it and the two would fight. Same dark card
   as the gear menu: both are the picture answering a question about itself. */
.line-readout {
  position: absolute;
  left: 0;
  top: 0;
  max-width: 22rem;
  background: rgba(24, 32, 38, 0.96);
  color: #fff;
  border-radius: var(--radius);
  padding: 0.45rem 0.6rem;
  font-size: 0.82rem;
  line-height: 1.35;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
  pointer-events: none;
  z-index: 6;
}
.line-readout[hidden] { display: none; }

/* The options panel, built to the measurements of YouTube's own — the player
   whose controls this transport already borrows its shape from. Taken from
   player build b7457b7c:

     .ytp-popup   background rgba(0,0,0,.6) + backdrop-filter blur(16px)
                  border-radius 12px
     .ytp-panel   min-width 250px, overflow-y auto
     .ytp-menuitem  height 40px
     transitions  all .25s cubic-bezier(.4,0,.2,1)

   The transparency is the point of copying it. At .96 this panel was a solid
   card sitting on the picture it configures, and since it also runs three
   quarters of the picture's height, flipping a switch showed you nothing. At
   .6 the constellation reads straight through it. The blur is what keeps the
   text legible at that opacity — without it, white type over a pale plinth is
   unreadable, which is why YouTube added the blur when it moved from .9 to .6.

   One deliberate difference. YouTube sits over video, which is usually dark;
   this sits over a light plinth, so the panel carries a hairline border to
   hold its edge where black-on-light would otherwise dissolve. */
.settings-menu {
  position: absolute;
  right: 0.75rem;
  bottom: 3.4rem;
  min-width: 250px;
  /* Never taller than the picture, less the transport and a margin at each
     end. The old panel had no bound at all: thirteen rows came to 457px and
     simply clipped off the top of a short viewer, with no way to reach what
     was above the fold. */
  max-height: calc(100% - 5.5rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: rgba(0, 0, 0, 0.6);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  border: 1px solid rgba(255, 255, 255, 0.12);
  color: #fff;
  border-radius: 12px;
  padding: 0.4rem;
  font-size: 0.85rem;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
  z-index: 5;
}
.settings-menu[hidden] { display: none; }
/* A sentence in the menu — what a row means, or why there is none. */
.settings-menu .menu-note {
  padding: 0.25rem 0.6rem 0.4rem;
  color: rgba(255, 255, 255, 0.72);
  font-size: 0.78rem;
  line-height: 1.35;
  max-width: 17rem;
}
.settings-menu .primary-item .item-label { color: var(--brand-green, #6eaa00); font-weight: 600; }

/* --- a file of the animation (js/download.js) ------------------------------ */
/* The hidden copy of the player a file is rendered in: clipped by a box of
   no size rather than hidden, so the page inside lays out at the picture's
   own width and keeps drawing. */
.render-frame { position: fixed; left: 0; bottom: 0; width: 0; height: 0; overflow: hidden; pointer-events: none; }
/* How far the file has got, over the picture, since the transport hides. */
.render-progress {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.4rem 0.5rem 0.4rem 0.8rem;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.62);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: #fff;
  font-size: 0.82rem;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
}
.render-progress[hidden] { display: none; }
.render-progress .rp-bar { width: 110px; height: 6px; border-radius: 3px; background: rgba(255, 255, 255, 0.25); overflow: hidden; }
.render-progress .rp-bar i { display: block; height: 100%; width: 0; background: var(--brand-green, #6eaa00); transition: width 0.3s; }
/* The cross is two bars, not a glyph: a typed × sits on the type's own
   baseline and lands off the circle's centre by a pixel or two, which a
   circle this small shows. */
.render-progress .rp-cancel {
  position: relative; border: 0; background: rgba(255, 255, 255, 0.14); width: 1.5rem; height: 1.5rem;
  border-radius: 999px; cursor: pointer; padding: 0; flex: none;
}
.render-progress .rp-cancel::before, .render-progress .rp-cancel::after {
  content: ''; position: absolute; left: 50%; top: 50%; width: 11px; height: 2px;
  background: #fff; border-radius: 1px; transform: translate(-50%, -50%) rotate(45deg);
}
.render-progress .rp-cancel::after { transform: translate(-50%, -50%) rotate(-45deg); }
.render-progress .rp-cancel:hover { background: rgba(255, 255, 255, 0.28); }
body.still .render-progress { display: none; }
.settings-menu .group + .group { border-top: 1px solid rgba(255, 255, 255, 0.2); margin-top: 0.3rem; padding-top: 0.3rem; }
.settings-menu .group-title {
  color: rgba(255, 255, 255, 0.62);
  padding: 0.3rem 0.6rem 0.15rem;
  font-size: 0.74rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.settings-menu .item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  /* YouTube's 40px row. The old 28px packed thirteen settings into a card the
     size of a receipt; a control a presenter reaches for mid-session wants a
     target, not a line of text. */
  min-height: 40px;
  padding: 0.35rem 0.6rem;
  border-radius: 6px;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.settings-menu .item:hover { background: rgba(255, 255, 255, 0.12); }
.settings-menu .item .tick { width: 1rem; flex: none; opacity: 0; }
.settings-menu .item.on .tick { opacity: 1; }

/* A control whose precondition is not met is shown, greyed, with the reason on
   the line beneath it — rather than hidden, or left live and silently inert.
   Both of those were here: the legend and the box plot did nothing whatever
   with the height view off, and said so in no way at all. */
.settings-menu .item[disabled],
.settings-menu .item[aria-disabled='true'] {
  cursor: default;
  opacity: 0.45;
}
.settings-menu .item[disabled]:hover,
.settings-menu .item[aria-disabled='true']:hover { background: none; }
/* The primary row reads grey rather than faded green while it is off. */
.settings-menu .primary-item[aria-disabled='true'] .item-label { color: inherit; }
.settings-menu .reason {
  display: block;
  font-size: 0.74rem;
  color: rgba(255, 255, 255, 0.62);
  padding: 0 0.6rem 0.35rem;
  margin-top: -0.25rem;
}

/* On/off settings get a switch instead of a tick — the knob overhangs the
   track, which is what makes the state readable at a glance rather than only
   by comparing two rows. */
.settings-menu .switch-item { gap: 1rem; }
.settings-menu .row-item { gap: 0.5rem; }
.settings-menu .row-value {
  color: rgba(255, 255, 255, 0.62);
  /* The value is what the row is for — it says what the setting is without
     opening it — but it must not be what decides how wide the panel is. The
     height metric's name ran to 397px and dragged every other row out with
     it. */
  max-width: 11rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The one setting on this panel that is a quantity rather than a choice. The
   label and its reading share the first line the way every other row does, and
   the track takes a line of its own underneath: a slider squeezed in beside a
   label is a slider nobody can aim at, and this one exists to be swept back and
   forth while watching the board. */
.settings-menu .range-item {
  flex-wrap: wrap;
  row-gap: 0.4rem;
  cursor: default;
}
.settings-menu .range-item:hover { background: none; }
.settings-menu .menu-range {
  flex: 1 1 100%;
  width: 100%;
  margin: 0;
  accent-color: #ffffff;
  cursor: pointer;
}
.settings-menu .chevron { color: rgba(255, 255, 255, 0.62); font-size: 1.05rem; line-height: 1; flex: none; }
.settings-menu .menu-back {
  gap: 0.5rem;
  font-weight: 600;
  border-bottom: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 6px 6px 0 0;
}
.settings-menu .switch-label { flex: 1; text-align: left; }
.settings-menu .item-label { flex: 1; text-align: left; }
.settings-menu .switch {
  position: relative;
  flex: none;
  width: 34px;
  height: 13px;
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.28);
  transition: background 0.08s cubic-bezier(0.4, 0, 1, 1);
}
.settings-menu .switch .knob {
  position: absolute;
  top: 50%;
  left: 0;
  width: 19px;
  height: 19px;
  margin-top: -9.5px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
  transition: transform 0.08s cubic-bezier(0.4, 0, 1, 1), background 0.08s;
}
.settings-menu .switch-item.on .switch { background: rgba(255, 255, 255, 0.45); }
.settings-menu .switch-item.on .knob { transform: translateX(15px); background: #ffffff; }

.legend {
  position: absolute;
  left: 0.75rem;
  top: 0.75rem;
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.5rem 0.7rem;
  font-size: 0.8rem;
  pointer-events: none;
  max-width: 260px;
  box-shadow: var(--shadow);
}
.legend .ramp {
  height: 10px;
  border-radius: 5px;
  margin: 0.35rem 0 0.2rem;
}

/* --- charts ---------------------------------------------------------- */

.chart { width: 100%; height: 320px; }
.chart svg { width: 100%; height: 100%; overflow: visible; }
.chart .axis line, .chart .axis path { stroke: var(--line); }
.chart .axis text { fill: var(--muted); font-size: 11px; }
.chart .series { fill: none; stroke: var(--accent-text); stroke-width: 2; }
.chart .dot { fill: var(--accent-text); }
.chart .bar { fill: var(--accent-text); }
.chart .gridline { stroke: var(--line); stroke-dasharray: 2 4; }

/* --- matrix ---------------------------------------------------------- */

.matrix { border-collapse: collapse; font-size: 0.8rem; }
.matrix th, .matrix td {
  border: 1px solid var(--line);
  padding: 0.25rem 0.4rem;
  text-align: center;
  min-width: 2.4rem;
}
.matrix th.rowhead, .matrix th.colhead { text-align: left; color: var(--text); font-weight: 600; }
.matrix td.self { background: var(--panel-2); color: var(--muted); }
.matrix-scroll { overflow: auto; max-width: 100%; }

/* Diagnostics scatter/graph panels: the SVGs are square, so left unconstrained
   they stretch to the full card width and each one becomes 600px tall. */
#diagnostics-body .card svg { max-width: 340px; display: block; }
#diagnostics-body .chart { max-width: 640px; }

/* The sensitivity write-up on the Settings page — a document in a window, so
   it reads like the analysis it is rather than competing with the controls it
   sits behind. */
dialog.sensitivity {
  max-width: 46rem;
  max-height: 82vh;
  overflow-y: auto;
  padding: 1.4rem 1.6rem;
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
dialog.sensitivity::backdrop { background: rgba(0, 0, 0, 0.45); }
dialog.sensitivity h3 { margin: 1.1rem 0 0.3rem; font-size: 1rem; }
dialog.sensitivity p { margin: 0 0 0.4rem; max-width: 62ch; }
/* The figure is a white card of its own (the SVG paints it), so it reads the
   same over the light and the dark panel. */
dialog.sensitivity figure { margin: 0.6rem 0 0.5rem; }
dialog.sensitivity figure img { width: 100%; height: auto; display: block; }
dialog.sensitivity figcaption {
  font-size: 0.85rem;
  color: var(--muted);
  margin-top: 0.35rem;
  max-width: 62ch;
}

/* A heading you can type in — the team's name on the Team page. Chromeless
   until it is pointed at, so the page still reads as a title rather than as a
   form, and the box only appears once there is something to click. */
h1.title-line { margin-bottom: 0.2rem; }
.title-input {
  font: inherit;
  color: inherit;
  width: 100%;
  max-width: 42rem;
  padding: 0.1rem 0.35rem;
  margin-left: -0.35rem;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
}
.title-input:hover { border-color: var(--line); }
.title-input:focus {
  border-color: var(--accent);
  background: var(--panel);
  outline: none;
}

/* --- the film's subtitles, over the picture ------------------------------
   The cue the playhead is inside of, above the transport band, in the dark
   translucent box the design draws. Not part of the transport: the chrome
   hides after three idle seconds and the film's words must not. */
/* The subtitles are drawn from the variables js/subtitles.js sets on the
   element (YouTube's options, 2026-09-06): the outer element is the
   *window*, the span inside it the *background* box hugging the words. */
.subtitles {
  position: absolute;
  left: 50%;
  bottom: clamp(28px, 5.6cqw, 110px);
  /* **Over the overlays, not under them** (2026-09-09). A matrix or a trend
     is placed by the coordinator and can be as tall as its own contents, so
     the two can meet however carefully each is sized; when they do, the words
     are the half that must not be hidden. */
  z-index: 4;
  transform: translateX(-50%);
  max-width: 78%;
  padding: 0.3rem 0.5rem;
  border-radius: 10px;
  background: var(--sub-window, transparent);
  /* **Sized against the picture, not in pixels.** At a fixed 1rem the box is
     a twelfth of a 720p frame and a *third* of a 360p one — three lines
     instead of one — so the same film buries its own board in the preview
     pane and in a small window. `.viewer-wrap` is already a size container
     (the transport sheds its text against it), so `cqw` measures the stage
     rather than the window, which a sidebar would make a liar.

     **Proportional, with a floor** — which is how a caption on video really
     behaves, and the reason a mixed `calc(rem + cqw)` was not enough: its
     fixed part is 41% of the size at 1280 and 58% at 640, so the box still
     grew from an eighth of the frame to a quarter. Straight `cqw` holds the
     share constant, which is the promise, and the floor keeps it legible on
     a stage too small to read anything on anyway.

     1.36cqw lands on 17.4px at 1280 and 26px at 1920 — near the 16px this
     replaced and the 21.6px the fullscreen rule used to hard-code, which is
     why that override goes. */
  font-size: clamp(9px, 1.36cqw, 30px);
  line-height: 1.35;
  text-align: center;
  pointer-events: none;
}
.subtitles .cue-text {
  display: inline-block;
  padding: 0.45rem 0.9rem;
  border-radius: 8px;
  background: var(--sub-bg, rgba(9, 45, 45, 0.75));
  color: var(--sub-color, #f4f6ef);
  font-family: var(--sub-font, inherit);
  font-variant: var(--sub-variant, normal);
  font-size: calc(1em * var(--sub-size, 1));
  text-shadow: var(--sub-shadow, none);
}
/* No font size here any more: the container units above already grow with a
   fullscreen stage. What fullscreen still changes is the room the transport
   wants under the words, and the width to hold a line to. */
.viewer-wrap:fullscreen .subtitles { bottom: clamp(32px, 6cqw, 130px); max-width: 70%; }

/* --- the end card ---------------------------------------------------------
   The motto over the last picture, in the print serif the motto is set in
   everywhere it appears, and the music's credit under it. The forest tint is
   the plaque's own; z-index 0 puts it over the picture and, being earlier in
   the document, under the transport — so the controls stay on top of it. */
.end-card {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2rem;
  padding: 2rem 8% 5rem;
  background: rgba(9, 45, 45, 0.84);
  color: #f4f6ef;
  text-align: center;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.9s ease;
}
.end-card.show { opacity: 1; }
.end-card[hidden] { display: none; }
.end-card .motto {
  margin: 0;
  max-width: 24ch;
  font-family: var(--font-serif);
  font-weight: 400;
  font-style: italic;
  font-size: clamp(1.5rem, 4.4vw, 3.4rem);
  line-height: 1.15;
  letter-spacing: -0.01em;
  text-wrap: balance;
}
.end-card .credits {
  max-width: 60ch;
  font-size: 0.78rem;
  line-height: 1.55;
  opacity: 0.78;
}
.end-card .credit-title {
  margin-bottom: 0.2rem;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0.8;
}
.viewer-wrap:fullscreen .end-card .credits { font-size: 0.95rem; }

/* The introduction's title, over its first picture. A green plate rather
   than a scrim: the thing being introduced is *behind* it and has to stay
   visible, which is why the ground is the brand green at four fifths and
   not the end card's near-opaque dark. It is drawn under the transport for
   the same reason the end card is, takes no pointer, and its opacity is set
   from the playhead in `syncTitleCard` rather than by a transition — a
   transition runs its own clock and would leave the card half up on a
   scrub. */
.title-card {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.title-card[hidden] { display: none; }
.title-card p {
  margin: 0;
  padding: clamp(0.9rem, 2.6vw, 2rem) clamp(1.6rem, 5vw, 3.6rem);
  max-width: 76%;
  border-radius: 14px;
  background: rgba(47, 106, 58, 0.82);
  color: #f4f6ef;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.5rem, 5.2vw, 4rem);
  line-height: 1.1;
  letter-spacing: -0.015em;
  text-align: center;
  text-wrap: balance;
  box-shadow: 0 12px 40px rgba(9, 45, 45, 0.28);
}
@media (prefers-reduced-motion: reduce) {
  .end-card { transition: none; }
}

/* Direct: what a line costs in time, beside its text. */
.cue .spoken { display: flex; gap: 0.4rem; align-items: center; margin-top: 0.3rem; }
.chapter-row .seconds { color: var(--muted); font-size: 0.8rem; font-variant-numeric: tabular-nums; }
/* Analyse's matrix card folds (Marc, 2026-09-09). The twirl is the library's
   and the lanes' — a caret that turns rather than two glyphs to keep in step
   — and the head's own switches go with the body, since they set up a table
   that is not on the page. */
.chapter-row .twirl {
  width: 18px; height: 18px; flex: none; padding: 0; border: 0; border-radius: 4px;
  background: none; color: var(--muted); font-size: 0.72rem; line-height: 18px;
  cursor: pointer; transition: transform 0.15s ease;
}
.chapter-row .twirl.open { transform: rotate(90deg); }
.card.folded .chapter-row .switch-field, .card.folded .chapter-row > .link { display: none; }
.card.folded { margin-bottom: 1rem; }

/* --- Analyse and Diagnose: the cockpit --------------------------------------
   The head of either page (spec/redesign/teamium-analyse.html): the picker
   and the dials on one ground. By default it wears the page's theme, light
   with light and dark with dark; the switch in its head can put the cockpit
   alone into the other dress (`applyCockpitTheme` in cockpit.js). Dark is
   the forest ground the plaque uses. Everything on it reads the tokens
   below, so the two dresses are one set of rules. There is no hero on these
   pages; the cockpit carries the head. */
/* **The dress reaches the whole page, not only the dials** (Marc,
   2026-09-05). The switch used to put the `.cockpit` section alone into the
   other look, which left Analyse and Diagnose reading half dark and half
   light: four dials on a forest ground, then a white matrix and white
   readings under them. The page carries `data-theme` now, so every ordinary
   rule below it — the cards, the tables, the type — picks up the other
   palette without a single new declaration (see the palettes at the top).

   The paint has to reach the gutters. `main` is centred inside its grid area
   at `max-width`, so on a screen wider than that its background would stop
   short and leave two pale margins down a dark page. A spread shadow paints
   them, clipped vertically to the element's own band so it stays out of the
   bar; the rail sits at z-index 40 and covers the side it spills onto. */
main[data-theme] {
  background: var(--bg);
  color: var(--text);
  box-shadow: 0 0 0 100vmax var(--bg);
  clip-path: inset(0 -100vmax);
}

/* **The cockpit is not a panel, and there is one dress rather than three**
   (Marc, 2026-09-09: "make all pages match this colour scheme"). It was a
   forest box holding a light-on-dark palette of its own, with `.light` a
   second declaration of the same eleven tokens and the print block a third —
   so the page had two colour schemes in it and the switch in the head swapped
   which one the top of the page wore.

   There is one now. The `--ck-*` tokens are the page's own palette, the
   section paints nothing, and the light/dark switch reaches all of it for
   free through `data-theme` on the page, which is what `applyCockpitTheme`
   has set since the dress became the whole page's. Everything written against
   these tokens — the head's ghost buttons, the notes, the chapter tabs, the
   dial cards, Diagnose's run cards — follows without one rule changing.

   The tokens live here rather than being deleted, because they are still what
   a *panel of dials somewhere else* would re-point: `.advanced-part` shares
   this block, and the two together are the whole of "one set of rules, and
   only the values move".

   Hover is a step toward the ink, not back to the ground. On a panel the two
   are the same move — the forest dress hovered to a lighter white wash and
   `.light` hovered to `--bg`, because their ground was `--panel`. Here the
   ground *is* `--bg`, so `--bg` made the head's ghost buttons disappear into
   the page under the pointer; `--line` lifts them in the dark dress and
   presses them in the light one. */
.cockpit,
.advanced-part {
  --ck-bg: transparent;
  --ck-ink: var(--text);
  --ck-muted: var(--muted);
  --ck-card: var(--panel);
  --ck-line: var(--line);
  --ck-hover: var(--line);
  --ck-ghost: var(--panel-2);
  --ck-ghost-line: var(--line);
  --ck-tab-line: var(--line);
  --ck-on-bg: var(--forest);
  --ck-on-ink: #f4f6ef;
}
/* Its head, its heading and its cards stand on the page's own ground and line
   up at the gutter the cards below them stand at. */
.cockpit {
  color: var(--ck-ink);
  margin-bottom: 1.6rem;
}
.cockpit h1, .cockpit h2, .cockpit h3 { color: inherit; margin: 0; }
.cockpit .eyebrow {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ck-muted);
}
.cockpit-head { display: flex; gap: 1rem; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; }
.cockpit-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; }
.cockpit .button.ghost, .cockpit button.ghost {
  border: 1px solid var(--ck-ghost-line);
  background: var(--ck-ghost);
  color: inherit;
}
.cockpit .button.ghost:hover, .cockpit button.ghost:hover { background: var(--ck-hover); }
.cockpit button.icon-only { padding: 0.45rem; }
.cockpit button.icon-only .icon svg { width: 20px; height: 20px; }
.cockpit .empty.light { color: inherit; opacity: 0.8; }
.cockpit-note { color: var(--ck-muted); margin: 0.3rem 0 0.9rem; }
/* A class rule that set `display` would beat the attribute; this one does not,
   and says so anyway — the trap has been fallen into twice in this file. */
.cockpit-note[hidden] { display: none; }
.cockpit .muted { color: var(--ck-muted); }
.chapter-picker { display: flex; gap: 0.35rem; flex-wrap: wrap; margin-top: 1rem; }
.chapter-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--ck-tab-line);
  border-radius: 999px;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.82rem;
  cursor: pointer;
}
.chapter-tab .mark { font-size: 0.7rem; opacity: 0.8; }
.chapter-tab.simulated { border-style: dashed; }
.chapter-tab:hover { background: var(--ck-hover); }
.chapter-tab.on { background: var(--ck-on-bg); color: var(--ck-on-ink); border-color: var(--ck-on-bg); font-weight: 600; }
/* The cockpit's dials, in two groups: the two failure modes lead, the other
   two readings follow (Marc, 2026-09-08). The lead group is two across at any
   width the pair fits in, so the two are always read side by side -- they are
   a trade against each other, and a wrapped column loses that. The rest group
   keeps the old auto-fit, since nothing is being compared there. */
.dial-groups { display: grid; gap: 1.4rem; }
.dial-group-title { font-size: 1.15rem; line-height: 1.2; margin: 0; }
.dial-group.lead .dial-group-title { font-size: 1.35rem; }
.dial-group.rest .dial-group-title { font-size: 1rem; opacity: 0.9; }
/* **The failure modes' note is the page's width, not a column's** (Marc,
   2026-09-09): it is the definition of both dials rather than a caption under
   either, so it reads as a paragraph across the top of the pair. The
   readability ceiling stays on the *paragraph*, which is what it is for. */
.dial-group-note { margin: 0.15rem 0 0.9rem; color: var(--ck-muted); }
.dial-group-note p { margin: 0; max-width: 92ch; }
/* The animation's own gauge, on a card: it fills the card's width the way it
   fills a box over the picture, and the two lines under it are the reading
   and what it was before. */
.dial-card .gauge-box { width: 100%; max-width: 260px; margin: 0.2rem auto 0; }
.dial-card .gauge-box svg { display: block; width: 100%; height: auto; }
.dial-card.lead .gauge-box { max-width: 320px; }
.dial-card .gauge-readout { font-weight: 700; font-size: 1rem; line-height: 1.2; text-align: center; }
.dial-card.lead .gauge-readout { font-size: 1.1rem; }
/* Where the needle came from, said in words beside the grey ghost that shows
   it — a still picture cannot animate the sweep, so it names it instead. */
.dial-card .gauge-before { font-size: 0.78rem; color: var(--ck-muted); text-align: center; }
.dials { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 0.9rem; }
/* **The half under the matrix wears the page's own colours, and is one grid**
   (Marc, 2026-09-09). The two dials there sat on a forest panel of their own,
   like the cockpit above, which said they were a second instrument; they are
   not — they are two of the *advanced metrics*, and the six readings beside
   them are ordinary cards. A panel of one colour holding two of a part's
   eight items reads as a section that got left behind, and its grid then left
   an empty cell beside the pair before the readings began on a new row.

   So the panel is gone outright and the two dials are the first two cards of
   the readings' own grid, which is what makes the fault line map continue
   where transactive memory clarity ends. The `--ck-*` tokens these cards are
   written against are declared once, up with `.cockpit`, which took the same
   dress a day later — see there for why.

   What is left here is what belongs to the *part* rather than to the dress:
   its heading, its spacing, and the card treatment its dials share with the
   two at the top. */
/* A card among cards: the readings' own border, shadow, radius and padding.
   Every card either page draws — the dials at the top of Analyse, the eight
   under its matrix, and the run's two on Diagnose — is the same card, which
   is the whole of "one colour scheme". */
.advanced-part .dial-card,
.cockpit .dial-card,
.cockpit .run-card {
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: 1.25rem 1.4rem;
  box-shadow: var(--card-shadow);
}
.advanced-part .dial-card a.link, .advanced-part .dial-card button.link,
.cockpit .dial-card a.link, .cockpit .dial-card button.link {
  color: var(--accent-text);
}
/* One part under the matrix: its heading is the page's, over the two dials
   and the readings beside them both. */
.advanced-part { margin-top: 1.6rem; }
.advanced-title { margin: 0; font-size: 1.35rem; }
.advanced-note { margin: 0.15rem 0 0; max-width: 92ch; }
.advanced-part .readings { margin-top: 0.9rem; }
.dial-group.lead .dials { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.dial-group.rest .dials { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
/* The lead cards carry the weight: a larger dial and a larger title. The dial
   itself is drawn bigger by `renderDials` rather than scaled here, so its type
   stays the size it was drawn at.

   The ring this used to draw — `inset 0 0 0 1px var(--ck-line)` — went with
   the forest panel: it was the only edge a card had while the ground under it
   was a colour, and a card that now has a real border and a real shadow does
   not want a second line inside the first. Its padding went with it for the
   same reason: it is a card, and a card's padding is the card rule's. */
.dial-card.lead h3 { font-size: 1.35rem; }
.dial-card.lead .verdict { font-size: 1.02rem; }
.dial-card {
  border-radius: 14px;
  background: var(--ck-card);
  padding: 0.9rem 1rem 1rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.35rem;
}
.dial-card h2, .dial-card h3 { font-size: 1.05rem; line-height: 1.2; }
.dial-tag, .reading-tag {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.75;
}
.dial { display: block; color: inherit; }
.dial.dim { opacity: 0.95; }
.verdict { display: inline-flex; align-items: center; gap: 0.4rem; font-weight: 600; font-size: 0.92rem; }
.zone-dot { display: inline-block; width: 0.6rem; height: 0.6rem; border-radius: 50%; }
.dial-card .plain, .reading .plain { font-size: 0.86rem; line-height: 1.45; margin: 0.2rem 0 0; }
.dial-card .plain { opacity: 0.85; }
.dial-card .plain .link { margin-left: 0.3rem; font-size: 0.82rem; }
.links { display: flex; gap: 0.35rem 0.9rem; flex-wrap: wrap; justify-content: center; margin-top: 0.4rem; }
.reading .links { justify-content: flex-start; }
button.link {
  border: 0;
  background: none;
  padding: 0;
  color: inherit;
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
a.link { font-size: 0.82rem; font-weight: 600; color: inherit; }
.dial-card a.link.ask { color: inherit; }
.reading a.link { color: var(--accent-text); }
.maths { font-size: 0.82rem; line-height: 1.45; text-align: left; opacity: 0.9; }
.maths .source { opacity: 0.75; }
.dial-card .maths { border-top: 1px solid var(--ck-line); padding-top: 0.5rem; margin-top: 0.4rem; width: 100%; }
.dial-card .detail { font-size: 0.78rem; color: var(--ck-muted); }
/* More words, in a window of their own. */
dialog.explain {
  width: min(44rem, calc(100vw - 2rem));
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 1.3rem 1.5rem 1.4rem;
  background: var(--panel);
  color: var(--text);
  box-shadow: var(--shadow);
}
dialog.explain::backdrop { background: rgba(9, 45, 45, 0.45); }
/* The narration as SRT (direct.html): the file itself, monospaced, most of the window tall. */
dialog.explain.srt { width: min(60rem, calc(100vw - 2rem)); }
dialog.explain.srt textarea {
  display: block; width: 100%; box-sizing: border-box; min-height: 52vh; margin: 0.6rem 0 0.4rem;
  padding: 0.6rem 0.8rem; border: 1px solid var(--line); border-radius: 8px;
  background: var(--bg); color: var(--text);
  font-family: 'Consolas', 'DejaVu Sans Mono', monospace; font-size: 0.82rem; line-height: 1.4;
  resize: vertical; white-space: pre; tab-size: 4;
}
dialog.explain.srt .srt-error { margin: 0 0 0.5rem; color: var(--danger, #b8452c); font-size: 0.85rem; }
dialog.explain.srt .srt-error[hidden] { display: none; }
dialog.explain .explain-head { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.6rem; }
dialog.explain .explain-head h2 { flex: 1; margin: 0; font-size: 1.25rem; }
dialog.explain p { margin: 0 0 0.7rem; line-height: 1.5; }
.trend { display: block; color: inherit; margin-top: 0.4rem; }
.trend-slot { margin-top: 0.6rem; }
/* A dial's trend opens in its own card like a reading's (Marc, 2026-09-09).
   The card is a centred column, so the slot has to claim the width back and
   set its own text left, the way the chart's caption is written. */
.dial-card .trend-slot { width: 100%; text-align: left; }
.dial-card .trend-slot .trend-head { justify-content: space-between; }

/* The readings under the cockpit: a value in the page's own green and a
   trend, no verdict — except the spectral gap, whose source gives a rule. */
.readings { margin-top: 1rem; }
/* The sixth reading given the width of the page: its chart is the one on this
   page a person actually reads a shape off, and half a column is not enough
   room for twenty chapters. */
.reading.standing.wide { grid-column: 1 / -1; }
.reading h2 { margin: 0.15rem 0 0.3rem; font-size: 1.15rem; }
.reading-value { display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap; }
.reading-value .big { font-family: var(--font-display); font-size: 2.1rem; font-weight: 700; color: var(--accent-text); line-height: 1; }
.reading-value .sub { color: var(--muted); font-size: 0.85rem; }
.scale-ends { display: flex; justify-content: space-between; margin-top: 0.3rem; }
.camps { display: grid; gap: 0.35rem; margin: 0.6rem 0; font-size: 0.85rem; }
.camp { display: flex; gap: 0.5rem; align-items: baseline; padding-left: 0.6rem; border-left: 4px solid var(--line); }
.camp strong { flex: none; }
.camp.a { border-color: #e8842a; }
.camp.b { border-color: #2f7fc1; }
.camp.bridges { border-color: var(--muted); }

/* The matrix: rows give, columns receive, the survey's colours, the
   diagonal outlined because it is assumed. */
.switch-field { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; }
.switch-field select { width: auto; }
.matrix-scroll { overflow-x: auto; margin-top: 0.5rem; }
/* As wide as its cells and no wider: the page's tables stretch to the full
   width, and with fixed value cells every spare pixel landed in the names
   column. The column names are written vertically rather than rotated, so
   the header row grows to fit them instead of the scroll box clipping them. */
table.matrix { width: auto; border-collapse: separate; border-spacing: 2px; font-size: 0.72rem; }
table.matrix th, table.matrix td { padding: 0 0.2rem; border-bottom: 0; }
table.matrix th { font-weight: 600; color: var(--muted); }
table.matrix th.corner { font-size: 0.62rem; line-height: 1.2; text-align: right; white-space: normal; vertical-align: bottom; }
table.matrix th.col { vertical-align: bottom; width: 2.5rem; padding: 0.2rem 0; }
table.matrix th.col span {
  display: inline-block;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  white-space: nowrap;
  max-height: 8.5rem;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.1;
}
table.matrix th.row { text-align: right; white-space: nowrap; padding-right: 0.4rem; }
table.matrix th.row span { display: block; max-width: 8.5rem; overflow: hidden; text-overflow: ellipsis; }
table.matrix td.cell {
  width: 2.5rem;
  min-width: 2.5rem;
  height: 1.7rem;
  text-align: center;
  border-radius: 4px;
  font-size: 0.7rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
table.matrix td.cell:hover { outline: 2px solid var(--ink); outline-offset: -2px; }
table.matrix td.self { outline: 2px solid var(--ink); outline-offset: -2px; font-weight: 700; cursor: default; }
table.matrix td.absent, table.matrix th.absent { color: var(--muted); opacity: 0.55; text-align: center; }
.matrix-trend { margin-top: 0.8rem; padding: 0.7rem 0.9rem; border-radius: 12px; background: var(--panel-2); }
.trend-head { display: flex; align-items: center; gap: 0.6rem; }
.trend-head .spacer { flex: 1; }

/* Diagnose: the run, the four tables of moves, and the plan. */
.run-grid { margin-top: 1rem; }
/* Radius and padding come from the card rule above, which every card on both
   cockpit pages shares; what is left here is the ground. */
.run-card { background: var(--ck-card); }
.run-card h3 { font-size: 0.95rem; margin-bottom: 0.4rem; }
.run-card h3 + h3, .run-card p + h3 { margin-top: 0.9rem; }
.share-value { font-family: var(--font-display); font-size: 1.8rem; font-weight: 700; line-height: 1.1; }
.share-value .small { font-family: var(--font); font-size: 0.8rem; font-weight: 400; opacity: 0.8; }
.run-card input[type="range"] { width: 100%; margin: 0.4rem 0 0.2rem; }
.params { display: flex; gap: 0.7rem; flex-wrap: wrap; font-size: 0.9rem; }
.param b { margin-right: 0.25rem; font-weight: 700; }
.run-count { font-size: 0.95rem; }
.run-count b { font-size: 1.4rem; font-family: var(--font-display); }
.progress { height: 6px; border-radius: 3px; background: var(--ck-line); margin: 0.5rem 0; overflow: hidden; }
.progress i { display: block; height: 100%; background: var(--accent); transition: width 0.4s ease; }
.risk-table.focus { outline: 2px solid var(--accent); }
.risk-head { display: flex; gap: 0.9rem; align-items: center; }
.risk-head .dial { color: var(--text); flex: none; }
.risk-head .best { margin-top: 0.3rem; font-size: 0.88rem; }
/* A ranked move: the sentence, its detail, the four dials in a row, and the
   same move both ways on the line under it, in brackets, all four dials.
   A list rather than a table, because in a half-width card the sentence
   column of a six-column table was two words wide. */
.moves-list { display: grid; gap: 0.5rem; margin-top: 0.7rem; }
.move {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.2rem 0.75rem;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: 12px;
}
.move.picked { background: var(--accent-soft); border-color: var(--accent); }
.move .sentence { font-weight: 600; }
.move .detail { font-size: 0.82rem; color: var(--muted); }
.move .pick { grid-column: 2; grid-row: 1 / span 4; align-self: start; display: flex; gap: 0.25rem; }
.move .values { display: flex; gap: 0.2rem 0.9rem; flex-wrap: wrap; font-size: 0.85rem; font-variant-numeric: tabular-nums; }
.move .values .code { color: var(--muted); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em; margin-right: 0.2rem; }
.move .values .ranked { font-weight: 700; }
.move .values .worse { color: var(--danger); }
.move .both { font-size: 0.82rem; color: var(--muted); }
.move .both .values { display: inline-flex; }
.move .both.better .verdict { color: var(--ok); font-weight: 700; }
.move .both.worse .verdict { color: var(--danger); font-weight: 700; }
/* The plan, kept in sight: a tray along the foot of the window once a move
   is picked, saying how many and what they do, and leading to the plan.

   It was forest with white ink, which is the one thing on Diagnose that could
   still be a second colour scheme after the cockpit gave its up. What holds it
   off the page is not a colour — it is a *edge*: it is stuck to the foot, so a
   hairline along its top and a shadow cast upward say it is over the page,
   which is what it is. The colour said something else, and said it in the
   scheme the page had stopped using. */
.plan-tray {
  position: sticky;
  bottom: 0;
  z-index: 15;
  display: flex;
  gap: 0.6rem 1.2rem;
  align-items: center;
  flex-wrap: wrap;
  margin: 1rem 0 0;
  padding: 0.6rem 1rem;
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  border: 1px solid var(--line);
  border-bottom: 0;
  background: var(--panel);
  color: var(--text);
  box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.18);
}
.plan-tray[hidden] { display: none; }
.plan-tray .tray-title { font-weight: 700; }
.plan-tray .tray-dials { display: flex; gap: 0.9rem; flex-wrap: wrap; font-size: 0.85rem; font-variant-numeric: tabular-nums; }
.plan-tray .tray-dials .code { opacity: 0.7; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em; margin-right: 0.25rem; }
.plan-tray .spacer { flex: 1; }
/* The white wash these wore was for a forest ground; on the page's own panel
   they are the page's own ghost buttons, which the cockpit's head wears too. */
.plan-tray .button.ghost, .plan-tray button.ghost { border: 1px solid var(--line); background: var(--panel-2); color: inherit; }
.plan-tray .button.ghost:hover, .plan-tray button.ghost:hover { background: var(--line); }
.plan-move .tune { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.3rem; font-size: 0.82rem; color: var(--muted); }
.plan-move .tune input[type="range"] { width: 9rem; margin: 0; }
.plan-move .tune b { color: var(--text); min-width: 2.6rem; }
.what-if-modes { display: grid; gap: 0.35rem; margin: 0.6rem 0; }
.what-if-modes label { display: flex; align-items: flex-start; gap: 0.5rem; margin: 0; font-size: 0.88rem; font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--text); }
.what-if-modes input { width: auto; margin-top: 0.2rem; }
table.moves { width: 100%; margin-top: 0.7rem; font-size: 0.86rem; }
table.moves th.num, table.moves td.num { text-align: right; font-variant-numeric: tabular-nums; width: 3.2rem; }
table.moves .ranked { font-weight: 700; }
table.moves td.worse { color: var(--danger); }
table.moves tr.picked td { background: var(--accent-soft); }
table.moves td.pick { width: 4.4rem; text-align: right; white-space: nowrap; }
table.moves td.pick button + button { margin-left: 0.25rem; }
table.moves .both { margin-top: 0.15rem; }
table.moves .both.better { color: var(--ok); }
table.moves .both.worse { color: var(--danger); }
table.moves .sentence { font-weight: 600; }
.plan { margin-top: 1rem; }
.plan-move { display: flex; gap: 0.7rem; align-items: flex-start; justify-content: space-between; padding: 0.5rem 0; border-top: 1px solid var(--line); }
.plan-risks { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.6rem; margin: 0.8rem 0; }
.plan-risk { padding: 0.5rem 0.7rem; border-radius: 10px; background: var(--panel-2); border-left: 4px solid var(--line); }
.plan-risk.zone-green { border-color: #7cc85a; }
.plan-risk.zone-amber { border-color: #f0b53a; }
.plan-risk.zone-red { border-color: #e2573f; }
.plan-delta { font-family: var(--font-display); font-size: 1.25rem; font-weight: 700; }
.plan-risks .plan-note { grid-column: 1 / -1; }
.plan-actions { margin-top: 0.5rem; }
.card.inner { margin-top: 0.8rem; background: var(--panel-2); }

/* Full screen is the whole page as one team cockpit, scrollable. */
main:fullscreen { overflow-y: auto; background: var(--surface); padding: 1.5rem 2rem; max-width: none; }
/* **The breadcrumb, carried into full screen** (`crumbsWhileFullscreen` in
   api.js). Full screen is `main` and the bar is outside it, so all three
   crumbs go with the page — and a set built a second time inside `main` was
   the obvious answer and the wrong one: two controls for one state is how
   they drift apart. It is the bar's own node, moved and put back.

   It therefore arrives with the bar's colours and lands on the cockpit's own
   ground, so the crumbs take the row's ink instead and the separators and
   chevrons follow it. That mattered most while the cockpit was forest and the
   bar was not; it is still right now that both are the page's, since the bar's
   chrome is its own rung and the crumbs should read as the row they are in. */
.bar-crumbs.in-fullscreen { min-width: 0; margin-right: auto; color: inherit; }
.cockpit .bar-crumbs.in-fullscreen .nav-switcher,
.cockpit .bar-crumbs.in-fullscreen .film-button,
.cockpit .bar-crumbs.in-fullscreen .chapter-button,
.cockpit .bar-crumbs.in-fullscreen .bar-chapter-name,
.cockpit .bar-crumbs.in-fullscreen .crumb-sep { color: inherit; }
.cockpit .bar-crumbs.in-fullscreen .nav-switcher:hover,
.cockpit .bar-crumbs.in-fullscreen .film-button:hover,
.cockpit .bar-crumbs.in-fullscreen .chapter-button:hover { background: var(--ck-hover); }

/* The one destructive thing a coordinator can do to a team, at the foot of
   the team's own page: quiet by default — a rule, not a red box — and loud
   only in the button and the typed confirmation it asks for. */
.card.danger-zone { border-color: var(--line); background: transparent; box-shadow: none; }
.card.danger-zone .row { align-items: center; gap: 1rem; }
.card.danger-zone button.danger { border-color: var(--danger); color: var(--danger); }
.card.danger-zone button.danger:hover { background: var(--danger); color: #fff; border-color: var(--danger); }

/* --- Direct: the head is the film ------------------------------------------
   The film's switcher (name, default mark, the team's films behind it with
   Duplicate, Rename, Make default and Delete), the three film-wide settings
   as small buttons that open small windows, and under it the toolbar with
   "Saved", undo and redo. There is no Save button: everything saves as it
   changes (spec/redesign/teamium-direct.html). */
/* The film in the bar: the second crumb and its gear. Direct had a head band
   of its own carrying the switcher, four setting buttons, the save state and
   undo/redo; all of it is here now, which is what took the page from three
   bands to one. */
.bar-film { position: relative; display: inline-flex; align-items: center; gap: 0.2rem; min-width: 0; }
.bar-film .film-button {
  display: inline-flex; align-items: center; gap: 0.35rem;
  min-width: 0; max-width: min(16rem, 100%); min-height: 2rem; padding: 0 0.45rem;
  border: 1px solid transparent; border-radius: 8px;
  background: transparent; color: var(--text);
  font: inherit; font-size: 0.92rem; font-weight: 600; cursor: pointer;
}
.bar-film .film-button:hover { background: var(--panel-2); }
.bar-film .film-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Watch shows the animation's name as a crumb too, and where the team has
   only one it is a label rather than a menu — one of anything is not a
   choice. */
.bar-film-name {
  display: inline-block; vertical-align: middle;
  max-width: 16rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 0.92rem; font-weight: 600; color: var(--text);
}
.bar-film .pop-menu { top: 2.3rem; left: 0; }
.film-gear {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  border: 1px solid transparent; border-radius: 8px;
  background: transparent; color: var(--muted); cursor: pointer;
}
.film-gear:hover { background: var(--panel-2); color: var(--text); }
.film-gear .icon svg { width: 18px; height: 18px; }
/* In the sequence pane's head it sits beside "+ Add", so it takes that row's
   own button height rather than the bar's. */
.pane-head .film-gear { width: 26px; height: 26px; border-color: var(--line); }

/* The third crumb: which chapter. The same button as the animation beside it,
   because they are the same kind of thing — where you are, and a way to
   somewhere else — and one narrower, since a chapter's name is longer than an
   animation's and it is the last thing in the row that may push the state
   pill off the end. */
.bar-chapter { position: relative; display: inline-flex; align-items: center; min-width: 0; }
.bar-chapter .chapter-button {
  display: inline-flex; align-items: center; gap: 0.35rem;
  min-width: 0; max-width: min(18rem, 100%); min-height: 2rem; padding: 0 0.45rem;
  border: 1px solid transparent; border-radius: 8px;
  background: transparent; color: var(--text);
  font: inherit; font-size: 0.92rem; font-weight: 600; cursor: pointer;
}
.bar-chapter .chapter-button:hover { background: var(--panel-2); }
.bar-chapter .chapter-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-chapter .chev { display: inline-flex; color: var(--muted); }
.bar-chapter .chev svg { width: 14px; height: 14px; }
.bar-chapter-name {
  font-size: 0.92rem; font-weight: 600; color: var(--text);
  max-width: 18rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* A film of twenty chapters is a long menu, and the numbers in front of the
   names are what it is read down, so they are not allowed to wrap away from
   them. The menu's own height is capped by `placeMenu`. */
.film-menu .menu-item .label, .chapter-menu .menu-item .label { min-width: 0; }
.chapter-menu .menu-item { text-align: left; }
.chapter-menu .menu-item .label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The settings menu is one page of rows with four pages behind it — the same
   shape the player's gear uses. A row says what it is and what it says now. */
.pop-menu .setting-row { display: flex; align-items: center; gap: 0.5rem; }
.pop-menu .setting-row .key {
  min-width: 4rem; color: var(--muted);
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
}
.pop-menu .setting-row .value { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pop-menu .setting-row .chev { display: inline-flex; color: var(--muted); transform: rotate(-90deg); }
.pop-menu .setting-row .chev svg { width: 14px; height: 14px; }
.pop-menu .back-row { display: flex; align-items: center; gap: 0.4rem; font-weight: 700; }
.pop-menu .back-row .chev { display: inline-flex; color: var(--muted); transform: rotate(90deg); }
.pop-menu .back-row .chev svg { width: 14px; height: 14px; }

/* "Saved", undo and redo, bar right — where Docs and Figma both put a
   document's state and its history. */
.save-state { font-size: 0.78rem; color: var(--muted); white-space: nowrap; }
.save-state.warn { color: var(--danger); }
.save-state.busy { opacity: 0.75; }
.bar-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  border: 1px solid transparent; border-radius: 8px;
  background: transparent; color: var(--muted); cursor: pointer;
}
.bar-icon:hover:not(:disabled) { background: var(--panel-2); color: var(--text); }
.bar-icon:disabled { opacity: 0.35; cursor: default; }
.bar-icon .icon svg { width: 17px; height: 17px; }
.film-switch, .setting { position: relative; }
.film-button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  max-width: min(28rem, 80vw);
  padding: 0.4rem 0.75rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.1rem;
  letter-spacing: -0.01em;
}
.film-button .film-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.film-button .chev { display: inline-flex; color: var(--muted); }
.film-button .chev svg { width: 14px; height: 14px; }
.default-mark {
  padding: 0.05rem 0.45rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--accent-text);
  font-family: var(--font);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}
.film-meta { white-space: nowrap; }
.setting-button { display: inline-flex; align-items: baseline; gap: 0.4rem; padding: 0.3rem 0.7rem; font-size: 0.85rem; }
.setting-button .key { color: var(--muted); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; }
.setting-button .value { font-weight: 600; max-width: 14rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The small windows: hung under their button, one open at a time. */
.pop-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 30;
  width: max-content;
  min-width: 14rem;
  max-width: min(28rem, calc(100vw - 2rem));
  padding: 0.4rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: var(--shadow);
  text-align: left;
}
.pop-menu.right { left: auto; right: 0; }
.pop-menu .menu-title { margin: 0.3rem 0.6rem 0.25rem; font-size: 0.66rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); }
.pop-menu .menu-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  text-decoration: none;
  text-align: left;
  font: inherit;
  font-size: 0.9rem;
  cursor: pointer;
}
.pop-menu .menu-item .label { flex: 1; min-width: 0; white-space: normal; overflow-wrap: anywhere; line-height: 1.3; }
.pop-menu .menu-item .label .default-mark { margin-left: 0.4rem; vertical-align: 0.1em; }
.pop-menu .menu-item .sub { display: block; color: var(--muted); font-size: 0.78rem; }
.pop-menu .menu-item:hover { background: var(--panel-2); border-color: transparent; }
.pop-menu .menu-item.on { background: var(--accent-soft); color: var(--accent-text); font-weight: 700; }
.pop-menu .menu-item:disabled { color: var(--muted); cursor: default; opacity: 1; }
.pop-menu .menu-item:disabled:hover { background: transparent; }
.pop-menu .menu-item.danger { color: var(--danger); }
.pop-menu .menu-state { font-size: 0.72rem; color: var(--muted); white-space: nowrap; flex: none; }
.pop-menu .menu-rule { margin: 0.3rem 0; border: 0; border-top: 1px solid var(--line); }
/* `white-space` because the menu hangs from a pane head that does not wrap:
   a note inherited that and ran on as one line the width of three menus. */
.pop-menu .menu-note { margin: 0.4rem 0.6rem 0.3rem; max-width: 30ch; color: var(--muted); font-size: 0.8rem; line-height: 1.4; white-space: normal; }
.pop-menu .menu-note a { color: var(--accent-text); font-weight: 600; }
.pop-menu .listen { flex: none; padding: 0.1rem 0.5rem; font-size: 0.8rem; }
/* A track and the bar under it: two pixels, the width of the pane, clickable
   anywhere along its length — a piece is judged by its middle as much as by
   how it opens. It fills as the track plays and empties when it stops. */
.pop-menu .track-row { margin-bottom: 0.3rem; }
.pop-menu .track-row .menu-item { margin-bottom: 0.15rem; }
.pop-menu .track-bar {
  position: relative;
  height: 2px;
  margin: 0 0.6rem;
  border-radius: 2px;
  background: var(--line);
  cursor: pointer;
}
/* The bar is two pixels and a pointer needs more than two pixels to aim at,
   so the target is grown over it rather than under it: absolute, so the line
   itself neither thickens nor moves what follows. */
.pop-menu .track-bar::before { content: ''; position: absolute; left: 0; right: 0; top: -6px; height: 14px; }
.pop-menu .track-bar i { display: block; height: 2px; width: 0; border-radius: 2px; background: var(--accent); }
.pop-menu .mix { display: grid; grid-template-columns: 6.5rem 1fr 3.2rem; gap: 0.35rem 0.5rem; align-items: center; margin: 0.5rem 0.6rem 0.3rem; font-size: 0.82rem; }
.pop-menu .mix label { margin: 0; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.pop-menu .mix input[type="range"] { width: 100%; margin: 0; }
.pop-menu .mix input[type="number"] { width: 100%; padding: 0.15rem 0.3rem; font-size: 0.82rem; font-variant-numeric: tabular-nums; }
.pop-menu .mix .read { font-variant-numeric: tabular-nums; color: var(--text); text-align: right; }
.pop-menu .mix .pair { display: flex; gap: 0.35rem; align-items: center; }

.direct-toolbar { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin: 0.8rem 0 0.4rem; }
.direct-toolbar .spacer { flex: 1; }
.save-state { min-width: 8rem; font-size: 0.82rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.save-state.warn { color: var(--danger); font-weight: 600; }
.save-state.busy { color: var(--accent-text); }

/* --- Direct: the workspace -------------------------------------------------
   Premiere's arrangement with one panel fewer: the chapters on the left, the
   film live in the centre and right, the sequence along the foot at full
   width. Built for 1280 pixels and up; below that the panes stack and the
   two-scale promise is not made. The grave accent gives the pane under the
   pointer the whole workspace (`max-*`).

   **The 1280 is the workspace's own width, not the window's** (2026-09-06).
   It was a media query, which was near enough while Direct collapsed the rail
   on opening and the workspace really was the window. It no longer does — the
   hamburger decides for every page — so with the rail open a 1400px window
   leaves the workspace about 1160 and the media query went on claiming there
   was room for two columns. A container query asks the question the rule was
   always about. */
/* **The second cut is four panes that fill the window** (Marc, 2026-09-06;
   spec/redesign/teamium-direct-v2.md). Premiere's 2×2: the chapter panel
   and the preview over the library and the sequence, the editor as tall as
   the window under the bar, never scrolling as a page — each pane scrolls
   inside itself. Three gutters divide it Premiere's way: the row split runs
   the full width, and the top pair and the bottom pair divide independently,
   so the sequence can take more width than the preview does. Their places
   are set by dragging and remembered per person (direct.html, `layout`).

   `main` scrolls rather than hides its overflow, and that is deliberate: in
   the 2×2 the workspace is exactly the height of `main` and nothing scrolls;
   under 900 px of workspace the panes stack (below) and the page has to. The
   floor was 1280 for the three-pane cut, where the chapter panel and the
   preview shared one row; with each pane scrolling inside itself and a
   240 px minimum a column, a 1183 px workspace — a 1440 px window with the
   rail open — is a comfortable 2×2, and stacking it there was the fault. */
main.direct {
  max-width: none;
  height: calc(100dvh - var(--bar-h));
  padding: 0.6rem 0.8rem 0.7rem;
  overflow: auto;
  container-type: inline-size;
  container-name: workspace;
}
body.focus main.direct { height: 100dvh; padding-top: 0.6rem; }
.workspace {
  --gutter: 8px;
  --split-row: 58%;
  --split-top: 40%;
  --split-bottom: 26%;
  position: relative;
  display: grid;
  grid-template-rows: var(--split-row) var(--gutter) minmax(0, 1fr);
  height: 100%;
  min-height: 0;
}
.wrow { display: grid; grid-template-columns: var(--split-x) var(--gutter) minmax(0, 1fr); min-height: 0; }
.wrow.top { --split-x: var(--split-top); }
.wrow.bottom { --split-x: var(--split-bottom); }
.pane {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.pane.preview { background: var(--scene-bg); }
.pane.preview .pane-head { background: var(--panel); }
/* The gutters: an 8 px strip that shows a hairline under the pointer and
   lights while it is held. Dragging one crosses the preview, whose iframe
   would take the pointer for its own document — so every iframe is deaf
   for the length of the drag (`gutter-drag` on the body). */
.gutter { position: relative; z-index: 4; touch-action: none; }
.gutter.col { cursor: col-resize; }
.gutter.row { cursor: row-resize; }
.gutter::after { content: ''; position: absolute; border-radius: 2px; background: var(--line); opacity: 0; transition: opacity 0.15s; }
.gutter.col::after { top: 0; bottom: 0; left: 3px; width: 2px; }
.gutter.row::after { left: 0; right: 0; top: 3px; height: 2px; }
.gutter:hover::after, .gutter.active::after { opacity: 1; background: var(--accent); }
body.gutter-drag { user-select: none; }
body.gutter-drag.gutter-col { cursor: col-resize; }
body.gutter-drag.gutter-row { cursor: row-resize; }
body.gutter-drag iframe { pointer-events: none; }
/* Every head is one line: the name and its sentence on one baseline (the
   two were centred against each other and sat a pixel apart), the rest of
   the head's controls centred against that line. */
.pane-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: nowrap;
  flex: none;
  /* One height for all four, whether or not the head carries buttons. */
  min-height: 2.8rem;
  padding: 0.3rem 0.9rem;
  border-bottom: 1px solid var(--line);
  box-sizing: border-box;
  font-size: 0.85rem;
  color: var(--muted);
  white-space: nowrap;
}
.pane-head .pane-title { display: inline-flex; align-items: baseline; gap: 0.5rem; flex: none; min-width: 0; }
.pane-head h2 { margin: 0; font-size: 0.95rem; color: var(--text); }
.pane-head .lead { color: var(--muted); }
.pane-head .spacer { flex: 1; min-width: 0.5rem; }
.pane-head .keys { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.pane-head .small { flex: none; }
.pane-head kbd {
  padding: 0 0.35rem;
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  border-radius: 5px;
  background: var(--panel-2);
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 0.72rem;
  color: var(--text);
}
.pane-body { flex: 1; min-height: 0; overflow: auto; padding: 0.7rem 0.8rem 0.8rem; }
.pane-body .card { margin: 0 0 0.7rem; }
.pane-body .card.selected { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.pane-body .card .chapter-row h2 { cursor: default; }
.pane.preview iframe { display: block; flex: 1 1 auto; min-height: 0; width: 100%; border: 0; background: var(--scene-bg); }
.pane.sequence #sequence { flex: 1 1 auto; min-height: 0; overflow: auto; }
/* A maximized pane lies over the other three rather than replacing them, so
   the preview's player keeps running under it and comes back as it was. */
.workspace.max-preview .pane.preview, .workspace.max-sequence .pane.sequence { position: absolute; inset: 0; z-index: 5; }
/* The library: rows with a tile in the kind's colour, in sections. */
.lib-section { margin: 0 0 0.7rem; }
.lib-section h3 { margin: 0.2rem 0 0.25rem; font-size: 0.68rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.lib-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0.35rem 0.5rem;
  border: 1px solid transparent;
  border-radius: 6px;
  background: none;
  color: var(--text);
  text-align: left;
  font: inherit;
  font-size: 0.85rem;
  line-height: 1.3;
  cursor: pointer;
}
.lib-row:hover, .lib-row.open { background: var(--panel-2); border-color: var(--line); }
.lib-row[disabled] { opacity: 0.5; cursor: default; }
.lib-row .lib-tile { width: 14px; height: 26px; border-radius: 3px; flex: none; }
.lib-row .lib-text { display: flex; flex-direction: column; min-width: 0; }
.lib-row .lib-sub { font-size: 0.72rem; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lib-form { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.8rem; margin: 0 0 0.4rem; padding: 0.3rem 0.5rem 0.4rem 1.9rem; font-size: 0.85rem; color: var(--muted); }
.lib-form .dials-row { margin: 0; }
.lib-form select { max-width: 100%; font-size: 0.85rem; }
/* The library's filter, its section heads with a twirl and a sort, and a
   row that is an action rather than a chapter. */
.lib-filter { width: 100%; margin: 0 0 0.6rem; padding: 0.35rem 0.55rem; font-size: 0.85rem; box-sizing: border-box; }
.lib-section h3 { display: flex; align-items: center; gap: 0.35rem; }
.lib-section h3 .twirl {
  width: 16px; height: 16px; padding: 0; border: 0; background: none; color: var(--muted);
  font-size: 0.7rem; line-height: 16px; cursor: pointer; transition: transform 0.15s;
}
.lib-section h3 .twirl.open { transform: rotate(90deg); }
.lib-section h3 .count { font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted); }
.lib-section h3 .spacer { flex: 1; }
.lib-section h3 .sort { padding: 0 0.4rem; border: 1px solid var(--line); border-radius: 999px; background: none; color: var(--muted); font: inherit; font-size: 0.64rem; letter-spacing: 0; text-transform: none; cursor: pointer; }
.lib-section h3 .sort:hover { border-color: var(--accent); color: var(--accent-text); }
.lib-row.action .lib-tile { border: 1px dashed var(--muted); background: none; }
.lib-empty { margin: 0.2rem 0 0.4rem 0.5rem; font-size: 0.78rem; font-style: italic; color: var(--muted); }
.cp-row.cp-body textarea { width: 100%; }
/* The chapter panel by kind (2026-09-06): the window a clip opens, the ▶ in
   a line's span, the script under the narration lane, the transition and
   animation clips. */
.cp-pop {
  position: fixed;
  z-index: 60;
  width: min(26rem, calc(100vw - 16px));
  padding: 0.6rem 0.7rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
  color: var(--text);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.22);
}
.cp-pop-body { display: flex; flex-direction: column; gap: 0.5rem; }
.cp-pop-head { display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; }
.cp-pop-head .spacer { flex: 1; }
.cp-pop-head .proposed { font-size: 0.64rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.cp-pop textarea { width: 100%; min-height: 3.6rem; resize: vertical; box-sizing: border-box; font-size: 0.9rem; }
.cp-pop-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; font-size: 0.8rem; color: var(--muted); }
.cp-pop-row .spacer { flex: 1; }
.cp-pop-row .spoken { display: inline-flex; align-items: center; gap: 0.35rem; }
.cp-pop-row select.lsel { max-width: 16rem; }
/* **A number in a window is a small field with its unit beside it.** The
   global `input { width: 100% }` reached these, so every one of them was as
   wide as the row and the unit wrapped onto a line of its own -- "Starts at
   0 s" cost three rows in a panel whose whole complaint is the scrolling.
   The shape is the lanes' own, which already read right. */
.cp-pop .hot { display: inline-flex; align-items: baseline; gap: 2px; color: var(--text); cursor: ew-resize; }
.cp-pop .hot i { font-style: normal; font-size: 0.68rem; color: var(--muted); }
.cp-pop .hotv { flex: none; width: 3.6rem; padding: 0.15rem 0.35rem; text-align: right; }
.cp-pop .hot:hover .hotv { border-color: var(--accent); }
.cp-pop .hotv:focus { cursor: text; }
/* A choice or a member list is still allowed the width it needs. */
.cp-pop .pop-row select, .cp-pop .pop-row input[type="text"]:not(.hotv) { width: auto; min-width: 6rem; }
.lane .cue-span { display: flex; align-items: center; gap: 0.2rem; }
.lane .cue-span .play.mute { opacity: 0.45; cursor: default; }
.lane .cue-span .words { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lane.script { min-height: 0; background: none; align-items: start; }
.cp-script { display: flex; flex-direction: column; gap: 2px; padding: 2px 0 6px; }
.cp-script .line {
  display: grid; grid-template-columns: 1.4rem 3.2rem minmax(0, 1fr); gap: 0.4rem; align-items: baseline;
  width: 100%; padding: 0.15rem 0.3rem; border: 1px solid transparent; border-radius: 5px;
  background: none; color: var(--text); text-align: left; font: inherit; font-size: 0.78rem; line-height: 1.35; cursor: pointer;
}
.cp-script .line:hover { background: var(--panel-2); border-color: var(--line); }
.cp-script .line b { color: var(--muted); }
.cp-script .line i { font-style: normal; color: var(--muted); font-variant-numeric: tabular-nums; }
.lane .tr.hls { height: auto; min-height: 22px; }
.lane .hl-span { height: 16px; }
.lane .tr-clip, .lane .an-clip {
  position: absolute; top: 3px; height: 16px; box-sizing: border-box;
  border-radius: 3px; padding: 0 0.3rem; overflow: hidden;
  background: color-mix(in srgb, var(--accent) 35%, var(--panel-2));
  border: 1px solid color-mix(in srgb, var(--accent) 70%, var(--line));
  color: var(--text); font-size: 0.62rem; line-height: 14px; white-space: nowrap;
}
.lane .tr-clip.instant { width: 4px !important; padding: 0; background: var(--accent); }
.lane .tr-clip.instant span { display: none; }
.lane .tr-clip b, .lane .an-clip b { position: absolute; top: 0; bottom: 0; width: 7px; cursor: ew-resize; }
.lane .tr-clip b[data-grip="end"], .lane .an-clip b[data-grip="end"] { right: 0; border-right: 2px solid color-mix(in srgb, var(--accent) 80%, var(--text)); }
.lane .an-clip b[data-grip="start"] { left: 0; border-left: 2px solid color-mix(in srgb, var(--accent) 80%, var(--text)); }
.lane .gbtn.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-text); }
/* The sound effects (2026-09-06): the library's rows with a ▶, its
   categories under one section, the clips on the panel's sound lane. */
.lib-sub { margin: 0.1rem 0 0.3rem 0.4rem; }
.lib-sub h4 { display: flex; align-items: center; gap: 0.35rem; margin: 0.15rem 0; font-size: 0.74rem; font-weight: 600; color: var(--text); }
.lib-sub h4 .twirl {
  width: 16px; height: 16px; padding: 0; border: 0; background: none; color: var(--muted);
  font-size: 0.7rem; line-height: 16px; cursor: pointer; transition: transform 0.15s;
}
.lib-sub h4 .twirl.open { transform: rotate(90deg); }
.lib-row.sfx { padding: 0.25rem 0.5rem 0.25rem 0.4rem; }
.lib-row .lib-play {
  flex: none; width: 22px; height: 22px; padding: 0; border: 1px solid var(--line); border-radius: 50%;
  background: var(--panel); color: var(--text); font-size: 0.6rem; line-height: 20px; cursor: pointer;
}
.lib-row .lib-play:hover { border-color: var(--accent); color: var(--accent-text); }
.seq-ghost.sfx { background: var(--kind-general); color: var(--kind-general-ink); }
.lane .sf-clip {
  position: absolute; top: 2px; height: 18px; box-sizing: border-box;
  display: flex; align-items: center; gap: 0.25rem; padding: 0 0.3rem; overflow: hidden;
  border-radius: 3px; background: var(--kind-general); color: var(--kind-general-ink);
  font-size: 0.64rem; line-height: 16px; white-space: nowrap; cursor: pointer;
}
.lane .sf-clip .play {
  flex: none; width: 13px; height: 13px; padding: 0; border: 0; border-radius: 50%;
  background: rgba(255, 255, 255, 0.85); color: #17302a; font-size: 0.45rem; line-height: 13px; cursor: pointer;
}
.lane .sf-clip .words { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.lane .sf-clip b { position: absolute; top: 0; bottom: 0; right: 0; width: 7px; cursor: ew-resize; border-right: 2px solid rgba(255, 255, 255, 0.6); }
.lane .tr.sfx { height: 22px; }
/* The logo lane's clips (direct.html): the wordmark's own green, the end the grip. */
.lane .lg-clip {
  position: absolute; top: 2px; height: 18px; box-sizing: border-box;
  display: flex; align-items: center; gap: 0.25rem; padding: 0 0.4rem; overflow: hidden;
  border-radius: 3px; background: var(--brand-green, #6eaa00); color: #0d1b17;
  font-size: 0.64rem; line-height: 16px; white-space: nowrap; cursor: pointer;
}
.lane .lg-clip .words { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.lane .lg-clip b { position: absolute; top: 0; bottom: 0; right: 0; width: 7px; cursor: ew-resize; border-right: 2px solid rgba(13, 27, 23, 0.45); }
.lane .tr.lgo { height: 22px; }
/* The library's overlays on the player (2026-09-06): a title page over the
   picture, the matrix of the round before, the trend of the dials so far.
   Each fades and lifts on its chapter's own clock (`--rise`). */
/* The wordmark over the picture (constellation.html `syncLogo`): a clip's
   size is its width as a share of the frame, its position one of nine
   anchors a margin in from the edges. */
.logo-overlay { position: absolute; z-index: 1; pointer-events: none; --lm: 4%; filter: drop-shadow(0 2px 10px rgba(9, 45, 45, 0.25)); }
.logo-overlay[hidden] { display: none; }
.logo-overlay svg { display: block; width: 100%; height: auto; }
.logo-overlay[data-position="center"] { left: 50%; top: 50%; transform: translate(-50%, -50%); }
.logo-overlay[data-position="top"] { left: 50%; top: var(--lm); transform: translateX(-50%); }
.logo-overlay[data-position="bottom"] { left: 50%; bottom: var(--lm); transform: translateX(-50%); }
.logo-overlay[data-position="left"] { left: var(--lm); top: 50%; transform: translateY(-50%); }
.logo-overlay[data-position="right"] { right: var(--lm); top: 50%; transform: translateY(-50%); }
.logo-overlay[data-position="top-left"] { left: var(--lm); top: var(--lm); }
.logo-overlay[data-position="top-right"] { right: var(--lm); top: var(--lm); }
.logo-overlay[data-position="bottom-left"] { left: var(--lm); bottom: var(--lm); }
.logo-overlay[data-position="bottom-right"] { right: var(--lm); bottom: var(--lm); }
.page-card { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.page-card .plate { position: absolute; pointer-events: auto; box-sizing: border-box; }
.page-card[hidden] { display: none; }
/* **The plate is sized against the picture, and it scales as one object**
   (Marc, 2026-09-09). It was `vw` with a ceiling, and both halves were wrong
   in the way the subtitles already were a day earlier: `vw` is the *window*,
   which the rail makes a liar — the stage inside a 1280 window is 1007 — and
   the ceilings were reached on any desktop at all (`4.2vw` passes `3.2rem` at
   1220px), so the type and the padding were simply **frozen** at 51.2px and
   38.4/57.6 while the plate's own width went on scaling with the frame.

   That is what made the wordmark drift. Its box is a share of the *plate*, so
   it grew; the padding it was aligned to did not. The two agreed at a plate
   896px wide — a canvas of about 1266, which is where it was measured — and
   nowhere else: at 1600 the mark stopped 15.2px short of the text column and
   sat 4px under the plate's top edge instead of centred in the band.

   In `cqw` against `.viewer-wrap` the whole plate is one proportion, so the
   padding is a fixed share of the plate (4.5 of 70, or 6.43%) and the mark's
   right edge at 93.5% lands on the text column's 93.57% at *every* size. The
   floors are for a small stage; there is deliberately no ceiling, because a
   title card should be the same share of a phone and a wall. */
.page-card .plate {
  transform: translateY(var(--rise, 0px));
  max-width: 70%;
  padding: max(0.75rem, 3cqw) max(1.1rem, 4.5cqw);
  border-radius: 16px;
  background: rgba(9, 45, 45, 0.86);
  color: #f4f6ef;
  text-align: center;
  box-shadow: 0 12px 40px rgba(9, 45, 45, 0.3);
}
.page-card h2 { margin: 0; font-family: var(--font-display); font-weight: 700; font-size: max(1.15rem, 4cqw); line-height: 1.1; text-wrap: balance; }
.page-card p { margin: 0.8em 0 0; font-size: max(0.8rem, 1.69cqw); line-height: 1.45; }
.page-card p[hidden] { display: none; }
.ov {
  position: absolute;
  left: 50%;
  top: 7%;
  z-index: 3;
  box-sizing: border-box;
  padding: 12px 14px 14px;
  border-radius: 14px;
  background: rgba(9, 45, 45, 0.9);
  color: #f4f6ef;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
  transform: translate(-50%, var(--rise, 0px));
  pointer-events: none;
}
.ov[hidden] { display: none; }
/* Nothing over the picture ever scrolls or spills. `fitMatrix` is what makes
   that true by measuring and scaling; this is the guarantee behind it. */
.ov { overflow: hidden; }
/* **Centred** (Marc, 2026-09-09). It was ranged left, which is a document's
   habit: on a plate over a picture the title belongs over the middle of the
   thing it names, and the matrix's own grid is centred under it. The
   qualifier stays on the same baseline beside it, and the two wrap together
   on a narrow plate rather than the sub falling under the title's first
   word. */
.ov .ov-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 0.6rem;
  margin-bottom: 8px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.05rem;
  text-align: center;
}
.ov .ov-sub { font-size: 0.72rem; font-weight: 400; color: #cfd8d2; }
.ov.size-small { width: 34%; }
.ov.size-medium { width: 52%; }
.ov.size-large { width: 72%; }
.ov.size-full { width: 94%; top: 3%; }
/* --- the interaction matrix over the picture ------------------------------
   **A grid, not a table**, and that is a fix rather than a preference: the
   stylesheet's own `th, td { border-bottom: 1px solid var(--line) }` and
   `th { color: var(--muted) }` reached the table this used to be, so every
   row label was a light-page grey on a dark plate with a hairline under it
   belonging to no row. A grid also puts each name in exactly its own track
   rather than near it, and lets a cell be transformed — which the build
   below needs and a table cell may not have.

   Every size is set on the element by `matrixMetrics` in constellation.html,
   because they are decided against the stage and the subtitle rather than
   against anything a stylesheet can see. */
.ov-matrix {
  display: grid;
  justify-content: center;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.ov-matrix .mx-corner { grid-column: 1; grid-row: 1; }
/* The column names are **rotated**, so a name has the head's height to use
   rather than a cell's width — which is what lets them be drawn at all on the
   nineteen-member matrix that used to drop them. Bottom-aligned, so the names
   sit against the grid however long they are. */
.ov-matrix .mx-col {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  overflow: hidden;
  border-radius: 4px;
  font-size: var(--mx-name, 12px);
  font-weight: 600;
  color: #f4f6ef;
}
/* Turned on its side, so a name has the head row's height to use rather than
   a cell's width — and clipped *with* an ellipsis where even that is not
   enough, because a name cut off silently reads as a misspelling rather than
   as a name that did not fit. In a vertical writing mode the inline axis is
   the block's height, which is what `max-height` is holding here. */
.ov-matrix .mx-col > span {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  white-space: nowrap;
  max-height: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  padding-bottom: 3px;
}
.ov-matrix .mx-name {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding-right: 0.45em;
  overflow: hidden;
  white-space: nowrap;
  border-radius: 4px;
  font-size: var(--mx-name, 12px);
  font-weight: 600;
  color: #f4f6ef;
}
/* `position: relative` on both, so a cell pointed at can be lifted over its
   neighbours while it grows — a grid item with no position of its own paints
   in document order, which would put half the grown square under the cells
   after it. */
.ov-matrix .mx-cell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  border: 1px solid rgba(9, 45, 45, 0.55);
  font-size: var(--mx-value, 9px);
  font-weight: 600;
}
.ov-matrix .mx-cell.self { background: rgba(255, 255, 255, 0.08); }

/* The grid and the colour bar side by side, and the box they are scaled
   inside. `mx-fit` is as wide as the plate and `mx-body` is as wide as its
   own content, so `fitMatrix` can measure the second against the first and
   scale it — a transform does not change layout, which is why the fit box is
   given the scaled height by hand. */
.mx-fit { display: flex; justify-content: center; overflow: hidden; }
.mx-body { display: flex; align-items: center; gap: 1.1em; transform-origin: top center; }

/* The survey's own ten steps: square, number, brace where a wording is
   shared, words. A grid of four columns and ten rows, so a run that shares a
   wording is one cell spanning its rows rather than the same sentence
   printed three times. */
.mx-scale {
  display: grid;
  grid-template-columns: auto auto auto minmax(0, 1fr);
  align-items: center;
  column-gap: 0.5em;
  row-gap: 2px;
  width: var(--mx-legend, 200px);
  font-size: var(--mx-step, 12px);
}
/* Drawn first and spanning every column, so it lies under the line it lights
   rather than over it. */
.mx-scale .mx-row { grid-column: 1 / -1; height: 100%; border-radius: 4px; }
.mx-scale .mx-sq {
  width: 1.15em;
  height: 1.15em;
  grid-column: 1;
  border-radius: 3px;
  border: 1px solid rgba(9, 45, 45, 0.55);
}
.mx-scale .mx-no {
  grid-column: 2;
  min-width: 1.4em;
  text-align: right;
  font-weight: 700;
  color: #e3e9e3;
}
.mx-scale .mx-brace { grid-column: 3; align-self: stretch; display: flex; color: #b9c8bd; }
.mx-scale .mx-brace svg { width: 0.7em; height: 100%; }
.mx-scale .mx-txt {
  grid-column: 4;
  align-self: center;
  color: #cfd8d2;
  line-height: 1.25;
}
.ov-legend { display: flex; flex-wrap: wrap; gap: 0.3rem 0.9rem; margin: 0 0 6px; font-size: 0.7rem; }
.ov-legend i { display: inline-block; width: 12px; height: 3px; margin-right: 0.3rem; vertical-align: middle; border-radius: 2px; }
.trend-overlay svg { display: block; width: 100%; height: auto; }
.trend-overlay text { fill: #f4f6ef; font-size: 11px; }
.trend-overlay .grid { stroke: rgba(255, 255, 255, 0.18); }
@container workspace (max-width: 899px) {
  .workspace { display: flex; flex-direction: column; gap: 0.8rem; height: auto; }
  .wrow { display: contents; }
  .gutter { display: none; }
  .pane { flex: none; min-height: 12rem; }
  .pane.preview { order: 1; height: 56vh; }
  .pane.sequence { order: 2; }
  .pane.chapters { order: 3; max-height: 60vh; }
  .pane.library { order: 4; max-height: 40vh; }
}
/* The same rule against the window, kept as a floor. The window can never be
   narrower than the workspace inside it, so this can only ever agree with the
   query above — and where `@container` is not understood at all it is the
   whole of the stacking, which leaves a narrow window right and only a wide
   one with a rail open wrong. */
@media (max-width: 899px) {
  .workspace { display: flex; flex-direction: column; gap: 0.8rem; height: auto; }
  .wrow { display: contents; }
  .gutter { display: none; }
  .pane { flex: none; min-height: 12rem; }
  .pane.preview { order: 1; height: 56vh; }
  .pane.sequence { order: 2; }
  .pane.chapters { order: 3; max-height: 60vh; }
  .pane.library { order: 4; max-height: 40vh; }
}

/* The sequence: two columns, the track names and the lanes, the ruler over
   the lanes and the playhead through them; the navigator under; the foot. */
.seq-pane { padding: 0.5rem 0.75rem 0.6rem; user-select: none; }
.seq-grid { display: grid; grid-template-columns: 7rem minmax(0, 1fr); gap: 0 0.5rem; }
.seq-heads { display: flex; flex-direction: column; gap: 4px; }
.seq-heads .th {
  display: flex;
  align-items: center;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.seq-heads .th.ruler { height: 22px; }
.seq-lanes { position: relative; display: flex; flex-direction: column; gap: 4px; overflow: hidden; cursor: text; }
.sruler { position: relative; height: 22px; border-bottom: 1px solid var(--line); }
.sruler .tick {
  position: absolute;
  top: 2px;
  transform: translateX(-50%);
  font-size: 0.66rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.sruler .tick::after { content: ''; position: absolute; left: 50%; bottom: -8px; height: 6px; width: 1px; background: var(--line); }
.sruler .mk {
  position: absolute;
  bottom: -1px;
  width: 7px;
  height: 7px;
  background: var(--kind-survey);
  border-radius: 1px;
  transform: translate(-50%, 50%) rotate(45deg);
  z-index: 1;
}
.strack { position: relative; border-radius: 6px; background: var(--panel-2); overflow: hidden; }
/* A block is two lines (Marc, 2026-09-06): the number and the title, and
   under them what the chapter is, how long it runs and how much it says —
   one line could not carry enough to tell the blocks apart. There is no ×
   on a block any more; Premiere has none either. */
.strack.chapters .ch {
  position: absolute;
  top: 2px;
  bottom: 2px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  padding: 0 0.45rem;
  box-sizing: border-box;
  border-radius: 4px;
  background: var(--kind-survey);
  color: var(--kind-survey-ink);
  font-size: 0.72rem;
  line-height: 1.15;
  white-space: nowrap;
  overflow: hidden;
  /* Taken hold of, like everything on the chapter pane's own timeline. */
  cursor: grab;
}
.strack.chapters .ch .l1 { display: flex; gap: 0.3rem; min-width: 0; font-weight: 600; }
.strack.chapters .ch .l1 .n { flex: none; opacity: 0.75; }
.strack.chapters .ch .l1 .t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.strack.chapters .ch .l2 { font-size: 0.64rem; opacity: 0.85; overflow: hidden; text-overflow: ellipsis; }
/* The kinds' colours, one set for the blocks, the ruler's marks, the tile in
   the chapter panel's head and the library's rows: introductions grey,
   circle constellations green, survey constellations yellow (simulated ones
   striped), analysis items blue, general items slate. */
.strack.chapters .ch.intro, .sruler .mk.intro, .kind-tile.intro { background: var(--kind-intro); color: var(--kind-intro-ink); }
.strack.chapters .ch.circle, .sruler .mk.circle, .kind-tile.circle { background: var(--kind-circle); color: var(--kind-circle-ink); }
.strack.chapters .ch.round, .sruler .mk.round, .kind-tile.round { background: var(--kind-survey); color: var(--kind-survey-ink); }
.strack.chapters .ch.simulated, .kind-tile.simulated {
  background: repeating-linear-gradient(45deg, var(--kind-survey) 0 6px, var(--kind-survey-2) 6px 12px);
  color: var(--kind-survey-ink);
}
.sruler .mk.simulated { background: var(--kind-survey); }
.strack.chapters .ch.meter, .sruler .mk.meter, .kind-tile.meter,
.strack.chapters .ch.matrix, .sruler .mk.matrix, .kind-tile.matrix,
.strack.chapters .ch.trend, .sruler .mk.trend, .kind-tile.trend { background: var(--kind-analysis); color: var(--kind-analysis-ink); }
.strack.chapters .ch.rewind, .strack.chapters .ch.end, .strack.chapters .ch.title,
.sruler .mk.rewind, .sruler .mk.end, .sruler .mk.title,
.kind-tile.rewind, .kind-tile.end, .kind-tile.title {
  background: var(--kind-general);
  color: var(--kind-general-ink);
}
.kind-tile { display: inline-block; width: 10px; height: 10px; border-radius: 2px; flex: none; }
/* The hands (2026-09-06): the selection, the placeholder a lifted block
   leaves, the group bar, the trim grip and the narration floor, the parked
   track, the marquee and the ghost that follows the pointer. */
.strack .ch.sel { outline: 2px solid var(--text); outline-offset: -2px; }
.strack .ch.on { outline: 2px solid var(--accent); outline-offset: -2px; }
.strack .ch.ph { background: transparent; border: 2px dashed var(--accent); box-sizing: border-box; cursor: grabbing; }
.strack.chapters .ch .grip { position: absolute; top: 0; bottom: 0; right: 0; width: 7px; cursor: ew-resize; }
.strack.chapters .ch .floor {
  position: absolute; left: 0; top: 0; bottom: 0;
  background: repeating-linear-gradient(-45deg, transparent 0 4px, rgba(0, 0, 0, 0.11) 4px 6px);
  pointer-events: none;
}
.strack.chapters .ch.trimming { outline: 2px solid var(--accent); outline-offset: -2px; }
.strack.chapters .ch.grouped { top: 5px; }
.strack.chapters .grpbar { position: absolute; top: 1px; height: 3px; border-radius: 2px; background: var(--text); opacity: 0.6; z-index: 1; pointer-events: none; }
.strack.parked .ch { top: 3px; bottom: 3px; padding: 0 0.4rem; justify-content: center; }
.strack.parked .hint { position: absolute; left: 0.6rem; top: 50%; transform: translateY(-50%); font-size: 0.68rem; font-style: italic; color: var(--muted); white-space: nowrap; pointer-events: none; }
.seq-lanes .marquee { position: absolute; border: 1px solid var(--accent); background: color-mix(in srgb, var(--accent) 18%, transparent); z-index: 4; pointer-events: none; }
.seq-ghost {
  position: fixed; z-index: 200;
  display: flex; align-items: center; gap: 0.4rem;
  max-width: 18rem; padding: 0.3rem 0.55rem;
  border-radius: 5px;
  background: var(--kind-survey); color: var(--kind-survey-ink);
  font-size: 0.75rem; font-weight: 600; white-space: nowrap;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.28);
  opacity: 0.94;
  pointer-events: none;
}
.seq-ghost .t { overflow: hidden; text-overflow: ellipsis; }
.seq-ghost .count { font-weight: 700; opacity: 0.8; }
.seq-ghost .badge { display: inline-grid; place-items: center; width: 18px; height: 18px; border-radius: 50%; background: rgba(255, 255, 255, 0.92); color: #17302a; font-size: 0.8rem; font-weight: 700; }
.seq-ghost.nodrop .badge { background: #c0392b; color: #fff; }
.seq-ghost.intro { background: var(--kind-intro); color: var(--kind-intro-ink); }
.seq-ghost.circle { background: var(--kind-circle); color: var(--kind-circle-ink); }
.seq-ghost.simulated { background: var(--kind-survey-2); }
.seq-ghost.meter, .seq-ghost.matrix, .seq-ghost.trend { background: var(--kind-analysis); color: var(--kind-analysis-ink); }
.seq-ghost.rewind, .seq-ghost.end, .seq-ghost.title { background: var(--kind-general); color: var(--kind-general-ink); }
body.chapter-drag { user-select: none; }
body.chapter-drag, body.chapter-drag * { cursor: grabbing !important; }
body.chapter-drag.no-drop, body.chapter-drag.no-drop * { cursor: not-allowed !important; }
body.chapter-drag iframe { pointer-events: none; }
body.chapter-trim, body.chapter-trim * { cursor: ew-resize !important; }
.strack.narration .cue2 {
  position: absolute;
  top: 2px;
  bottom: 2px;
  padding: 0 0.3rem;
  box-sizing: border-box;
  border-radius: 3px;
  background: var(--water);
  color: #fff;
  font-size: 0.68rem;
  line-height: 20px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}
.strack.narration .cue2.lit { outline: 2px solid var(--accent); outline-offset: -2px; }
.seq-lanes .playhead { position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px; background: var(--text); z-index: 3; pointer-events: none; }
.seq-lanes .playhead::before { content: ''; position: absolute; top: 0; left: 50%; width: 10px; height: 10px; border-radius: 50%; background: var(--text); transform: translateX(-50%); }
/* --- the navigator (js/navigator.js) ----------------------------------------
 *
 * The scroll bar under a timeline — the sequence's and the chapter pane's,
 * one class and one set of rules, because two of them on one page have to
 * behave and read identically or the hand learns one and is wrong on the
 * other. Premiere's: a thin track, the visible part as a bar on it, and the
 * two ends as **outlined circles**, which say "take hold of me" where a
 * filled block only says "here is the edge".
 */
.snav { position: relative; height: 10px; margin-top: 6px; border-radius: 5px; background: var(--panel-2); cursor: pointer; }
.snav .win {
  position: absolute; top: 0; bottom: 0; border-radius: 5px;
  background: color-mix(in srgb, var(--moss) 26%, var(--panel-2)); cursor: grab;
}
.snav .win .grip {
  position: absolute; top: 50%; width: 14px; height: 14px; box-sizing: border-box;
  border: 2px solid var(--moss); border-radius: 50%; background: var(--panel);
  transform: translateY(-50%); cursor: ew-resize;
}
.snav .win { cursor: grab; }
.snav .win .grip { cursor: ew-resize; }
.snav .win .grip.l { left: -7px; }
.snav .win .grip.r { right: -7px; }
.snav .win:hover .grip, .snav .win .grip:hover { border-color: var(--accent); }
/* Looking at the whole thing: the window is the track, so nothing is said. */
.snav .win.whole, .snav.fitted .win { background: transparent; }
.snav b { position: absolute; top: -2px; bottom: -2px; width: 2px; margin-left: -1px; background: var(--text); pointer-events: none; }

/* The chapter pane's, laid out on the track side of the pane's own divider so
   that what it spans is what the tracks span. */
/* **The zoom bar is always in reach.** It is the control that says which
   stretch of the clock the pane is showing, so having to scroll a tall
   chapter to the bottom to reach it is the wrong way round -- you scroll
   *because* you cannot see what you want, and the bar is what fixes that.
   Sticky rather than moved out of the scroller: it stays in the flow, so a
   short chapter puts it directly under the last lane rather than leaving a
   gap above it. */
.cp-nav { --cp-pad: 0.6rem; display: grid; grid-template-columns: var(--cp-split, 260px) minmax(0, 1fr); gap: 0.5rem; align-items: center; margin: 0; padding: 0.4rem calc(var(--cp-pad) + 1px) 0.5rem; }
.cp-nav .snav { margin-top: 0; }
/* The chapter pane's bar is a foot of the pane, so nothing is ever under
   it; the sequence's is sticky inside its own scroller, where the tracks are
   few and the bar is the last of them. */
.pane-foot { flex: none; border-top: 1px solid var(--line); background: var(--panel); }
.seq-grid.nav-row {
  position: sticky; bottom: 0; z-index: 4;
  background: var(--panel); box-shadow: 0 -6px 10px -8px rgba(0, 0, 0, 0.35);
}
.seq-grid.nav-row { padding: 0.35rem 0 0.45rem; }
.seq-grid.nav-row .snav { margin-top: 0; }
.seq-foot { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.4rem; padding-left: 7.5rem; font-size: 0.72rem; color: var(--muted); }
.seq-foot .spacer { flex: 1; }
.seq-foot kbd { padding: 0 0.3rem; border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 4px; background: var(--panel-2); font-size: 0.66rem; color: var(--text); }

/* A copy of a chapter, and a meter chapter, in the sequence. */
.strack.chapters .ch.copy { box-shadow: inset 3px 0 0 rgba(0, 0, 0, 0.28); }
.strack.chapters .ch.end { cursor: default; }
/* The rewind's picture on the player: a VHS pass drawn over the frame. */
.vhs { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
/* The attribute has to win over the canvas reset's display, or the pass lingers. */
.vhs[hidden] { display: none; }

/* Which of the team's films Watch is playing, for a coordinator. */
.film-pick { display: inline-flex; align-items: center; gap: 0.4rem; margin-left: auto; font-size: 0.85rem; color: var(--muted); }
.film-pick select { padding: 0.2rem 0.4rem; font-size: 0.85rem; }
.mottos label { display: flex; align-items: flex-start; gap: 0.45rem; margin: 0.25rem 0; font-size: 0.9rem; line-height: 1.35; text-transform: none; letter-spacing: 0; color: var(--text); }
.mottos label input { width: auto; margin-top: 0.25rem; }
.chapter-row .kind.copy, .chapter-row .kind.meter { color: var(--muted); }
.dials-row { display: flex; gap: 0.8rem; flex-wrap: wrap; margin: 0.5rem 0 0.2rem; }
.dials-row label { display: inline-flex; align-items: center; gap: 0.35rem; margin: 0; font-size: 0.85rem; text-transform: none; letter-spacing: 0; color: var(--text); }
.dials-row label input { width: auto; }

/* --- Direct: the chapter panel ---------------------------------------------
   Premiere's Effect Controls: a parameter side and a track side under one
   ruler that spans the chapter, with a divider between them.

   The divider is not decoration. The first cut laid the stopwatch, the
   name, the value and the keyframe navigator in one flex row of 9.5rem,
   where the name was the only thing that could shrink — so on a 618 px
   pane "Rotation" and "Pan" were laid out at *zero* pixels and the camera
   rows carried no names at all. The name has a floor now, the wide
   controls (a choice, the members) take a line of their own under it, and
   how much room the whole side gets is the coordinator's to set. */
.cp-head { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.5rem; }
.cp-head .title { flex: 1; min-width: 10rem; font-family: var(--font-display); font-weight: 700; font-size: 1.05rem; padding: 0.25rem 0.5rem; }
.cp-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; margin: 0.35rem 0; font-size: 0.85rem; color: var(--muted); }
.cp-row label { display: inline-flex; align-items: center; gap: 0.35rem; margin: 0; text-transform: none; letter-spacing: 0; font-size: 0.85rem; color: var(--text); }
.cp-row label input[type="checkbox"] { width: auto; }
.cp-row input[type="number"] { width: 5rem; padding: 0.15rem 0.35rem; font-size: 0.85rem; font-variant-numeric: tabular-nums; }
.cp-row .spacer { flex: 1; }
/* The same shape as a window's: the global `input { width: 100% }` reaches
   these too, so the field filled the row and the unit wrapped under it. */
.cp-row .hot { display: inline-flex; align-items: baseline; gap: 2px; cursor: ew-resize; }
.cp-row .hot i { font-style: normal; font-size: 0.68rem; color: var(--muted); }
.cp-row .hotv { flex: none; width: 3.6rem; padding: 0.15rem 0.35rem; text-align: right; }
.cp-row .hot:hover .hotv { border-color: var(--accent); }
.cp-row .hotv:focus { cursor: text; }
.cp-follow {
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  color: var(--muted);
  font-size: 0.72rem;
  cursor: pointer;
}
.cp-follow.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-text); }

.lanes {
  --cp-split: 260px;
  --cp-pad: 0.6rem;
  position: relative;
  margin: 0.6rem 0 0.3rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 0.4rem var(--cp-pad) 0.5rem;
  background: var(--panel-2);
  user-select: none;
}
.lane { display: grid; grid-template-columns: var(--cp-split) minmax(0, 1fr); align-items: center; gap: 0.5rem; min-height: 26px; }
.lane.set { background: color-mix(in srgb, var(--accent-soft) 30%, transparent); }
.lane.ruler { min-height: 22px; background: none; }
.lane.empty { min-height: 22px; }
.lane.empty .hint { padding-left: 1.15rem; font-size: 0.72rem; font-style: italic; color: var(--muted); }

/* A group's head: the twirl, the name, and what the group can be given. */
.lane.grp { margin-top: 0.5rem; min-height: 20px; background: none; }
.lane.grp .nm { font-size: 0.64rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
.lane.grp .nm .t { flex: 0 0 auto; min-width: 0; }
.lane .twirl {
  width: 14px; height: 14px; flex: none; padding: 0; border: 0; border-radius: 3px;
  background: transparent; color: var(--muted); font-size: 9px; line-height: 14px; cursor: pointer;
  transition: transform 0.15s ease;
}
.lane .twirl.open { transform: rotate(90deg); }
.lane .gbtn {
  flex: none; padding: 0.05rem 0.4rem; border: 1px solid var(--line); border-radius: 5px;
  background: var(--panel); color: var(--text); font-size: 0.66rem; font-weight: 600;
  letter-spacing: 0; text-transform: none; cursor: pointer;
}
.lane .gbtn:hover { border-color: var(--accent); color: var(--accent-text); }

/* The parameter side. The name has a floor, so it can never be squeezed
   out; a wide control takes the whole of a second line. */
.lane .nm { display: flex; align-items: center; gap: 0.3rem; min-width: 0; flex-wrap: wrap; font-size: 0.78rem; }
.lane .nm > .t { flex: 1 1 auto; min-width: 3.2rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lane .nm > .grow { flex: 1 1 auto; }
.lane .nm > .val { flex: 0 1 auto; margin-left: auto; display: inline-flex; gap: 0.15rem; align-items: center; min-width: 0; font-variant-numeric: tabular-nums; font-weight: 600; }
.lane .nm > .val b { font-weight: 400; color: var(--muted); }
.lane .nm > .val.dim { color: var(--muted); font-weight: 400; }
.lane .nm.wide > .val { flex: 1 0 100%; margin-left: 1.15rem; }
.lane.ruler .clock { margin-left: auto; font-size: 0.66rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* The stopwatch: constant against animated, and the only control that says
   a value may change over the chapter. A hollow clock, filled when on. */
.lane .sw {
  width: 16px; height: 16px; flex: none; padding: 0; border: 0; border-radius: 4px;
  background: transparent; color: var(--muted); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.lane .sw svg { width: 13px; height: 13px; }
.lane .sw:hover { background: var(--line); color: var(--text); }
.lane .sw.on { background: var(--accent); color: var(--accent-ink); }
.lane .sw.ghost { cursor: default; opacity: 0; }

/* Premiere's hot text: drag it to change it, click it to type. */
.lane .hot { display: inline-flex; align-items: baseline; gap: 1px; cursor: ew-resize; }
.lane .hot.dragging { cursor: ew-resize; }
.lane .hot i { font-style: normal; font-size: 0.62rem; color: var(--muted); }
.lane .hotv {
  width: 2.5rem; padding: 0.05rem 0.2rem; border: 1px solid transparent; border-radius: 4px;
  background: transparent; color: inherit; font: inherit; font-size: 0.74rem;
  font-variant-numeric: tabular-nums; text-align: right; cursor: ew-resize;
}
.lane .hot:hover .hotv { border-color: var(--line); background: var(--panel); }
.lane .hotv:focus { border-color: var(--accent); background: var(--panel); cursor: text; outline: none; }
.lane .hlrow .hotv { width: 2.2rem; }

.lane .lsel { max-width: 100%; min-width: 0; padding: 0.05rem 0.25rem; font-size: 0.72rem; }
.lane .lsel[multiple] { height: 3.4rem; }
.lane .hlrow .who { flex: 1 1 4rem; min-width: 3rem; }

/* The keyframe navigator: only once a lane is animated, because before
   that there is nothing to walk. */
.lane .kn { display: inline-flex; gap: 2px; flex: none; }
.lane .kn[hidden] { display: none; }
.lane .kn button {
  width: 15px; height: 15px; padding: 0; border: 1px solid var(--line); border-radius: 3px;
  background: var(--panel); color: var(--muted); font-size: 8px; line-height: 13px; cursor: pointer;
}
.lane .kn button:hover { border-color: var(--accent); color: var(--accent-text); }
.lane .kn button.on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.lane .rm {
  width: 15px; height: 15px; flex: none; padding: 0; border: 0; border-radius: 3px;
  background: transparent; color: var(--muted); font-size: 11px; line-height: 13px; cursor: pointer;
}
.lane .rm:hover { background: var(--danger-soft, var(--line)); color: var(--danger, var(--text)); }
.lane .chain {
  flex: none; padding: 0 0.3rem; height: 15px; border: 1px solid var(--line); border-radius: 3px;
  background: var(--panel); color: var(--muted); font-size: 0.62rem; line-height: 13px; cursor: pointer;
}
.lane .chain.on { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-text); }

/* The track side. */
.lane .tr { position: relative; height: 22px; }
.lane .tr::before { content: ''; position: absolute; left: 0; right: 0; top: 50%; border-top: 1px dashed var(--line); }
.lane.grp .tr::before, .lane.ruler .tr::before, .lane.empty .tr::before { border-top: 0; }
/* Everything on this timeline is taken hold of the same way: an open hand
   over it, a closed one while it is carried (`body[data-dragging]`). The
   trim grips keep the arrows, because trimming is not carrying. */
.lane .kf {
  position: absolute; top: 50%; width: 10px; height: 10px; margin: 0; padding: 0; border: 0;
  background: var(--text); transform: translate(-50%, -50%) rotate(45deg); cursor: grab;
}
.lane .kf.hold { transform: translate(-50%, -50%); border-radius: 1px; }
.lane .kf.ease { transform: translate(-50%, -50%); border-radius: 50%; }
.lane .kf.on { background: var(--accent-text); outline: 2px solid var(--accent-soft); }
/* A line's span moves and does not trim: its length is the voice's. */
.lane .cue-span {
  position: absolute; top: 2px; bottom: 2px; padding: 0 0.3rem; box-sizing: border-box;
  border-radius: 3px; background: var(--water); color: #fff; font-size: 0.66rem; line-height: 18px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: grab;
}
.lane .cue-span.fixed { box-shadow: inset 2px 0 0 rgba(255, 255, 255, 0.75); }
.lane .cue-span.lit { outline: 2px solid var(--accent); outline-offset: -2px; }
/* A highlight is a clip: the middle moves it, the ends trim it. */
.lane .hl-span {
  position: absolute; top: 3px; bottom: 3px; box-sizing: border-box; border-radius: 3px;
  background: var(--sand); color: #17302a; font-size: 0.66rem; line-height: 16px;
  padding: 0 0.35rem; white-space: nowrap; overflow: hidden; cursor: grab;
  display: flex; align-items: center; justify-content: center;
}
.lane .hl-span b {
  position: absolute; top: 0; bottom: 0; width: 6px; cursor: ew-resize;
}
.lane .hl-span b[data-grip="from"] { left: 0; border-left: 2px solid rgba(23, 48, 42, 0.35); }
.lane .hl-span b[data-grip="to"] { right: 0; border-right: 2px solid rgba(23, 48, 42, 0.35); }
.lane .hl-span:hover b { border-color: rgba(23, 48, 42, 0.8); }

/* The labels sit to the **right** of their mark rather than centred on it,
   which is how a timeline's ruler is drawn and, here, the difference between
   0:00 reading and 0:00 lying half in the names column on top of the
   playhead's own clock (Marc, 2026-09-06). The last label may run past the
   right edge, and is clipped there. */
.cp-ruler { position: relative; height: 20px; overflow: hidden; border-bottom: 1px solid var(--line); cursor: text; }
.cp-ruler .tick { position: absolute; top: 1px; padding-left: 3px; font-size: 0.62rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.cp-ruler .tick::after { content: ''; position: absolute; left: 0; bottom: -6px; height: 5px; width: 1px; background: var(--line); }
.lanes .cp-playhead { position: absolute; top: 0.4rem; bottom: 0.5rem; width: 2px; margin-left: -1px; background: var(--text); z-index: 2; pointer-events: none; }
.lanes .cp-playhead::before { content: ''; position: absolute; top: 0; left: 50%; width: 8px; height: 8px; border-radius: 50%; background: var(--text); transform: translateX(-50%); }
.lanes .cp-split {
  position: absolute; top: 0.4rem; bottom: 0.5rem; z-index: 3;
  left: calc(var(--cp-pad) + var(--cp-split) + 0.25rem);
  width: 9px; margin-left: -4px; cursor: col-resize;
}
.lanes .cp-split::before { content: ''; position: absolute; top: 0; bottom: 0; left: 4px; width: 1px; background: var(--line); }
.lanes .cp-split:hover::before { background: var(--accent); }

/* --- an overlay's handles, in the editor's preview -------------------------
 *
 * A box with four corners round whichever overlay is selected or under the
 * pointer. It is drawn over the picture rather than around the overlay
 * itself, because the overlays are laid out by percentage and a border on
 * one would change what it measures.
 */
.ov-handles {
  position: absolute; z-index: 6; pointer-events: none;
  outline: 1px solid var(--accent); outline-offset: 0;
}
.ov-handles[hidden] { display: none; }
.ov-handles .grip {
  position: absolute; width: 10px; height: 10px; box-sizing: border-box;
  background: #fff; border: 2px solid var(--accent); border-radius: 2px;
  pointer-events: auto; cursor: nwse-resize;
}
.ov-handles .grip.nw { left: -5px; top: -5px; }
.ov-handles .grip.ne { right: -5px; top: -5px; cursor: nesw-resize; }
.ov-handles .grip.se { right: -5px; bottom: -5px; }
.ov-handles .grip.sw { left: -5px; bottom: -5px; cursor: nesw-resize; }
/* An overlay the editor can move says so under the pointer. */
body.embed [data-item] { cursor: move; }

/* --- an item's bar ---------------------------------------------------------
 *
 * Everything on a chapter's timeline is one of these: a start, an end, an
 * entrance and an exit. It is dragged by its body, trimmed at its ends and
 * opened with a double-click, and three states are drawn rather than
 * explained. An **open** bar has no right grip and runs into the chapter's
 * edge, which is how a thing carries on into the next chapter. A **carried**
 * bar is hatched and has no left grip, because it began somewhere else. The
 * **entrance and exit** are shaded inside the bar and are not dragged there —
 * they are a kind and a number, and both belong in the window.
 */
.lane .it-bar {
  position: absolute; height: 18px; box-sizing: border-box; border-radius: 3px;
  padding: 0 0.4rem; font-size: 0.66rem; line-height: 18px; cursor: grab;
  display: flex; align-items: center; overflow: hidden;
  background: var(--sand); color: #17302a;
}
.lane .it-bar .words { position: relative; z-index: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lane .it-bar b { position: absolute; top: 0; bottom: 0; width: 7px; z-index: 2; cursor: ew-resize; }
/* While a gesture is under way the whole document wears its cursor: the
   pointer wanders off the bar it started on, and what a hand sees is what is
   under it. */
body[data-dragging="move"], body[data-dragging="move"] * { cursor: grabbing !important; }
body[data-dragging="resize"], body[data-dragging="resize"] * { cursor: ew-resize !important; }
body[data-dragging="col"], body[data-dragging="col"] * { cursor: col-resize !important; }
/* Held over somewhere it cannot land: the hand keeps the sign, as Premiere's
   does. Nothing has moved and letting go here puts the thing back. */
body[data-dragging="no"], body[data-dragging="no"] * { cursor: no-drop !important; }
body[data-dragging] { user-select: none; }
.lane .it-bar b[data-grip="start"] { left: 0; border-left: 2px solid rgba(23, 48, 42, 0.35); }
.lane .it-bar b[data-grip="end"] { right: 0; border-right: 2px solid rgba(23, 48, 42, 0.35); }
.lane .it-bar:hover b { border-color: rgba(23, 48, 42, 0.85); }
.lane .it-bar.on { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The entrance and the exit, shaded inside the bar. */
.lane .it-bar .fx { position: absolute; top: 0; bottom: 0; background: rgba(255, 255, 255, 0.45); pointer-events: none; }
.lane .it-bar .fx.in { left: 0; }
.lane .it-bar .fx.out { right: 0; }
/* Open: no end to grip, and the edge says so. */
.lane .it-bar.open { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.lane .it-bar.open::after {
  content: ''; position: absolute; right: 0; top: 0; bottom: 0; width: 4px;
  background: repeating-linear-gradient(180deg, rgba(23, 48, 42, 0.5) 0 2px, transparent 2px 4px);
}
/* Carried in from an earlier chapter: hatched, and only its end is the hand's. */
/* A carried bar began in an earlier chapter, so its body cannot be moved
   here — only its end dragged in, to stop it. */
.lane .it-bar.carried { cursor: default; }
.lane .it-bar.carried {
  background-image: repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.55) 0 4px, transparent 4px 8px);
  border-top-left-radius: 0; border-bottom-left-radius: 0;
}
/* The kinds, so a lane reads at a glance. */
.lane .it-bar.k-highlight { background: var(--sand); }
.lane .it-bar.k-camp { background: #e0b07a; }
.lane .it-bar.k-lines, .lane .it-bar.k-pair { background: #9fc4e8; }
.lane .it-bar.k-height { background: #a8d08d; }
.lane .it-bar.k-title, .lane .it-bar.k-logo, .lane .it-bar.k-plaque { background: #cfc7e8; }
.lane .it-bar.k-dial, .lane .it-bar.k-matrix, .lane .it-bar.k-trend { background: #b7cbe0; }
/* A step of the matrix's own scale: the same family, one shade in, so it
   reads as belonging to the matrix above it rather than as a fifth overlay. */
.lane .it-bar.k-matrix_line, .lane .it-bar.k-matrix_cell { background: #94b3d1; }
/* Two small buttons on one row of a window, for a row that offers a choice of
   two things to make. */
.cp-pop .row-pair { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.lane .it-bar.k-sound { background: var(--forest); color: #fff; }
.lane .it-bar.k-sound b[data-grip] { border-color: rgba(255, 255, 255, 0.6); }
.lane .it-bar.k-follow { background: #d8cfa8; }
/* The walk, shaded on the chapter's own ruler: the team is arriving over it. */
.cp-ruler .walk-zone {
  position: absolute; left: 0; top: 0; bottom: 0; z-index: 0;
  background: repeating-linear-gradient(45deg, rgba(23, 48, 42, 0.08) 0 5px, transparent 5px 10px);
  border-right: 1px dashed var(--line); pointer-events: none;
}
/* A window's head: the name, a bin that removes and a × that only closes. */
.cp-pop .pop-head { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.5rem; }
.cp-pop .pop-head .grow { flex: 1; }
.cp-pop .pop-row { display: flex; align-items: center; gap: 0.4rem; margin: 0.3rem 0; }
.cp-pop .pop-row > span:first-child { flex: 0 0 5.6rem; color: var(--muted); font-size: 0.72rem; }
.cp-pop .pop-row.pop-wide { display: block; }
.cp-pop .pop-row .grow { flex: 1; }
.cp-pop .pop-row select.anchored { font-weight: 600; }
.cp-pop .camp-row input[type="text"] { flex: 1; }
/* A dial box holds one to four readings, each a row of "which reading" and
   "what to call it here"; the row has no label, since the select says it. */
.cp-pop .dial-row input[type="text"] { flex: 1; min-width: 6rem; }
.cp-pop .dial-row select { flex: 0 0 auto; max-width: 11rem; }
/* One column, in alphabetical order: a list of people reads down, and a
   wrapping grid put them in an order nobody could follow and left the names
   ragged against their boxes. */
.cp-pop .people { display: flex; flex-direction: column; gap: 0.05rem; max-height: 15rem; overflow: auto; min-width: 12rem; }
.cp-pop .people label {
  display: flex; align-items: center; gap: 0.45rem; padding: 0.12rem 0.35rem;
  border-radius: 5px; font-size: 0.78rem;
}
.cp-pop .people label:hover { background: var(--panel-2); }
.cp-pop .people input[type="checkbox"] { width: auto; flex: none; margin: 0; }
.cp-pop .people select { flex: 0 0 auto; width: auto; min-width: 7rem; }
.cp-pop .people label > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* The head's Play chapter wears the disc without being a button of its own. */
.play-btn.as-glyph { margin-right: 0.1rem; }
.cp-pop button.icon.danger svg { stroke: var(--danger, #b8452c); }

.cp-list h3 { margin: 0.8rem 0 0.2rem; font-size: 0.8rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }

/* The menu a right-click opens, hung where it was asked for. */
.pmenu {
  position: fixed; z-index: 120; min-width: 11rem; padding: 0.25rem;
  border: 1px solid var(--line); border-radius: 8px; background: var(--panel);
  box-shadow: var(--shadow-lg, 0 10px 30px rgba(0, 0, 0, 0.22));
}
.pmenu button {
  display: flex; align-items: baseline; gap: 0.4rem; width: 100%; padding: 0.3rem 0.5rem;
  border: 0; border-radius: 5px; background: transparent; color: var(--text);
  font-size: 0.82rem; text-align: left; cursor: pointer;
}
.pmenu button:hover { background: var(--panel-2); }
.pmenu button.on { color: var(--accent-text); font-weight: 600; }
.pmenu button.danger { color: var(--danger, #a33); }
.pmenu button i { font-style: normal; font-size: 0.7rem; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pmenu hr { margin: 0.25rem 0.3rem; border: 0; border-top: 1px solid var(--line); }

/* The dial items over the picture: one box, one to four gauges in it.
   `js/gauge.js` draws the gauge; this is the plate around it, its outer
   title and its grid. */
.meter-overlay {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}
.meter-card {
  --meter-plate: rgba(9, 45, 45, 0.88);
  --meter-ink: #f4f6ef;
  --meter-muted: #cfd8d2;
  /* Absolutely placed, because `place` is a box in per cent of the frame and
     every other overlay reads it that way. The cards used to be laid out by a
     flex row on the host, so a dial's own x and y were quietly ignored and
     `DIAL_PLACE_STEP` — a second dial dropping below the first — never
     happened. */
  position: absolute;
  padding: 10px 10px 12px;
  border-radius: 14px;
  background: var(--meter-plate);
  color: var(--meter-ink);
  text-align: center;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
  opacity: 0;
}
/* The other dress, chosen per dial item: a light plate for a dark board. The
   gauge's own theme is set from the same field (js/gauge.js). */
.meter-card[data-theme="light"] {
  --meter-plate: rgba(249, 250, 246, 0.93);
  --meter-ink: #14332c;
  --meter-muted: #4c5f59;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
}
/* No box at all: the gauges stand on the picture, which is what the drop
   shadow under each of them is for. */
.meter-card.bare { padding: 0; background: none; box-shadow: none; }
.meter-head {
  font-family: 'Barlow Semi Condensed', 'Source Sans 3', system-ui, sans-serif;
  font-size: 1.02rem;
  font-weight: 600;
  line-height: 1.15;
  margin: 0 0 6px;
  color: var(--meter-ink);
}
.meter-grid {
  display: grid;
  grid-template-columns: repeat(var(--cols, 1), minmax(0, 1fr));
  gap: 4px 10px;
}
.meter-one { min-width: 0; }
.meter-label { font-size: 0.68rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--meter-muted); }
/* The gauge fills its cell, whatever width `place` gives the box — it used to
   be drawn at a fixed 150px inside a card sized in per cent of the frame,
   which is why the arc stood outside its own plate. */
.meter-dial { margin-top: 4px; }
.meter-dial svg { display: block; width: 100%; height: auto; }
/* The reading, on one line: the number, then the level the needle is standing
   in, in that level's colour -- "32%, Active Polarization" (Marc,
   2026-09-08). The number used to be drawn on the face beside the hub and the
   words on this line, which is one reading said twice; the face gave up both
   the number and the room under it, so this sits where it does and everything
   above it moved up. The words replaced the three-zone verdict, which cut at
   30/70 against the levels' fifths and so could name a different thing than
   the face lit. */
.meter-level { min-height: 1.3em; margin-top: 3px; font-weight: 700; font-size: 0.95rem; line-height: 1.15; }

/* The player inside Direct's preview: the picture and its transport, and
   nothing of Watch around it. */
body.embed { background: var(--scene-bg); }
body.embed main { padding: 0; max-width: none; }
body.embed .viewer-wrap { border: 0; border-radius: 0; }
body.embed #stage { height: 100vh; }
body.embed .page-head, body.embed .watch-next, body.embed .watch-cards { display: none; }

/* A still (`?at=&still=1`): one instant of the animation, to look at rather
   than to watch — what `tools/animation.py shots` opens, one URL a chapter,
   and what a link to one moment of an animation should show. The picture and
   its subtitle, nothing else. The subtitle stays because half of what a
   chapter *is* is what is said over it, and judging a still without the
   words is judging half of it. */
body.still { background: var(--scene-bg); --rail: 0rem; }
body.still .sidenav { display: none; }
body.still main { padding: 0; max-width: none; }
body.still .viewer-wrap { border: 0; border-radius: 0; }
body.still #stage { height: 100vh; }
body.still .page-head, body.still .watch-next,
body.still .watch-cards, body.still .transport, body.still header.topbar,
/* The paused ▶ badge is the player telling a viewer it is stopped; a still
   is stopped by definition and the badge would sit in the middle of every
   frame being judged. */
body.still .play-flash { display: none; }
body.still #subtitles { opacity: 1; }

/* --- Direct: the sequence, one chapter a card ---------------------------- */
.chapter-row { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.chapter-row .number {
  display: inline-grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.chapter-row .kind {
  padding: 0.1rem 0.55rem;
  border: 1px solid var(--status-line, var(--line));
  border-radius: 999px;
  color: var(--accent-text);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.chapter-row h2 { margin: 0; font-size: 1.15rem; }
.chapter-row .spacer { flex: 1; }
.chapter-row label { display: inline-flex; align-items: center; gap: 0.4rem; margin: 0; text-transform: none; letter-spacing: 0; font-size: 0.9rem; font-weight: 600; color: var(--text); }
.chapter-row label input { width: auto; }
.card.off { opacity: 0.55; }
.cue { display: grid; grid-template-columns: 5.5rem 1fr auto; gap: 0.5rem; align-items: start; margin-top: 0.6rem; }
.cue input { font-variant-numeric: tabular-nums; }
.cue textarea { min-height: 3.2rem; resize: vertical; }
.cue .proposed { font-size: 0.66rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }

/* --- Survey: the hero band and the three cards -----------------------------
   The page of the design (spec/redesign/teamium-start.html and the rail's
   "survey open" state): a forest hero at 150 px, then three cards in the
   order things happen — the team, the survey, sending it — or, while a
   survey is open, the answers arriving, the link to share and the demo. The
   card colours are the design's: moss, water, sand. */
:root, [data-theme='light'] { --water: #5c909c; --sand: #d8c6a1; }
[data-theme='dark'] { --water: #86b9c4; --sand: #cdb893; }
/* The chapter kinds' colours (Direct, 2026-09-06), each with the ink that
   reads on it: the yellow takes dark type, the rest white. The dark theme
   lifts each a step so the blocks stand off the panel. */
:root, [data-theme='light'] {
  --kind-intro: #6f7780; --kind-intro-ink: #ffffff;
  --kind-circle: #357f50; --kind-circle-ink: #ffffff;
  --kind-survey: #e8bf3f; --kind-survey-2: #f2d472; --kind-survey-ink: #2a2410;
  --kind-analysis: #3a6fb5; --kind-analysis-ink: #ffffff;
  --kind-general: #5a6673; --kind-general-ink: #ffffff;
}
[data-theme='dark'] {
  --kind-intro: #7c848c; --kind-intro-ink: #0f1417;
  --kind-circle: #4fa66d; --kind-circle-ink: #0c1a11;
  --kind-survey: #dcb23a; --kind-survey-2: #e9c96a; --kind-survey-ink: #231c08;
  --kind-analysis: #5b8fd6; --kind-analysis-ink: #0b1626;
  --kind-general: #6b7885; --kind-general-ink: #0f1418;
}
.hero {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  min-height: 150px;
  overflow: hidden;
  padding: 1.5rem 2rem;
  margin-bottom: 1rem;
  border-radius: var(--radius-card);
  color: #f4f6ef;
  background: linear-gradient(90deg, var(--forest-deep) 0%, var(--forest) 60%, var(--moss) 100%);
}
/* The concept's artwork, down the right of the band: a terraced island drawn
   as contour lines, warm gold along every terrace and one pale line at the
   shoreline, which is the picture the animation itself makes.

   It is not traced by hand. `tools/hero_terraces.py` builds a height field,
   cuts it at a fixed step and walks the contours with marching squares —
   the same construction as the board's own terraces — so the lines nest and
   branch the way real ones do. Edit that tool, not the SVG.

   A stand-in grid of two repeating gradients was here before it, from the
   days when the artwork was a reference nobody had in the repository.

   The mask is what keeps it decoration: it fades in from nothing under the
   text to full at the right edge, so the band's own words never compete
   with it however wide the window is. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0.9;
  background: url("../img/hero-terraces.svg") right center / auto 122% no-repeat;
  mask-image: linear-gradient(90deg, transparent 34%, #000 82%);
}
.hero .copy { position: relative; z-index: 2; }
.hero .eyebrow {
  margin: 0 0 0.35rem;
  color: #cbd9cf;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.hero h1 { margin: 0 0 0.35rem; font-size: 2.6rem; color: inherit; }
.hero p { margin: 0; max-width: 54ch; color: #dfe8e2; }
/* **A hero belongs to a front door, not to a workspace** (Marc, 2026-09-09,
   asking what best-in-class does). Linear, Notion, Figma, Vercel, GitHub and
   Stripe's dashboard have no coloured band on an interior page; Stripe's
   *homepage* has one. A hero orients somebody who arrived from a search with
   nothing else to go on, which is not the person who reached Survey from the
   rail. Material 3 and Apple's HIG add the dark-mode half: desaturate brand
   colour and keep it to small elements, because a large saturated fill on a
   dark ground vibrates and reads brighter than it is.

   The argument that settles it is already this file's own, written when
   Direct's `<h1>` went: repeating a name is a website habit, right for a page
   somebody landed on and wrong for a workspace somebody lives in. In three of
   Survey's four states the hero's eyebrow is `plainTeamName(team.name)` —
   the bar's first crumb, restated a few pixels under it.

   So the band is kept for exactly the state that earns it. `new` is the start
   flow: no team yet, so the bar has nothing to say and this is somebody's
   first screen of the product. Everywhere else the section is a plain page
   head — an `h1` and a lede on the page's own ground, the shape Analyse and
   Diagnose now use — and the eyebrow is dropped rather than restyled, because
   what was wrong with it was that it was the crumb. */
.hero.plain {
  min-height: 0;
  overflow: visible;
  padding: 0;
  margin-bottom: 1.25rem;
  border-radius: 0;
  background: none;
  color: inherit;
}
.hero.plain::before { content: none; }
.hero.plain h1 { font-size: 2rem; }
.hero.plain p { color: var(--muted); }
.survey .cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 0.9fr); gap: 1rem; align-items: start; }
.survey .cols.open { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }
@media (max-width: 1180px) { .survey .cols { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
@media (max-width: 800px) { .survey .cols, .survey .cols.open { grid-template-columns: minmax(0, 1fr); } }
.survey .card { margin-bottom: 1rem; }
.card.moss { border-top: 3px solid var(--moss); }
.card.water { border-top: 3px solid var(--water); }
.card.sand { border-top: 3px solid var(--sand); }
.card.pending { opacity: 0.6; }
.card-heading { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.9rem; }
.card-heading h3 { margin: 0; font-size: 1.15rem; }
.step {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 2rem;
  height: 2rem;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--muted);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
}
.step.done { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.ghost-lines { display: grid; gap: 0.5rem; }
.ghost-lines span { display: block; height: 2.1rem; border-radius: 10px; background: var(--panel-2); }
.ghost-lines span.pin { height: 3rem; width: 60%; }
.ghost-lines span.tall { height: 5rem; }
.survey .hint { margin-top: 0.4rem; font-size: 0.85rem; color: var(--muted); }
.survey .card p { margin: 0 0 0.75rem; }
/* The then-and-now switch: a checkbox drawn as a toggle. */
.toggle-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0 0 0.8rem;
  color: var(--text);
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  cursor: pointer;
}
input.toggle {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex: 0 0 auto;
  width: 2.1rem;
  height: 1.25rem;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 99px;
  background: var(--line);
  cursor: pointer;
}
input.toggle::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: calc(1.25rem - 4px);
  height: calc(1.25rem - 4px);
  border-radius: 50%;
  background: #fff;
  transition: left 0.15s ease;
}
input.toggle:checked { background: var(--accent); }
input.toggle:checked::after { left: calc(2.1rem - 1.25rem + 2px); }
.toggle-row .rec {
  padding: 0.05rem 0.5rem;
  border: 1px solid var(--accent);
  border-radius: 99px;
  color: var(--accent-text);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
/* The question as a member sees it, with a real name in it. */
.question { padding: 0.8rem 0.9rem; border: 1px dashed var(--line); border-radius: 12px; background: var(--bg); margin-bottom: 0.9rem; }
.question .q { margin: 0 0 0.6rem; font-size: 0.9rem; line-height: 1.45; }
.question .q b { color: var(--moss); font-weight: 700; }
.slider-preview { position: relative; height: 12px; border-radius: 99px; }
.slider-preview i {
  position: absolute;
  top: 50%;
  left: 61%;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--ink);
  transform: translate(-50%, -50%);
}
.slider-ends { display: flex; justify-content: space-between; margin-top: 0.35rem; font-size: 0.75rem; color: var(--muted); }
.button.wide, button.wide { width: 100%; justify-content: center; min-height: 2.6rem; }
/* The roster: who is in the team, who has answered, who left. */
table.roster { width: 100%; font-size: 0.9rem; }
table.roster td.act { text-align: right; white-space: nowrap; }
table.roster td.act button.link { font-size: 0.82rem; }
table.roster td.act button.link + button.link { margin-left: 0.6rem; }
table.roster tr.left td { color: var(--muted); }
table.roster tr.left td.name { text-decoration: line-through; }
table.roster tr.new td.name { font-weight: 600; }
table.roster .tick {
  display: inline-grid;
  place-items: center;
  width: 1.3rem;
  height: 1.3rem;
  border: 1px solid var(--line);
  border-radius: 50%;
  font-size: 0.75rem;
}
table.roster .tick.in { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 700; }
table.roster .tick.busy { border-style: dashed; color: var(--muted); }
/* The same reading the projected window gives, in the page's own table:
   where each person is, how far through the sliders, how long they have
   been quiet, whether they have agreed. */
table.roster th.num, table.roster td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
table.roster th { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); text-align: left; padding-bottom: 0.3rem; }
table.roster td.state { color: var(--muted); white-space: nowrap; }
table.roster tr.done td.state { color: var(--ok); font-weight: 600; }
table.roster tr.waiting td.state { color: var(--danger); }
table.roster td .agreed { color: var(--ok); font-weight: 700; }
@media (max-width: 900px) {
  table.roster th.num, table.roster td.num, table.roster td.state, table.roster th:nth-child(3) { display: none; }
}
.addrow { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.5rem; align-items: center; margin-top: 0.75rem; }
.addrow input { margin: 0; }
.rounds-chips { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.8rem; }
.rounds-chips span {
  padding: 0.2rem 0.65rem;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-text);
  font-size: 0.78rem;
  font-weight: 700;
}
.count { margin: 0; font-family: var(--font-display); font-size: 1.6rem; font-weight: 600; }
.count small { margin-left: 0.5rem; font-family: var(--font); font-size: 0.85rem; font-weight: 400; color: var(--muted); }
.answers-bar { height: 8px; margin: 0.6rem 0 0.8rem; border-radius: 99px; background: var(--panel-2); overflow: hidden; }
.answers-bar i { display: block; height: 100%; width: 0; background: var(--accent); transition: width 0.4s ease; }
.compute-row { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin: 0 0 1rem; }
/* The link, the QR code for a room, and the PIN for a laptop — with the two
   buttons that replace one that leaked.
   The link takes a row of its own, being thirty characters that only wrap
   badly; the PIN and the code share the next, and the code goes under the PIN
   where the card is too narrow for both — an eight-digit PIN broken over two
   lines reads as two numbers. */
.pinrow { display: flex; flex-wrap: wrap; gap: 0.5rem 0.75rem; align-items: flex-end; }
.pinrow .pin-link { flex: 1 1 100%; min-width: 0; }
.pinrow .pin-link p { margin: 0.2rem 0 0.2rem; }
.pinrow .pin-main { flex: 1 1 13rem; min-width: 0; }
.pinrow .renew-row { margin-top: 0.4rem; align-items: center; }
.pin-big { margin: 0.3rem 0 0.1rem; font-family: var(--font-display); font-size: 2.1rem; font-weight: 600; letter-spacing: 0.18em; line-height: 1.1; white-space: nowrap; }
.join-link { font-weight: 700; color: var(--accent-text); word-break: break-all; }
.qr-box { display: grid; justify-items: center; gap: 0.25rem; padding: 0.5rem; border: 1px solid var(--line); border-radius: 10px; background: #fff; color: #17302a; }
.qr-box img { display: block; width: 128px; height: 128px; image-rendering: pixelated; }
.qr-box small { font-size: 0.68rem; color: #68766f; }
textarea.invite { margin-top: 0.75rem; min-height: 7rem; font-size: 0.9rem; line-height: 1.45; resize: vertical; }
.warn-line { color: var(--danger); }
.ok-line { color: var(--ok); }
/* The demo, as a still with a play button, until the team's own film exists. */
.thumb { position: relative; display: block; aspect-ratio: 16 / 9; margin-top: 0.6rem; overflow: hidden; border: 1px solid var(--line); border-radius: 12px; background: #000; }
.thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.thumb .play {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(9, 45, 45, 0.84);
  color: #fff;
}
.thumb .play svg { width: 20px; height: 20px; }
.thumb:hover .play { background: var(--forest); }
/* The rounds already measured, on Survey.

   A list of rows rather than the table this was on the workbench, and that
   is the whole of what makes it readable: a round carries a title somebody
   wrote and a paragraph somebody wrote, and neither of them fits a cell.
   The number is the only thing narrow enough to be a column, so it is the
   only column; everything else stacks in the middle and the verbs sit at
   the end where a row's verbs go. */
.round-list { display: grid; gap: 0.5rem; }
.round-row {
  display: grid;
  grid-template-columns: 2.2rem minmax(0, 1fr) auto;
  gap: 0.25rem 0.75rem;
  align-items: start;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--panel);
}
.round-row .no {
  font: 600 1.05rem/1.6 var(--head);
  color: var(--muted);
  text-align: right;
  padding-top: 0.1rem;
}
.round-row .body { min-width: 0; }
.round-row .meta { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin: 0.2rem 0 0.35rem; }
.round-row .acts { display: flex; flex-wrap: wrap; gap: 0.4rem; justify-content: flex-end; }
/* A round whose chapter is switched off in every animation. Stood back, not
   struck through: it still holds its answers and its result, and it is still
   a link in the alignment chain. */
.round-row.out { opacity: 0.72; }
.round-row.out .no::after { content: ' \00b7'; }
@media (max-width: 700px) {
  .round-row { grid-template-columns: 2.2rem minmax(0, 1fr); }
  .round-row .acts { grid-column: 2; justify-content: flex-start; }
}
/* The note is three lines in the list and as tall as its text while it is
   being written in. Several sample teams carry a few hundred words a round,
   and eight of those at full height is not a list of rounds any more. */
.round-row .round-note { max-height: 4.25rem; transition: max-height 0.15s ease; }
.round-row .round-note:focus { max-height: 26rem; }
.warn-note { margin: 0 0 0.75rem; display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: baseline; }

/* --- Print: Analyse and Diagnose as a report ---------------------------------
   The chrome goes, the cockpit turns white whatever dress it wears on
   screen, and nothing that only works by pressing it is printed. */
@media print {
  body.framed { display: block; background: #fff; }
  header.topbar, .sidenav, .cockpit-actions, .links, .plan-tray, .plan-actions, dialog, .toast,
  .chapter-row button, .trend-head button, .move .pick { display: none !important; }
  main { padding: 0; max-width: none; }
  /* Paper is white whatever dress the screen wears, so the page's own
     palette comes back and the spread shadow that paints the gutters goes. */
  main[data-theme] { background: #fff; color: #000; box-shadow: none; clip-path: none; }
  /* `.advanced-part` is named beside the cockpit because the two share one
     token block on screen; without it, half of Analyse printed in the dark
     dress's own panel and hairline. The frame goes: the section is not a panel
     on screen and it lost its padding with its ground, so a border drawn round
     it would hug the words. */
  .cockpit, .cockpit.light, .cockpit.dark, .advanced-part {
    --ck-bg: #fff; --ck-ink: #000; --ck-muted: #444; --ck-card: #f4f4f4; --ck-line: #999;
    --ck-tab-line: #999; --ck-on-bg: #000; --ck-on-ink: #fff;
    border: 0; box-shadow: none; break-inside: avoid;
  }
  /* Paper's own card, and every card on both pages is the same one — a grey
     hairline, and no shadow, which prints as a smear. */
  .advanced-part .dial-card, .cockpit .dial-card, .cockpit .run-card {
    border-color: #999; box-shadow: none; break-inside: avoid;
  }
  .card { box-shadow: none; break-inside: avoid; }
  .dials { grid-template-columns: repeat(2, 1fr); }
  .dial-groups { gap: 1rem; }
  .dial-card .maths[hidden] { display: none; }
}

/* --- Watch, state 3: what a coordinator does next -----------------------------
   Under the film: the two next-step buttons (Share until shared, then Direct),
   the first-session card and the dynamics offer, none of it drawn over the
   demo. The cockpit strip that stood between them went with its rules
   (Marc, 2026-09-09); the cockpit is Analyse's. */
.watch-next { margin: 0.9rem 0 0; }
.watch-next .next-buttons { display: flex; gap: 0.6rem; flex-wrap: wrap; align-items: center; }
.watch-next .next-buttons .icon svg { width: 18px; height: 18px; }
.watch-next p { margin: 0.4rem 0 0; }
.watch-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: 1rem;
  margin-top: 1rem;
}
.watch-cards:not(:has(> :not([hidden]))) { display: none; }
.watch-cards .card { margin: 0; }
.watch-cards .card h2 { margin: 0; font-size: 1.15rem; }
.first-film ol.steps { margin: 0.5rem 0 0; padding-left: 1.3rem; }
.first-film li { margin: 0.3rem 0; }
.first-film li.done { text-decoration: line-through; color: var(--muted); }
.first-film li.done a, .first-film li.done .link { color: var(--muted); }
/* The line over the demo, for a team whose own film is not there yet. */
.demo-line {
  position: absolute;
  left: 50%;
  top: 12px;
  z-index: 3;
  transform: translateX(-50%);
  max-width: 82%;
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  background: rgba(9, 45, 45, 0.84);
  color: #f4f6ef;
  font-size: 0.85rem;
  text-align: center;
  pointer-events: none;
}
/* "Edit in Direct" on a subtitle, for a coordinator: shown on hover, and
   always on a screen that cannot hover. The box takes the pointer only while
   it carries the link. */
.subtitles.editable { pointer-events: auto; }
.subtitles .edit-link {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.72rem;
  color: #cbd9cf;
  opacity: 0;
  transition: opacity 0.15s ease;
}
.subtitles.editable:hover .edit-link, .subtitles .edit-link:focus { opacity: 1; }
@media (hover: none) { .subtitles .edit-link { opacity: 1; } }

/* The library's two kinds of item (2026-09-06): a chapter takes time, an effect takes a lane. */
.lib-group { margin: 0.55rem 0 0.3rem; }
.lib-group h2 { margin: 0; font-size: 0.82rem; letter-spacing: 0.02em; }
.lib-rule { margin: 0.1rem 0 0.45rem; font-size: 0.72rem; color: var(--muted); }
.lib-row.effect .lib-tile { width: 14px; height: 26px; border-radius: 3px; flex: none; }

/* A line between two members, as a clip on the chapter's lines lane; a carried one hatched. */
.lane .tr.prs { height: auto; min-height: 22px; }
.lane .pr-clip {
  position: absolute; height: 18px; box-sizing: border-box; display: flex; align-items: center; gap: 0.25rem;
  padding: 0 0.5rem; overflow: hidden; border-radius: 3px; cursor: grab; white-space: nowrap;
  font-size: 0.64rem; line-height: 16px; color: #17302a;
  background: linear-gradient(90deg, #91cd00 0 50%, #e8842a 50% 100%);
}
.lane .pr-clip.plain { background: #c9cdd2; }
.lane .pr-clip.inherited {
  background: repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.55) 0 4px, transparent 4px 8px),
    linear-gradient(90deg, #91cd00 0 50%, #e8842a 50% 100%);
  border-left: 2px dashed rgba(23, 48, 42, 0.5);
}
.lane .pr-clip.inherited.plain {
  background: repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.55) 0 4px, transparent 4px 8px), #c9cdd2;
}
.lane .pr-clip.open::after { content: '▸'; position: absolute; right: 9px; top: 0; font-size: 0.7rem; }
.lane .pr-clip .words { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.lane .pr-clip b { position: absolute; top: 0; bottom: 0; width: 7px; cursor: ew-resize; }
.lane .pr-clip b[data-grip="start"] { left: 0; border-left: 2px solid rgba(23, 48, 42, 0.35); }
.lane .pr-clip b[data-grip="end"] { right: 0; border-right: 2px solid rgba(23, 48, 42, 0.35); }
.lane .pr-clip:hover b { border-color: rgba(23, 48, 42, 0.8); }
.lane .pr-clip .rm {
  flex: none; width: 13px; height: 13px; padding: 0; border: 0; border-radius: 50%;
  background: rgba(255, 255, 255, 0.8); color: #17302a; font-size: 0.6rem; line-height: 13px; cursor: pointer;
}

/* Who is on the board: the round's members with answers, ticked. */
.cp-row.roster { align-items: center; }
.roster-pick {
  display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.15rem 0.45rem;
  border: 1px solid var(--line); border-radius: 6px; background: var(--panel); color: var(--text);
  font: inherit; font-size: 0.78rem; cursor: pointer;
}
.roster-pick:hover { border-color: var(--accent); }
.roster-pick .caret { color: var(--muted); font-size: 0.7rem; }
/* One column, in alphabetical order, the way a list of people reads. */
.roster-list { display: flex; flex-direction: column; gap: 0.05rem; min-width: 12rem; padding: 0.2rem 0.1rem; }
.roster-list label {
  display: flex; align-items: center; gap: 0.45rem; padding: 0.15rem 0.4rem;
  border-radius: 5px; font-size: 0.8rem; color: var(--text); cursor: pointer;
}
.roster-list label:hover { background: var(--panel-2); }
.roster-list label.muted { color: var(--muted); cursor: default; }
.roster-list input[type="checkbox"] { width: auto; flex: none; margin: 0; }

/* The chapter pane's one-row groups (2026-09-06): the clips ride in the head. */
.lane.grp.clips { margin-top: 0.45rem; min-height: 24px; align-items: center; }
.lane.grp .twirl.ghost { display: inline-block; width: 14px; }
.lane.grp.clips .tr::before { border-top: 1px solid var(--line); }
.lane.grp.clips .tr.hls, .lane.grp.clips .tr.prs { min-height: 22px; }

/* A play glyph drawn, centred in its disc, on every button that plays. */
.play-icon { display: inline-flex; align-items: center; justify-content: center; width: 100%; height: 100%; }
.play-icon svg { width: 9px; height: 9px; display: block; fill: currentColor; }
/* **One play button, everywhere it plays**: a disc with a triangle in it.
   There were three -- a dark disc on a clip, a bordered rectangle in a
   window and a plain glyph in the library -- and a control that plays is
   the same control wherever it is (Marc, 2026-09-06). */
/* The disc and the triangle in it are one size everywhere, and the triangle
   fills the disc: at 7px in a 16px circle it read as a dot with a lot of
   paint round it (Marc, 2026-09-06). The glyph's own path spans a little
   over half its viewBox, so 12px of svg puts about 6.5px of triangle in an
   18px disc — the proportion a play button is usually drawn at. */
/* **The disc and the triangle are the page's own contrast pair** (Marc,
   2026-09-09: in dark mode "the triangles in the circles" look wrong). The
   disc was `--wordmark-ink` with a `#fff` glyph, which is right in the light
   dress by accident — that token is the wordmark's navy there — and wrong in
   the dark one, where **its whole job is to be near-white**: api.js sets it to
   the surface colour so the letters of "team" read on a dark chrome. So the
   button was a #E8EBE3 disc carrying a #FFF triangle, about **1.08:1**, which
   is a white dot with a ghost in it.

   `--text` on `--bg` cannot go wrong in either dress, being the pair the page
   already proves legible: a dark disc with a pale triangle in the light dress,
   which is what this looked like, and a pale disc with a dark one in the dark
   dress, which is how a play control is drawn on every dark interface. */
.play-btn {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 18px; height: 18px; padding: 0; border: 0; border-radius: 50%;
  background: var(--text); color: var(--bg); cursor: pointer;
}
.play-btn .play-icon svg { width: 12px; height: 12px; }
/* **A play button is never padded.** Its width is its border box, so padding
   eats the content box and squashes the glyph: two rules left over from the
   bordered Listen button it replaced left the triangle 5px wide inside its
   18px disc (Marc, 2026-09-06). Written here so no future rule can do it
   again by being more specific about some context the button sits in. */
button.play-btn, span.play-btn { padding: 0; line-height: 1; }
/* **`--accent-ink` is what goes on the accent; `--accent-text` is the accent
   used as text on a card.** Hover took the second, so the triangle was drawn
   in the disc's own colour — identical to it on an installation whose stored
   `accent_text` is the brand green, and 2.7:1 at the shipped defaults. It is
   the one token derived to be legible here: #6EAA00 is bright enough that
   white on it is 1.9:1, which is why the ink is near-black at 9.2:1. */
.play-btn:hover { background: var(--accent); color: var(--accent-ink, #0d1b17); }
.play-btn.mute { opacity: 0.45; cursor: default; }
.cp-head .play-chapter { display: inline-flex; align-items: center; gap: 0.35rem; }
.cp-head .play-chapter .play-icon { width: auto; height: auto; }

/* The library, compact: tight sections, a one-line rule, the count at the label's size. */
.lib-group { margin: 0.35rem 0 0.1rem; }
.lib-group h2 { font-size: 0.8rem; }
.lib-rule { margin: 0 0 0.25rem; font-size: 0.68rem; line-height: 1.25; }
.lib-section { margin: 0 0 0.15rem; }
.lib-section h3 { margin: 0.05rem 0; line-height: 1.4; }
.lib-section h3 .count { font-size: 0.68rem; line-height: 1; }
.lib-row { padding: 0.2rem 0.5rem; }
.lib-row.sfx { padding: 0.15rem 0.5rem 0.15rem 0.4rem; }
.lib-sub { margin: 0.05rem 0 0.15rem 0.4rem; }
.lib-sub h4 { margin: 0.05rem 0; }

/* --- signing in (Teamium_Going_Live_Build.md §3, §8) -----------------------

   The account menu's head: who is signed in, in the words they chose, with
   the address under the name because two people can share one. */
.account-menu.nav-menu { min-width: 15rem; }
.account-who {
  display: grid;
  gap: 0.05rem;
  padding: 0.35rem 0.6rem 0.55rem;
  margin-bottom: 0.3rem;
  border-bottom: 1px solid var(--line);
}
.account-who .account-name { font-weight: 700; overflow-wrap: anywhere; }
.account-who .account-email { color: var(--muted); font-size: 0.82rem; overflow-wrap: anywhere; }
.account-who .account-role {
  margin-top: 0.2rem;
  color: var(--muted);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* **The development strip.** A hairline along the very top of the window and
   a tab hanging from its middle, both fixed, both deaf to the pointer: it has
   to be impossible to miss and impossible to get in the way, and a row of its
   own in the shell's grid would have pushed the rail's height out by its own.
   Amber, which nothing else in the interface is, so it cannot be read as part
   of the page. A still is a photograph of the picture and leaves it out. */
.dev-banner {
  position: fixed;
  inset: 0 0 auto;
  height: 3px;
  background: #c77d0b;
  z-index: 90;
  pointer-events: none;
}
.dev-banner span {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  padding: 0.05rem 0.7rem 0.12rem;
  border-radius: 0 0 7px 7px;
  background: #c77d0b;
  color: #1b1204;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
body.still .dev-banner { display: none; }
/* On a phone the middle of the top edge is the wordmark's; the right is free. */
@media (max-width: 640px) {
  .dev-banner span { left: auto; right: 0.75rem; transform: none; padding: 0 0.45rem 0.08rem; font-size: 0.6rem; }
}

/* The three pages somebody reaches before they are anybody: sign in, set up an
   account from an invitation, choose a new password. No bar and no rail —
   there is nowhere to navigate to yet — so one card in the middle of the
   page, the wordmark over it, and the language under it. */
body.auth-page {
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 2rem 1rem;
  box-sizing: border-box;
}
.auth {
  /* `main` is placed in the shell's named `main` area everywhere else, and an
     area this grid does not have would put the card in a column of its own. */
  grid-area: auto;
  width: 100%;
  max-width: 27rem;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 1rem;
}
.auth-brand { display: block; justify-self: center; line-height: 0; }
.auth-brand svg { height: 2rem; width: auto; }
.auth-card { margin: 0; padding: 1.6rem 1.7rem 1.4rem; }
.auth-card h1 { margin-bottom: 0.35rem; }
.auth-card .lede { margin-bottom: 1.1rem; }
.auth-card .field:last-of-type { margin-bottom: 0.4rem; }
.auth-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem 1rem;
  margin-top: 1rem;
}
.auth-actions .primary { justify-content: center; min-width: 8rem; }
.auth-message {
  margin: 0.75rem 0 0;
  padding: 0.55rem 0.75rem;
  border-radius: var(--radius);
  font-size: 0.88rem;
  background: var(--panel-2);
}
.auth-message.error { color: var(--danger); background: var(--panel-2); }
.auth-message[hidden] { display: none; }
/* A note standing over the form rather than under it, such as "sign-in is off". */
.auth-card > .auth-message { margin: 0 0 1rem; }
.auth-fixed { font-weight: 600; overflow-wrap: anywhere; }
.auth-foot {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 0.5rem;
  color: var(--muted);
  font-size: 0.85rem;
}
.auth-foot select { width: auto; padding: 0.3rem 0.6rem; font-size: 0.85rem; }
.auth-foot a { color: inherit; }
