/* ==========================================================================
   Kimball — first-party theme for kimballtrombone.com
   Basic frame: typography (spec §1), dual-mode palette (spec §2),
   layout (spec §3). See docs/superpowers/specs/2026-07-28-kimball-theme-design.md
   ========================================================================== */

/* -- Fonts ---------------------------------------------------------------- */

@font-face {
  font-family: 'EB Garamond';
  src: url('../fonts/eb-garamond-var.woff2') format('woff2');
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'EB Garamond';
  src: url('../fonts/eb-garamond-italic-var.woff2') format('woff2');
  font-weight: 400 800;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: 'Public Sans';
  src: url('../fonts/public-sans-var.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* -- Palette tokens --------------------------------------------------------
   Mode mechanism: :root defaults to LIGHT (Daylight L3). An explicit
   data-theme attribute (set by the toggle, persisted in localStorage)
   overrides the OS preference in both directions.
   ========================================================================== */

:root {
  /* LIGHT — "Daylight" (L3), cool navy-tinted white */
  --kb-page: #F7F9FB;
  --kb-ink: #1B2634;
  --kb-muted: #5B6774;
  /* Bronze-gold, darkened from #9C7239 to clear WCAG AA (4.5:1) against both
     the page (5.95:1) and the hairline used as a hover fill (4.92:1). Same
     hue and saturation as before, lightness 42% -> 33%. */
  --kb-accent: #7B5A2D;
  --kb-hairline: #E0E4E8;
  --kb-band-bg: #002E5D;
  --kb-band-bg-2: #002E5D;
  --kb-band-text: #F4F2EC;
  --kb-band-accent: #C9A45C;
  --kb-texture-1: rgba(31, 42, 56, 0.022);
  --kb-texture-2: rgba(31, 42, 56, 0.015);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    /* DARK — "Evening Navy" (N3) */
    --kb-page: #0F1D2E;
    --kb-ink: #E8E3D7;
    --kb-muted: #97A0AE;
    --kb-accent: #C9A45C;
    --kb-hairline: #233349;
    --kb-band-bg: #0A1624;
    --kb-band-bg-2: #0A1624;
    --kb-band-text: #E8E3D7;
    --kb-band-accent: #C9A45C;
    --kb-texture-1: rgba(255, 255, 255, 0.018);
    --kb-texture-2: rgba(255, 255, 255, 0.012);
  }
}

[data-theme='dark'] {
  --kb-page: #0F1D2E;
  --kb-ink: #E8E3D7;
  --kb-muted: #97A0AE;
  --kb-accent: #C9A45C;
  --kb-hairline: #233349;
  --kb-band-bg: #0A1624;
  --kb-band-bg-2: #0A1624;
  --kb-band-text: #E8E3D7;
  --kb-band-accent: #C9A45C;
  --kb-texture-1: rgba(255, 255, 255, 0.018);
  --kb-texture-2: rgba(255, 255, 255, 0.012);
}

/* -- Reset / base ----------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }
html {
  font-size: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}
body {
  margin: 0;
  background: var(--kb-page);
  color: var(--kb-ink);
  font-family: 'Public Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 1.125rem;
  font-weight: 410;
  line-height: 1.65;
  letter-spacing: 0.001em;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  position: relative;
  transition: background-color 0.15s ease, color 0.15s ease;
}

/* Linen texture — self-contained crosshatch weave, fixed behind content */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background:
    repeating-linear-gradient(0deg, var(--kb-texture-1) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg, var(--kb-texture-2) 0 1px, transparent 1px 3px);
}

#kb-header, #kb-main, #kb-footer { position: relative; z-index: 1; }
#kb-main { flex: 1 0 auto; }

img { max-width: 100%; height: auto; }

/* -- Containers & reading column --------------------------------------------
   1200px container; the reading column anchors LEFT inside it (measure
   ~66ch / 46.5rem), leaving the right side free for a future rail.
   ========================================================================== */

.kb-container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 2rem;
}

.kb-reading {
  max-width: 46.5rem;
  margin-left: 0;
  margin-right: auto;
}

@media (max-width: 640px) {
  .kb-container { padding: 0 1.25rem; }
}

/* -- Type scale (spec §1) ---------------------------------------------------- */

h1, h2, h3 {
  font-family: 'EB Garamond', Garamond, Georgia, serif;
  color: var(--kb-ink);
}

h1 {
  font-size: 3.45rem;
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -0.005em;
  margin: 0 0 0.9rem;
}
@media (max-width: 600px) {
  h1 { font-size: calc(3.45rem * 0.78); }
}

h2 {
  font-size: 1.8rem;
  font-weight: 450;
  line-height: 1.25;
  margin: 3rem 0 1rem;
}

h3 {
  font-size: 1.375rem;
  font-weight: 490;
  line-height: 1.3;
  margin: 2.2rem 0 0.7rem;
}

p { margin: 0 0 1.25rem; }

.kb-eyebrow {
  font-family: 'Public Sans', sans-serif;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--kb-accent);
  margin-bottom: 1.6rem;
}

/* Meta line — "August 7, 2009 · Alto Trombone · Trombone History" */
.kb-meta {
  font-size: 0.85rem;
  color: var(--kb-muted);
  margin: 0 0 2.6rem;
}
.kb-meta a { color: var(--kb-muted); }

/* Captions */
.kb-caption, figcaption {
  font-size: 0.83rem;
  color: var(--kb-muted);
  margin-top: 0.4rem;
}

/* Timeline entry header — "1620—Bologna, Italy" (repeats ~4,000x) */
.kb-timeline-heading, .timeline-heading {
  font-family: 'EB Garamond', Garamond, Georgia, serif;
  font-size: 1.5rem;
  font-weight: 490;
  line-height: 1.3;
  margin: 0 0 0.6rem;
}

/* -- Links -------------------------------------------------------------- */

a {
  color: var(--kb-accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
a:hover { text-decoration-thickness: 2px; }

/* Visible keyboard focus everywhere */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
  outline: 2px solid var(--kb-accent);
  outline-offset: 2px;
}

/* -- Blockquote ----------------------------------------------------------- */

blockquote {
  font-family: 'EB Garamond', Garamond, Georgia, serif;
  font-style: italic;
  font-weight: 400;
  font-size: 1.22em;
  line-height: 1.5;
  border-left: 2px solid var(--kb-accent);
  padding: 0.3rem 0 0.3rem 1.4rem;
  margin: 2rem 0;
  color: var(--kb-ink);
}
blockquote p:last-child { margin-bottom: 0; }
blockquote cite { display: block; font-family: 'Public Sans', sans-serif; font-style: normal; font-size: 0.8rem; color: var(--kb-muted); margin-top: 0.7rem; }

/* -- Figures / images ------------------------------------------------------ */

figure { margin: 2rem 0; }
figure img { display: block; }

/* -- Tables ----------------------------------------------------------------- */

table {
  width: 100%;
  border-collapse: collapse;
  margin: 1.5rem 0;
  font-size: 0.95rem;
}
th, td {
  text-align: left;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--kb-hairline);
}
th { font-weight: 600; }

/* -- Rule ------------------------------------------------------------------- */

hr {
  border: 0;
  border-top: 1px solid var(--kb-hairline);
  margin: 2.5rem 0;
}

/* -- Code --------------------------------------------------------------------- */

code, pre {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.92em;
}
code {
  background: var(--kb-hairline);
  padding: 0.1em 0.35em;
  border-radius: 3px;
}
pre {
  background: var(--kb-hairline);
  padding: 1rem 1.25rem;
  border-radius: 4px;
  overflow-x: auto;
}
pre code { background: none; padding: 0; }

/* -- Lists -------------------------------------------------------------------- */

ul, ol { padding-left: 1.4rem; margin: 0 0 1.25rem; }
li { margin-bottom: 0.35rem; }

/* ==========================================================================
   Header band (navy both modes, spec §2 band tokens)
   ========================================================================== */

#kb-header {
  /* The band's vertical padding is a token because the nav panels key off it:
     nav items stretch to the content box, so the drop from an item's bottom
     edge to the band's bottom edge is exactly this value. */
  --kb-band-pad-y: 1.1rem;
  background: var(--kb-band-bg);
  color: var(--kb-band-text);
}

.kb-header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: nowrap;
  gap: 0.75rem 1.5rem;
  padding: var(--kb-band-pad-y) 2rem;
}

.kb-logo {
  font-family: 'EB Garamond', Garamond, Georgia, serif;
  font-size: 1.3rem;
  font-weight: 490;
  letter-spacing: 0.01em;
  color: var(--kb-band-text);
  text-decoration: none;
  white-space: nowrap;
}
.kb-logo:hover { color: var(--kb-band-accent); }

/* Screen-reader-only label (nav toggles carry their name this way) */
.kb-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The band must out-stack #kb-main (z-index: 1) or its panels paint behind
   the page content. */
#kb-header { z-index: 10; }

/* -- Primary nav: band row -------------------------------------------------
   One rendering serves both breakpoints. Desktop: a row in the band with
   floating submenus. Below 1024px: .kb-nav-row becomes the hamburger panel
   and the submenus become accordions (see the mobile block further down).
   ========================================================================== */

.kb-nav-row {
  display: flex;
  align-items: center;
  flex: 1 1 auto;
  min-width: 0;
  justify-content: center;
}

.kb-nav {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 0 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.kb-nav-item {
  position: relative;
  display: flex;
  align-items: center;
  margin: 0;
}
/* Megamenus anchor to the band itself, not to their item, so the panel can
   span the full width. Must follow .kb-nav-item to win. */
.kb-nav-item--mega { position: static; }

.kb-nav-link {
  display: inline-block;
  padding: 0.4rem 0.1rem;
  color: var(--kb-band-text);
  text-decoration: none;
  font-family: 'Public Sans', sans-serif;
  font-size: 0.92rem;
  font-weight: 500;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
}
.kb-nav-link:hover,
.kb-nav-link:focus-visible,
.kb-nav-link.is-active {
  color: var(--kb-band-accent);
  border-bottom-color: var(--kb-band-accent);
}

/* Chevron disclosure — keyboard and touch route through this button so the
   parent link keeps navigating. Both this and the mobile panel are gated on
   .kb-nav-ready, which kimball-nav.js sets on itself: if that script never
   runs, the menu degrades to a plain expanded list rather than stranding the
   mobile nav behind a hamburger that cannot open. */
.kb-nav-toggle { display: none; }
.kb-nav-ready .kb-nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  margin-left: 0.1rem;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--kb-band-text);
  line-height: 1;
  cursor: pointer;
}
.kb-nav-ready .kb-nav-toggle:hover { color: var(--kb-band-accent); }
.kb-chevron { transition: transform 0.16s ease; }
.kb-nav-item.is-open > .kb-nav-toggle .kb-chevron { transform: rotate(180deg); }

.kb-nav-link:focus-visible,
.kb-nav-toggle:focus-visible,
.kb-burger:focus-visible,
.kb-mode-toggle:focus-visible,
.kb-search-link:focus-visible {
  outline: 2px solid var(--kb-band-accent);
  outline-offset: 2px;
}

/* -- Submenu shell (shared by dropdown + megamenu) --------------------------
   visibility rather than display: it animates, and it still removes the
   links from the tab order while closed.
   ========================================================================== */

.kb-submenu {
  position: absolute;
  z-index: 30;
  visibility: hidden;
  opacity: 0;
  transform: translateY(-0.35rem);
  transition: opacity 0.16s ease, transform 0.16s ease, visibility 0s linear 0.16s;
  background: var(--kb-page);
  color: var(--kb-ink);
  border: 1px solid var(--kb-hairline);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28);
}

.kb-nav-item.is-open > .kb-submenu {
  visibility: visible;
  opacity: 1;
  transform: none;
  transition-delay: 0s;
}

.kb-dropdown {
  top: 100%;
  left: -0.9rem;
  min-width: 15rem;
  margin: 0;
  padding: 0.45rem 0;
  list-style: none;
  border-radius: 4px;
}
.kb-dropdown li { margin: 0; }
.kb-dropdown a {
  display: block;
  padding: 0.5rem 1.1rem;
  color: var(--kb-ink);
  text-decoration: none;
  font-size: 0.89rem;
  white-space: nowrap;
}

.kb-megamenu {
  top: 100%;
  left: 0;
  right: 0;
  border-left: 0;
  border-right: 0;
  padding: 2rem 0 2.25rem;
}
.kb-megamenu-inner {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1.5rem 2.5rem;
}
.kb-mega-head {
  display: block;
  font-family: 'Public Sans', sans-serif;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--kb-accent);
  text-decoration: none;
  margin: 0 0 0.65rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--kb-hairline);
}
a.kb-mega-head:hover { color: var(--kb-ink); }
.kb-mega-links { list-style: none; margin: 0; padding: 0; }
.kb-mega-links li { margin: 0; }
.kb-mega-links a {
  display: block;
  padding: 0.3rem 0;
  color: var(--kb-ink);
  text-decoration: none;
  font-size: 0.88rem;
  line-height: 1.35;
}

.kb-dropdown a:hover,
.kb-mega-links a:hover { background: var(--kb-hairline); color: var(--kb-accent); }
.kb-mega-links a:hover { background: none; }
.kb-dropdown a:focus-visible,
.kb-mega-links a:focus-visible,
.kb-mega-head:focus-visible {
  outline: 2px solid var(--kb-accent);
  outline-offset: 2px;
}
.kb-dropdown a[aria-current='page'],
.kb-mega-links a[aria-current='page'],
.kb-mega-head[aria-current='page'] { color: var(--kb-accent); font-weight: 600; }

/* -- Band actions ---------------------------------------------------------- */

.kb-header-actions {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.kb-search-link {
  color: var(--kb-band-text);
  text-decoration: none;
  font-size: 0.9rem;
  white-space: nowrap;
}
.kb-search-link:hover,
.kb-search-link:focus-visible { color: var(--kb-band-accent); }

.kb-mode-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  padding: 0;
  border: 1px solid rgba(244, 242, 236, 0.3);
  border-radius: 50%;
  background: transparent;
  color: var(--kb-band-text);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  flex: none;
}
.kb-mode-toggle:hover { border-color: var(--kb-band-accent); color: var(--kb-band-accent); }

/* Hamburger — JS-only, mobile-only */
.kb-burger { display: none; }
.kb-burger-bars,
.kb-burger-bars::before,
.kb-burger-bars::after {
  display: block;
  width: 20px;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
}
.kb-burger-bars { position: relative; }
.kb-burger-bars::before { content: ''; position: absolute; left: 0; top: -6px; transition: top 0.15s ease, transform 0.15s ease; }
.kb-burger-bars::after { content: ''; position: absolute; left: 0; top: 6px; transition: top 0.15s ease, transform 0.15s ease; }
#kb-header.kb-menu-open .kb-burger-bars { background: transparent; }
#kb-header.kb-menu-open .kb-burger-bars::before { top: 0; transform: rotate(45deg); }
#kb-header.kb-menu-open .kb-burger-bars::after { top: 0; transform: rotate(-45deg); }

.kb-panel-actions { display: none; }

/* -- Desktop ---------------------------------------------------------------- */

@media (min-width: 1024px) {
  /* Stretch the items to the band's content box so the gap below them is
     exactly --kb-band-pad-y. Panels can then sit flush under the band
     instead of cutting up into it. */
  .kb-nav-row { align-self: stretch; align-items: stretch; }
  .kb-nav-row > nav { display: flex; align-items: stretch; }
  .kb-nav { align-items: stretch; }

  /* Megamenus already anchor to the band; dropdowns anchor to their item, so
     they need the band's padding added to clear it. */
  .kb-dropdown { top: calc(100% + var(--kb-band-pad-y)); }

  /* Bridges the padding gap between item and panel so the pointer can travel
     down without :hover lapsing mid-transit. Sized to the gap exactly — any
     taller and the full-width megamenu strip would swallow hover on the
     neighbouring items. Inert while the panel is hidden. */
  .kb-submenu::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 100%;
    height: var(--kb-band-pad-y);
  }

  /* Hover opening lives in CSS so it survives a JS failure; the script only
     mirrors it into aria-expanded. */
  .kb-nav-item:hover > .kb-submenu {
    visibility: visible;
    opacity: 1;
    transform: none;
    transition-delay: 0s;
  }

  /* Set by the script when the chevron is clicked shut while the pointer is
     still on the item: without it the hover rule holds the panel open and the
     button looks broken. Out-specifies the rule above, yields to .is-open so
     keyboard opening still works, and is dropped on mouseleave. */
  .kb-nav-item.kb-hover-off:not(.is-open):hover > .kb-submenu {
    visibility: hidden;
    opacity: 0;
    transform: translateY(-0.35rem);
    transition: opacity 0.16s ease, transform 0.16s ease, visibility 0s linear 0.16s;
  }
}

/* Between 1024px and 1200px the seven labels plus logo and actions only fit
   on one row if everything tightens up. */
@media (min-width: 1024px) and (max-width: 1200px) {
  .kb-header-inner { padding-left: 1.25rem; padding-right: 1.25rem; gap: 0.5rem 1rem; }
  .kb-nav { gap: 0 0.85rem; }
  .kb-nav-link { font-size: 0.84rem; }
  .kb-logo { font-size: 1.15rem; }
  .kb-search-link { font-size: 0.84rem; }
  .kb-nav-ready .kb-nav-toggle { width: 1.25rem; }
}

/* -- Mobile panel (below 1024px) --------------------------------------------
   Without scripting the menu simply renders stacked and fully expanded;
   .kb-nav-ready switches it to the absolutely-positioned hamburger panel so the
   band itself never shifts.
   ========================================================================== */

@media (max-width: 1023.98px) {
  .kb-header-inner { flex-wrap: wrap; }
  .kb-nav-row { display: block; flex: 1 0 100%; }

  .kb-nav-ready .kb-burger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--kb-band-text);
    cursor: pointer;
    flex: none;
  }
  .kb-nav-ready .kb-burger:hover { color: var(--kb-band-accent); }

  .kb-nav-ready .kb-nav-row {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 40;
    max-height: 78vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 0.25rem 0 1.25rem;
    background: var(--kb-band-bg);
    border-top: 1px solid rgba(244, 242, 236, 0.14);
    box-shadow: 0 14px 34px rgba(0, 0, 0, 0.4);
    visibility: hidden;
    opacity: 0;
    transform: translateY(-0.5rem);
    transition: opacity 0.18s ease, transform 0.18s ease, visibility 0s linear 0.18s;
  }
  .kb-nav-ready #kb-header.kb-menu-open .kb-nav-row {
    visibility: visible;
    opacity: 1;
    transform: none;
    transition-delay: 0s;
  }

  .kb-nav { display: block; }
  .kb-nav-item {
    position: static;
    flex-wrap: wrap;
    border-bottom: 1px solid rgba(244, 242, 236, 0.1);
  }
  .kb-nav-link {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 0.6rem 1.5rem;
    font-size: 0.98rem;
    white-space: normal;
    border-bottom: 0;
  }
  .kb-nav-link.is-active {
    border-bottom: 0;
    box-shadow: inset 3px 0 0 var(--kb-band-accent);
  }
  .kb-nav-ready .kb-nav-toggle { width: 44px; height: 44px; margin: 0 0.75rem 0 0; }

  /* Accordions: instant, and expanded by default when there is no scripting. */
  .kb-submenu {
    position: static;
    display: block;
    width: 100%;
    flex-basis: 100%;
    visibility: visible;
    opacity: 1;
    transform: none;
    transition: none;
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    min-width: 0;
    padding: 0 0 0.5rem;
  }
  .kb-nav-ready .kb-submenu { display: none; }
  .kb-nav-ready .kb-nav-item.is-open > .kb-submenu { display: block; }

  .kb-megamenu { padding: 0 0 0.5rem; }
  .kb-megamenu-inner { display: block; max-width: none; padding: 0; }
  .kb-mega-col { margin: 0 0 0.35rem; }
  .kb-mega-head {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 0.7rem 1.5rem 0.3rem 2.5rem;
    margin: 0;
    border-bottom: 0;
    color: var(--kb-band-accent);
  }
  a.kb-mega-head:hover { color: var(--kb-band-text); }

  .kb-dropdown a,
  .kb-mega-links a {
    display: flex;
    align-items: center;
    min-height: 40px;
    padding: 0.45rem 1.5rem 0.45rem 2.5rem;
    color: var(--kb-band-text);
    font-size: 0.92rem;
    white-space: normal;
  }
  .kb-dropdown a:hover,
  .kb-mega-links a:hover {
    color: var(--kb-band-accent);
    background: rgba(244, 242, 236, 0.06);
  }
  .kb-dropdown a:focus-visible,
  .kb-mega-links a:focus-visible,
  .kb-mega-head:focus-visible {
    outline: 2px solid var(--kb-band-accent);
    outline-offset: -2px;
  }
  .kb-dropdown a[aria-current='page'],
  .kb-mega-links a[aria-current='page'],
  .kb-mega-head[aria-current='page'] { color: var(--kb-band-accent); }

  /* The band drops its search link and mode toggle at these widths, so the
     panel carries them instead. */
  .kb-header-actions .kb-search-link,
  .kb-header-actions .kb-mode-toggle { display: none; }
  .kb-panel-actions {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    padding: 1.1rem 1.5rem 0.25rem;
  }
  .kb-panel-actions .kb-search-link {
    display: flex;
    align-items: center;
    min-height: 40px;
    font-size: 0.98rem;
  }
  .kb-panel-actions .kb-mode-toggle { width: 2.75rem; height: 2.75rem; }
}

@media (prefers-reduced-motion: reduce) {
  .kb-submenu,
  .kb-nav-ready .kb-nav-row,
  .kb-chevron,
  .kb-burger-bars::before,
  .kb-burger-bars::after { transition: none !important; }
}

.kb-icon-moon { display: none; }
[data-theme='dark'] .kb-icon-sun { display: none; }
[data-theme='dark'] .kb-icon-moon { display: inline; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .kb-icon-sun { display: none; }
  :root:not([data-theme='light']) .kb-icon-moon { display: inline; }
}

/* ==========================================================================
   Main content
   ========================================================================== */

#kb-main { padding: 3rem 0 5rem; }

.kb-page-title { margin-bottom: 0.5rem; }

/* ==========================================================================
   Blog listing
   ========================================================================== */

.kb-blog-list { list-style: none; margin: 0; padding: 0; }
.kb-blog-item {
  padding: 1.75rem 0;
  border-bottom: 1px solid var(--kb-hairline);
}
.kb-blog-item:first-child { padding-top: 0; }
.kb-blog-item h2 {
  margin: 0 0 0.4rem;
  font-size: 1.8rem;
}
.kb-blog-item h2 a { color: var(--kb-ink); text-decoration: none; }
.kb-blog-item h2 a:hover { color: var(--kb-accent); }
.kb-blog-item .kb-meta { margin-bottom: 0.75rem; }
.kb-blog-item .kb-summary { color: var(--kb-ink); }
.kb-blog-item .kb-summary p:last-child { margin-bottom: 0; }

/* Pagination — superseded by the WS-pagination cleanup block appended at the
   end of this file (2026-08-28); this original block was removed at
   integration because its `.pagination li a` selector (0,1,2 specificity)
   would otherwise beat the new block's `.pagination a` (0,1,1) regardless of
   source order, silently clobbering the chevron restyle for anchors. */

/* ==========================================================================
   Timeline period / entry
   ========================================================================== */

.kb-timeline-jump {
  border-top: 1px solid var(--kb-hairline);
  border-bottom: 1px solid var(--kb-hairline);
  padding: 0.75rem 0;
  margin: 1.5rem 0 2.5rem;
  font-size: 0.9rem;
}
.kb-timeline-jump strong { margin-right: 0.75rem; font-weight: 600; }
.kb-timeline-jump a { margin-right: 1.1rem; color: var(--kb-ink); }
.kb-timeline-jump a:hover { color: var(--kb-accent); }

.timeline-entry { margin-bottom: 2rem; }
.timeline-entry.decade-start {
  border-top: 1px solid var(--kb-hairline);
  padding-top: 1.75rem;
}
.timeline-heading a { color: inherit; text-decoration: none; }
.timeline-heading a:hover { color: var(--kb-accent); text-decoration: underline; }
.timeline-entry img { max-width: 100%; height: auto; }

/* ==========================================================================
   Comments (form + list) — inherit reading styles, simple hairline inputs
   ========================================================================== */

.kb-comments { margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--kb-hairline); }
.kb-comments h3 { margin-top: 2rem; }
.kb-comments h3:first-child { margin-top: 0; }

.kb-comments form { margin: 0 0 1.5rem; }
.kb-comments form > div { margin-bottom: 1rem; }
.kb-comments label {
  display: block;
  font-size: 0.85rem;
  font-weight: 600;
  margin-bottom: 0.3rem;
  color: var(--kb-ink);
}
.kb-comments input[type="text"],
.kb-comments input[type="email"],
.kb-comments textarea {
  width: 100%;
  max-width: 100%;
  font-family: inherit;
  font-size: 1rem;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--kb-hairline);
  border-radius: 3px;
  background: var(--kb-page);
  color: var(--kb-ink);
}
.kb-comments textarea { min-height: 8rem; resize: vertical; }
.kb-comments input:focus-visible,
.kb-comments textarea:focus-visible {
  outline: 2px solid var(--kb-accent);
  outline-offset: 1px;
  border-color: var(--kb-accent);
}
.kb-comments .honeypot-field { position: absolute; left: -9999px; }
.kb-comments button[type="submit"],
.kb-comments .button {
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  padding: 0.6rem 1.4rem;
  border: 1px solid var(--kb-accent);
  border-radius: 3px;
  background: transparent;
  color: var(--kb-accent);
  cursor: pointer;
}
.kb-comments button[type="submit"]:hover { background: var(--kb-accent); color: var(--kb-page); }
.kb-comments .toast {
  padding: 0.75rem 1rem;
  border: 1px solid var(--kb-hairline);
  border-radius: 3px;
  margin-bottom: 1.5rem;
  font-size: 0.9rem;
}

.kb-comments table { font-size: 0.95rem; }
.kb-comments table td { border-bottom: 1px solid var(--kb-hairline); padding: 1rem 0; }

/* ==========================================================================
   Search results (simplesearch)
   ========================================================================== */

.simplesearch .search-header { margin-bottom: 1rem; }
.simplesearch form { margin-bottom: 2rem; }
.simplesearch input[type="text"],
.simplesearch input[type="search"] {
  font-family: inherit;
  font-size: 1rem;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--kb-hairline);
  border-radius: 3px;
  background: var(--kb-page);
  color: var(--kb-ink);
}
.simplesearch input:focus-visible { outline: 2px solid var(--kb-accent); outline-offset: 1px; }

/* ==========================================================================
   Footer
   ========================================================================== */

#kb-footer {
  border-top: 1px solid var(--kb-hairline);
  padding: 1.5rem 0;
  font-size: 0.85rem;
  color: var(--kb-muted);
}
.kb-footer-inner {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  justify-content: space-between;
}
#kb-footer a { color: var(--kb-muted); }
#kb-footer a:hover { color: var(--kb-accent); }

/* ==========================================================================
   Error page
   ========================================================================== */

.kb-error { padding: 2rem 0; }


/* == 2026-08-28 cleanup: imageflow == */
/* == WS-imageflow staging file — merged into kimball.css at integration == */

/* ============================================================
   WS-2: Image/text flow (issue #1) + whitespace rhythm (issue #14)
   Governed by docs/superpowers/specs/2026-08-28-image-treatment-decision.md:
   - Article/prose pages: portrait-ish images float with text wrap and get a
     border; separated image/text gets real vertical spacing.
   - Timeline contexts (article.timeline-period incl. its section.timeline-entry
     blocks, and article.timeline-entry-single): NO wrap, images centered.
   Colors come only from --kb-* tokens, so both modes work.
   ============================================================ */

/* ---- Article/prose contexts ----
   default.html.twig and item.html.twig render a bare <article>; the timeline
   templates carry .timeline-period / .timeline-entry-single classes, so
   :not() on those two excludes every timeline context. */

/* Any image that leads its paragraph becomes block-level: text that follows
   in the same paragraph (the hard-return case) breaks below it with real
   vertical spacing instead of hanging off the image's baseline. */
article:not(.timeline-period):not(.timeline-entry-single) p > img:first-child,
article:not(.timeline-period):not(.timeline-entry-single) p > a:first-child > img {
  display: block;
  margin: 0.35rem 0 1rem;
}

/* Portrait-ish / small images: WordPress thumbnail filenames bound to a
   300px or 150px box (-WxH suffix), i.e. at most ~300px wide — far narrower
   than the 66ch reading column. These get the framed border in both modes. */
article:not(.timeline-period):not(.timeline-entry-single) p > img:first-child[src*="x300."],
article:not(.timeline-period):not(.timeline-entry-single) p > img:first-child[src*="-300x"],
article:not(.timeline-period):not(.timeline-entry-single) p > img:first-child[src*="-150x150."],
article:not(.timeline-period):not(.timeline-entry-single) p > a:first-child > img[src*="x300."],
article:not(.timeline-period):not(.timeline-entry-single) p > a:first-child > img[src*="-300x"],
article:not(.timeline-period):not(.timeline-entry-single) p > a:first-child > img[src*="-150x150."] {
  border: 1px solid var(--kb-hairline);
  padding: 0.3rem;
  background: var(--kb-page);
  border-radius: 2px;
  margin-left: auto;
  margin-right: auto;
}

/* ...and from tablet width up they float left so the text wraps around them,
   per the decision doc. clear:left keeps successive floats from stacking
   beside each other; generous right margin keeps wrapped text readable. */
@media (min-width: 768px) {
  article:not(.timeline-period):not(.timeline-entry-single) p > img:first-child[src*="x300."],
  article:not(.timeline-period):not(.timeline-entry-single) p > img:first-child[src*="-300x"],
  article:not(.timeline-period):not(.timeline-entry-single) p > img:first-child[src*="-150x150."],
  article:not(.timeline-period):not(.timeline-entry-single) p > a:first-child > img[src*="x300."],
  article:not(.timeline-period):not(.timeline-entry-single) p > a:first-child > img[src*="-300x"],
  article:not(.timeline-period):not(.timeline-entry-single) p > a:first-child > img[src*="-150x150."] {
    float: left;
    clear: left;
    max-width: min(46%, 320px);
    margin: 0.35rem 1.75rem 0.9rem 0;
  }
}

/* Floats never bleed across major section boundaries: h1/h2, rules, figures
   and the article's own end clear. h3/h4 deliberately do NOT clear — subsection
   headings (e.g. the timeline hub's "Why?"/"Terminology" labels) are meant to
   sit beside their floated thumbnail, per the decision doc's wrap treatment.
   (No floats exist in timeline contexts, so scoping to all articles is safe.) */
article h1, article h2, article hr, article figure {
  clear: both;
}
article::after {
  content: "";
  display: block;
  clear: both;
}

/* ---- Timeline contexts: centered block images, no wrap, text below ---- */
article.timeline-period img,
article.timeline-entry-single img,
.timeline-entry img {
  float: none;
  display: block;
  margin: 1.4rem auto;
  max-width: 100%;
}

/* ---- Whitespace rhythm pass (punch-list issue 14) ----
   Academic prose needs a little more air than the current rhythm
   (p 1.25rem, figure 2rem, h2 3rem/1rem, h3 2.2rem/0.7rem in kimball.css)
   without drifting into an airy marketing layout. */
.kb-reading p { margin-bottom: 1.45rem; }
.kb-reading ul, .kb-reading ol { margin-bottom: 1.45rem; }
.kb-reading h2 { margin: 3.25rem 0 1.1rem; }
.kb-reading h3 { margin: 2.5rem 0 0.85rem; }
.kb-reading figure { margin: 2.4rem 0; }
.kb-reading hr { margin: 3rem 0; }
.timeline-entry { margin-bottom: 2.5rem; }

/* == end WS-imageflow == */

/* == end imageflow == */


/* == 2026-08-28 cleanup: recordings == */
/* == WS-1: YouTube/Vimeo embed normalization (site-wide base) == */
/* .kb-embed wraps every converted <iframe> (youtube-nocookie.com / player.vimeo.com)
   left behind by normalize-youtube-embeds.py. Responsive 16:9, capped to the
   66ch reading column, consistent vertical rhythm matching figure { margin: 2rem 0 }.
   WS-3 (Solo & Chamber Recordings redesign) extends this file below this block
   with .kb-recital / .kb-video-grid — do not remove or restructure this block. */

.kb-embed {
  position: relative;
  width: 100%;
  max-width: 46.5rem; /* matches .kb-reading's 66ch measure */
  aspect-ratio: 16 / 9;
  margin: 2rem 0;
  background: var(--kb-hairline);
  overflow: hidden;
}

.kb-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
/* == /WS-1 == */

/* == WS-3: Solo & Chamber Recordings (sound-clips) redesign == */
/* Markup emitted by ops/cleanup-2026-08/restructure-sound-clips.py:
   <section class="kb-recital"> > h2.kb-recital-title (+ .kb-recital-type tag)
   > div.kb-recital-piece > h3.kb-recital-piecetitle + div.kb-video-grid
   > .kb-embed cells (WS-1 base above). Tokens only (--kb-*), so light and
   dark modes both work; EB Garamond headings inherit from the theme scale. */

/* This page is heading + video only (no prose), so it may leave the 66ch
   reading measure and let the grid use the full 1200px container. Browsers
   without :has() simply keep the 46.5rem column and a 2-across grid. */
.kb-reading:has(.kb-recital) {
  max-width: none;
}

/* Album / ensemble section heading: hairline rule above each section after
   the first, italic serif title, with the ensemble type as a small
   Public Sans tag in the accent color. */
.kb-recital-title {
  display: flex;
  align-items: baseline;
  column-gap: 0.9rem;
  flex-wrap: wrap;
  border-top: 1px solid var(--kb-hairline);
  padding-top: 2.1rem;
  margin: 3.2rem 0 0.5rem;
}
.kb-recital-title em { font-style: italic; }
.kb-recital:first-of-type .kb-recital-title {
  border-top: 0;
  padding-top: 0;
  margin-top: 1.2rem;
}

.kb-recital-type {
  font-family: 'Public Sans', sans-serif;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--kb-accent);
  white-space: nowrap;
}

/* Piece line: bibliographic caption-level heading, kept at the reading
   measure so long composer/arranger lines stay comfortable to read. */
.kb-recital-piecetitle {
  font-size: 1.15rem;
  font-weight: 500;
  line-height: 1.4;
  max-width: 46.5rem;
  margin: 2.1rem 0 0.9rem;
}

.kb-recital-piece { margin: 0 0 2.4rem; }

/* Responsive video grid: 3 across in the full container, 2 on tablets,
   1 on phones. auto-fill (not auto-fit) so a single video keeps card
   width instead of stretching across the page. */
.kb-video-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
  gap: 1.25rem;
  margin: 0;
}

.kb-video-grid .kb-embed {
  margin: 0;
  max-width: none;
  border: 1px solid var(--kb-hairline);
}
/* == /WS-3 == */

/* == end recordings == */


/* == 2026-08-28 cleanup: timelinenav == */
/* == WS-timelinenav staging file — merged into kimball.css at integration ==
   WS-3 (2026-08-28): sticky full-width timeline jump bar + generic kb-rail
   dot navigator. Tokens only (--kb-*), so both color modes work untouched.
   Collision notes, verified against kimball.css: #kb-header is NOT sticky
   (it scrolls away; the only fixed element is the body texture overlay), so
   the jump bar sticks at top: 0 with no header overlap. #kb-main sits at
   z-index: 1 under #kb-header (10) / nav panels (30), so z-index: 5 here is
   scoped inside main and the mega-menu still paints over the stuck bar. */

/* -- Sticky jump bar (issue #6) ------------------------------------------
   The template moved .kb-timeline-jump out of .kb-reading, so it now spans
   the full .kb-container content width; this class makes it stick. */
.kb-jump-sticky {
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--kb-page);
}

/* Anchor jumps land under the stuck bar without this. Covers a two-line
   bar; harmless extra breathing room for a one-line bar. */
.timeline-entry.decade-start {
  scroll-margin-top: 5.5rem;
}

/* -- Two-column layout: reading column + right rail ---------------------- */
.kb-rail-layout {
  display: grid;
  grid-template-columns: minmax(0, 46.5rem) minmax(0, 1fr);
  column-gap: 3rem;
}
.kb-rail-layout > .kb-reading {
  min-width: 0;
  /* the grid track is the measure now; keep the inner cap from double-capping */
  max-width: none;
}

/* -- kb-rail: generic sticky dot navigator (issue #7; reusable — the
      bibliography alphabet rail reuses these classes) -------------------- */
.kb-rail {
  position: sticky;
  top: 7rem;                 /* clear of the stuck jump bar */
  align-self: start;
  justify-self: end;
  max-height: calc(100vh - 9rem);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding: 0.3rem 0;
  scrollbar-width: thin;
}

.kb-rail-dot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.6rem;
  min-height: 1.35rem;
  padding: 0.1rem 0;
  text-decoration: none;
  border-bottom: none;
  color: var(--kb-muted);
  font-family: 'Public Sans', 'Helvetica Neue', Arial, sans-serif;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  line-height: 1;
  white-space: nowrap;
}

.kb-rail-marker {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  border: 1px solid var(--kb-muted);
  background: transparent;
  transition: background-color 160ms ease, border-color 160ms ease,
    transform 160ms ease;
}

.kb-rail-label {
  opacity: 0;
  transition: opacity 160ms ease;
}

.kb-rail-dot:hover,
.kb-rail-dot:focus-visible {
  color: var(--kb-accent);
}
.kb-rail-dot:hover .kb-rail-marker,
.kb-rail-dot:focus-visible .kb-rail-marker {
  border-color: var(--kb-accent);
}
.kb-rail-dot:hover .kb-rail-label,
.kb-rail-dot:focus-visible .kb-rail-label,
.kb-rail-dot.is-current .kb-rail-label {
  opacity: 1;
}

.kb-rail-dot.is-current {
  color: var(--kb-accent);
}
.kb-rail-dot.is-current .kb-rail-marker {
  background: var(--kb-accent);
  border-color: var(--kb-accent);
  transform: scale(1.25);
}

@media (prefers-reduced-motion: reduce) {
  .kb-rail-marker,
  .kb-rail-label {
    transition: none;
  }
}

/* Narrow viewports: the right column disappears, so the rail hides and the
   layout collapses back to the plain reading column. 1024px matches the
   nav's own desktop/mobile breakpoint. */
@media (max-width: 1023px) {
  .kb-rail { display: none; }
  .kb-rail-layout { display: block; }
  .kb-rail-layout > .kb-reading { max-width: 46.5rem; }
}

/* == end timelinenav == */


/* == 2026-08-28 cleanup: pagination == */
/* == WS-pagination staging file — merged into kimball.css at integration ==
   Restyles .pagination (rendered by themes/kimball/templates/partials/
   pagination.html.twig) now that plugins/pagination's built_in_css is off
   and delta is condensed (see user/config/plugins/pagination.yaml).
   Markup shape produced by the twig partial:
     <ul class="pagination">
       <li><a rel="prev">&laquo; Prev</a></li>        -- or --  <li class="disabled"><span>&laquo; Prev</span></li>
       <li class="active"><span>N</span></li>
       <li><a>N</a></li>
       <li class="disabled"><span>&hellip;</span></li>  -- delta-border ellipsis
       <li><a rel="next">Next &raquo;</a></li>          -- or --  <li class="disabled"><span>Next &raquo;</span></li>
     </ul>
   Prev/Next are always the first/last <li> regardless of state, so they are
   targeted structurally rather than by a class the twig markup doesn't add. */

.pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  list-style: none;
  margin: 2.75rem 0 1rem;
  padding: 0;
  font-family: 'Public Sans', sans-serif;
  font-size: 0.95rem;
}

.pagination li { display: flex; }

.pagination a,
.pagination > li > span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.25rem;
  height: 2.25rem;
  padding: 0 0.6rem;
  border: 0;
  border-radius: 4px;
  color: var(--kb-ink);
  text-decoration: none;
  line-height: 1;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.pagination a:hover,
.pagination a:focus-visible {
  background: var(--kb-hairline);
  color: var(--kb-accent);
  text-decoration: none;
}

/* Current page */
.pagination li.active > span {
  background: var(--kb-accent);
  color: var(--kb-page);
  font-weight: 600;
}

/* Ellipsis (delta border) and any other disabled, non-edge slot */
.pagination li.disabled > span {
  color: var(--kb-muted);
  background: transparent;
  cursor: default;
}

/* -- Prev / Next: chevrons, not text ------------------------------------- */
/* Structural targeting (first/last <li>) so it applies whether the slot is
   an active <a> or a .disabled <span>. Visually hides the "« Prev"/"Next »"
   text via font-size:0 (the text remains in the DOM and in the accessible
   name — only display glyphs are suppressed, unlike display:none) and draws
   a clean CSS chevron in its place. */
.pagination > li:first-child a,
.pagination > li:first-child > span,
.pagination > li:last-child a,
.pagination > li:last-child > span {
  position: relative;
  font-size: 0;
  min-width: 2.25rem;
}

.pagination > li:first-child a::before,
.pagination > li:first-child > span::before,
.pagination > li:last-child a::before,
.pagination > li:last-child > span::before {
  content: '';
  display: block;
  width: 0.5rem;
  height: 0.5rem;
  border: solid currentColor;
  border-width: 0 2px 2px 0;
}

/* Prev: point left */
.pagination > li:first-child a::before,
.pagination > li:first-child > span::before {
  transform: rotate(135deg);
  margin-left: 0.15rem;
}

/* Next: point right */
.pagination > li:last-child a::before,
.pagination > li:last-child > span::before {
  transform: rotate(-45deg);
  margin-right: 0.15rem;
}

.pagination > li:first-child a,
.pagination > li:last-child a {
  color: var(--kb-ink);
}
.pagination > li:first-child a:hover,
.pagination > li:first-child a:focus-visible,
.pagination > li:last-child a:hover,
.pagination > li:last-child a:focus-visible {
  color: var(--kb-accent);
}
.pagination > li.disabled:first-child > span,
.pagination > li.disabled:last-child > span {
  color: var(--kb-muted);
  opacity: 0.4;
}

@media (max-width: 420px) {
  .pagination { gap: 0.1rem; }
  .pagination a,
  .pagination > li > span { min-width: 2rem; height: 2rem; padding: 0 0.4rem; }
}

/* == end pagination == */


/* == 2026-08-28 cleanup: altobib == */
/* == WS-altobib staging file — merged into kimball.css at integration ==
   Workstream 5 — Quote/List Entry Restructuring (issues #10, #11).
   Alto subpages (person/date/label-headed entries) + the 3 bibliography-
   shaped pages (citation entries). Built entirely around two inline <span>
   markers that restructure-entry-lists.py inserts into the markdown (see
   that script's docstring for why: Grav's markdown.extra is off, so a
   block-level <div> would stop being markdown-parsed — everything here
   keys off a <span> as the FIRST child of an auto-generated <p>, via
   :has(), so links/italics/images inside the paragraph keep working).
   Both color modes: every value below is a --kb-* token, no literals.
   ========================================================================== */

/* -- 1. Name-headed entries ------------------------------------------------
   alto-quotes, alto-with-voice's tail, treatises-on-alto's Sources list,
   extant-altos' instrument records, learning-the-alto-trombone's steps,
   and the mini-bibliography's image+citation heading. */

.kb-entry-name {
  display: block;
  font-family: 'EB Garamond', Garamond, Georgia, serif;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--kb-accent);
  line-height: 1.35;
  margin-bottom: 0.4rem;
}
.kb-entry-name img {
  display: inline-block;
  width: 2.5rem;
  height: 2.5rem;
  object-fit: cover;
  border-radius: 3px;
  vertical-align: middle;
  margin-right: 0.6rem;
  border: 1px solid var(--kb-hairline);
}

/* The paragraph Parsedown builds around the span is the actual "entry" —
   give it the separator/rhythm; :has() targets the parent from the child
   marker so no block-level wrapper is ever needed in the markdown. */
p:has(> .kb-entry-name) {
  margin-top: 2.1rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--kb-hairline);
}
/* No dangling top border directly under a heading or at the top of the
   page's reading column — the heading/title already separates it. */
h1 + p:has(> .kb-entry-name),
h2 + p:has(> .kb-entry-name),
h3 + p:has(> .kb-entry-name),
.kb-meta + p:has(> .kb-entry-name) {
  margin-top: 1.3rem;
  padding-top: 0;
  border-top: none;
}

/* -- 2. Citation entries (bibliography-shaped) ------------------------------
   alto-with-voice's works list, praetorius-on-alto-trombone's Sources tail,
   and the two plain-citation bibliographies. Each paragraph already IS one
   citation; a zero-width marker at its start is only a :has() hook plus,
   on the first entry per starting letter, the alphabet-rail anchor id. */

.kb-biblio-mark {
  display: inline;
}
p:has(> .kb-biblio-mark) {
  text-indent: -1.5rem;
  padding-left: 1.5rem;
  margin-bottom: 1rem;
  line-height: 1.55;
}
h2 + p:has(> .kb-biblio-mark),
h3 + p:has(> .kb-biblio-mark) {
  margin-top: 0.6rem;
}

/* Anchor ids sit on both marker spans (kb-entry-name id="letter-x" on the
   mini-bibliography, kb-biblio-mark id="letter-x" on the two plain
   bibliographies) purely for #letter-x deep-linking / scroll targets —
   the alphabet rail itself is WS-6-adjacent work in ws-bibrail.css. */

/* == end altobib == */


/* == 2026-08-28 cleanup: bibrail == */
/* == WS-bibrail staging file — merged into kimball.css at integration ==
   WS-6 (2026-08-28, issue #11 follow-up): alphabet-rail skin for the two
   plain-citation bibliographies (trombone-history-bibliography,
   alto-trombone-history-bibliography) and the mini-bibliography item page.
   Reuses WS-3's shared .kb-rail / .kb-rail-dot / .kb-rail-marker /
   .kb-rail-label / .kb-rail-layout base classes from ws-timelinenav.css
   completely unmodified — everything below is scoped under the extra
   .kb-rail-alpha class the bibliography templates add, so the timeline
   dot-rail's own look and behavior are untouched. JS is untouched too:
   kimball-rail.js auto-initializes any [data-kb-rail] via the same
   IntersectionObserver current-stop logic already used by the timeline
   rail. Tokens only (--kb-*), so both color modes work without any
   overrides here. */

/* Letters are the whole point of an A-Z rail — show them always, not just
   on hover/current like the timeline rail's word labels (which stay
   hidden until interaction because a full label list would be busy). */
.kb-rail-alpha .kb-rail-label {
  opacity: 1;
  display: block;
  width: 1.1rem;
  text-align: center;
  font-family: 'Public Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0;
}

/* A single glyph already carries "which stop is this" — the timeline
   rail's separate round dot marker is redundant here, so drop it and
   center the letter instead of right-justifying a label+dot pair. */
.kb-rail-alpha .kb-rail-marker {
  display: none;
}

.kb-rail-alpha .kb-rail-dot {
  justify-content: center;
  min-height: 1.1rem;
  gap: 0;
}

/* Current-letter indicator (scroll position): .kb-rail-dot.is-current
   already turns the whole link --kb-accent via ws-timelinenav.css, which
   the label inherits — this just adds a heavier weight + underline so the
   "you are here" letter is unambiguous at a glance without the dot. */
.kb-rail-alpha .kb-rail-dot.is-current .kb-rail-label {
  font-weight: 700;
  text-decoration: underline;
  text-decoration-thickness: 1.5px;
  text-underline-offset: 0.2rem;
}

/* == end bibrail == */


/* == 2026-08-28 round-2: century == */
/* == WS-century staging file (round 2) — merged into kimball.css at integration == */

/* -- Dead-video note --------------------------------------------------------
   audit-dead-videos.py replaces each kbembed whose YouTube video is
   definitively gone (oEmbed 400/404) with this note. Tokens only (--kb-*),
   so light and dark modes both work; sized to the same 66ch reading measure
   as .kb-embed but without the 16:9 reservation — it's a one-line notice,
   not a player. */

.kb-embed-unavailable {
  max-width: 46.5rem; /* matches .kb-embed / .kb-reading's 66ch measure */
  margin: 2rem 0;
  padding: 0.85rem 1.1rem;
  border: 1px solid var(--kb-hairline);
  border-left: 3px solid var(--kb-accent);
  background: color-mix(in srgb, var(--kb-hairline) 35%, transparent);
  color: var(--kb-muted);
  font-size: 0.9375rem;
  line-height: 1.5;
}

.kb-embed-unavailable a {
  color: var(--kb-accent);
  text-decoration-color: color-mix(in srgb, var(--kb-accent) 50%, transparent);
}

.kb-embed-unavailable a:hover {
  text-decoration-color: var(--kb-accent);
}
/* == /WS-century == */

/* == end == */


/* == 2026-08-28 round-2: rails == */
/* == WS-rails staging file (round 2) — merged into kimball.css at integration ==
   Lane B (2026-08-28): rail & navigation polish. Overrides the WS-3/WS-6
   rail blocks in kimball.css by cascade order (this file loads after it);
   tokens only (--kb-*), so both color modes work untouched.

   Contents:
     1. Sticky jump bar — match the page surface (texture, not flat fill)
     2. kb-rail — viewport-centered, bigger dots, neutral-inactive/gold-
        active, connecting spine, slide-in hover labels; clipped-dot fix
     3. kb-rail-alpha — same treatment for the bibliography letter rails
     4. kb-backtop — site-wide floating back-to-top button
        (created by js/kimball-backtop.js, loaded from partials/nav.html.twig)
*/

/* -- 1. Sticky jump bar: same surface as the page ------------------------
   Root cause of the "different color band": body::after lays a fixed
   2%-opacity crosshatch weave (--kb-texture-*) over --kb-page everywhere,
   but the stuck bar needs an opaque fill, and its flat var(--kb-page)
   covered the weave — a subtly flatter/lighter band. Reproduce the exact
   same token-driven weave on the bar itself so it is indistinguishable
   from the page in both modes. The existing top/bottom hairlines stay as
   the only stuck-state separation (kept subtle by design). */
.kb-jump-sticky {
  background:
    repeating-linear-gradient(0deg, var(--kb-texture-1) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg, var(--kb-texture-2) 0 1px, transparent 1px 3px),
    var(--kb-page);
}

/* -- 2. Dot rail: vertically centered in the viewport --------------------
   The rail becomes a full-viewport sticky flex box (top: 0, height: 100vh)
   and the new .kb-rail-track wrapper (added in the three rail templates)
   centers inside it via margin: auto 0 — the safe-centering idiom: when
   the stack is taller than the viewport the auto margins collapse and the
   rail top-aligns and scrolls instead of clipping both ends.

   Clipped-dot bug fix: overflow-y: auto forces computed overflow-x: auto,
   and the old `padding: 0.3rem 0` left zero horizontal room, so the
   scaled .is-current marker's right edge was shaved flat at the scroll
   container's edge. The 0.75rem horizontal padding gives the scale + glow
   room to paint. Vertical padding keeps the dots clear of the stuck jump
   bar (top) and the back-to-top button (bottom). */
.kb-rail {
  top: 0;
  height: 100vh;
  max-height: 100vh;
  overflow-y: auto;
  padding: 4.5rem 0.75rem;
  gap: 0;
}
.kb-rail-track {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin: auto 0;
}

/* Connecting spine: a hairline through the marker centers, from the first
   dot's center to the last's. Markers are the last flex item in each
   right-justified row, so their centers sit 0.4rem (half of 0.8rem) in
   from the track's right edge; rows are 1.5rem tall, so half a row in
   from the top/bottom ends the spine at the outer dot centers. */
.kb-rail-track::before {
  content: '';
  position: absolute;
  top: 0.75rem;
  bottom: 0.75rem;
  right: calc(0.4rem - 0.5px);
  width: 1px;
  background: var(--kb-hairline);
}

/* Bigger dots; inactive = muted neutral (never the gold accent): hairline
   fill + muted ring works in both modes. position: relative lifts markers
   above the spine; the opaque fill keeps the spine from showing through. */
.kb-rail-dot {
  min-height: 1.5rem;
  gap: 0.7rem;
  font-size: 0.75rem;
}
.kb-rail-marker {
  position: relative;
  width: 0.8rem;
  height: 0.8rem;
  border: 1px solid var(--kb-muted);
  background: var(--kb-hairline);
  transition: background-color 160ms ease, border-color 160ms ease,
    transform 160ms ease, box-shadow 160ms ease;
}
.kb-rail-dot:hover .kb-rail-marker,
.kb-rail-dot:focus-visible .kb-rail-marker {
  transform: scale(1.15);
}
/* Current stop: gold fill (from kimball.css) + a touch more scale and a
   soft halo. color-mix keeps the halo tokenized for both modes; browsers
   without color-mix simply drop the halo. */
.kb-rail-dot.is-current .kb-rail-marker {
  transform: scale(1.35);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--kb-accent) 22%, transparent);
}

/* Labels slide in with the fade (kimball.css handles opacity). */
.kb-rail-label {
  transform: translateX(0.3rem);
  transition: opacity 160ms ease, transform 160ms ease;
}
.kb-rail-dot:hover .kb-rail-label,
.kb-rail-dot:focus-visible .kb-rail-label,
.kb-rail-dot.is-current .kb-rail-label {
  transform: translateX(0);
}

/* -- 3. Alphabet rail (bibliographies) -----------------------------------
   Same centered treatment via the shared .kb-rail rules above. Letters
   bigger for readability; markers are display:none here (WS-6), so the
   spine has no centers to connect — hide it. No sticky jump bar on these
   pages, so less top padding; bottom padding still clears the back-to-top
   button. Rows kept tight so a 26-letter stack fits most viewports before
   the safe-centering scroll fallback kicks in. */
.kb-rail-alpha { padding: 1.5rem 0.75rem 4.75rem; }
.kb-rail-alpha .kb-rail-track::before { display: none; }
.kb-rail-alpha .kb-rail-track { gap: 0.15rem; }
.kb-rail-alpha .kb-rail-dot { min-height: 1.45rem; }
.kb-rail-alpha .kb-rail-label {
  font-size: 0.9rem;
  width: 1.4rem;
  transform: none;
}

/* -- 4. Back-to-top button (site-wide) -----------------------------------
   Created by js/kimball-backtop.js after ~1.5 viewport-heights of scroll.
   Fixed bottom-right; the rails' 4.5rem+ bottom padding keeps the lowest
   dot/letter clear of it on rail pages. Same page-surface weave as the
   jump bar. z-index 20 sits above the stuck jump bar (5) and below the
   nav's floating panels (30). */
.kb-backtop {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 20;
  width: 2.75rem;
  height: 2.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--kb-hairline);
  background:
    repeating-linear-gradient(0deg, var(--kb-texture-1) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg, var(--kb-texture-2) 0 1px, transparent 1px 3px),
    var(--kb-page);
  color: var(--kb-muted);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.14);
  cursor: pointer;
  opacity: 0;
  transform: translateY(0.5rem);
  pointer-events: none;
  transition: opacity 200ms ease, transform 200ms ease,
    color 160ms ease, border-color 160ms ease;
}
.kb-backtop.is-visible {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
.kb-backtop:hover,
.kb-backtop:focus-visible {
  color: var(--kb-accent);
  border-color: var(--kb-accent);
}
.kb-backtop:focus-visible {
  outline: 2px solid var(--kb-accent);
  outline-offset: 2px;
}
.kb-backtop svg {
  display: block;
  width: 1.1rem;
  height: 1.1rem;
}

@media (prefers-reduced-motion: reduce) {
  .kb-rail-marker,
  .kb-rail-label,
  .kb-backtop {
    transition: none;
  }
  .kb-backtop { transform: none; }
}

@media print {
  .kb-backtop { display: none; }
}

/* == end == */


/* == 2026-08-28 round-2: typography == */
/* == WS-typography staging file (round 2) — merged into kimball.css at integration ==
   Lane C (2026-08-28): typography & spacing sweep. Overrides kimball.css's
   type-scale/rhythm blocks by cascade order (this file loads after it, see
   base.html.twig link order); rem units + weight/line-height only, no
   colors touched (tokens already correct upstream). Both color modes
   inherit unchanged since nothing here sets color.

   Reconciles two proposals (classical-hierarchy vs density-and-restraint):
   shared ground kept as-is (h1 unchanged, .kb-entry-name -> 24px, paragraph
   spacing -> 1.6rem); where they split, sized to satisfy Jacob's explicit
   floor (serif headings clearly larger than the body they head, his
   measured .kb-entry-name example >= 24px) while keeping h3 clearly above
   the pseudo-heading tier it co-occurs with on the same pages (e.g.
   treatises-on-alto: h3 "Germany — …" groups several .kb-entry-name year
   entries under it — h3 must out-rank them, not match them) and avoiding
   proposal 1's riskier 42px h2 (wrapping risk on this site's longer
   titles, flagged by its own author).

   Reconciled scale (px at the 18px body/16px root):
     body                              18px    1×      (unchanged, per brief)
     .kb-entry-name / .timeline-heading 24px    1.333×  (Jacob's measured floor)
     .kb-recital-piecetitle            21.6px  1.2×    (bibliographic caption,
                                                          one tier below h3/entry)
     h3                                28px    1.556×  (~"half again")
     .kb-blog-item h2                  33.6px  1.867×  (restrained: repeats
                                                          ~10x per listing page)
     h2                                36px    2×
     h1                                55.2px  3.07×   (unchanged — already
                                                          sits correctly on
                                                          the scale)

   EXCLUDED (owned elsewhere / out of scope, per brief): .kb-rail-*/.kb-rail
   dots & letters (lane B), .kb-embed sizing, image rules, megamenu/nav
   text, body font-size (stays 18px).

   Contents:
     1. Heading scale — h3, h2, .kb-blog-item h2
     2. Entry-headed pseudo-headings — .kb-entry-name, .kb-recital-piecetitle
     3. Paragraph / list / heading-margin rhythm (wider end of standard)
     4. Page-title-to-content clearance (fixes FAQ + any bare title->body page)
     5. Mobile scale-down for h2/h3 (h1 already had one)
*/

/* -- 1. Heading scale ------------------------------------------------------
   h3 currently sits at 22px/1.22× body — clearly not "clearly larger", and
   on pages like treatises-on-alto and fall-2026-ensemble-auditions it is
   the real subsection heading grouping several .kb-entry-name entries, so
   it needs to out-rank that 24px entry tier, not match it. h2 at 28.8px is
   already 1.6×, past the "half again" floor on its own — left alone rather
   than pushed to a size that risks wrapping this site's longer titles. */
h3 {
  font-size: 1.75rem;   /* 28px, was 1.375rem/22px */
  font-weight: 500;     /* was 490 — a touch more presence at the new size */
  line-height: 1.3;     /* unchanged */
}

h2 {
  font-size: 2.25rem;   /* 36px, was 1.8rem/28.8px */
  font-weight: 460;     /* unchanged */
  line-height: 1.2;     /* was 1.25 — tightened slightly for the larger size */
}

/* Blog-list titles repeat ~10x per listing page — same direction as h2,
   restrained size so a full list doesn't get disproportionately tall. */
.kb-blog-item h2 {
  font-size: 2.1rem;    /* 33.6px, was 1.8rem/28.8px */
}

/* -- 2. Entry-headed pseudo-headings ----------------------------------------
   .kb-entry-name is Jacob's cited example (treatises-on-alto year entries,
   learning-the-alto-trombone steps, alto-quotes, extant-altos, the
   mini-bibliography) — confirmed via curl at 16.8px, smaller than the body
   it heads. Both proposals independently landed on the same fix. */
.kb-entry-name {
  font-size: 1.5rem;    /* 24px, was 1.05rem/16.8px */
  line-height: 1.3;     /* was 1.35 */
}

/* Piece-title line in Solo & Chamber Recordings (h3.kb-recital-piecetitle —
   confirmed serif: it inherits EB Garamond from the base h1,h2,h3 rule and
   sets no font-family of its own). At 18.4px/1.02× it reads as body text,
   not a heading; bumped to a deliberate caption tier — clearly above body,
   still below the h3/entry tier it sits under in the recital markup
   (h2.kb-recital-title > h3.kb-recital-piecetitle). */
.kb-recital-piecetitle {
  font-size: 1.35rem;   /* 21.6px, was 1.15rem/18.4px */
  line-height: 1.4;     /* unchanged */
}

/* -- 3. Paragraph / list / heading-margin rhythm ----------------------------
   Both proposals agreed: round-1's widening was the right direction, push
   it a bit further toward the wide end of the standard range, not a redo. */
.kb-reading p,
.kb-reading ul,
.kb-reading ol {
  margin-bottom: 1.6rem;  /* was 1.45rem */
}
p { margin-bottom: 1.4rem; } /* was 1.25rem — base/non-.kb-reading contexts */

.kb-reading h2 { margin: 3.5rem 0 1.2rem; }    /* was 3.25rem 0 1.1rem */
.kb-reading h3 { margin: 2.75rem 0 0.95rem; }  /* was 2.5rem 0 0.85rem */
.kb-entry-name { margin-bottom: 0.55rem; }      /* was 0.4rem */

/* -- 4. Page-title-to-content clearance -------------------------------------
   Root cause of the FAQ complaint (and any custom page like it): the h1's
   own margin-bottom is only 0.5rem, and adjacent block margins collapse to
   the LARGER of the two — so a page-title followed directly by body text
   (0 top-margin) or a .kb-meta line (also 0 top-margin) both currently
   collapse to just 0.5rem/8px of air. Where a .kb-meta line follows, that
   is fine as-is: the meta line's own 2.6rem bottom margin already supplies
   the real clearance before body text starts. Where nothing does (bare
   h1 -> paragraph, as on the FAQ page, treatises-on-alto, and
   learning-the-alto-trombone; or h1 -> h3, as on fall-2026-ensemble-
   auditions, where the h3's own 2.75rem top margin already dominates the
   collapse and this rule is a no-op) the gap needs its own fix. Scoped with
   :has() the same way round-1 already does elsewhere in this theme
   (p:has(> .kb-entry-name) etc.), and its higher specificity beats the
   base .kb-page-title rule regardless of file load order. */
.kb-page-title:not(:has(+ .kb-meta)) {
  margin-bottom: 2rem;   /* was 0.5rem (via kimball.css's .kb-page-title) */
}

/* -- 5. Mobile scale-down ----------------------------------------------------
   h1 already scales down at 600px; h2/h3 didn't, and both grew today. */
@media (max-width: 600px) {
  h2 { font-size: 1.9rem; }               /* 30.4px */
  h3 { font-size: 1.55rem; }              /* 24.8px */
  .kb-blog-item h2 { font-size: 1.8rem; } /* 28.8px */
}

/* == end == */


/* == 2026-08-28 round-2: images == */
/* == WS-images staging file (round 2) — merged into kimball.css at integration == */

/* -- Lane D, item 2: Alto Quotes composer portraits ------------------------
   kimball.css's shared `.kb-entry-name img` rule (2.5rem/40px) is reused
   by 5 different pages (alto-quotes, alto-with-voice, treatises-on-alto,
   extant-altos, learning-the-alto-trombone) — see kimball.css's WS-5
   comment block. Alto Quotes' composer portraits specifically render too
   small there; this scopes a ~2x display size (5rem/80px) to that one page
   only, without touching kimball.css or the shared class everywhere else.

   No body/page class exists to key off (base.html.twig is off-limits and
   carries none), so the scope hook is the page's own `<link rel="canonical">`
   in <head> — a domain-agnostic suffix match on its href, reached via
   :has() the same way kimball.css's WS-5 rules already key off :has() for
   its span-marker technique. `html:has(...)` covers <head>'s descendants
   even though <link> isn't inside <body>. Source images are WordPress
   "-150x150" (or same-ballpark, e.g. 125x150/132x150) originals — no larger
   variant exists on disk for any of them, and 150px is already >1x the
   80px display target, so no image reference repointing was needed. */
html:has(link[rel="canonical"][href$="/alto-trombone/alto-quotes"])
  .kb-entry-name img {
  width: 5rem;
  height: 5rem;
  border-radius: 4px;
  margin-right: 0.9rem;
}

/* == end == */


/* == 2026-08-28 round-2: downloads == */
/* == WS-downloads staging file (round 2) — merged into kimball.css at integration == */

/* -------------------------------------------------------------------------
   Lane E — Download link treatment
   .kb-download: the designed affordance for the [kbdownload] shortcode
   (config/www/user/plugins/shortcode-core/shortcode-core.yaml), applied by
   ops/cleanup-2026-08/convert-download-links.py to markdown links whose
   target is a file asset meant to be downloaded (the alto trombone slide
   position chart PDF and similar). Tokens only (--kb-*); both color modes
   inherit automatically since every color here is a token already defined
   for both modes in kimball.css. Renders inline (the shortcode outputs an
   <a>, not a <div>) so it drops cleanly into running prose or stands alone
   on its own line.
   ------------------------------------------------------------------------- */

a.kb-download {
  display: inline-flex;
  align-items: center;
  gap: 0.65em;
  margin: 0.2em 0;
  padding: 0.55em 1em 0.55em 0.8em;
  border: 1px solid var(--kb-hairline);
  border-radius: 4px;
  background: var(--kb-page);
  color: var(--kb-ink);
  text-decoration: none;
  font-family: 'Public Sans', sans-serif;
  line-height: 1.3;
  vertical-align: middle;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

a.kb-download:hover {
  border-color: var(--kb-accent);
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08);
  transform: translateY(-1px);
}

:root:not([data-theme='light']) a.kb-download:hover,
:root[data-theme='dark'] a.kb-download:hover {
  box-shadow: 0 3px 12px rgba(0, 0, 0, 0.35);
}

a.kb-download:focus-visible {
  outline: 2px solid var(--kb-accent);
  outline-offset: 2px;
}

a.kb-download:hover .kb-download-icon,
a.kb-download:focus-visible .kb-download-icon {
  color: var(--kb-ink);
}

.kb-download-icon {
  flex-shrink: 0;
  display: inline-flex;
  color: var(--kb-accent);
  transition: color 0.15s ease;
}

.kb-download-icon svg {
  display: block;
  width: 1.05rem;
  height: 1.05rem;
}

.kb-download-text {
  display: flex;
  flex-direction: column;
  gap: 0.15em;
}

.kb-download-label {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--kb-ink);
}

.kb-download-meta {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--kb-muted);
}

/* == end == */

/* == 2026-08-28 round-2: rail-less page centering (Jacob-approved) ==
   Pages with a right-hand rail (timelines, bibliographies) wrap .kb-reading
   in .kb-rail-layout and stay left-anchored so the rail owns the right side.
   Plain article pages have .kb-reading directly under .kb-container; with no
   rail there, the empty right side reads as imbalance — center the column. */
.kb-container > .kb-reading {
  margin-left: auto;
  margin-right: auto;
}
/* == end == */


/* == 2026-08-28 round-3: timeline nav + back-to-top == */
/* Jacob's round-3 directives (2026-08-28): (1) the timeline jump bar is
   primary navigation now — full-viewport-width band flush under the header,
   present from page load, dates only, bigger type; (2) on narrow viewports
   the link row becomes a native <select>; (3) the back-to-top button gets an
   accent surface so it is actually visible. Tokens only (--kb-*); every rule
   works in both color modes by construction. */

/* -- 1. Jump band: full width, flush under the header band ---------------
   The timeline template now emits the bar as a direct child of #kb-main
   (before .kb-container), so a plain block spans the full viewport width —
   no 100vw hacks, no scrollbar overflow. The -3rem top margin cancels
   #kb-main's `padding: 3rem 0 5rem` top pad so the band sits tight under
   the navy header band. Sticky behavior and the round-2 page-surface weave
   come from .kb-jump-sticky unchanged (header is not sticky, top: 0 holds).
   The inner .kb-container row re-aligns the dates with the content column. */
.kb-jump-band {
  margin: -3rem 0 2.75rem;
  padding: 0;                 /* spacing moves to the inner row */
  border-top: 0;              /* sits against the navy band; only the bottom
                                 hairline separates it from content */
  font-size: 1.0625rem;       /* primary navigation scale, up from 0.9rem */
}

.kb-jump-links {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.3rem 1.7rem;
  padding: 0.8rem 0;
}

.kb-jump-band a {
  margin-right: 0;            /* the old .kb-timeline-jump link gutter — the
                                 flex gap owns spacing now */
  color: var(--kb-ink);
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
}
.kb-jump-band a:hover {
  color: var(--kb-accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}

/* -- 2. Mobile: link row becomes a themed native <select> ----------------
   640px is the theme's established mobile breakpoint. The select keeps the
   anchors' semantics via location.hash (set in the template), so the CSS
   smooth-scroll rule and scroll-margin-top govern the jump exactly as for
   the desktop links. appearance: none drops the UA chrome; the wrapper
   paints a token-colored chevron in its place. */
.kb-jump-select-wrap { display: none; }

@media (max-width: 640px) {
  .kb-jump-links { display: none; }

  .kb-jump-select-wrap {
    position: relative;
    display: block;
    width: 100%;
    padding: 0.7rem 0;
  }
  .kb-jump-select {
    width: 100%;
    appearance: none;
    -webkit-appearance: none;
    font-family: inherit;
    font-size: 1rem;
    font-weight: 600;
    color: var(--kb-ink);
    background: transparent;   /* the band's weave shows through */
    border: 1px solid var(--kb-hairline);
    border-radius: 0.4rem;
    padding: 0.6rem 2.6rem 0.6rem 0.95rem;
    cursor: pointer;
  }
  .kb-jump-select-wrap::after {
    content: '';
    position: absolute;
    right: 1rem;
    top: 50%;
    width: 0.55rem;
    height: 0.55rem;
    border-right: 1.5px solid var(--kb-muted);
    border-bottom: 1.5px solid var(--kb-muted);
    transform: translateY(-70%) rotate(45deg);
    pointer-events: none;
  }
}

/* -- 3. Sticky-stack clearance -------------------------------------------
   The band is the only sticky element (the header scrolls away). One line
   of 1.0625rem links + 2 × 0.8rem padding ≈ 3.3rem; a wrapped two-line band
   (possible on 640–1023px, where the dot rail is already hidden) ≈ 5.1rem.
   6rem clears both. 6rem = 96px, inside kimball-rail.js's activation-line
   assumption (landings within ~120px of the viewport top) — do not raise
   this past that without revisiting the rail script. Supersedes the WS-3
   5.5rem rule earlier in this file. */
.timeline-entry.decade-start {
  scroll-margin-top: 6rem;
}

/* -- 4. Back-to-top: visible this time -----------------------------------
   Jacob could barely see the round-2 weave-surfaced button. Solid accent
   fill instead; the arrow uses var(--kb-page), which contrasts with the
   accent in both modes by construction (the accent is the link color ON
   the page surface: dark bronze on near-white in light mode, light gold on
   dark navy in dark mode — invert the pair and it still reads). Slightly
   larger, stronger shadow, and a rise-and-settle entrance. Supersedes the
   round-2 .kb-backtop surface/size/hover rules (same selectors, later in
   the cascade). */
.kb-backtop {
  width: 3.25rem;
  height: 3.25rem;
  border: 1px solid color-mix(in srgb, var(--kb-accent) 80%, var(--kb-ink));
  background: var(--kb-accent);
  color: var(--kb-page);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.28);
  opacity: 0;
  transform: translateY(0.75rem) scale(0.92);
  transition: opacity 220ms ease, transform 220ms ease,
    background-color 160ms ease;
}
.kb-backtop.is-visible {
  opacity: 1;
  transform: none;
}
.kb-backtop:hover,
.kb-backtop:focus-visible {
  color: var(--kb-page);
  border-color: color-mix(in srgb, var(--kb-accent) 70%, var(--kb-ink));
  background: color-mix(in srgb, var(--kb-accent) 86%, var(--kb-ink));
}
.kb-backtop svg {
  width: 1.35rem;
  height: 1.35rem;
}

/* The rails' bottom padding kept the lowest dot/letter clear of a 2.75rem
   button (4rem of button + offset vs 4.5rem pad); at 3.25rem the button
   column reaches exactly 4.5rem, so give the rails back their clearance. */
.kb-rail { padding-bottom: 5.25rem; }
.kb-rail-alpha { padding-bottom: 5.5rem; }

/* Re-assert reduced-motion stillness: the transform/transition base rules
   above are later in the file than the round-2 reduced-motion override, so
   they would win the cascade without this. */
@media (prefers-reduced-motion: reduce) {
  .kb-backtop {
    transition: none;
    transform: none;
  }
}
/* == end == */

/* == 2026-08-28 round-3: contact form ==
   Grav Form plugin markup (.form-field / .form-label / .form-input-wrapper /
   .buttons) styled to the theme. Scoped to .kb-reading so the comments form
   (its own .kb-comments markup) is untouched. Tokens only — both modes. */
.kb-reading .form-wrapper { margin: 2.5rem 0 3rem; }
.kb-reading .form-field { margin: 0 0 1.4rem; }
.kb-reading .form-label {
  display: block;
  font-family: "Public Sans", system-ui, sans-serif;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--kb-muted);
  margin-bottom: 0.4rem;
}
.kb-reading .form-field input[type="text"],
.kb-reading .form-field input[type="email"],
.kb-reading .form-field input[type="tel"],
.kb-reading .form-field select,
.kb-reading .form-field textarea {
  width: 100%;
  font: inherit;
  font-size: 1rem;
  color: var(--kb-ink);
  background: var(--kb-page);
  border: 1px solid var(--kb-hairline);
  border-radius: 6px;
  padding: 0.65rem 0.8rem;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.kb-reading .form-field input:focus,
.kb-reading .form-field select:focus,
.kb-reading .form-field textarea:focus {
  outline: none;
  border-color: var(--kb-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--kb-accent) 25%, transparent);
}
.kb-reading .form-field textarea { resize: vertical; min-height: 10rem; }
/* checkbox row: undo the block label treatment, sit label beside the box */
.kb-reading .form-field .checkboxes,
.kb-reading .form-field.checkbox-field { display: flex; align-items: baseline; gap: 0.5rem; }
.kb-reading .form-field input[type="checkbox"] {
  width: 1.05rem;
  height: 1.05rem;
  accent-color: var(--kb-accent);
  flex: none;
}
.kb-reading .form-field .checkboxes label,
.kb-reading .form-field label[for*="terms"] {
  display: inline;
  font-family: inherit;
  font-size: 1rem;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  color: var(--kb-ink);
}
.kb-reading .buttons { margin-top: 2rem; }
.kb-reading .buttons .button {
  font-family: "Public Sans", system-ui, sans-serif;
  font-size: 1rem;
  font-weight: 600;
  color: #fff;
  background: var(--kb-accent);
  border: none;
  border-radius: 999px;
  padding: 0.75rem 2.2rem;
  cursor: pointer;
  transition: filter 120ms ease, transform 120ms ease;
}
.kb-reading .buttons .button:hover { filter: brightness(1.12); }
.kb-reading .buttons .button:active { transform: translateY(1px); }
.kb-reading .form-errors,
.kb-reading .alert {
  border: 1px solid var(--kb-hairline);
  border-left: 4px solid var(--kb-accent);
  border-radius: 6px;
  background: color-mix(in srgb, var(--kb-accent) 8%, var(--kb-page));
  padding: 0.9rem 1.1rem;
  margin: 1.5rem 0;
}
/* == end round-3: contact form == */

/* == 2026-08-28 round-3: contact form v2 (design pass; supersedes v1 block) ==
   The form sits in a bordered panel to give it presence on the page; email +
   phone share a row on wide screens; inputs sit on the page surface so they
   read as wells inside the panel; the post-submit flash is a .toast (the Form
   plugin's class), styled as a proper notice. Tokens only — both modes. */
.kb-reading .form-wrapper {
  margin: 2.5rem 0 3.5rem;
  padding: 2.25rem 2.25rem 2.4rem;
  background: color-mix(in srgb, var(--kb-ink) 4%, var(--kb-page));
  border: 1px solid var(--kb-hairline);
  border-radius: 12px;
  box-shadow:
    0 1px 2px color-mix(in srgb, var(--kb-ink) 10%, transparent),
    0 10px 32px color-mix(in srgb, var(--kb-ink) 7%, transparent);
}
@media (max-width: 640px) {
  .kb-reading .form-wrapper { padding: 1.4rem 1.1rem 1.6rem; }
}
.kb-reading .form-wrapper form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 1.4rem;
}
.kb-reading .form-wrapper .form-field,
.kb-reading .form-wrapper .buttons { grid-column: 1 / -1; }
.kb-reading .form-wrapper .form-field { margin: 0 0 1.55rem; }
@media (min-width: 641px) {
  /* field order: name, email, phone, subject, message, terms */
  .kb-reading .form-wrapper .form-field:nth-of-type(2),
  .kb-reading .form-wrapper .form-field:nth-of-type(3) { grid-column: span 1; }
}
.kb-reading .form-wrapper .form-label { margin-bottom: 0.45rem; }
.kb-reading .form-field input[type="text"],
.kb-reading .form-field input[type="email"],
.kb-reading .form-field input[type="tel"],
.kb-reading .form-field select,
.kb-reading .form-field textarea {
  background: var(--kb-page);
  border-radius: 8px;
  padding: 0.7rem 0.9rem;
}
.kb-reading .form-field input:hover,
.kb-reading .form-field select:hover,
.kb-reading .form-field textarea:hover {
  border-color: color-mix(in srgb, var(--kb-accent) 45%, var(--kb-hairline));
}
/* checkbox row: kill the empty leading label block the plugin emits, then
   lay the box and its inline label on one line */
.kb-reading .form-field .form-label:has(> label.inline:empty) { display: none; }
.kb-reading .form-field .form-input-wrapper:has(> input[type="checkbox"]) {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
}
.kb-reading .form-field input[type="checkbox"] {
  width: 1.15rem;
  height: 1.15rem;
  accent-color: var(--kb-accent);
  translate: 0 2px;
}
.kb-reading .form-field input[type="checkbox"] + label {
  font-size: 1rem;
  color: var(--kb-ink);
}
.kb-reading .form-field input[type="checkbox"] + label a {
  color: var(--kb-accent);
}
.kb-reading .form-wrapper .buttons {
  margin-top: 0.9rem;
  padding-top: 1.6rem;
  border-top: 1px solid var(--kb-hairline);
}
.kb-reading .buttons .button {
  font-size: 1.05rem;
  letter-spacing: 0.02em;
  padding: 0.8rem 2.5rem;
  box-shadow: 0 2px 10px color-mix(in srgb, var(--kb-accent) 35%, transparent);
}
/* post-submit flash + validation errors */
.kb-reading .form-wrapper .toast,
.kb-reading .form-errors {
  border: 1px solid color-mix(in srgb, var(--kb-accent) 40%, var(--kb-hairline));
  border-left: 4px solid var(--kb-accent);
  border-radius: 8px;
  background: color-mix(in srgb, var(--kb-accent) 10%, var(--kb-page));
  color: var(--kb-ink);
  padding: 1rem 1.2rem;
  margin: 0 0 1.75rem;
  font-family: "Public Sans", system-ui, sans-serif;
  font-size: 1rem;
}
/* == end round-3: contact form v2 == */


/* == 2026-08-28 round-4: current-aware jump selects + bibliography mobile band == */
/* Jacob's round-4 directives (2026-08-28): the mobile jump <select> now
   DISPLAYS the current decade/century/letter (kimball-rail.js syncs it via
   [data-kb-jump-select]) instead of a "Jump to…" placeholder — no CSS
   change needed for that beyond what round-3 built. This block only adds
   the bibliography pages' MOBILE-ONLY band: on phones the alphabet rail is
   hidden and those pages had no navigation; on desktop Jacob explicitly
   wants NO band there — the rail stays the desktop navigation. Tokens
   only; both color modes inherit from the round-3 band rules. */

/* Hidden at all desktop/tablet widths; becomes the round-3 band (sticky,
   flush under the header, themed select) only at the theme's 640px mobile
   breakpoint, where round-3 already shows .kb-jump-select-wrap. */
.kb-jump-band--mobile-only { display: none; }

@media (max-width: 640px) {
  .kb-jump-band--mobile-only { display: block; }

  /* The bib letter markers (WS-5 `id="letter-x"` spans) never needed a
     scroll offset before — desktop bibliography has no sticky bar, and
     keeps none now. On mobile the new band is sticky, so letter jumps
     must land clear of it: same 6rem as the timeline anchors, inside
     kimball-rail.js's activation-line assumption (min(30vh, 160px)). */
  [id^="letter-"] { scroll-margin-top: 6rem; }
}
/* == end round-4 == */


/* == 2026-08-28 round-5: header line icons + inline search == */
/* Jacob's round-5 directives (2026-08-28): (a) replace the mode toggle's
   text glyphs — the sun sat above center, and he disliked the pair — with
   modern Feather-style stroke SVGs (stroke: currentColor, so the band
   tokens color them in both modes), and drop the surrounding circle;
   (b) the Search text link is now a matching line-icon button that swaps
   the nav row for an inline search input (behavior in kimball-search.js,
   keyed off #kb-header.kb-search-open). Everything here lives on the navy
   band and uses the band tokens, so both color modes work untouched. */

/* Shared sizing for the new header line icons. SVG geometry centers
   itself inside the flex-centered buttons — no glyph-metric drift. */
.kb-icon {
  display: block;
  width: 1.2rem;
  height: 1.2rem;
}

/* Mode toggle: bare icon, per Jacob — the border drops the round-1 circle
   chrome (that rule's border-color hover becomes inert). The 2.1rem box
   stays as the hit target; the small radius shapes the focus outline. */
.kb-mode-toggle {
  border: 0;
  border-radius: 0.35rem;
}

/* Re-assert the sun/moon swap at higher specificity: the .kb-icon sizing
   rule above ties the original `.kb-icon-moon { display: none }` at
   (0,1,0) and, being later in the file, would win — showing both icons.
   Same light/dark logic as the original swap block, scoped to the toggle. */
.kb-mode-toggle .kb-icon-moon { display: none; }
[data-theme='dark'] .kb-mode-toggle .kb-icon-sun { display: none; }
[data-theme='dark'] .kb-mode-toggle .kb-icon-moon { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .kb-mode-toggle .kb-icon-sun { display: none; }
  :root:not([data-theme='light']) .kb-mode-toggle .kb-icon-moon { display: block; }
}

/* Search toggle: same bare-icon treatment. A real <a> (no-JS fallback to
   /search), so it needs the button-ish box the old text link never had. */
.kb-search-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 0.35rem;
  color: var(--kb-band-text);
  flex: none;
}
.kb-search-btn:hover { color: var(--kb-band-accent); }
.kb-search-btn:focus-visible {
  outline: 2px solid var(--kb-band-accent);
  outline-offset: 2px;
}

/* Glass at rest, x while the bar is open. */
.kb-search-btn .kb-icon-close { display: none; }
#kb-header.kb-search-open .kb-icon-search { display: none; }
#kb-header.kb-search-open .kb-search-btn .kb-icon-close { display: block; }

/* Open state: the bar takes the nav row's slot in the band. The id
   selector outranks every .kb-nav-row layout rule at both breakpoints.
   The burger parks too (mobile): the panel it opens is hidden while
   searching, and a dead burger is worse than a hidden one — Esc or the x
   brings both back. */
#kb-header.kb-search-open .kb-nav-row { display: none; }
#kb-header.kb-search-open .kb-burger { display: none; }

.kb-search-bar {
  /* Zero basis, not auto: with auto the input's intrinsic width forces the
     wrapping .kb-header-inner onto a second row on phones — the x ended up
     alone under the logo. Basis 0 makes the bar take exactly the leftover
     band space at every width. */
  flex: 1 1 0%;
  min-width: 0;
}
.kb-search-bar[hidden] { display: none; }

/* Phones: give the input the whole band while searching — logo yields.
   Esc or the x brings it straight back. */
@media (max-width: 640px) {
  #kb-header.kb-search-open .kb-logo { display: none; }
}

.kb-search-input {
  width: 100%;
  font-family: inherit;
  font-size: 1rem;
  color: var(--kb-band-text);
  background: color-mix(in srgb, var(--kb-band-text) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--kb-band-text) 35%, transparent);
  border-radius: 0.4rem;
  padding: 0.45rem 0.85rem;
}
.kb-search-input::placeholder {
  color: color-mix(in srgb, var(--kb-band-text) 55%, transparent);
}
.kb-search-input:focus-visible {
  outline: 2px solid var(--kb-band-accent);
  outline-offset: 2px;
}
/* == end round-5 == */


/* == 2026-08-28 round-6: mobile band layout + full-band search == */
/* Jacob's round-6 directives (2026-08-28), all below the nav's 1024px
   breakpoint — desktop keeps its round-5 behavior exactly:
   1. The band lays out wordmark left / search + mode toggle + burger
      right: the mode toggle un-hides in the band (it used to live in the
      burger panel, whose Search/mode entries nav.html.twig no longer
      renders — the .kb-panel-actions rules earlier in this file now match
      nothing and are left inert).
   2. Open search owns the ENTIRE band: wordmark and mode toggle yield
      alongside the burger (hidden by round-5), leaving input + x as the
      band's only contents. This broadens round-5's under-640 logo-yield
      rule, which it makes redundant but harmless.
   Tokens only; the swap rules are display-only, so both color modes are
   untouched. */

@media (max-width: 1023.98px) {
  /* Overrides the earlier `.kb-header-actions .kb-mode-toggle
     { display: none }` in this query (same specificity, later wins). */
  .kb-header-actions .kb-mode-toggle { display: inline-flex; }

  #kb-header.kb-search-open .kb-logo { display: none; }
  #kb-header.kb-search-open .kb-mode-toggle { display: none; }
}
/* == end round-6 == */

/* == 2026-08-28: home page ==
   Full-width band design (Jacob-approved). Navy bands (hero, listen) ride
   the --kb-band-* tokens so they read as extensions of the header in both
   modes; surface bands use page tokens. The century strip is the signature
   element: the site's rail-and-dot idiom laid horizontally, one station per
   century, artwork behind each. #kb-main's top padding is cancelled so the
   hero sits flush under the header band. */
.kb-home { margin-top: -3rem; }
.kb-home-wrap {
  max-width: 74rem;
  margin: 0 auto;
  padding: 0 2rem;
}
@media (max-width: 640px) { .kb-home-wrap { padding: 0 1.25rem; } }
.kb-home section { padding: 4.5rem 0; }
.kb-home .kb-home-sechead { margin-bottom: 2.5rem; }
.kb-home .kb-home-sechead h2 {
  font-size: 2.1rem;
  margin: 0 0 0.5rem;
}
.kb-home .kb-home-sechead p {
  color: var(--kb-muted);
  font-size: 1.05rem;
  margin: 0;
}
.kb-home-sechead-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}
.kb-home-more {
  font-family: "Public Sans", system-ui, sans-serif;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--kb-accent);
  text-decoration: none;
}
.kb-home-more:hover { text-decoration: underline; }

/* buttons */
.kb-btn {
  display: inline-block;
  font-family: "Public Sans", system-ui, sans-serif;
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-decoration: none;
  border-radius: 999px;
  padding: 0.78rem 1.9rem;
  transition: filter 120ms ease, transform 120ms ease, background 120ms ease;
}
.kb-btn:active { transform: translateY(1px); }
.kb-btn-gold {
  background: var(--kb-band-accent);
  color: #0F1D2E;
  box-shadow: 0 2px 12px color-mix(in srgb, var(--kb-band-accent) 35%, transparent);
}
.kb-btn-gold:hover { filter: brightness(1.08); }

/* ---- hero (navy band, both modes) ---- */
.kb-home-hero {
  background:
    radial-gradient(ellipse 60% 70% at 78% 25%,
      color-mix(in srgb, var(--kb-band-accent) 12%, transparent), transparent 70%),
    var(--kb-band-bg);
  color: var(--kb-band-text);
}
.kb-home-hero .kb-btn-ghost,
.kb-home-listen .kb-btn-ghost {
  border: 1px solid color-mix(in srgb, var(--kb-band-text) 45%, transparent);
  color: var(--kb-band-text);
}
.kb-home-hero .kb-btn-ghost:hover,
.kb-home-listen .kb-btn-ghost:hover {
  border-color: var(--kb-band-accent);
  color: var(--kb-band-accent);
}
.kb-home-hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  gap: 3.5rem;
  align-items: center;
  padding-top: 2rem;
  padding-bottom: 2rem;
}
.kb-home-eyebrow {
  font-family: "Public Sans", system-ui, sans-serif;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--kb-band-accent);
  margin: 0 0 1.2rem;
}
.kb-home-headline {
  font-size: clamp(2.6rem, 5.5vw, 4.3rem);
  line-height: 1.06;
  color: var(--kb-band-text);
  margin: 0 0 1.4rem;
}
.kb-home-sub {
  font-size: 1.15rem;
  line-height: 1.65;
  color: color-mix(in srgb, var(--kb-band-text) 82%, transparent);
  max-width: 34em;
  margin: 0 0 2.2rem;
}
.kb-home-cta-row { display: flex; flex-wrap: wrap; gap: 0.9rem; }
.kb-home-portrait { margin: 0; justify-self: end; }
.kb-home-portrait img {
  display: block;
  width: min(100%, 23rem);
  height: auto;
  border: 1px solid color-mix(in srgb, var(--kb-band-accent) 60%, transparent);
  outline: 1px solid color-mix(in srgb, var(--kb-band-accent) 25%, transparent);
  outline-offset: 7px;
  filter: saturate(0.94) contrast(1.02);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.35);
}

/* ---- stats ---- */
.kb-home-stats { border-bottom: 1px solid var(--kb-hairline); }
.kb-home.kb-home .kb-home-stats { padding: 2.6rem 0; }
.kb-home-stats-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.5rem;
}
.kb-home-stat { text-align: center; }
.kb-home-stat + .kb-home-stat { border-left: 1px solid var(--kb-hairline); }
.kb-home-stat-value {
  display: block;
  font-family: 'EB Garamond', Garamond, Georgia, serif;
  font-size: 2.7rem;
  line-height: 1.1;
  color: var(--kb-accent);
}
.kb-home-stat-label {
  display: block;
  font-family: "Public Sans", system-ui, sans-serif;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--kb-muted);
  margin-top: 0.3rem;
}

/* ---- century strip (signature) ---- */
.kb-century-strip {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 1.1rem;
  position: relative;
  padding-top: 2.1rem;
}
/* the rail: one segment BETWEEN each pair of dots (none behind the tiles,
   none past the ends) — each card except the last draws a line from its dot
   center to the next card's dot center (100% of card width + the grid gap) */
.kb-century-card:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 50%;
  top: calc(-2.1rem + 5.5px); /* dot top + half its 11px border-box = center */
  transform: translateY(-50%);
  width: calc(100% + 1.1rem);
  height: 1px;
  background: color-mix(in srgb, var(--kb-accent) 55%, var(--kb-hairline));
}
.kb-century-card {
  position: relative;
  display: block;
  text-decoration: none;
  text-align: center;
}
.kb-century-dot {
  position: absolute;
  top: -2.1rem;
  left: 50%;
  translate: -50% 0;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--kb-accent);
  border: 2px solid var(--kb-accent);
  z-index: 1;
  transition: transform 140ms ease, box-shadow 140ms ease;
}
.kb-century-card:hover .kb-century-dot,
.kb-century-card:focus-visible .kb-century-dot {
  transform: scale(1.3);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--kb-accent) 25%, transparent);
}
.kb-century-img {
  display: block;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border: 1px solid var(--kb-hairline);
  border-radius: 6px;
  background: var(--kb-band-bg);
}
.kb-century-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: saturate(0.9);
  transition: transform 300ms ease, filter 300ms ease;
}
.kb-century-card:hover .kb-century-img img,
.kb-century-card:focus-visible .kb-century-img img {
  transform: scale(1.045);
  filter: saturate(1);
}
.kb-century-label {
  display: block;
  font-family: 'EB Garamond', Garamond, Georgia, serif;
  font-size: 1.3rem;
  color: var(--kb-ink);
  margin-top: 0.75rem;
  line-height: 1.2;
}
.kb-century-tag {
  display: block;
  font-family: "Public Sans", system-ui, sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--kb-muted);
  margin-top: 0.15rem;
}
.kb-century-card:hover .kb-century-label { color: var(--kb-accent); }

/* ---- collections ---- */
.kb-collection-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13.5rem, 1fr));
  gap: 1.3rem;
}
/* one row of four */
@media (min-width: 901px) {
  .kb-collection-grid { grid-template-columns: repeat(4, 1fr); }
}
.kb-collection-card {
  display: block;
  text-decoration: none;
  border: 1px solid var(--kb-hairline);
  border-radius: 8px;
  overflow: hidden;
  background: color-mix(in srgb, var(--kb-ink) 3%, var(--kb-page));
  transition: border-color 140ms ease, transform 140ms ease, box-shadow 140ms ease;
}
.kb-collection-card:hover,
.kb-collection-card:focus-visible {
  border-color: color-mix(in srgb, var(--kb-accent) 55%, var(--kb-hairline));
  transform: translateY(-3px);
  box-shadow: 0 10px 28px color-mix(in srgb, var(--kb-ink) 12%, transparent);
}
.kb-collection-img {
  display: block;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--kb-band-bg);
}
.kb-collection-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: saturate(0.9);
}
.kb-collection-card:hover .kb-collection-img img { filter: saturate(1); }
.kb-collection-body { display: block; padding: 1rem 1.1rem 1.2rem; }
.kb-collection-title {
  display: block;
  font-family: 'EB Garamond', Garamond, Georgia, serif;
  font-size: 1.35rem;
  color: var(--kb-ink);
  margin-bottom: 0.3rem;
}
.kb-collection-desc {
  display: block;
  font-size: 0.93rem;
  line-height: 1.5;
  color: var(--kb-muted);
}

/* ---- listen (navy band) ---- */
.kb-home-listen {
  background: var(--kb-band-bg);
  color: var(--kb-band-text);
}
.kb-home-listen-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: 3rem;
  align-items: center;
}
.kb-home-listen-video { margin: 0; }
.kb-home-listen-copy h2 {
  color: var(--kb-band-text);
  font-size: 2.1rem;
  margin: 0 0 1rem;
}
.kb-home-listen-piece {
  font-family: 'EB Garamond', Garamond, Georgia, serif;
  font-size: 1.35rem;
  line-height: 1.4;
  color: var(--kb-band-accent);
  margin: 0 0 1rem;
}
.kb-home-listen-piece span {
  font-family: "Public Sans", system-ui, sans-serif;
  font-size: 0.85rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--kb-band-text) 70%, transparent);
}
.kb-home-listen-blurb {
  color: color-mix(in srgb, var(--kb-band-text) 82%, transparent);
  line-height: 1.65;
  margin: 0 0 1.8rem;
}

/* ---- players ---- */
.kb-home.kb-home .kb-home-players { padding: 3rem 0 0; }
.kb-home-players-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.3rem;
}
.kb-player-card {
  display: block;
  text-decoration: none;
  border: 1px solid var(--kb-hairline);
  border-left: 4px solid var(--kb-accent);
  border-radius: 8px;
  padding: 1.4rem 1.6rem;
  transition: border-color 140ms ease, background 140ms ease;
}
.kb-player-card:hover,
.kb-player-card:focus-visible {
  background: color-mix(in srgb, var(--kb-accent) 7%, var(--kb-page));
  border-color: color-mix(in srgb, var(--kb-accent) 55%, var(--kb-hairline));
}
.kb-player-title {
  display: block;
  font-family: 'EB Garamond', Garamond, Georgia, serif;
  font-size: 1.45rem;
  color: var(--kb-ink);
  margin-bottom: 0.25rem;
}
.kb-player-desc { display: block; color: var(--kb-muted); font-size: 0.95rem; }

/* ---- blog ---- */
.kb-home-blog-track {
  display: flex;
  gap: 1.8rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  outline: none;
}
.kb-home-blog-track::-webkit-scrollbar { display: none; }
.kb-home-blog-track .kb-home-post {
  flex: 0 0 calc((100% - 3.6rem) / 3);
  scroll-snap-align: start;
}
.kb-home-blog-nav {
  display: flex;
  justify-content: flex-end;
  gap: 0.7rem;
  margin-top: 1.6rem;
}
.kb-blog-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 50%;
  border: 1px solid var(--kb-hairline);
  background: transparent;
  color: var(--kb-muted);
  cursor: pointer;
  transition: border-color 140ms ease, color 140ms ease, background 140ms ease;
}
.kb-blog-arrow svg { width: 1.2rem; height: 1.2rem; }
.kb-blog-arrow:hover,
.kb-blog-arrow:focus-visible {
  border-color: var(--kb-accent);
  color: var(--kb-accent);
  background: color-mix(in srgb, var(--kb-accent) 8%, transparent);
}
.kb-home-post {
  display: block;
  text-decoration: none;
  border-top: 1px solid var(--kb-hairline);
  padding-top: 1.1rem;
}
.kb-home-post-date {
  display: block;
  font-family: "Public Sans", system-ui, sans-serif;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--kb-accent);
  margin-bottom: 0.4rem;
}
.kb-home-post-title {
  display: block;
  font-family: 'EB Garamond', Garamond, Georgia, serif;
  font-size: 1.35rem;
  line-height: 1.3;
  color: var(--kb-ink);
  margin-bottom: 0.45rem;
}
.kb-home-post:hover .kb-home-post-title { color: var(--kb-accent); }
.kb-home-post-summary {
  display: block;
  font-size: 0.93rem;
  line-height: 1.55;
  color: var(--kb-muted);
}

/* ---- close ---- */
.kb-home-close { text-align: center; }
.kb-home-close h2 { font-size: 2.1rem; margin: 0 0 0.5rem; }
.kb-home-close p { color: var(--kb-muted); margin: 0 0 1.8rem; }

/* ---- entrance (hero only, one orchestrated moment) ---- */
@media (prefers-reduced-motion: no-preference) {
  .kb-home-hero-copy > * {
    animation: kb-home-rise 550ms ease both;
  }
  .kb-home-hero-copy > *:nth-child(2) { animation-delay: 70ms; }
  .kb-home-hero-copy > *:nth-child(3) { animation-delay: 140ms; }
  .kb-home-hero-copy > *:nth-child(4) { animation-delay: 210ms; }
  .kb-home-portrait { animation: kb-home-rise 650ms 150ms ease both; }
}
@keyframes kb-home-rise {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: none; }
}

/* ---- responsive ---- */
@media (max-width: 900px) {
  .kb-home-hero-grid { grid-template-columns: 1fr; gap: 2.2rem; }
  .kb-home-portrait { justify-self: start; }
  .kb-home-portrait img { width: min(100%, 17rem); }
  .kb-home-listen-grid { grid-template-columns: 1fr; gap: 2rem; }
  .kb-home-blog-track .kb-home-post { flex-basis: 100%; }
  /* mobile: the rail runs VERTICALLY down the left — same dots, same line,
     phone-native scrolling; a small artwork thumb + labels sit beside each
     dot. (The horizontal swipe strip read poorly on phones.) */
  .kb-century-strip { display: block; padding-top: 0.4rem; }
  .kb-century-card {
    position: relative;
    display: grid;
    grid-template-columns: 4.6rem 1fr;
    grid-template-areas: "img label" "img tag";
    align-content: center;
    column-gap: 1rem;
    row-gap: 0.1rem;
    text-align: left;
    padding: 1.25rem 0 1.25rem 2.1rem;
  }
  .kb-century-dot {
    top: 50%;
    left: 5.5px;
    translate: 0 -50%;
  }
  .kb-century-card:not(:last-child)::before {
    left: 10.5px; /* dot left 5.5px + half its 11px width, minus half the 1px line */
    top: 50%;
    transform: none;
    width: 1px;
    height: 100%;
  }
  .kb-century-img {
    grid-area: img;
    aspect-ratio: 1 / 1;
    width: 4.6rem;
  }
  .kb-century-label {
    grid-area: label;
    margin-top: 0;
    align-self: end;
    font-size: 1.6rem;
  }
  .kb-century-tag {
    grid-area: tag;
    align-self: start;
    font-size: 0.95rem;
  }
}
@media (max-width: 640px) {
  .kb-home section { padding: 3.2rem 0; }
  .kb-home-stats-grid { grid-template-columns: 1fr 1fr; gap: 1.6rem 0; }
  .kb-home-stat:nth-child(3) { border-left: none; }
  .kb-home-players-grid { grid-template-columns: 1fr; }
  .kb-home-stat-value { font-size: 2.2rem; }
}
/* == end home page == */

/* == 2026-08-28: home page — critique fixes == */
/* (5-across rule superseded by the 2+3 layout above) */
/* frame the featured recording so the navy-on-navy box reads as intentional */
.kb-home-listen-video {
  border: 1px solid color-mix(in srgb, var(--kb-band-accent) 45%, transparent);
  outline: 1px solid color-mix(in srgb, var(--kb-band-accent) 20%, transparent);
  outline-offset: 6px;
  box-shadow: 0 16px 44px rgba(0, 0, 0, 0.3);
}
/* == end critique fixes == */

/* == 2026-08-28: bibliography letter dividers ==
   A large display capital opens each letter division on the bibliography
   pages (data-letter set by ops/cleanup-2026-08/add-biblio-letter-headings.py;
   divisions restart in each alphabetized section). Two markup shapes carry
   it: the empty kb-biblio-mark spans on the main bibliographies, and the
   kb-entry-name entry spans on the mini-bibliography post. */
.kb-biblio-mark[data-letter] {
  display: flex;
  align-items: baseline;
  gap: 1.1rem;
  margin: 2.9rem 0 1.3rem;
}
.kb-biblio-mark[data-letter]::before {
  content: attr(data-letter);
  font-family: 'EB Garamond', Garamond, Georgia, serif;
  font-size: 2.9rem;
  line-height: 1;
  color: var(--kb-accent);
}
.kb-biblio-mark[data-letter]::after {
  content: "";
  flex: 1;
  height: 1px;
  background: color-mix(in srgb, var(--kb-accent) 30%, var(--kb-hairline));
  transform: translateY(-0.55rem);
}
/* first division of a section sits close under the section heading —
   trim the outsized gap the base margin would create */
h2 + p > .kb-biblio-mark[data-letter]:first-child,
h3 + p > .kb-biblio-mark[data-letter]:first-child { margin-top: 1.2rem; }
/* mini-bibliography shape: block letter above the entry, rule underneath */
.kb-entry-name[data-letter]::before {
  content: attr(data-letter);
  display: block;
  font-family: 'EB Garamond', Garamond, Georgia, serif;
  font-size: 2.9rem;
  line-height: 1;
  color: var(--kb-accent);
  border-bottom: 1px solid color-mix(in srgb, var(--kb-accent) 30%, var(--kb-hairline));
  padding-bottom: 0.4rem;
  margin: 2.9rem 0 1.3rem;
}
/* == end bibliography letter dividers == */

/* == 2026-08-28: letter divider indent fix ==
   The divider consumes its paragraph's hanging-indent first line, leaving
   that entry's every line pushed 1.5rem right. Drop the hanging treatment
   on divider paragraphs; the big letter already separates the group. */
p:has(> .kb-biblio-mark[data-letter]) {
  text-indent: 0;
  padding-left: 0;
}
/* == end == */

/* == 2026-08-28: home players image (v2: one image per card, Jacob) ==
   Each for-players card is image-topped (Hartman lithograph for Breathing,
   the BYU choir photo for the studio); the gold left rule gave way to a
   hairline border + short gold underline on the title. */
.kb-player-card {
  border-left: 1px solid var(--kb-hairline);
  overflow: hidden;
  padding: 0;
}
.kb-player-card:hover,
.kb-player-card:focus-visible {
  border-left-color: color-mix(in srgb, var(--kb-accent) 55%, var(--kb-hairline));
}
.kb-player-img {
  display: block;
  aspect-ratio: 21 / 9;
  overflow: hidden;
  background: var(--kb-band-bg);
}
.kb-player-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 30%;
  display: block;
  filter: saturate(0.95);
  transition: transform 300ms ease;
}
.kb-player-card:hover .kb-player-img img { transform: scale(1.03); }
.kb-player-body { display: block; padding: 1.3rem 1.6rem 1.5rem; }
.kb-player-title { position: relative; padding-bottom: 0.45rem; }
.kb-player-title::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 2.2rem;
  height: 2px;
  background: var(--kb-accent);
}
/* the choir photo reads best framed lower */
.kb-player-card:nth-child(2) .kb-player-img img { object-position: center 72%; }
/* == end home players image == */

/* == 2026-08-28: bibliography grouped rail ==
   One letter-set per alphabetized section; kimball-rail.js marks the group
   the reader is in (.is-current-group) and only that group's letters show —
   the A-Z follows you from section to section. Group labels are always
   visible and jump to their section. Without JS (no .js-grouped) every
   group renders stacked, so the rail never dead-ends. */
.kb-rail-group-label {
  display: block;
  font-family: "Public Sans", system-ui, sans-serif;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--kb-muted);
  text-decoration: none;
  text-align: right;
  padding-right: 1.05rem;
  margin: 0.55rem 0 0.3rem;
  white-space: nowrap;
  transition: color 140ms ease;
}
.kb-rail-group:first-child .kb-rail-group-label { margin-top: 0; }
.kb-rail-group-label:hover,
.kb-rail-group-label:focus-visible { color: var(--kb-accent); }
.kb-rail-group.is-current-group .kb-rail-group-label { color: var(--kb-accent); }
.kb-rail-group-track {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.kb-rail.js-grouped .kb-rail-group:not(.is-current-group) .kb-rail-group-track {
  display: none;
}
@media (prefers-reduced-motion: no-preference) {
  .kb-rail-group.is-current-group .kb-rail-group-track {
    animation: kb-rail-groupfade 220ms ease;
  }
}
@keyframes kb-rail-groupfade {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}
/* == end bibliography grouped rail == */

/* single-section bibliography pages: one group, no label needed */
.kb-rail-group:only-child .kb-rail-group-label { display: none; }

/* bibliography hub pages: section cards */
.kb-biblio-hub {
  display: grid;
  gap: 1.1rem;
  margin: 2.2rem 0 1rem;
}
