/* =========================================================
   site_claude.css
   Bare-bones stylesheet for MAIN_CLAUDE.html — only the rules
   actually needed to (a) render the new James True design and
   (b) support the classes the CGI .nv templates and existing
   assets/js/*.js emit directly. player.js / transcript.js /
   slides.js inject their own scoped <style> blocks, so no
   .viewer / .tfx-* / .wavebar / .video-embed / .media-stage
   rules are carried over from site.css — those were dead CSS.
   ========================================================= */

:root{
  --bg:#14100f;
  --panel:#1c1613;
  --card:#1c1613;
  --text:#f3ece4;
  --muted:#8a7d76;
  --hairline:rgba(255,255,255,.08);
  --pill:rgba(255,255,255,.08);
  --pill-text:#c9bfb7;
  --accent:#6d9eeb;   /* brand blue, as given */
  --accent-hover:#96bcf2; /* lighter tint for hover, still reads on dark */
  --avatar-size:40px;
  --avatar-radius:10px;
  --menu-minw:180px;
}
html[data-theme="light"]{
  --bg:#fbf9f6;
  --panel:#f3ece4;
  --card:#ffffff;
  --text:#241119;
  --muted:#6b6058;
  --hairline:rgba(0,0,0,.08);
  --pill:#f3ece4;
  --pill-text:#6b6058;
  --accent:#2f6fd1;   /* deeper shade of the same blue, AA on light bg */
  --accent-hover:#6d9eeb; /* the brand blue itself */
}
/* Auto = no data-theme attribute at all. Dark is the default palette
   already declared on :root above, so the only case Auto needs to
   handle itself is following the OS into light mode live, with no
   JS polling required. An explicit Light/Dark choice (data-theme set
   by theme.js) always wins over this, via the :not() guards below. */
@media (prefers-color-scheme: light){
  html:not([data-theme="dark"]):not([data-theme="light"]){
    --bg:#fbf9f6;
    --panel:#f3ece4;
    --card:#ffffff;
    --text:#241119;
    --muted:#6b6058;
    --hairline:rgba(0,0,0,.08);
    --pill:#f3ece4;
    --pill-text:#6b6058;
    --accent:#2f6fd1;
    --accent-hover:#6d9eeb;
  }
}

/* ---------- base ---------- */
*{ box-sizing:border-box }
html,body{ margin:0; height:100% }
body{
  background:var(--bg); color:var(--text);
  font:15px/1.5 'Work Sans', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
}
a{ color:var(--accent); text-decoration:none }
a:hover{ color:var(--accent-hover) }
.wrap{ max-width:1200px; margin:0 auto; padding:20px }

/* ---------- header ---------- */
.jt-header{ position:relative }
.jt-banner{
  position:relative; height:168px; border-radius:18px; overflow:hidden;
  background:
    url('/assets/images/hero.jpg') center/cover no-repeat,
    linear-gradient(120deg,#7a3410 0%,#b0521f 38%,#c8842a 62%,#3a1f3d 100%);
}
.jt-banner::after{
  content:""; position:absolute; inset:0;
  background:linear-gradient(180deg, rgba(20,16,15,.12) 0%, rgba(20,16,15,.6) 100%);
}
.jt-identity-row{
  position:relative; z-index:1; /* sit above .jt-banner, which is itself
     position:relative for its ::after overlay and would otherwise paint
     on top of this non-positioned sibling despite coming first in DOM order */
  display:flex; align-items:flex-end; justify-content:space-between;
  padding:0 20px; margin-top:-28px; flex-wrap:wrap; gap:16px;
}
.jt-identity{ display:flex; align-items:flex-end; gap:18px }
.jt-avatar-link{ display:inline-block; line-height:0 }
.jt-avatar{
  width:92px; height:92px; border-radius:999px; flex-shrink:0;
  background:var(--avatar-img) center/cover no-repeat, #241119;
  border:4px solid var(--bg);
}
.jt-name-block{ padding-bottom:10px }
.jt-name{ font-family:'Cormorant Garamond', serif; font-weight:600; font-size:26px; line-height:1; color:var(--text) }
.jt-tagline{ font-size:12.5px; color:var(--muted); letter-spacing:.4px; margin-top:4px }

/* ---------- nav ---------- */
.jt-nav{
  display:flex; align-items:center; gap:26px; flex-wrap:wrap;
  padding-bottom:14px; font-size:14.5px; font-weight:500;
}
.jt-nav a{ color:var(--text) }
.jt-nav a:hover, .jt-nav a.is-active{ color:var(--accent-hover) }
html[data-theme="light"] .jt-nav a:hover,
html[data-theme="light"] .jt-nav a.is-active{ color:var(--accent) }
.jt-search{ display:flex; align-items:center }

/* ---------- user menu ---------- */
.user-menu{ position:relative }
.icon-btn{
  width:var(--avatar-size); height:var(--avatar-size);
  padding:0; margin:0; display:inline-flex; align-items:center; justify-content:center;
  background:var(--pill); border:1px solid var(--hairline);
  border-radius:var(--avatar-radius); overflow:hidden; line-height:0; cursor:pointer;
  color:var(--text); /* buttons don't inherit color from the page by default
    (browser UA style sets it explicitly), so the gear's stroke="currentColor"
    was rendering plain black in every theme until this was set directly */
}
.icon-btn img{ width:100%; height:100%; object-fit:cover; border-radius:inherit; display:block }
.menu{
  position:absolute; right:0; top:calc(100% + 8px);
  min-width:var(--menu-minw); padding:6px;
  border:1px solid var(--hairline); border-radius:10px;
  background:var(--card); color:var(--text);
  box-shadow:0 6px 20px rgba(0,0,0,.25); z-index:50;
}
.menu[hidden]{ display:none }
.menu .item{
  display:block; width:100%; padding:.55rem .7rem; border:0; background:transparent;
  border-radius:.5rem; text-align:left; text-decoration:none; color:inherit; font:inherit; cursor:pointer;
}
.menu .item:hover{ background:var(--pill) }
.menu-label{
  padding:.3rem .7rem; font-size:11px; letter-spacing:.5px; text-transform:uppercase;
  color:var(--muted);
}
.theme-option{ display:flex; align-items:center; justify-content:space-between; gap:10px }
.theme-option::after{
  content:""; width:7px; height:7px; border-radius:999px; background:transparent;
  border:1px solid var(--hairline); flex-shrink:0;
}
.theme-option[aria-checked="true"]{ color:var(--accent-hover); font-weight:600 }
.theme-option[aria-checked="true"]::after{ background:var(--accent-hover); border-color:var(--accent-hover) }

/* ---------- crumbs + search ---------- */
.crumbrow{
  display:flex; gap:10px; align-items:center; flex-wrap:wrap;
  background:var(--panel); padding:10px 14px; border-radius:14px; margin-top:16px;
}
.crumbs{ color:var(--muted); font-size:13px; display:flex; gap:6px; flex-wrap:wrap }
.crumbs a{ color:var(--text) }
.crumbs a:hover{ color:var(--accent-hover) }
.searchflex{ display:flex; align-items:center; gap:10px; margin-left:auto; min-width:260px; flex:1 }
.searchflex input[type=search]{
  width:100%; padding:10px 12px; background:var(--bg);
  border:1px solid var(--hairline); border-radius:10px; color:var(--text);
}
.btn{
  padding:9px 14px; background:var(--accent); border:none; border-radius:10px;
  color:#fff; font-weight:600; cursor:pointer; white-space:nowrap;
}
.btn:hover{ background:var(--accent-hover) }

/* ---------- library grid (CARD_ROWS_ID) ---------- */
/* --slide-w is set by the size slider; 280px is the default it starts at */
.grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(var(--slide-w,280px),1fr)); gap:16px; margin-top:22px }
a.card, a.card:hover, a.card:focus{ text-decoration:none !important; color:inherit }
.card{
  position:relative; background:var(--card); border-radius:16px; overflow:hidden;
  border:1px solid var(--hairline); display:flex; flex-direction:column;
}
.thumb{ position:relative; aspect-ratio:16/9; width:100%; background:#241119 center/cover no-repeat; flex:0 0 auto }
.duration-badge{
  position:absolute; right:8px; bottom:8px; padding:3px 6px; border-radius:6px;
  background:rgba(0,0,0,.72); color:#fff; font-size:12px; font-weight:700; border:1px solid rgba(255,255,255,.15);
}
.duration-badge:empty, .duration-badge[data-count="0"]{ display:none }
.corner-icon{ position:absolute; left:4px; top:4px; opacity:.9 }
/* The pill used to be absolutely positioned with 44px of padding reserved for
   it. At small thumbnail sizes the caption needed more room than that and ran
   underneath it. As a normal flex item pushed down by margin-top:auto it sits
   in the same place but can never be overlapped. */
.card .meta{ position:relative; padding:12px 14px 12px; display:flex; flex-direction:column; gap:6px; flex:1 1 auto }
.card .title{
  font-family:'Cormorant Garamond', serif; font-weight:600; font-size:17.5px; line-height:1.3;
}
.desc-snippet{
  color:var(--muted); font-size:13px; line-height:1.35; pointer-events:none;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.meta-bottom{
  margin-top:auto; padding-top:8px;
  display:flex; align-items:center; justify-content:space-between; gap:8px;
}
.meta-bottom .left{ display:flex; align-items:center; gap:8px }
.meta-bottom .right{ display:flex; align-items:center }

/* ---------- badges (shared: grid cards + item view) ---------- */
.badge{
  font-size:11px; padding:4px 8px; border-radius:999px;
  background:var(--pill); border:1px solid var(--hairline); color:var(--pill-text);
}
.badge.public{ border-color:rgba(109,158,235,.5); color:#96bcf2 }
.badge.private{ border-color:rgba(251,191,36,.5); color:#fde68a }
.badge.locked{ border-color:rgba(248,113,113,.55); color:#fecaca }
.badge.episode{ background:rgba(109,158,235,.15); border-color:rgba(109,158,235,.45); color:#96bcf2 }
.badge.date{ font-weight:700; background:rgba(148,163,184,.15); border-color:rgba(148,163,184,.35); color:var(--text) }
.badge:empty, .badge[data-count="0"]{ display:none }

/* ---------- item detail (VIEW_ITEM_ID) ---------- */
#viewerHost{ margin-top:22px }
.main{ display:flex; flex-direction:column; gap:0 }
.main .left{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:14px }
.panel{
  margin:12px 0; border:1px solid var(--hairline); border-radius:12px;
  overflow:hidden; background:var(--card);
}
.panel-body{ padding:20px }
.vtitle{ font-family:'Cormorant Garamond', serif; font-weight:600; font-size:26px; margin:0 0 10px }

/* ---------- footer ---------- */
footer{ color:var(--muted); margin:28px 0 10px; font-size:13px; text-align:center }

/* ---------- responsive ---------- */
@media (max-width:680px){
  .jt-name-block .jt-tagline{ display:none }
  .searchflex{ min-width:160px }
}

/* ---------- missing video ----------
   Both rules key off the row's ytid. Default is hidden, so every episode
   that still has a video is untouched; the notice only appears, and the
   dead YouTube badge only disappears, when ytid is empty. */
.novideo{
  display:none;
  margin:10px 0 0;
  padding:10px 14px;
  border:1px solid rgba(251,191,36,.35);
  border-left-width:3px;
  border-radius:8px;
  background:rgba(251,191,36,.07);
  color:#fde68a;
  font-size:13.5px;
}
.novideo[data-ytid=""]{ display:block }
.ytlink[data-ytid=""]{ display:none }
/* no transcript -> hide the panel rather than iframe a 404 */
#secTranscript[data-words="0"], #secTranscript[data-words=""]{ display:none }

/* ---------- slide viewer ----------
   A window, not a takeover. Its width is set at runtime to match the hero
   image/video on the page, so a slide opens at the size the episode's own
   media occupies. Navigation on the image, a filmstrip and a filter below. */
.slb{position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,.55);
     display:flex;align-items:center;justify-content:center;padding:16px}
.slb[hidden]{display:none!important}
.slb-win{background:var(--card);border:1px solid var(--hairline);
  border-radius:12px;box-shadow:0 24px 70px rgba(0,0,0,.5);max-width:100%;
  max-height:calc(100vh - 32px);display:flex;flex-direction:column;overflow:hidden}
.slb-bar{display:flex;align-items:center;gap:10px;padding:8px 10px 8px 12px;
  border-bottom:1px solid var(--hairline)}
.slb-find{flex:1 1 auto;min-width:0;padding:5px 10px;font:inherit;font-size:13px;
  color:inherit;background:var(--bg);
  border:1px solid var(--hairline);border-radius:6px}
.slb-pos{font-size:12px;color:var(--muted);white-space:nowrap}
.slb-x{background:none;border:0;color:var(--muted);font-size:22px;line-height:1;
  cursor:pointer;padding:0 4px}
.slb-x:hover{color:var(--text)}
.slb-stage{position:relative;display:flex;align-items:center;justify-content:center;
  background:#000;min-height:120px}
.slb-img{display:block;max-width:100%;max-height:56vh;object-fit:contain}
.slb-prev,.slb-next{position:absolute;top:50%;transform:translateY(-50%);
  background:rgba(0,0,0,.55);color:var(--text);border:1px solid rgba(255,255,255,.16);
  border-radius:999px;width:38px;height:38px;font-size:22px;line-height:1;cursor:pointer;
  display:flex;align-items:center;justify-content:center}
.slb-prev{left:10px}.slb-next{right:10px}
.slb-prev:hover,.slb-next:hover{background:rgba(0,0,0,.8)}
/* Fixed height. Captions run from one line to a full paragraph, and letting the
   box grow made the filmstrip jump up and down on every slide. */
.slb-cap{padding:12px 16px;color:var(--text);font-size:13.5px;line-height:1.6;
  height:104px;overflow-y:auto}
.slb-title{display:flex;align-items:baseline;gap:8px;padding:10px 14px 8px;
  border-bottom:1px solid var(--hairline)}
.slb-ep{font-weight:600;font-size:14px;color:var(--text);text-decoration:none;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
a.slb-ep[href]{color:var(--accent)}
a.slb-ep[href]:hover{text-decoration:underline}
/* On an episode page the title links nowhere -- you are already there. It
   now closes the viewer and returns you to the top instead, so it has to
   look actionable without looking like navigation to somewhere else. */
.slb-ep-self{cursor:pointer}
.slb-ep-self:hover{text-decoration:underline;text-decoration-style:dotted;
  text-underline-offset:3px;color:var(--accent)}
.slb-title .slb-pos{margin-left:auto;padding-left:10px}
.slb-eplabel{font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);flex:0 0 auto}
.slb-sku{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;
  color:var(--muted);letter-spacing:.04em}
.slb-cap p{margin:6px 0 0}
.slb-strip{display:flex;gap:6px;overflow-x:auto;padding:8px 10px;
  border-top:1px solid var(--hairline);background:rgba(0,0,0,.18)}
.slb-thumb{flex:0 0 auto;padding:0;border:2px solid transparent;border-radius:5px;
  background:none;cursor:pointer;line-height:0;opacity:.55}
.slb-thumb img{width:84px;height:47px;object-fit:cover;border-radius:3px;display:block;background:#000}
.slb-thumb:hover{opacity:.85}
.slb-thumb.on{opacity:1;border-color:var(--accent)}
@media (max-width:680px){
  .slb{padding:8px}
  .slb-img{max-height:44vh}
  .slb-thumb img{width:64px;height:36px}
}

/* ---------- episode slide deck (inlined, collapsible) ---------- */
#secSlides[hidden]{display:none!important}
.decktools{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin:12px 0 14px;
  font-size:13px;color:var(--muted)}
.decktools input[type=search]{flex:1 1 220px;min-width:180px;padding:6px 10px;font:inherit;
  color:inherit;background:var(--bg);border:1px solid var(--hairline);
  border-radius:6px}
.decktools input[type=range]{vertical-align:middle;width:120px}
.decktools label{white-space:nowrap;display:flex;align-items:center;gap:6px}
.deckgrid{display:grid;gap:14px;
  grid-template-columns:repeat(auto-fill,minmax(var(--slide-w,200px),1fr))}
.deckgrid a.card[hidden]{display:none}

/* ---------- maximize a panel ---------- */
.pmax-btn{margin-left:12px;font:inherit;font-size:12px;font-weight:500;cursor:pointer;
  padding:2px 10px;border-radius:999px;color:var(--muted);
  background:var(--pill);border:1px solid var(--hairline)}
.pmax-btn:hover{color:var(--text);background:rgba(148,163,184,.20)}
html.pmax-lock{overflow:hidden}
section.panel.pmax{position:fixed;inset:0;z-index:9000;margin:0;border-radius:0;
  overflow:auto;background:var(--card);box-shadow:none}
/* maximized: the grid gets the whole window, so more thumbnails fit per row.
   The transcript is prose, so it keeps a readable measure instead. */
section.panel.pmax > .panel-body{padding:20px 24px 40px;max-width:none;margin:0}
section.panel.pmax#secTranscript > .panel-body{max-width:1100px;margin:0 auto}
section.panel.pmax summary{position:sticky;top:0;z-index:2;padding:10px 0;
  background:var(--card)}
/* the transcript is an iframe with an inline height -- give it the window */
section.panel.pmax iframe{height:calc(100vh - 150px)!important}
/* and the slide filter bar should stay reachable while you scroll the grid */
section.panel.pmax .decktools{position:sticky;top:44px;z-index:1;
  background:var(--card);padding:10px 0;margin-top:0}

/* ---------- slide deck: clipped peek when closed ----------
   Closed is not empty. One row of thumbnails stays visible, faded off at the
   bottom, so the deck advertises itself and the toggle reads as min/max rather
   than show/hide. That is why this panel has no separate maximize button. */
.decktoggle{display:block;width:100%;text-align:left;font:inherit;font-weight:600;
  cursor:pointer;color:inherit;background:none;border:0;padding:2px 0 8px}
.decktoggle-caret{display:inline-block;width:1em;color:var(--muted)}
#slideCount{font-weight:400;font-size:12px;color:var(--muted)}
#slideCount::before{content:"("}
#slideCount::after{content:")"}
.deckwrap[data-open="0"] #slidesHost{
  max-height:calc(var(--slide-w,200px) * 0.68 + 62px);
  overflow:hidden;
  -webkit-mask-image:linear-gradient(#000 62%,transparent 100%);
          mask-image:linear-gradient(#000 62%,transparent 100%);
}
.deckwrap[data-open="0"] .decktools{display:none}
.deckwrap[data-open="1"] #slidesHost{max-height:none;overflow:visible;
  -webkit-mask-image:none;mask-image:none}
/* load-more bar under the slide listing */
.loadmore{display:flex;justify-content:center;padding:22px 0 8px}
.loadmore[hidden]{display:none!important}
#slideMore{font:inherit;font-size:13.5px;font-weight:600;cursor:pointer;
  padding:9px 26px;border-radius:999px;color:var(--text);
  background:var(--pill);border:1px solid var(--hairline)}
#slideMore:hover{background:rgba(148,163,184,.22)}

/* Light mode was flat: #ffffff cards on a #fbf9f6 ground have almost no edge.
   Give them a hairline and a soft lift so the grid reads as cards again. */
html[data-theme="light"] .card,
html:not([data-theme="dark"]):not([data-theme="light"]) .card{
  border:1px solid rgba(36,17,25,.09);
  box-shadow:0 1px 2px rgba(36,17,25,.04);
}
html[data-theme="light"] .slb-win,
html:not([data-theme="dark"]):not([data-theme="light"]) .slb-win{
  box-shadow:0 24px 70px rgba(36,17,25,.28);
}
/* filter narrowed to one episode -> a way straight through to it */
.epjump{margin-left:12px;font-size:13px;font-weight:600;color:var(--accent);text-decoration:none}
.epjump:hover{text-decoration:underline}
.epjump[hidden]{display:none!important}

/* ---------- deck tool bar: scope on the left, size on the right ---------- */
.decktools{justify-content:flex-start}
.deckscope{font-size:11px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  padding:3px 10px;border-radius:999px;white-space:nowrap;
  background:var(--pill);color:var(--pill-text);border:1px solid var(--hairline)}
.deckscope-slides{color:var(--accent);border-color:var(--accent)}
.decktools > label{margin-left:auto}          /* size slider hugs the right */
#slideShown{color:var(--muted)}

/* ---------- episode card: badges on the thumbnail, title + date below ------ */
.thumb{position:relative}
/* top-RIGHT, and just the number -- the word lives in the tooltip */
/* The slide count moved off the thumbnail and into the foot row, where it
   sits beside the duration and shares its pill exactly -- same background,
   same border, same weight. Keeping the old accent-blue treatment there
   would have made one of two adjacent numbers look like an alert. */
.card-facts{display:flex;align-items:baseline;gap:6px;flex:0 0 auto}
.card-slides{font-size:11px;font-weight:600;color:var(--pill-text);
  background:var(--pill);border:1px solid var(--hairline);
  padding:1px 8px;border-radius:999px;white-space:nowrap;
  min-width:22px;text-align:center;cursor:default}
.card-slides:empty,.card-slides[data-count="0"]{display:none}
/* date on the left, duration on the right, one line under the title */
.card-foot{display:flex;align-items:baseline;justify-content:space-between;
  gap:10px;margin-top:4px}
.card-date{font-size:12px;color:var(--muted)}
.card-dur{font-size:11px;font-weight:600;color:var(--pill-text);
  background:var(--pill);border:1px solid var(--hairline);
  padding:1px 8px;border-radius:999px;white-space:nowrap}
.card-dur:empty{display:none}

/* the episode listing's tool bar sits where the pager used to */
.menu-wrap .decktools{margin:18px 0 4px}
.menu-count{font-size:13px;color:var(--muted)}
.menu-wrap .loadmore{padding:26px 0 10px}
#menuMore{font:inherit;font-size:13.5px;font-weight:600;cursor:pointer;
  padding:9px 26px;border-radius:999px;color:var(--text);
  background:var(--pill);border:1px solid var(--hairline)}
#menuMore:hover{background:var(--hairline)}
.loadmore[hidden]{display:none!important}

/* ---------- pinned card: first in the grid, highlighted ---------- */
a.card.pinned{position:relative;border:2px solid var(--accent);
  box-shadow:0 0 0 4px color-mix(in srgb, var(--accent) 18%, transparent)}
.pin-tag{position:absolute;top:8px;right:8px;z-index:2;
  font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  padding:2px 8px;border-radius:999px;background:var(--accent);color:#fff}
/* the slider carries no label now, so push it right on its own */
.decktools > input[type=range]{margin-left:auto}

/* Cards built client-side from slides/index.json carry a real <img> so the
   library fallback can catch a 404; the server-rendered ones use a background
   image. Without this the <img> renders at its natural height and the card
   grows into a tall column instead of keeping the 16/9 thumb. */
.thumb img{width:100%;height:100%;object-fit:cover;display:block}


/* ---- episode chapters -------------------------------------------------
   Same collapse mechanics as the slides deck so the two panels behave
   alike. Each row is a real <button> because it performs an action (seek
   the player) rather than navigating -- a link with no href reads as
   broken to a keyboard or a screen reader. */
.deckwrap[data-open="0"] #chaptersHost{display:none}
#chaptersHost{list-style:none;margin:10px 0 0;padding:0}
.chapter{margin:0}
.chapter-hit{display:flex;gap:12px;align-items:baseline;width:100%;text-align:left;
  background:none;border:0;font:inherit;color:inherit;padding:6px 8px;
  border-radius:8px;cursor:pointer}
.chapter-hit:hover{background:var(--pill)}
.chapter-hit:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.chapter.on .chapter-hit{background:var(--pill)}
.chapter-t{color:var(--accent);font-weight:600;font-size:13px;
  font-variant-numeric:tabular-nums;white-space:nowrap;min-width:52px}
.chapter-c{flex:1}
/* no video to seek: still readable, just not clickable */
.chapter > .chapter-t{padding-left:8px}
.chapter > .chapter-c{padding-right:8px}

/* the caption keeps the paragraph breaks the writer produced without
   needing a second, pre-rendered HTML column to hold them */
.epcaption{white-space:pre-line}

/* ---- who was in the room -------------------------------------------------
   Rendered by speakers.js from the data-list attribute. The names come from
   Zoom's per-speaker audio tracks, so this is a measurement of who actually
   spoke, not a guess from the transcript. Each chip links back to the dojo
   listing filtered to that person, which is what makes it a control rather
   than a caption. Built on the same --pill tokens as .badge so it reads as
   part of the page in both themes. */
.speakers{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:14px 0 2px}
.speakers .spk-label{font-size:12px;color:var(--muted);margin-right:2px}
.speakers a.spk{
  font-size:12px;line-height:1;padding:6px 11px;border-radius:999px;
  background:var(--pill);color:var(--pill-text);
  border:1px solid var(--hairline);text-decoration:none;white-space:nowrap;
  transition:background .12s ease,color .12s ease,border-color .12s ease;
}
.speakers a.spk:hover{
  color:var(--accent-hover);border-color:rgba(109,158,235,.45);text-decoration:none;
}
