/* 45B design system — tokens are the authority in notes/45b-design.md.
   Declared once here and referenced everywhere; do not inline hex values. */
:root {
  --bg: #121314;
  --surface: #2a2a2a;
  --accent: #4455BB;
  --alt-accent: #aaddaa;
  --alert: #ec6476;
  /* Not one of the six org tokens: a per-property addition (2026-09-23) for
     "act before this passes" - the cohorts' end dates. Amber, so it reads as a
     warning without borrowing --alert's "something is wrong". */
  --warn: #f2b045;
  --alt-text: #dddddd;
  --alt-text-rgb: 221 221 221;
  --radius-btn: 30px;
}

* { box-sizing: border-box; }
/* Everything on the page is sized in rem or em from these two declarations, so
   the whole type scale moves together from here. 115% of the 16px browser
   default drives every rem; 20.7px is the design system's 18px body, both up
   15% (asked for 2026-09-14). notes/45b-design.md still owns the 18px base -
   this is a per-property override until Lucas decides it belongs org-wide. */
html { color-scheme: dark; font-size: 115%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--alt-text);
  font-family: Chivo, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 20.7px;
  line-height: 1.6;
}
@font-face {
  font-family: "Handel Gothic D";
  src: url("/assets/Handel%20Gothic%20D%20Bold.otf") format("opentype");
  font-weight: 700;
  font-display: swap;
}
h1, h2, h3, .brand {
  font-family: "Handel Gothic D", Chivo, system-ui, sans-serif;
  font-weight: 700;
  letter-spacing: .01em;
}
h1 { font-size: 1.8rem; margin: 0 0 .2rem; }
h2 { font-size: 1.15rem; margin: 0 0 .6rem; }
a { color: var(--alt-text); }

/* gov.45b.io is a narrow column; learn is a laptop-wide portal
   (notes/45b-design.md). It still has to collapse to one usable column. */
.masthead, main, .foot, .callbar { max-width: 1600px; margin: 0 auto; padding-inline: 20px; }

/* Brand, nav, call title and call description stay pinned while the transcript
   scrolls - the transcript is long and the depth switcher needs the context.
   Kept deliberately short: two tight rows, description beside the title. */
.sticky {
  position: sticky; top: 0; z-index: 20;
  background: var(--bg);
  border-bottom: 1px solid rgb(var(--alt-text-rgb) / .12);
}
.masthead {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap;            /* the nav drops to its own line before it pushes
                                 the page wider - see .navsearch below */
  gap: 4px 14px;
  padding-block: 8px;
}
.masthead > * { min-width: 0; }
.masthead nav {
  display: flex; align-items: center; gap: 14px;
  flex: 1 1 auto; flex-wrap: wrap; justify-content: flex-end; min-width: 0;
}
.brand { text-decoration: none; font-size: 1.05rem; }
.brand span { color: rgb(var(--alt-text-rgb) / .55); font-weight: 400; }
.masthead nav a { text-decoration: none; color: rgb(var(--alt-text-rgb) / .75); font-size: .9rem; }
.masthead nav a:hover { color: var(--alt-accent); }
.adminpill {
  font-size: .7rem; text-transform: uppercase; letter-spacing: .08em;
  padding: .15em .7em; border-radius: var(--radius-btn);
  background: color-mix(in srgb, var(--alt-accent) 22%, transparent);
  color: var(--alt-accent);
}
/* The admin pop-out (templates/layout_head.php). Anchored to the pill, opening
   down and to the left so it never runs off the right edge of a phone. */
.adminmenu { position: relative; }
.adminmenu > summary { list-style: none; cursor: pointer; user-select: none; }
.adminmenu > summary::-webkit-details-marker { display: none; }
.adminpanel {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 30;
  display: flex; flex-direction: column; min-width: 12rem;
  padding: .4rem 0; border-radius: 10px;
  background: var(--surface); border: 1px solid rgb(var(--alt-text-rgb) / .15);
  box-shadow: 0 8px 24px rgb(0 0 0 / .45);
}
.masthead nav .adminpanel a { padding: .45rem 1rem; font-size: .9rem; }
.masthead nav .adminpanel a:hover { background: rgb(var(--alt-text-rgb) / .06); }
.adminpanel .signout { border-top: 1px solid rgb(var(--alt-text-rgb) / .12); margin-top: .3rem; }

/* The per-call row inside the sticky block. Title and description share one
   line; the description truncates rather than wrapping the bar taller. */
.callbar {
  display: flex; align-items: baseline; gap: 8px 18px;
  justify-content: space-between; padding-block: 6px 9px;
  border-top: 1px solid rgb(var(--alt-text-rgb) / .08);
}
.cb-id { display: flex; align-items: baseline; gap: 12px; min-width: 0; flex: 1 1 auto; }
.cb-id h1 { font-size: 1.15rem; margin: 0; white-space: nowrap; }
.cb-desc {
  margin: 0; min-width: 0; font-size: .85rem;
  color: rgb(var(--alt-text-rgb) / .6);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cb-side { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
.cb-side .btn-secondary { padding: .25em 1em; font-size: .85rem; }

/* Site-wide experimental-status notice - not sticky, scrolls away with the
   page, so it doesn't permanently eat header space (2026-09-21, Lucas's ask). */
.experimental-banner {
  background: var(--alt-accent);
  color: var(--bg);
  text-align: center;
  padding: 8px 20px;
  font-size: .85rem;
}
.experimental-banner a { color: var(--bg); font-weight: 700; text-decoration: underline; }

main { padding-block: 18px 40px; }
.foot { padding-block: 20px 36px; color: rgb(var(--alt-text-rgb) / .45); font-size: .85rem; }

.muted { color: rgb(var(--alt-text-rgb) / .6); }
.small { font-size: .8rem; }
.lede { max-width: 68ch; }

.btn-secondary {
  display: inline-block; padding: .45em 1.2em; border-radius: var(--radius-btn);
  border: 1px solid rgb(var(--alt-text-rgb) / .35); text-decoration: none;
  transition: background .15s ease, border-color .15s ease; white-space: nowrap;
}
.btn-secondary:hover {
  background: color-mix(in srgb, var(--accent) 80%, transparent);
  border-color: transparent;
}

.notice {
  background: color-mix(in srgb, var(--alert) 14%, transparent);
  border-left: 3px solid var(--alert);
  padding: .7em 1em; border-radius: 6px; font-size: .92rem;
}

/* ---- publish flags (admin only) ---- */
.flags { display: flex; gap: 6px; flex-wrap: wrap; }
.flag {
  font-size: .7rem; letter-spacing: .04em; white-space: nowrap;
  padding: .15em .7em; border-radius: var(--radius-btn); border: 1px solid transparent;
}
.flag.on  { background: color-mix(in srgb, var(--alt-accent) 20%, transparent); color: var(--alt-accent); }
.flag.off { border-color: rgb(var(--alt-text-rgb) / .25); color: rgb(var(--alt-text-rgb) / .5); }
.adminnote {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-left-color: var(--accent);
}

/* ---- login ---- */
.loginwrap { max-width: 30rem; margin-inline: auto; padding-block: 6vh; }
.loginform { display: grid; gap: 10px; justify-items: start; margin-block: 20px 24px; }
.loginform label { font-size: .85rem; color: rgb(var(--alt-text-rgb) / .7); }
.loginform input {
  font: inherit; width: 100%; color: var(--alt-text);
  background: var(--surface); border: 1px solid rgb(var(--alt-text-rgb) / .2);
  border-radius: 10px; padding: .55em .8em;
}
.loginform input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.btn-primary {
  font: inherit; cursor: pointer; color: #fff; background: var(--accent);
  border: 0; border-radius: var(--radius-btn); padding: .45em 1.6em;
  transition: background .15s ease;
}
.btn-primary:hover { background: color-mix(in srgb, var(--accent) 80%, transparent); }

/* ---- index ---- */
.cohort { margin-block: 28px; }
.cohort h2 { display: flex; align-items: baseline; gap: .5rem; }
/* The heading is a link to the cohort's own page: it reads as the heading it
   already was, and only picks up the underline on hover/focus. */
.cohort h2 a.cohortlink { color: inherit; text-decoration: none; }
.cohort h2 a.cohortlink:hover,
.cohort h2 a.cohortlink:focus-visible { text-decoration: underline; }
.cohort .count {
  font-family: Chivo, sans-serif; font-size: .8rem; font-weight: 400;
  color: rgb(var(--alt-text-rgb) / .5);
}
.calllist { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
/* The row is the hit area. The anchor stays a real link (focusable,
   middle-clickable, shows its target) and ::after stretches it over the whole
   row, so there is no nested-interactive markup and no JS click handler. */
.calllist li {
  position: relative;
  background: var(--surface); border-radius: 10px; padding: 12px 16px;
  display: flex; flex-wrap: wrap; gap: 4px 18px; align-items: baseline;
  justify-content: space-between;
  transition: background .15s ease;
}
.calllist li:hover { background: color-mix(in srgb, var(--accent) 28%, var(--surface)); }
.calllist li:hover .ct { color: var(--alt-accent); }
.calllist li.is-stub { opacity: .55; }
.cmain { min-width: 0; }
.clink { text-decoration: none; display: block; }
.clink::after { content: ""; position: absolute; inset: 0; border-radius: 10px; }
.clink:focus-visible::after { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Anything that must stay clickable or selectable sits above the stretched link. */
.calllist .rt, .calllist .flags, .calllist .warn { position: relative; z-index: 1; }
.ct { display: block; font-weight: 600; }
.cd { display: block; font-size: .82rem; color: rgb(var(--alt-text-rgb) / .55); }
.rt { font-size: .82rem; color: rgb(var(--alt-text-rgb) / .7); min-width: 0; }
.rtl { white-space: nowrap; display: inline-block; }
.rt b { font-weight: 600; color: rgb(var(--alt-text-rgb) / .5); }
.warn { font-size: .78rem; color: var(--alert); flex-basis: 100%; }

/* ---- depth switcher ---- */
.stagebar {
  grid-area: bar;
  display: flex; align-items: flex-start; gap: 12px;
  justify-content: space-between; flex-wrap: wrap;
}
.depthbar { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; }
.depth {
  font: inherit; color: var(--alt-text); cursor: pointer;
  background: var(--surface); border: 1px solid transparent;
  border-radius: var(--radius-btn); padding: .4em 1.1em;
  display: flex; flex-direction: column; line-height: 1.25;
  transition: background .15s ease, color .15s ease;
}
.depth:hover { background: color-mix(in srgb, var(--accent) 80%, transparent); }
.depth[aria-pressed="true"] { background: var(--accent); color: #fff; }
.depth .dn { font-weight: 600; font-size: .9rem; }
.depth .dm { font-size: .75rem; opacity: .75; }

.theater {
  font: inherit; font-size: .85rem; cursor: pointer; color: var(--alt-text);
  background: transparent; border: 1px solid rgb(var(--alt-text-rgb) / .35);
  border-radius: var(--radius-btn); padding: .4em 1.2em; white-space: nowrap;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.theater:hover { background: color-mix(in srgb, var(--accent) 80%, transparent); border-color: transparent; }
.theater[aria-pressed="true"] { background: var(--accent); border-color: transparent; color: #fff; }

/* Everything on the right of the stage bar: language, captions, theater. They
   wrap together rather than one at a time, so the row never half-breaks. */
.barside { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* ---- language ----
   Deliberately quieter than the depth switcher. Depth is the feature; language
   is a preference, and on most calls there are only two of them. */
.langbar { display: flex; gap: 4px; padding: 2px; border-radius: var(--radius-btn);
  background: var(--surface); }
.lang {
  font: inherit; font-size: .85rem; color: var(--alt-text); cursor: pointer;
  background: transparent; border: 0; border-radius: var(--radius-btn);
  padding: .35em 1em; display: inline-flex; align-items: baseline; gap: .45em;
  transition: background .15s ease, color .15s ease;
}
.lang:hover { background: color-mix(in srgb, var(--accent) 60%, transparent); }
.lang[aria-pressed="true"] { background: var(--accent); color: #fff; }
/* "auto" marks a machine translation. It has to be readable, not decorative:
   notes/learn-45b.md is explicit that these tracks go out for community review
   and are not presented as finished. */
.lang .mt {
  font-size: .62rem; letter-spacing: .06em; text-transform: uppercase;
  opacity: .65; border: 1px solid currentColor; border-radius: 4px;
  padding: 0 .35em; line-height: 1.5;
}

/* A depth above this viewer's cap. Visible, so the gradient is legible, but
   plainly not reachable. */
.depth.locked { opacity: .4; cursor: not-allowed; }
.depth.locked:hover { background: var(--surface); }

/* ---- the stage ----
   One DOM, two layouts, switched by grid areas alone - nothing moves in JS.

   Normal: video capped at 900px with the section list to its right, transcript
   full width below. That cap is what leaves real margin at the sides of the
   page, and the depth switcher sits inside the same band as the video.

   Theater: the video takes the whole band, and the section list drops down to
   scroll alongside the transcript. */
.stage {
  max-width: 1280px; margin-inline: auto;
  display: grid; align-items: start; gap: 18px;
  /* The section column needs a real minimum. With minmax(0, 1fr) the video
     track took its full 900px cap at every width above the 900px breakpoint,
     so between roughly 900 and 1180px the section list was squeezed to a few
     dozen pixels and pushed the page into a horizontal scroll. The video
     shrinks below its cap instead. */
  grid-template-columns: minmax(0, 900px) minmax(260px, 1fr);
  grid-template-areas:
    "notice     notice"
    "bar        bar"
    "video      sections"
    "transcript transcript";
}
.st-notices    { grid-area: notice; display: flex; flex-direction: column; gap: 8px; }
.st-notice     { margin: 0; }
.pane-video    { grid-area: video; }
.pane-sections { grid-area: sections; }
.pane-transcript { grid-area: transcript; }

.stage[data-theater="on"] {
  grid-template-columns: minmax(0, 1fr) minmax(0, 340px);
  grid-template-areas:
    "notice     notice"
    "bar        bar"
    "video      video"
    "transcript sections";
}
/* Beside the transcript the list is a companion column, so it scrolls to the
   same height instead of running long. */
.stage[data-theater="on"] .sections { max-height: 62vh; }

@media (max-width: 900px) {
  .stage, .stage[data-theater="on"] {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "notice" "bar" "video" "sections" "transcript";
  }
  /* Nothing to widen on a phone, so the toggle would do nothing. */
  .theater { display: none; }
}

.pane { background: var(--surface); border-radius: 12px; padding: 14px 16px; min-width: 0; }
.pane-video { padding: 0; overflow: hidden; background: #000; }
video { width: 100%; display: block; aspect-ratio: 16 / 9; background: #000; max-width: 100%; }
/* Sits at the top of the video pane, directly under the depth switcher, and is
   sized to match a section heading (.sec, .88rem) so the two read as one
   register. Change both together.
   The pane is overflow:hidden with a 12px radius, so this clips to the top
   corners on its own and needs no radius of its own. */
.skipnote {
  margin: 0; padding: .5em .9em; font-size: .88rem; background: var(--surface);
  color: var(--alt-accent);
  border-bottom: 1px solid rgb(var(--alt-text-rgb) / .1);
}
/* The filtered cut has run out. A different state from "playing at depth N",
   and it needs to read as an explanation rather than a status line. */
.skipnote.is-end {
  color: var(--alt-text);
  background: color-mix(in srgb, var(--alert) 16%, var(--surface));
  border-bottom-color: var(--alert);
}

.transcript, .sections { list-style: none; margin: 0; padding: 0; }
.transcript { max-height: 62vh; overflow-y: auto; scroll-behavior: smooth; }
/* Beside the video, the list tracks the video's own height. */
.sections   { max-height: 46vh; overflow-y: auto; }

.transcript li {
  padding: 5px 8px; border-radius: 6px; cursor: pointer; font-size: .92rem;
  border-left: 3px solid transparent;
}
.transcript li:hover { background: rgb(var(--alt-text-rgb) / .07); }
.transcript li .sp { font-weight: 600; color: rgb(var(--alt-text-rgb) / .55); font-size: .78rem; }
.transcript li .tm { float: right; font-size: .72rem; color: rgb(var(--alt-text-rgb) / .35); }
/* below the chosen depth: still readable, visibly not in the cut */
.transcript li.out { opacity: .34; }
.transcript li.current {
  background: color-mix(in srgb, var(--accent) 34%, transparent);
  border-left-color: var(--alt-accent); opacity: 1;
}

.sections li { margin-bottom: 4px; }
.sec {
  width: 100%; text-align: left; font: inherit; color: var(--alt-text);
  background: transparent; border: 0; border-left: 3px solid transparent;
  padding: 6px 8px; border-radius: 6px; cursor: pointer; font-size: .88rem;
  display: flex; gap: 8px; justify-content: space-between; align-items: baseline;
}
/* Real headings are sentences, not the two-word term-lists the auto-derived
   ones were, so the heading must be allowed to wrap and the timestamp must not
   be squeezed. Without this the section list pushed the whole page into a
   horizontal scroll - the one thing notes/45b-org.md says must never happen. */
.sec .sh { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.sec .st { flex: 0 0 auto; }
.sec:hover { background: rgb(var(--alt-text-rgb) / .07); }
.sec.current {
  background: color-mix(in srgb, var(--accent) 30%, transparent);
  border-left-color: var(--alt-accent);
}
.sec .st { font-size: .74rem; color: rgb(var(--alt-text-rgb) / .45); white-space: nowrap; }
.sec.dim .sh { opacity: .45; }

/* On a phone the call bar has no room for one line; let it stack, and drop the
   truncation so the description is still readable. Org rule: this has to be
   usable at 400px, and iOS needs checking specifically (notes/45b-org.md). */
@media (max-width: 640px) {
  .callbar { flex-wrap: wrap; padding-block: 6px 8px; }
  .cb-id { flex-direction: column; gap: 0; flex-basis: 100%; }
  .cb-id h1 { font-size: 1rem; white-space: normal; }
  .cb-desc { white-space: normal; overflow: visible; font-size: .8rem; }
  .cb-side { flex-basis: 100%; justify-content: flex-start; }
  .depth { padding: .35em .8em; }
  .transcript { max-height: 50vh; }
}

/* ---- catch me up ----
   A plain single column: no depth switcher, no transcript. The page has one
   job and the playlist is the only control beside the player. */
.stage-plain { display: block; max-width: 900px; }
.cu-head h1 { margin-bottom: .2em; }
.cu-head .lede { font-size: 1.05rem; margin: 0 0 .4em; }
.cu-queue .sec { width: 100%; text-align: left; }
.cu-queue .sec.done .sh { opacity: .5; text-decoration: line-through; }
.cu-queue .sec.current { background: color-mix(in srgb, var(--accent) 30%, transparent); }
.cu-queue .cd { display: block; font-size: .78rem; color: rgb(var(--alt-text-rgb) / .55); }

/* The resume offer, shown in the skip-note strip. A button rather than an
   automatic seek: being dropped 40 minutes into a recording you opened to
   watch from the start is worse than the click it saves. */
.resumebtn {
  margin-left: .5rem;
  padding: .15rem .8rem;
  border-radius: 30px;
  border: 1px solid var(--alt-accent);
  background: transparent;
  color: var(--alt-accent);
  font: inherit;
  font-size: .85rem;
  cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.resumebtn:hover {
  background: color-mix(in srgb, var(--alt-accent) 80%, transparent);
  color: var(--bg);
}

/* The masthead search box.
 *
 * It is the only nav item with no natural maximum width, so it is the one that
 * has to yield: `flex: 1 1 6rem` with `min-width: 0` lets it shrink to nothing
 * and, past that, the wrapping nav drops it to a second row. Added fixed-width
 * on 2026-09-15 it pushed EVERY page - /calls included - to 450px of content in
 * a 360px viewport, which is the horizontal scroll notes/45b-org.md forbids.
 * Measured, not assumed: 351px without the box, 450px with it. */
.navsearch { display: flex; min-width: 0; flex: 1 1 6rem; max-width: 12rem; }
.navsearch input[type="search"] {
  min-width: 0;
  width: 100%;
  max-width: 100%;
  padding: .3rem .8rem;
  font: inherit;
  font-size: .85rem;
  color: var(--alt-text);
  background: rgb(var(--alt-text-rgb) / .08);
  border: 1px solid rgb(var(--alt-text-rgb) / .12);
  border-radius: var(--radius-btn);
}
.navsearch input[type="search"]:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* The DISPLAY-language switcher in the masthead.
 *
 * Deliberately not styled like the spoken-language filter on /topics. That one
 * is a row of full language names inside the page and it narrows the corpus;
 * this is a two-letter toggle in the chrome and it narrows nothing. Two
 * controls one click apart must not read as one setting.
 *
 * It sits in the nav's flex row, which already wraps rather than widening the
 * page - the masthead has forced a horizontal scroll once. `nowrap` keeps EN
 * and PT together when the row wraps; the row, not the pair, is what breaks. */
.uiswitch {
  display: inline-flex; align-items: center; gap: 6px;
  white-space: nowrap; font-size: .78rem;
}
.uilabel {
  text-transform: uppercase; letter-spacing: .08em;
  color: rgb(var(--alt-text-rgb) / .45);
}
.uiswitch .uiopt {
  font-size: .78rem; padding: .1em .45em; border-radius: var(--radius-btn);
  text-decoration: none; color: rgb(var(--alt-text-rgb) / .6);
}
.uiswitch a.uiopt:hover { color: var(--alt-accent); }
.uiswitch .uiopt.is-on {
  background: color-mix(in srgb, var(--alt-accent) 18%, transparent);
  color: var(--alt-accent); font-weight: 600;
}
/* The two footer lines the display language adds: what stays in its own
   language, and - only while a translation is incomplete - how many phrases
   fell back to English on this page. */
.uinote, .uimiss { font-size: .82rem; margin: 0 0 .4em; }
.uimiss { color: color-mix(in srgb, var(--alt-accent) 70%, currentColor); }
