/* 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;
  /* Derived, not a seventh colour: --accent mixed toward white until it
     clears AA on --bg (#4455BB alone is ~3:1 on the dark ground). Used for
     link hover. Light theme, if it lands, re-derives this from its own accent. */
  --accent-bright: color-mix(in srgb, var(--accent) 55%, white);
  --radius-btn: 30px;
  /* Not one of the six org tokens: the amber of 45b.io/web3's Apply to Join
     button (45b-website ApplyButton), for the masthead's apply pill. */
  --apply: #ffc857;
}

* { 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); }
/* Plain prose links glow indigo on hover, portal-wide (2026-09-24). Scoped
   to class-less anchors so pills, cards and nav keep their own hovers; any
   rule of higher specificity (e.g. .masthead nav a:hover) still wins. */
/* Headings that are links glow the same way (2026-09-24) - a page's own
   heading-link rule should reuse --link-glow rather than pick a colour.
   Card-shaped links (whole card is the hit area) glow their heading on card
   hover; see .cohcard in cohorts.css. */
:root { --link-glow: 0 0 .6em color-mix(in srgb, var(--accent) 80%, transparent); }
a:not([class]), :is(h1, h2, h3) a { transition: color .15s ease, text-shadow .15s ease; }
a:not([class]):hover, a:not([class]):focus-visible,
:is(h1, h2, h3) a:hover, :is(h1, h2, h3) a:focus-visible {
  color: var(--accent-bright);
  text-shadow: var(--link-glow);
}

/* 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; }

/* STICKY TABLE HEADERS - the standard for every table (2026-09-27, Lucas;
   the org rule is in notes/45b-design.md). Once a table's header row scrolls
   past, it stays pinned under the masthead; --stickytop is the masthead's
   height, measured in layout_foot.php because it wraps. Sticky only works
   when no ancestor scrolls, so from 1000px up a .tablewrap stops being a
   sideways scroller; below that the table scrolls sideways in its box and the
   header scrolls with it - a sideways-scrolling table cannot also pin to the
   page. The whole <thead> sticks, so a two-row header moves as one.
   Opt out with <table class="nosticky">. Opted out: the public upcoming-calls
   grid (templates/_upcoming.php).  */
@media (min-width: 1000px) {
  main .tablewrap { overflow-x: visible; }
  main table:not(.nosticky) > thead {
    position: sticky; top: var(--stickytop, 0px); z-index: 5;
    box-shadow: 0 1px 0 rgb(var(--alt-text-rgb) / .15);
  }
  main table:not(.nosticky) > thead th { background: var(--bg); }
}

/* 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.35rem; color: var(--accent-bright); }
/* "· Learn" stays at 1.2rem while "45B" grows (2026-09-24). */
.brand span { color: rgb(var(--alt-text-rgb) / .55); font-weight: 400; font-size: 1.2rem; }
.masthead nav a { text-decoration: none; color: rgb(var(--alt-text-rgb) / .75); font-size: .9rem; }
.masthead nav a:hover { color: var(--alt-accent); }

/* THE PHONE MASTHEAD (2026-09-28, Lucas). Three tidy rows instead of the nav
   wrapping wherever it ran out of room:
     1. 45B · Learn ............................ [Apply to join!]
     2. ..................... Cohorts  Topics  Knowledgebase
     3. ........... search  Sign in / Admin  INTERFACE EN PT FR ES
   The nav's box steps aside (display: contents) so its items join the
   masthead's own flex row, where `order` places them; the masthead's two
   pseudo-elements are the zero-height line breaks between the rows. */
@media (max-width: 640px) {
  .masthead { justify-content: flex-end; gap: 8px 14px; }
  .masthead nav { display: contents; }
  .masthead .brand { order: 0; margin-right: auto; }
  .masthead nav > * { order: 5; }
  .masthead nav .applypill { order: 1; }
  .masthead::before, .masthead::after { content: ""; flex-basis: 100%; height: 0; }
  .masthead::before { order: 2; }
  .masthead nav .navlink { order: 3; font-size: .95rem; }
  .masthead::after { order: 4; }
  .masthead .navsearch { flex: 1 1 6rem; max-width: none; }
}
/* The apply pill - ported from 45b-website's ApplyButton at masthead size:
   amber fill, near-black label, hover mixed toward white, and the same
   diagonal shine every 5s (off under reduced motion). */
.masthead nav .applypill {
  position: relative; overflow: hidden;
  padding: .3em 1.05em; border-radius: var(--radius-btn);
  background: var(--apply); color: var(--bg);
  font-size: .85rem; font-weight: 700; letter-spacing: .01em; white-space: nowrap;
  transition: background-color .15s ease, transform .15s ease;
}
.masthead nav .applypill:hover,
.masthead nav .applypill:focus-visible {
  background: color-mix(in srgb, var(--apply) 80%, white); color: var(--bg);
  transform: translateY(-1px);
}
button.applypill { border: 0; font-family: inherit; cursor: pointer; }
.applypill span { position: relative; z-index: 1; }
.applypill::after {
  content: ""; position: absolute; top: -50%; bottom: -50%; left: 0; width: 40%;
  background: linear-gradient(100deg, rgb(255 255 255 / 0) 0%, rgb(255 255 255 / .55) 50%, rgb(255 255 255 / 0) 100%);
  transform: translateX(-250%) skewX(-20deg);
  animation: applyShine 5s ease-in-out infinite; pointer-events: none;
}
@keyframes applyShine {
  0%   { transform: translateX(-250%) skewX(-20deg); }
  18%  { transform: translateX(350%) skewX(-20deg); }
  100% { transform: translateX(350%) skewX(-20deg); }
}
@media (prefers-reduced-motion: reduce) {
  .applypill::after { animation: none; }
  .masthead nav .applypill { transition: none; }
  .masthead nav .applypill:hover { transform: none; }
}
.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; }
/* Dashboard sits above the groups (2026-09-25). */
.masthead nav .adminpanel a.admindash { font-weight: 700; border-bottom: 1px solid rgb(var(--alt-text-rgb) / .08); }
/* The slim reminder a dismissed host cover leaves behind (2026-09-25). */
.hostbanner { background: color-mix(in srgb, var(--warn) 18%, var(--bg)); color: var(--alt-text);
  border-bottom: 1px solid var(--warn); padding: .35rem 16px; font-size: .85rem; text-align: center; }
.hostbanner a { color: inherit; text-decoration: underline; }
.masthead nav .adminpanel a:hover { background: rgb(var(--alt-text-rgb) / .06); }
/* Group headings in the admin menu (2026-09-24). */
.adminpanel .adminhead {
  padding: .6rem 1rem .15rem; font-size: .68rem; text-transform: uppercase;
  letter-spacing: .08em; color: rgb(var(--alt-text-rgb) / .45);
}
.adminpanel .adminhead:not(:first-child) { border-top: 1px solid rgb(var(--alt-text-rgb) / .08); margin-top: .3rem; }
.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; }
/* The indigo link glow would vanish on the green; hover here thickens the
   underline instead. */
.experimental-banner a:hover, .experimental-banner a:focus-visible {
  color: var(--bg); text-shadow: none; text-decoration-thickness: 2px;
}

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); }
/* A destructive action (design notes, 2026-10-01): a filled coral pill,
   always behind a confirm(). In a dialog's button row it sits alone at the
   far left (.dlgbtns-left), with Cancel / Save together at the right. */
.btn-danger {
  font: inherit; cursor: pointer; color: #fff; background: var(--alert);
  border: 0; border-radius: var(--radius-btn); padding: .45em 1.6em;
  transition: background .15s ease;
}
.btn-danger:hover { background: color-mix(in srgb, var(--alert) 80%, transparent); }
.dlgbtns-left { margin-right: auto; }
.dlgbtns-left[hidden] { display: none; }

/* ---- 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; color: var(--accent-bright); text-shadow: var(--link-glow); }
.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; min-width: 0; max-width: 100%; margin-left: auto; }
/* With our own controls (player.js) depth lives on the video, so the
   switcher goes and the bar keeps only the right-aligned side, as on the
   testimonials stage. iOS and `?legacy` keep the switcher. */
.stage.has-cp .depthbar { display: none; }

/* ---- 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; flex-wrap: wrap; gap: 4px; padding: 2px; border-radius: var(--radius-btn);
  background: var(--surface); max-width: 100%; }
.lang .lc { display: none; }
.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; }
/* A video-only call (no transcript yet) hides these; their display rules
   would otherwise win over the attribute. */
.depthbar[hidden], .pane[hidden], .sections[hidden] { display: none; }

.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; }
/* Arrived from the landing carousel and the browser would not start the
   sound by itself (app.js, `play=1`): one tap on this plays from the moment. */
.pane-video, .vp-frame { position: relative; }
.deep-play {
  position: absolute; top: 50%; left: 50%; z-index: 2; transform: translate(-50%, -50%);
  width: 88px; height: 88px; border: none; border-radius: 50%; cursor: pointer;
  background: color-mix(in srgb, var(--accent) 88%, transparent); color: #fff;
  box-shadow: 0 2px 14px rgba(0, 0, 0, .45);
}
.deep-play svg { width: 44%; height: 44%; margin-left: 4px; }

/* The CC overlay and our own player controls live in player.css (2026-10-02).
   CC took the top left, so the unmute pill sits top centre - both players. */
.vp-frame .vp-unmute { left: 50%; transform: translateX(-50%); }

/* Opened from an Autoplay link and the browser only allowed it muted
   (app.js): one tap brings the sound back. */
.vp-unmute {
  position: absolute; top: 10px; left: 10px; z-index: 3;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 18px 10px 14px; border: none; border-radius: var(--radius-btn); cursor: pointer;
  background: color-mix(in srgb, var(--accent) 88%, transparent); color: #fff;
  font: 700 1rem/1 var(--font-body, inherit); box-shadow: 0 2px 14px rgba(0, 0, 0, .45);
}
.vp-unmute svg { width: 22px; height: 22px; }
@media (max-width: 640px) { .vp-unmute { top: 6px; left: 6px; padding: 8px 14px 8px 10px; font-size: .9rem; } }

/* Right-click menu on the video (app.js, 2026-10-01) - YouTube's dark card. */
.vp-menu {
  position: absolute; z-index: 4; min-width: 240px; max-width: calc(100% - 8px);
  padding: 6px 0; border-radius: 12px; background: rgba(28, 28, 28, .94);
  box-shadow: 0 4px 18px rgba(0, 0, 0, .5); color: #fff;
}
.vp-menu button {
  display: block; width: 100%; padding: 9px 16px 9px 40px; border: none;
  background: none; color: inherit; text-align: left; cursor: pointer;
  font: .95rem/1.3 var(--font-body, inherit); position: relative;
}
.vp-menu button:hover, .vp-menu button:focus-visible { background: rgba(255, 255, 255, .12); outline: none; }
.vp-menu button[aria-checked="true"]::before {
  content: "\2713"; position: absolute; left: 16px; color: var(--alt-accent);
}
.vp-menu button[aria-checked="false"]::before {
  content: ""; position: absolute; left: 15px; top: 50%; width: 12px; height: 12px;
  transform: translateY(-50%); border: 1.5px solid rgba(255, 255, 255, .6); border-radius: 3px;
}
.vp-menu-hint { margin: 4px 0 0; padding: 6px 16px 2px; border-top: 1px solid rgba(255, 255, 255, .15);
  font-size: .8rem; color: rgba(255, 255, 255, .6); }
.vp-toast {
  position: absolute; z-index: 4; left: 50%; bottom: 64px; transform: translateX(-50%);
  padding: 8px 16px; border-radius: var(--radius-btn); background: rgba(0, 0, 0, .8);
  color: #fff; font-size: .95rem; pointer-events: none;
}

/* Captions a readable size with a solid backing (what browsers let a page
   style of their own caption line; iOS ignores most of it). */
#player::cue { background: rgba(0, 0, 0, .75); color: #fff; font-size: 1.05em; line-height: 1.35; }
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; }
  .lang .ln { display: none; }
  .lang .lc { display: inline; font-weight: 600; }
  .lang { padding: .35em .75em; }
  .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 pick-list under the search box (2026-09-28, navsearch.js). About five
   rows tall so there is visibly more below - the list scrolls inside itself,
   not the page. Wider than the box when the box is narrow; navsearch.js shifts
   it left if it would overhang the viewport. Rows are tall enough to tap. */
.navsearch { position: relative; }
.navsuggest {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 30;
  width: max(100%, 19rem); max-width: calc(100vw - 32px);
  background: var(--surface);
  border: 1px solid rgb(var(--alt-text-rgb) / .15);
  border-radius: 12px;
  box-shadow: 0 8px 24px rgb(0 0 0 / .45);
  overflow: hidden;
}
.navsuggest[hidden] { display: none; }
.navsuggest-head {
  display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
  padding: .5rem .9rem .35rem;
  font-size: .95rem; color: rgb(var(--alt-text-rgb) / .6);
  border-bottom: 1px solid rgb(var(--alt-text-rgb) / .1);
}
.masthead nav .navsuggest-head a { font-size: .95rem; color: var(--accent-bright); white-space: nowrap; }
.navsuggest-list {
  list-style: none; margin: 0; padding: .25rem 0;
  max-height: 14.5rem; overflow-y: auto; overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
.masthead nav .navsuggest-list a {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  min-height: 2.75rem; padding: .45rem .9rem;
  color: var(--alt-text); font-size: .95rem;
}
.navsuggest-list .ns-term { overflow-wrap: anywhere; }
.navsuggest-list .ns-topic {
  flex: 0 1 auto; text-align: right;
  font-size: .95rem; color: rgb(var(--alt-text-rgb) / .5);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 55%;
}
.masthead nav .navsuggest-list a:hover,
.masthead nav .navsuggest-list a.is-active { background: rgb(var(--alt-text-rgb) / .08); color: var(--alt-accent); }

/* 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); }

/* The call description ("Understanding Web3 usage") under its title, on every
   call list - the cohort page and /calls (moved here 2026-09-24). Sits between
   the title and the date line; .cmain is min-width:0, so a long one wraps
   inside the row instead of widening it. */
.calllist .cdesc {
  margin: .15em 0 .1em; font-size: .85rem;
  color: rgb(var(--alt-text-rgb) / .7); overflow-wrap: anywhere;
}

/* The share dialog behind the signed-in "Share with friends" pill (2026-09-24). */
.sharedlg {
  width: min(30rem, calc(100vw - 32px)); padding: 20px 22px;
  background: var(--surface); color: var(--alt-text);
  border: 1px solid rgb(var(--alt-text-rgb) / .15); border-radius: 14px;
}
.sharedlg::backdrop { background: rgb(0 0 0 / .6); }
.sharedlg h2 { margin-bottom: .5rem; }
.sharemsg { font-size: .9rem; margin: 0 0 1rem; padding: .7em .9em; border-radius: 10px;
  background: rgb(var(--alt-text-rgb) / .06); }
.sharemsg .shareurl { color: var(--accent-bright); white-space: nowrap; }
.shareopts { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 1rem; }
.sharedlg .shareopt {
  display: inline-block; padding: .4em 1.1em; border-radius: var(--radius-btn);
  background: var(--accent); color: #fff; border: 0; font: inherit; font-size: .9rem;
  text-decoration: none; cursor: pointer; transition: background .15s ease;
}
.sharedlg .shareopt:hover, .sharedlg .shareopt:focus-visible {
  background: color-mix(in srgb, var(--accent) 80%, transparent); color: #fff;
}
.sharedlg form { margin: 0; text-align: right; }
.btn-primary { display: inline-block; text-decoration: none; }

/* On localhost only: the "local" pill on admin menu links, and the cover over
   a page whose live copy is on the host (layout_head.php, 2026-09-24). */
.localpill {
  display: inline-block; margin-left: .4em; padding: 0 .55em; border-radius: var(--radius-btn);
  font-size: .62rem; text-transform: uppercase; letter-spacing: .06em; vertical-align: .15em;
  background: color-mix(in srgb, var(--warn) 22%, transparent); color: var(--warn);
}
.hostcover {
  position: fixed; inset: 0; z-index: 50; display: flex; align-items: center; justify-content: center;
  padding: 16px; background: rgb(0 0 0 / .72); backdrop-filter: blur(3px);
}
.hostcard {
  max-width: 30rem; padding: 22px 24px; border-radius: 14px;
  background: var(--surface); border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
}
.hostcard h2 { color: var(--warn); }
.hostbtns { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 0; }

/* Facebook/LinkedIn copied the message as they opened (layout_head.php). */
.sharepaste { margin: 10px 0 0; color: var(--alt-accent); font-weight: 600; }
