/* =============================================================================
   Impact Week — Programme (prototype-06-clean)
   Single stylesheet. No WordPress chrome, no cascade fights, no !important.
   Tokens at the top, components below.
   ============================================================================= */

:root {
  /* Aurora canvas — same lightness as the prototype-05 baseline but cool
     hue pushed ~5% more chromatic so the blue tint reads. */
  --bg-base:        #F0F5FB;
  --bg-grad-end:    #E5EDF7;
  --aurora-1:       rgba(95, 127, 162, 0.18);
  --aurora-2:       rgba(239, 141, 56, 0.10);
  --aurora-3:       rgba(168, 185, 212, 0.16);

  /* Ink */
  --ink-900:        #0F1B2D;
  --ink-800:        #1F2C45;
  --ink-700:        #2C3A57;
  --ink-500:        #6B7280;
  --ink-400:        #9CA3AF;
  --ink-300:        #D1D5DB;

  /* Brand accents */
  --steel-500:      #5F7FA2;
  --steel-700:      #3B5577;
  --amber-500:      #EF8D38;
  --amber-600:      #D6781F;

  /* Surfaces (glass) */
  --glass-30:       rgba(255, 255, 255, 0.30);
  --glass-55:       rgba(255, 255, 255, 0.55);
  --glass-65:       rgba(255, 255, 255, 0.65);
  --glass-80:       rgba(255, 255, 255, 0.80);
  --hairline:       rgba(15, 27, 45, 0.08);
  --hairline-soft:  rgba(15, 27, 45, 0.05);

  /* Modal surface — RGB triplet so it can drive both the panel background
     and alpha-stops inside fade gradients (linear-gradient can't mix from
     a hex/var alpha 0 to alpha 1 cleanly without going through black). */
  --modal-surface-rgb: 255, 255, 255;

  /* Shadow */
  --shadow-1:       0 1px 2px rgba(15, 27, 45, 0.04), 0 6px 16px rgba(15, 27, 45, 0.05);
  --shadow-2:       0 2px 4px rgba(15, 27, 45, 0.05), 0 14px 32px rgba(15, 27, 45, 0.10);

  /* Type — same Google Fonts as prototype-05 */
  --font:          "Funnel Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "Inter", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display:  "Funnel Display", "Funnel Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "Inter", Roboto, sans-serif;

  /* Spacing scale (4-pt) */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-7: 32px;
  --s-8: 48px;
}

/* ----------------------------------------------------------------------------
   Reset (minimal, scoped)
   --------------------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
/* Atone WP theme sets `overflow-x: hidden` on html and body, which creates a
   scroll container at the root and breaks `position: sticky` for descendants.
   `overflow-x: clip` blocks horizontal scroll without creating a scroll
   container, so sticky pins to the viewport again. */
html, body {
  overflow-x: clip !important;
  overflow-y: visible !important;
}
body {
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink-900);
  background: var(--bg-base) !important;
  min-height: 100vh;
  min-height: 100dvh;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
/* Neutralize vendored atone-style.min.css `html, body { min-width: 380px }` — clips phones <380px */
html, body { min-width: 0; }
button { font: inherit; color: inherit; }

/* ----------------------------------------------------------------------------
   Aurora background layer
   --------------------------------------------------------------------------- */

.aurora {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  /* same mesh as prototype-05: 3 soft radial tints + the pale gradient */
  background:
    radial-gradient(1200px 800px at 18% 12%, var(--aurora-1), transparent 60%),
    radial-gradient(900px 700px at 92% 88%, var(--aurora-2), transparent 65%),
    radial-gradient(700px 600px at 78% 18%, var(--aurora-3), transparent 55%),
    linear-gradient(180deg, var(--bg-base) 0%, var(--bg-grad-end) 100%);
}
.aurora__orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(60px);
  opacity: 0.55;
  contain: layout paint;
}
.aurora__orb--a {
  width: 520px; height: 520px;
  top: -120px; left: -120px;
  background: radial-gradient(circle, var(--aurora-1) 0%, transparent 70%);
}
.aurora__orb--b {
  width: 620px; height: 620px;
  bottom: -180px; right: -160px;
  background: radial-gradient(circle, var(--aurora-2) 0%, transparent 70%);
}
.aurora__orb--c {
  width: 380px; height: 380px;
  top: 38%; right: 24%;
  background: radial-gradient(circle, var(--aurora-3) 0%, transparent 70%);
}

/* ----------------------------------------------------------------------------
   Site header + footer chrome comes from styles/atone-style.min.css
   (the original WordPress theme). The overrides below recolour the masthead
   from white-on-navy (theme default) to dark-on-light so it reads against
   the aurora canvas — same rules prototype-05's zz-style-05.css used.
   --------------------------------------------------------------------------- */

/* Atone theme defaults the masthead to position:fixed / z-index:999, which
   overlays my sticky topbar. Force it into the natural document flow and
   keep its z-index below the topbar. */
#masthead,
#masthead.site-header {
  position: relative !important;
  top: 0 !important;
  left: auto !important;
  width: 100% !important;
  z-index: 5 !important;
  background: transparent !important;
  color: var(--ink-900) !important;
}
@media (max-width: 782px) {
  #masthead, #masthead.site-header { top: 0 !important; }
}
#masthead a,
#masthead .menu-item a,
#masthead .primary-menu-container a,
#masthead .primary-menu-container ul,
#masthead #primary-menu,
#masthead .menu {
  color: var(--ink-900) !important;
  text-shadow: none !important;
  font-weight: 600;
}
#masthead a:hover,
#masthead .menu-item a:hover {
  color: var(--steel-700) !important;
  opacity: 1 !important;
}
#masthead .header-menu-burger,
#masthead .header-menu-burger * { color: var(--ink-900) !important; }
#masthead .header-menu-burger .icon-wrapper > *,
#masthead .header-menu-burger span { background-color: var(--ink-900) !important; }

/* ----------------------------------------------------------------------------
   Programme tabs — two large segmented pills above the sticky topbar.
   Active drives the sidebar parent expand/collapse and schedule filter.
   --------------------------------------------------------------------------- */

.programme-tabs {
  position: relative;
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 4px;
  width: max-content;
  max-width: calc(100% - var(--s-6) * 2);
  margin: 18px auto 10px;
  padding: 4px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--glass-65);
  -webkit-backdrop-filter: blur(10px) saturate(160%);
  backdrop-filter: blur(10px) saturate(160%);
}
.programme-tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 11px;
  padding: 14px 28px;
  border-radius: 999px;
  border: 0;
  background: transparent;
  color: var(--ink-900);
  cursor: pointer;
  font: inherit;
  transition: background-color 160ms ease, color 160ms ease;
}
.programme-tab__icon {
  flex: 0 0 20px;
  width: 20px;
  height: 20px;
  color: currentColor;
}
/* Festival tab uses a filled lotus glyph with more internal padding than the
   line icons, so nudge it up a touch — scoped to Festival only, never Forum. */
.programme-tab[data-parent="festival"] .programme-tab__icon {
  flex: 0 0 24px;
  width: 24px;
  height: 24px;
}
.programme-tab__label {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1.15;
  color: currentColor;
}
.programme-tab:hover:not(.is-active) { background: var(--glass-80); }
.programme-tab.is-active {
  background: var(--ink-900);
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.programme-tab:focus-visible {
  outline: 2px solid var(--steel-500);
  outline-offset: 2px;
}
@media (max-width: 540px) {
  .programme-tabs { margin: 14px auto 8px; }
  .programme-tab { padding: 11px 20px; gap: 9px; }
  .programme-tab__label { font-size: 15px; }
  .programme-tab__icon { flex: 0 0 17px; width: 17px; height: 17px; }
  .programme-tab[data-parent="festival"] .programme-tab__icon { flex: 0 0 20px; width: 20px; height: 20px; }
}

/* ----------------------------------------------------------------------------
   Topbar — sticky. Date pills + controls only (Programme h1 is above).
   --------------------------------------------------------------------------- */

.topbar {
  position: sticky;
  top: 0;
  z-index: 100;
  background: transparent;
  border: 0;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  transition: background-color 200ms ease, box-shadow 200ms ease, backdrop-filter 200ms ease;
}
/* On scroll, give the topbar an opaque glass surface so the content
   scrolling beneath it doesn't bleed through. At the top of the page
   (no scroll), it stays fully transparent. */
.topbar.is-scrolled {
  background: transparent;                     /* no fill at all */
  -webkit-backdrop-filter: blur(60px) saturate(200%);
  backdrop-filter: blur(60px) saturate(200%);  /* pure frosted blur, no tint */
  box-shadow: none;
  border: 0;
}

/* In CALENDAR view, on scroll, collapse the secondary row entirely:
   category pills, divider, search, All-sessions dropdown, view toggle all
   hide so only the day selector + clear filter remain pinned at the top. */
body.view-calendar .topbar.is-scrolled .cat-filter-row,
body.view-calendar .topbar.is-scrolled .cal-venue-slot,
body.view-calendar .topbar.is-scrolled .topbar__divider,
body.view-calendar .topbar.is-scrolled .topbar__controls {
  display: none;
}
body.view-calendar .topbar.is-scrolled .topbar__row {
  row-gap: 0;                                  /* no leftover gap from the hidden row */
  padding-bottom: 4px;
}
.topbar__inner {
  margin: 0 auto;
  padding: 12px var(--s-6) 12px;
}
.topbar__inner { max-width: 1328px; }
/* Topbar row is identical in both views (same max-width + 24px padding) so its
   elements sit in exactly the same place when toggling list <-> calendar. The
   wider calendar grid keeps its own 40px gutters via .layout below. */
.topbar__row {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  flex-wrap: wrap;
}
.topbar__controls {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--s-2);
}

/* ----------------------------------------------------------------------------
   Date nav — horizontal pill row.
   --------------------------------------------------------------------------- */

.day-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  align-items: center;
}
.day-pill {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-width: 96px;
  padding: 8px 28px 10px;
  border-radius: 999px;          /* full capsule — previous pill shape */
  border: 1px solid transparent;
  background: transparent;
  color: var(--ink-900);
  cursor: pointer;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  transition: background-color 180ms ease, border-color 180ms ease, transform 180ms ease;
  position: relative;
}
.day-pill__top {
  display: inline-flex;
  flex-direction: row;
  align-items: baseline;
  gap: 5px;
  line-height: 1;
}
/* Hover applies ONLY when the pill is NOT active. Otherwise .day-pill:hover
   wins on specificity over .day-pill--active and you get the white-on-light
   invisibility bug right after toggling. */
.day-pill:hover:not(.day-pill--active) {
  background: var(--glass-65);
  border-color: var(--hairline);
  transform: translateY(-1px);
}
.day-pill--active:hover,
.day-pill--active:focus-visible {
  background: var(--ink-900);
  color: #fff;
}
.day-pill__wd,
.day-pill__mo {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-500);
  line-height: 1;
}
.day-pill__num {
  font-size: 20px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--ink-900);
}
.day-pill__mo { display: none; }   /* month hidden — pill shows just the date number + weekday */
.day-pill__mo { color: var(--ink-400); }

.day-pill--active {
  background: var(--ink-900);
  color: #fff;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    0 6px 18px rgba(15, 27, 45, 0.22),
    0 0 0 1px rgba(239, 141, 56, 0.30);
}
.day-pill--active .day-pill__wd  { color: rgba(255, 255, 255, 0.65); }
.day-pill--active .day-pill__num { color: #fff; }
.day-pill--active .day-pill__mo  { color: rgba(255, 255, 255, 0.55); }
.day-pill--active::after { content: none; }

/* ----------------------------------------------------------------------------
   Controls: search + mode button
   --------------------------------------------------------------------------- */

.search {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  padding: 8px 14px 8px 12px;
  border-radius: 18px;
  background: var(--glass-65);
  border: 1px solid var(--hairline);
  color: var(--ink-500);
  transition: background-color 180ms ease, border-color 180ms ease, box-shadow 180ms ease;
  min-width: 240px;
}
.search:focus-within {
  background: var(--glass-80);
  border-color: var(--steel-500);
  box-shadow: 0 0 0 3px rgba(95, 127, 162, 0.18);
}
.search input {
  border: 0;
  outline: 0;
  background: transparent;
  font-size: 14px;
  color: var(--ink-900);
  flex: 1 1 140px;
  min-width: 100px;
  padding: 0;
}
.search input::placeholder { color: var(--ink-400); }

/* Tag chips — committed search filters living inside the search bar.
   Sit between the search icon and the text input. Whole chip is clickable
   to remove; the inline x icon is a visual cue. */
.search-tags {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.search-tags:empty { display: none; }
.search-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 220px;
  padding: 4px 8px 4px 10px;
  border-radius: 999px;
  background: rgba(239, 141, 56, 0.10);
  border: 1px solid rgba(239, 141, 56, 0.35);
  color: var(--ink-900);
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
}
.search-tag:hover {
  background: rgba(239, 141, 56, 0.18);
  border-color: var(--amber-500);
}
.search-tag:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px rgba(239, 141, 56, 0.45);
}
.search-tag--venue {
  background: rgba(95, 127, 162, 0.10);
  border-color: rgba(95, 127, 162, 0.35);
}
.search-tag--venue:hover {
  background: rgba(95, 127, 162, 0.18);
  border-color: var(--steel-500);
}
.search-tag > svg:first-child { color: var(--steel-700); flex: 0 0 12px; }
.search-tag > svg:last-child  { color: var(--ink-500); flex: 0 0 12px; }
.search-tag:hover > svg:last-child { color: var(--ink-800); }
.search-tag__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* The search input sits inside a wrapper; the dropdown is its sibling so we
   can position it absolutely beneath the input. */
.search { position: relative; }
.search-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  min-width: 100%;
  max-width: 360px;
  background: #fff;
  border: 1px solid var(--hairline);
  border-radius: 14px;
  box-shadow: var(--shadow-2);
  padding: 6px;
  z-index: 80;
  max-height: 380px;
  overflow-y: auto;
}
.search-dropdown[hidden] { display: none; }
.search-dropdown__group-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--ink-400);
  padding: 10px 10px 6px;
}
.search-dropdown__group-label + .search-dropdown__group-label { margin-top: 4px; }
.search-dropdown__item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  background: transparent;
  border: 0;
  border-radius: 8px;
  font: inherit;
  font-size: 13px;
  color: var(--ink-800);
  text-align: left;
  cursor: pointer;
  transition: background-color 140ms ease, color 140ms ease;
}
.search-dropdown__item:hover,
.search-dropdown__item.is-active {
  background: rgba(239, 141, 56, 0.10);
  color: var(--ink-900);
}
.search-dropdown__item-icon {
  flex: 0 0 14px;
  color: var(--steel-700);
}
.search-dropdown__item-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.search-dropdown__item-meta {
  flex: 0 0 auto;
  font-size: 11px;
  color: var(--ink-400);
}
.search-dropdown__empty {
  padding: 14px;
  font-size: 13px;
  color: var(--ink-400);
  text-align: center;
}

/* Venue filter chip — sits before the category chips in the topbar. Uses
   a pin icon prefix and an inline ✕ for one-click clear. */
.venue-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  background: var(--glass-65);
  border: 1px solid var(--steel-500);
  border-radius: 999px;
  color: var(--ink-900);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 160ms ease, border-color 160ms ease;
}
.venue-chip:hover {
  background: var(--glass-80);
  border-color: var(--steel-700);
}
.venue-chip:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px rgba(239, 141, 56, 0.45);
}
.venue-chip > svg:first-child { color: var(--steel-700); flex: 0 0 12px; }
.venue-chip > svg:last-child { color: var(--ink-500); flex: 0 0 12px; }

/* View toggle (List ↔ Calendar) — segmented pill of two icon buttons. */
.view-toggle {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--glass-65);
  padding: 2px;
  gap: 0;
  -webkit-backdrop-filter: blur(10px) saturate(160%);
  backdrop-filter: blur(10px) saturate(160%);
}
.view-toggle__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ink-900);
  border-radius: 999px;
  cursor: pointer;
  transition: background-color 160ms ease, color 160ms ease, transform 160ms ease;
}
.view-toggle__btn:hover:not(.is-active) {
  background: var(--glass-80);
  transform: translateY(-1px);
}
.view-toggle__btn.is-active {
  background: var(--ink-900);
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.view-toggle__btn svg { display: block; flex: 0 0 auto; }
.view-toggle__btn:focus-visible {
  outline: 3px solid var(--steel-500);
  outline-offset: 2px;
}

/* ----------------------------------------------------------------------------
   Layout — sidebar + schedule
   --------------------------------------------------------------------------- */

.layout {
  position: relative;
  z-index: 1;
  margin: 0 auto;
  padding: var(--s-5) var(--s-6) var(--s-8);
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr);
  gap: var(--s-6);
  align-items: start;
}
body:not(.view-calendar) .layout { max-width: 1328px; }
body.view-calendar .layout { padding-left: 40px; padding-right: 40px; }

@media (max-width: 1023px) {
  .layout {
    grid-template-columns: 1fr;
  }
}

/* ----------------------------------------------------------------------------
   Sidebar
   --------------------------------------------------------------------------- */

.sidebar {
  position: sticky;
  top: 100px;                              /* sits just below the sticky topbar */
  align-self: start;
  max-height: calc(100vh - 120px);          /* scrolls internally if chips overflow */
  max-height: calc(100dvh - 120px);
  overflow-y: auto;
  padding-right: 4px;
  /* Subtle scrollbar so it doesn't fight the aurora canvas */
  scrollbar-width: thin;
  scrollbar-color: rgba(15, 27, 45, 0.20) transparent;
}
.sidebar::-webkit-scrollbar { width: 6px; }
.sidebar::-webkit-scrollbar-thumb {
  background: rgba(15, 27, 45, 0.20);
  border-radius: 999px;
}
.sidebar::-webkit-scrollbar-track { background: transparent; }
@media (max-width: 1023px) {
  .sidebar { position: static; max-height: none; overflow: visible; }
}

.filter-group {
  margin-bottom: var(--s-5);
}
.filter-group__label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-500);
  margin-bottom: var(--s-2);
}

/* Venue filter group's head row: label on the left, Clear filter on the
   right. The Clear button moved up from the bottom scope row. */
.filter-group--venue .filter-group__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: var(--s-2);
}
.filter-group--venue .filter-group__head .filter-group__label {
  margin-bottom: 0;
}

/* Collapsible parent group (Forum / Festival) wrapping subsections. */
.filter-group--parent { margin-bottom: var(--s-5); }
.filter-group--parent .filter-group__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 8px 4px;
  margin-bottom: var(--s-2);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--hairline);
  color: var(--ink-800);
  cursor: pointer;
  font: inherit;
  text-align: left;
  transition: color 160ms ease;
}
.filter-group--parent .filter-group__toggle:hover { color: var(--ink-900); }
.filter-group--parent .filter-group__toggle-label {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--ink-900);
}
.filter-group--parent .filter-group__caret {
  color: var(--ink-500);
  transition: transform 200ms ease;
}
.filter-group--parent .filter-group__toggle[aria-expanded="true"] .filter-group__caret {
  transform: rotate(180deg);
}
.filter-group__body[hidden] { display: none; }
.filter-group--parent .filter-group__body > .filter-group {
  margin-bottom: var(--s-4);
  padding: 12px 12px 14px;
  background: rgba(15, 27, 45, 0.05);
  border: 1px solid var(--hairline);
  border-radius: 14px;
}
.filter-group--parent .filter-group__body > .filter-group:last-child {
  margin-bottom: 0;
}
.filter-group--parent .filter-group__body > .filter-group > .filter-group__sub-toggle .filter-group__label,
.filter-group--parent .filter-group__body > .filter-group > .filter-group__label {
  font-size: 10px;
  letter-spacing: 0.12em;
  color: var(--ink-400);
  margin-bottom: 8px;
}

/* Living Habits — steel blue, matching the Partner-led accent. */
.filter-group--living .chip--active {
  background: linear-gradient(135deg, var(--steel-500), var(--steel-700));
  color: #fff;
  border-color: transparent;
}
.filter-group--living .chip--active .chip__icon  { color: #fff; }
.filter-group--living .chip--active .chip__count { color: rgba(255, 255, 255, 0.80); }
.filter-group--living .chip__icon { color: var(--steel-700); }

/* Partner-led active chip — neutral steel fill to keep it distinct from
   the ecosystem (amber) and infrastructure (green) parents. */
.filter-group--partner .chip--active {
  background: linear-gradient(135deg, var(--steel-500), var(--steel-700));
  color: #fff;
  border-color: transparent;
}
.filter-group--partner .chip--active .chip__icon  { color: #fff; }
.filter-group--partner .chip--active .chip__count { color: rgba(255, 255, 255, 0.80); }
.filter-group--partner .chip__icon { color: var(--steel-700); }
/* China Track active chip — crimson fill, distinct from partner (steel),
   ecosystem (amber) and infrastructure (green). */
.filter-group--china .chip--active {
  background: linear-gradient(135deg, #C0392B, #8E2A1F);
  color: #fff;
  border-color: transparent;
}
.filter-group--china .chip--active .chip__icon  { color: #fff; }
.filter-group--china .chip--active .chip__count { color: rgba(255, 255, 255, 0.80); }
.filter-group--china .chip__icon { color: #8E2A1F; }

/* China Track language badge — shown only on China Track sessions to flag
   whether a session is Chinese-only, English-only, or English with live
   Chinese translation. Glyph pills, sized to sit inline in meta rows. */
.lang-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: 0.02em;
  white-space: nowrap;
  border: 1px solid transparent;
}
.lang-pill--zh   { background: linear-gradient(135deg, #C0392B, #8E2A1F); color: #fff; }
.lang-pill--en   { background: transparent; color: var(--steel-700); border-color: var(--steel-500); }
.lang-pill--enzh { background: rgba(192, 57, 43, 0.08); color: #8E2A1F; border-color: #C0392B; }
.modal__facts-lang { display: flex; align-items: center; gap: 8px; }
.modal__facts-lang-text { font-size: 13px; color: var(--steel-700); }
.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.chip-col {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: var(--glass-55);
  color: var(--ink-800);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 180ms ease, border-color 180ms ease, transform 180ms ease;
  text-align: left;
}
/* Hover applies ONLY when chip is not active — otherwise the .chip:hover
   selector wins on specificity over .chip--active and you get white text on
   a near-white hover background (the invisibility bug on toggle). */
.chip:hover:not(.chip--active) {
  background: var(--glass-80);
  transform: translateY(-1px);
}
.chip--active {
  background: linear-gradient(135deg, var(--steel-500), var(--steel-700));
  color: #fff;
  border-color: transparent;
}
.chip--active:hover,
.chip--active:focus-visible {
  /* Keep the active fill on hover so text never sits on light glass. */
  filter: brightness(1.05);
}
.chip--active .chip__icon  { color: #fff; }
.chip--active .chip__count { color: rgba(255, 255, 255, 0.75); }

/* Group-specific active state: Ecosystem → amber gradient (matches the
   amber icon colour of the group), Infrastructure → green gradient. */
.filter-group--ecosystem .chip--active {
  background: linear-gradient(135deg, var(--amber-500) 0%, var(--amber-600) 100%);
  color: #fff;
}
.filter-group--ecosystem .chip--active .chip__icon  { color: #fff; }
.filter-group--ecosystem .chip--active .chip__count { color: rgba(255, 255, 255, 0.80); }

.filter-group--infrastructure .chip--active {
  background: linear-gradient(135deg, #10654B 0%, #0B4A37 100%);
  color: #fff;
}
.filter-group--infrastructure .chip--active .chip__icon  { color: #fff; }
.filter-group--infrastructure .chip--active .chip__count { color: rgba(255, 255, 255, 0.80); }

.chip-col .chip {
  width: 100%;
  justify-content: flex-start;
}

/* Chip icon — colour-coded per group */
.chip__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 16px;
  width: 16px;
  height: 16px;
  color: var(--steel-700);
}
.filter-group--ecosystem      .chip__icon { color: var(--amber-500); }
.filter-group--infrastructure .chip__icon { color: #10654B; }   /* leafy green */

.chip__count {
  margin-left: auto;
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-400);
}
.chip--active .chip__count { color: rgba(255, 255, 255, 0.65); }

/* ----------------------------------------------------------------------
   Sidebar Venue multi-select
   Trigger holds selected venues as pills (reuses .search-tag--venue).
   Panel reveals a search input + checkbox list of venues.
   ----------------------------------------------------------------------*/
.venue-select { position: relative; }

.venue-select__trigger {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: var(--s-2);
  width: 100%;
  min-height: 40px;
  padding: 4px 14px 4px 12px;
  border-radius: 18px;
  border: 1px solid var(--hairline);
  background: var(--glass-65);
  color: var(--ink-500);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: text;
  transition: background-color 180ms ease, border-color 180ms ease;
  position: relative;
}
.venue-select__trigger.is-filled { cursor: pointer; }
.venue-select__trigger:hover { background: var(--glass-80); }
.venue-select__trigger[aria-expanded="true"] { background: var(--glass-80); }
.venue-select__pills {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  align-items: center;
  flex: 1 1 0;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.venue-select__pills::-webkit-scrollbar { display: none; }
.venue-select__pills:empty { display: none; }
.venue-select__pill { flex: 0 0 auto; }
.venue-select__trigger.is-filled .venue-select__input { display: none; }

/* Inline search input lives inside the trigger and grows to fill remaining
   space so any empty area is effectively the input's hit target. Visual
   parity with .search input. */
/* Specificity has to beat input[type=search]{border:1px solid #ccc;...} from
   the WordPress theme stylesheet — otherwise the input renders an inset
   rectangle inside our pill. */
.venue-select__trigger input.venue-select__input,
.venue-select__trigger input[type="search"].venue-select__input,
.venue-select__trigger input[type="text"].venue-select__input {
  flex: 1 1 100px;
  min-width: 80px;
  border: 0;
  outline: 0;
  box-shadow: none;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  font: inherit;
  font-size: 14px;
  color: var(--ink-900);
  padding: 0;
  border-radius: 0;
  height: auto;
}
.venue-select__trigger input.venue-select__input:focus,
.venue-select__trigger input.venue-select__input:focus-visible { outline: 0; box-shadow: none; }
.venue-select__trigger input.venue-select__input::placeholder { color: var(--ink-400); }
.venue-select__trigger input.venue-select__input::-webkit-search-cancel-button,
.venue-select__trigger input.venue-select__input::-webkit-search-decoration { display: none; appearance: none; -webkit-appearance: none; }

.venue-select__caret-btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  margin-left: auto;
  border: 0;
  background: transparent;
  color: var(--ink-500);
  cursor: pointer;
  transition: color 160ms ease;
}
@media (max-width: 720px) {
  /* 18x18 is well under the 44px tap-target guideline. Growing the box
     itself would force the venue-select row taller (it's a flex child
     sized by align-items:center), so instead keep the 18x18 box in flow
     (row geometry untouched) and extend the hit area with an invisible
     absolutely-positioned ::after — clicks anywhere in the 44x44 zone
     still land on the button. */
  .venue-select__caret-btn { position: relative; }
  .venue-select__caret-btn::after {
    content: "";
    position: absolute;
    inset: -13px;
  }
}
.venue-select__caret-btn:hover { color: var(--ink-800); }
.venue-select__caret-btn:focus-visible { outline: none; }
.venue-select__caret {
  transition: transform 180ms ease;
}
.venue-select__trigger[aria-expanded="true"] .venue-select__caret {
  transform: rotate(180deg);
}
.venue-select__trigger[aria-expanded="true"] .venue-select__caret-btn {
  color: var(--steel-700);
}

.venue-select__panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 70;
  background: #fff;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  box-shadow: var(--shadow-2);
  padding: 8px;
  max-height: 320px;
  display: flex;
  flex-direction: column;
}
.venue-select__panel[hidden] { display: none; }

.venue-select__list {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  flex: 1 1 auto;
}
/* Row mirrors .search-dropdown__item from the topbar so the two pickers
   feel identical. Click toggles selection. */
.venue-select__item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  background: transparent;
  border: 0;
  border-radius: 8px;
  font: inherit;
  font-size: 13px;
  color: var(--ink-800);
  text-align: left;
  cursor: pointer;
  transition: background-color 140ms ease, color 140ms ease;
}
.venue-select__item:hover,
.venue-select__item.is-active {
  background: rgba(239, 141, 56, 0.10);
  color: var(--ink-900);
}
.venue-select__item.is-selected {
  background: rgba(95, 127, 162, 0.10);
  color: var(--ink-900);
  font-weight: 600;
}
.venue-select__item.is-selected:hover { background: rgba(95, 127, 162, 0.18); }
.venue-select__item.is-highlighted { background: rgba(239, 141, 56, 0.18); }
.venue-select__item:active { opacity: 0.7; }

.venue-select__clear-row {
  border-top: 1px solid rgba(15, 27, 45, 0.08);
  margin-top: 4px;
  padding-top: 4px;
  list-style: none;
}
.venue-select__clear {
  display: block;
  width: 100%;
  background: transparent;
  border: 0;
  padding: 8px 14px;
  color: var(--steel-700);
  font-size: 12px;
  font-weight: 600;
  text-align: center;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.venue-select__clear:hover { color: var(--steel-900); text-decoration-thickness: 2px; }
.venue-select__clear:active { opacity: 0.7; }
.venue-select__item-icon {
  flex: 0 0 14px;
  width: 14px;
  height: 14px;
  color: var(--steel-700);
}
.venue-select__item.is-selected .venue-select__item-icon { color: var(--steel-700); }
.venue-select__item-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.venue-select__item-count {
  flex: 0 0 auto;
  font-size: 11px;
  color: var(--ink-400);
}
.venue-select__empty {
  padding: 14px;
  font-size: 13px;
  color: var(--ink-400);
  text-align: center;
}

/* Accordion: venues fold into collapsible families (Sanctuary / Impact Lab /
   Impact Forum / Other). Header toggles its body; selected venues show a count
   badge on the header so a collapsed group still signals what's active. */
.venue-select__group-head {
  list-style: none;
  border-top: 1px solid var(--hairline);
  margin-top: 2px;
  padding-top: 2px;
}
.venue-select__group-head:first-child {
  border-top: 0;
  margin-top: 0;
  padding-top: 0;
}
.venue-select__group-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 10px;
  background: transparent;
  border: 0;
  border-radius: 8px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-900);
  text-align: left;
  cursor: pointer;
  transition: background-color 140ms ease;
}
.venue-select__group-toggle:hover { background: rgba(239, 141, 56, 0.08); }
.venue-select__group-toggle:active { opacity: 0.7; }
.venue-select__group-caret {
  flex: 0 0 14px;
  width: 14px;
  height: 14px;
  color: var(--ink-500);
  transition: transform 160ms ease;
}
.venue-select__group-head.is-open .venue-select__group-caret { transform: rotate(180deg); }
.venue-select__group-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.venue-select__group-badge {
  flex: 0 0 auto;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: var(--steel-700);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.venue-select__group-count {
  flex: 0 0 auto;
  font-size: 11px;
  color: var(--ink-400);
}
.venue-select__group-body { list-style: none; }
.venue-select__group-body[hidden] { display: none; }
.venue-select__group-list {
  list-style: none;
  margin: 0;
  padding: 0 0 4px;
}
.venue-select__group-list .venue-select__item { padding-left: 24px; }

/* Hide venue pills inside the topbar search bar: venue selection now lives
   entirely in the sidebar trigger so the topbar stays a pure text search. */
.search .search-tag--venue { display: none; }

.btn-clear {
  background: transparent;
  border: 0;
  padding: 4px 0;
  color: var(--steel-700);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  white-space: nowrap;
}
.btn-clear:hover {
  color: var(--steel-900);
  text-decoration-thickness: 2px;
}
.btn-clear:active { opacity: 0.7; }

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Scope row: amber gradient line above (same vocab as the bucket eyebrow),
   then "Showing X of Y sessions" on the left and "Clear filter" on the right. */
.scope-row {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  margin-top: var(--s-5);
  padding-top: 12px;
}
.scope-row::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 1px;
  background: linear-gradient(90deg,
    var(--amber-500) 0%,
    rgba(239, 141, 56, 0.65) 35%,
    rgba(239, 141, 56, 0.18) 65%,
    rgba(239, 141, 56, 0) 100%);
}
.scope {
  font-size: 12px;
  color: var(--ink-500);
}
.scope strong {
  color: var(--ink-900);
  font-weight: 700;
}

/* ----------------------------------------------------------------------------
   Schedule
   --------------------------------------------------------------------------- */

.schedule {
  min-width: 0;
}

.day {
  padding-top: var(--s-4);
  margin-bottom: var(--s-6);
  border-top: 1px solid var(--hairline);
}
.day:first-child { border-top: 0; padding-top: 0; }

.day__heading {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  margin: 0 0 var(--s-4);
}
.day__title {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink-900);
  margin: 0;
}
.day__count {
  font-size: 13px;
  color: var(--ink-500);
  font-weight: 500;
}

/* Time-bucket eyebrow — navy uppercase label followed by an amber gradient line
   that fills the remaining row width and fades to transparent on the right. */
.bucket {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding: 14px 0 10px;
  margin-top: var(--s-4);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-900);
}
.bucket::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg,
    var(--amber-500) 0%,
    rgba(239, 141, 56, 0.65) 35%,
    rgba(239, 141, 56, 0.18) 65%,
    rgba(239, 141, 56, 0) 100%);
}
.bucket:first-child { margin-top: 0; }

/* ----------------------------------------------------------------------------
   Event card  ★ THE IMPORTANT ONE ★
   Flexbox row: time / content / star. No grid. No revert. No !important.
   --------------------------------------------------------------------------- */

.event {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: var(--s-4);
  margin: 10px 0;
  /* Fixed height so the square KV fills the card with no trailing white space.
     Text is clamped (title/desc) to fit. Released to auto on mobile below. */
  height: 152px;
  overflow: hidden;
  padding: var(--s-4);
  background: var(--glass-65);
  border: 1px solid var(--hairline);
  border-radius: 16px;
  box-shadow: var(--shadow-1);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  backdrop-filter: blur(14px) saturate(160%);
  transition: background-color 180ms ease, border-color 180ms ease, transform 220ms ease, box-shadow 220ms ease;
}
.event:hover {
  background: var(--glass-80);
  border-color: rgba(15, 27, 45, 0.12);
  transform: translateY(-2px);
  box-shadow: var(--shadow-2);
}

/* Key visual: square type image that stretches to the full card height so its
   bottom edge always aligns with the bottom-anchored meta row — equal white
   space beneath both, regardless of title/description length. Width is pinned
   to the content-box height (152px card − 2×16px padding = 120px) to stay
   square. Released to a fixed 84px on mobile below. */
.event__kv {
  flex: 0 0 120px;
  width: 120px;
  align-self: stretch;
  height: auto;
  border-radius: 12px;
  overflow: hidden;
  background: var(--glass-80);
  border: 1px solid var(--hairline);
}
.event__kv img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.event__time {
  flex: 0 0 88px;
  width: 88px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* Left pad so the left-aligned time + icon block reads as visually centered
     in the 88px column (box-sizing: border-box). Tuned to center a 2-icon row
     (2×30px cat + 6px gap = 66px): (88 − 66) / 2 = 11px. Reset on mobile below. */
  padding-top: 2px;
  padding-left: 11px;
  font-variant-numeric: tabular-nums;
}
/* Start/end wrapper — invisible on desktop (stacks like before); becomes the
   glass time pill overlaid on the banner image on mobile. */
.event__time-pill {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.event__time-start {
  font-size: 15px;
  font-weight: 700;
  color: var(--ink-900);
  line-height: 1.2;
}
.event__time-end {
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-500);
  line-height: 1.2;
}

.event__content {
  flex: 1 1 0%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* Fill the full card height so the meta row can pin to the bottom. */
  align-self: stretch;
}
.event__title {
  margin: 0;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.35;
  color: var(--ink-900);
  word-wrap: break-word;
  /* Clamp to 2 lines with ellipsis so the fixed card height holds. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.event__desc {
  margin: 0;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.5;
  color: var(--ink-500);
  /* Clamp teaser to 2 lines with ellipsis to fit the fixed card height. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Type row: [type icon] [bold type label] [eco circle] [infra circle]
              · [pin] [venue text]  — all on one wrap-friendly row */
.event__type-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  /* Pin to the bottom of the content column so it aligns with the KV's bottom
     edge. On mobile (height auto) this resolves to no extra space. */
  margin: 2px 0 0;
  margin-top: auto;
  font-size: 14px;
  color: var(--ink-900);
}
.event__type-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-700);
  flex: 0 0 auto;
}
.event__type-icon svg { display: block; width: 16px; height: 16px; flex: 0 0 auto; }
.event__type-label {
  font-weight: 700;
  letter-spacing: 0;
  color: var(--ink-900);
  flex: 0 0 auto;
}

/* Category row: lives at the bottom of the time column, circles in a single
   horizontal row (side by side, no wrap). */
.event__cat-row {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  row-gap: 6px;
  margin: 10px 0 0;
  max-width: 102px;
}

/* Category circle — outlined circle, border colour = icon colour.
   Icon inside is the same 16-px SVG the sidebar chip uses, same stroke
   weight, so the two read as the same component at different scales. */
.event__cat {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: transparent;
  border: 1.5px solid currentColor;
  flex: 0 0 30px;
  box-sizing: border-box;
}
.event__cat svg,
.event__cat .chip__icon {
  display: block;
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  color: currentColor;
}
.event__cat--eco    { color: var(--amber-500); }   /* amber border + icon */
.event__cat--infra  { color: #10654B; }            /* green border + icon */
.event__cat--living { color: var(--steel-700); }   /* steel blue border + icon */
/* Overflow chip shown in place of the 4th+ icons (e.g. "+2"). */
.event__cat--more {
  color: var(--ink-500);
  border-color: var(--hairline);
  background: var(--glass-80);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.01em;
}

/* Inline venue inside the type row */
.event__venue-inline {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--ink-500);
  font-size: 14px;
  font-weight: 400;
}
.event__venue-inline svg {
  flex: 0 0 14px;
  width: 14px;
  height: 14px;
  color: var(--ink-400);
}
.event__venue-text { color: var(--ink-500); }
.event__meta-sep {
  width: 1px;
  height: 14px;
  background: var(--hairline);
  flex: 0 0 1px;
  display: inline-block;
}

/* Empty state — transparent container, no border, no fill. */
.empty {
  text-align: center;
  padding: 40px 24px;
  background: transparent;
  border: 0;
  border-radius: 0;
  margin: 20px 0;
}
.empty h3 {
  margin: 0 0 6px;
  font-size: 18px;
  font-weight: 600;
  color: var(--ink-900);
}
.empty p {
  margin: 0;
  color: var(--ink-500);
}

/* Footer chrome comes from atone-style.min.css; no overrides needed. */

/* ----------------------------------------------------------------------------
   Calendar view — topbar chip row, layout swap, grid, collapsed cards.
   --------------------------------------------------------------------------- */

/* View-mode visibility */
.cat-filter-row,
.day-selector,
.topbar__divider { display: none; }
/* Day-pills now show in both views — the single-select pill row drives
   state.dayIndex in calendar mode too. The legacy multi-day segment bar is
   retired and renders nothing. */
body.view-calendar .sidebar { display: none; }
body.view-calendar .layout  { grid-template-columns: 1fr; }

/* Calendar topbar: row 1 = day selector full-width.
   Row 2 = category chips (left) | vertical divider | controls (right). */
/* 5-column grid: cats | flexible space | divider | flexible space | controls
   The two 1fr spacers split the empty area equally, so the divider always
   sits centred with the same gap on its left (cats → divider) and its
   right (divider → controls). */
body.view-calendar .topbar__row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas:
    "selector clear  controls"   /* row 1: pills left, clear filter centered, controls right */
    "cats     cats   cats"       /* row 2: centered chip stack */
    "venue    venue  venue";     /* row 3: venue sits below the category pills */
  column-gap: 0;
  row-gap: 16px;
  align-items: center;
}
/* Pin each child to its named area. Without explicit grid-area, auto-placement
   spills items across columns and the row-1 list-view parity breaks. */
body.view-calendar .day-nav         { grid-area: selector; }
body.view-calendar .topbar__controls { grid-area: controls; }
body.view-calendar .cal-venue-slot  { grid-area: venue; justify-self: center; }
body.view-calendar .cat-filter-row  { grid-area: cats; }
/* Legacy multi-day segment bar retired: stays hidden in both views.
   The single-select day-pills (#dayNav) take its place in the calendar
   topbar grid-area. */
.day-selector-wrap { display: none !important; }
.btn-clear--cal    { display: none; }
.topbar__hr        { display: none !important; }
/* Single topbar Clear-filter button, shown in BOTH views in the same spot
   (right after the day pills). In calendar view it occupies the grid "clear"
   area; in the list-view flex row that property is simply ignored. */
.btn-clear--cal:not([hidden]) {
  display: inline-flex;
  align-items: center;
  grid-area: clear;
  justify-self: start;
  margin-left: 16px;   /* calendar grid has column-gap 0, so this sets the gap from the day pills */
  padding: 8px 12px;
  min-height: 32px;
}
/* List view's flex row already contributes a 16px gap before the button, so
   drop the margin there — both views land the clear at the same x. */
body:not(.view-calendar) .btn-clear--cal:not([hidden]) { margin-left: 0; }
/* Row 1 mirrors list view: pills hug the left, controls hug the right. */
body.view-calendar .day-nav        { justify-content: flex-start; }
body.view-calendar .topbar__controls { margin-left: 0; }

/* Calendar-view venue slot: empty in list mode; in calendar mode it hosts
   the relocated .venue-select element. Sits centered, on its own row
   directly above the chip stack. */
.cal-venue-slot { display: none; }
body.view-calendar .cal-venue-slot {
  display: flex;
  align-items: center;
  justify-content: center;
  width: clamp(260px, 40vw, 420px);
}
body.view-calendar .cal-venue-slot .venue-select { width: 100%; }
/* Three balanced columns: Ecosystems | Infrastructures | (Partner-led + China
   Track stacked). align-items: stretch makes all three columns share the
   tallest height so the row reads as one tidy band. */
body.view-calendar .cat-filter-row  {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-items: stretch;
  gap: 16px;
  max-width: 1200px;
  margin: 0 auto;
}
/* Festival tab (Living Habits only) and the no-tab state span the full width. */
body.view-calendar .cat-filter-row .filter-group--living { grid-column: 1 / -1; }

/* Third column: Partner-led + China Track as two separate stacked cards that
   split the column height evenly, so they stay visually distinct yet balanced. */
body.view-calendar .cat-filter-row__side {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
body.view-calendar .cat-filter-row__side .cat-filter-row__group { flex: 1 1 0; }

/* Every group is a card matching the list-view sidebar (bg + hairline border). */
body.view-calendar .cat-filter-row__group {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  padding: 12px 12px 14px;
  background: rgba(15, 27, 45, 0.05);
  border: 1px solid var(--hairline);
  border-radius: 14px;
}
body.view-calendar .cat-filter-row__group .filter-group__label {
  margin: 0;
  text-align: left;
}
/* Chip block fills the card and centers vertically so stretched cards stay tidy. */
body.view-calendar .cat-filter-row__chips {
  display: grid;
  gap: 6px;
  flex: 1 1 auto;
  align-content: center;
}
/* Ecosystems & Infrastructures: 2x2 grid -> identical height. */
body.view-calendar .cat-filter-row .filter-group--ecosystem .cat-filter-row__chips,
body.view-calendar .cat-filter-row .filter-group--infrastructure .cat-filter-row__chips {
  grid-template-columns: 1fr 1fr;
}
/* Living habits (festival): wider multi-column grid across the full row. */
body.view-calendar .cat-filter-row .filter-group--living .cat-filter-row__chips {
  grid-template-columns: repeat(4, 1fr);
}
/* Full-width chips inside the cards, mirroring the sidebar .chip-col .chip. */
body.view-calendar .cat-filter-row__chips .chip {
  width: 100%;
  justify-content: flex-start;
  white-space: nowrap;
}
.cat-filter-row__sep { display: none !important; }   /* no longer used */
/* Vertical divider retired in the new 3-row calendar topbar layout. */
body.view-calendar .topbar__divider { display: none; }

/* ----- Day selector: pills inside a glass container ------------------- */
.day-selector {
  display: inline-flex;
  align-items: center;
  gap: 12px;                          /* matches calendar grid column gap */
  border: 1px solid var(--hairline);
  background: var(--glass-65);
  padding: 6px 10px;
  border-radius: 999px;
  -webkit-backdrop-filter: blur(10px) saturate(160%);
  backdrop-filter: blur(10px) saturate(160%);
}
.day-seg {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-width: 0;                 /* let the grid cell size the pill */
  width: 100%;
  padding: 8px 12px 10px;        /* moderate padding; pill stretches via grid */
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  color: var(--ink-900);
  cursor: pointer;
  position: relative;
  font-variant-numeric: tabular-nums;
  transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease, transform 160ms ease;
}
/* Hairline divider in the gap between two unselected pills. Auto-hides when
   either neighbour fills navy. */
.day-seg + .day-seg::before {
  content: "";
  position: absolute;
  left: -9px;                /* centre of the 18px gap */
  top: 28%;
  bottom: 28%;
  width: 1px;
  background: rgba(15, 27, 45, 0.18);
  pointer-events: none;
}
.day-seg.is-on::before,
.day-seg.is-on + .day-seg::before {
  content: none;
}
.day-seg:hover:not(.is-on) {
  background: var(--glass-65);
  border-color: var(--hairline);
  transform: translateY(-1px);
}
.day-seg.is-on {
  background: var(--ink-900);
  color: #fff;
}
.day-seg.is-on:hover { background: var(--ink-800); }
/* Same typography as .day-pill in list view */
.day-seg__num {
  font-size: 20px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--ink-900);
}
.day-seg__wd {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-500);
  line-height: 1;
}
.day-seg.is-on .day-seg__num { color: #fff; }
.day-seg.is-on .day-seg__wd  { color: rgba(255, 255, 255, 0.65); }
.day-seg:focus-visible {
  outline: 3px solid var(--steel-500);
  outline-offset: 2px;
  z-index: 1;
}
.day-seg.shake {
  animation: shakeSeg 360ms ease;
}
@keyframes shakeSeg {
  0%, 100% { transform: translateX(0); }
  25%      { transform: translateX(-3px); }
  50%      { transform: translateX(3px); }
  75%      { transform: translateX(-2px); }
}
@media (prefers-reduced-motion: reduce) {
  .day-seg.shake { animation: none; }
}

.cat-filter-row__group {
  display: flex;
  flex-direction: column;     /* label sits above the chip row */
  align-items: center;        /* center label + chip row in the group */
  gap: 4px;
}
.cat-filter-row__chips {
  display: flex;
  flex-wrap: wrap;            /* let chips flow to multiple rows when overflow */
  align-items: center;
  justify-content: center;    /* center wrapped chips */
  gap: 6px;
}
/* Each chip stays a single line so chips share the same height even when
   labels are long (e.g. "Emotional Centredness"). Long labels grow the
   chip width and the row wraps to the next line instead. */
.cat-filter-row__chips .chip { white-space: nowrap; }
.cat-filter-row__group .filter-group__label {
  margin: 0;                  /* the parent's gap handles spacing */
}
/* Chips in calendar view match list-view chip dimensions. Search keeps its
   full 220 px width (default rule), no calendar-view override. */
.cat-filter-row__sep {
  display: inline-block;
  width: 1px;
  height: 18px;
  background: var(--hairline);
  margin: 0 4px;
}

/* 5-column day grid */
/* Calendar view: separate sticky head row + horizontally scrollable body.
   Splitting the two lets the venue titles stick to the viewport (sticky needs
   to live outside any horizontal scroll container) while cards scroll left /
   right beneath. ~3.5 columns visible at typical desktop width.

   - .cal-head-row    sticky, transparent at rest, frosted blur on scroll
   - .cal-head-row__viewport  overflow:hidden, mirrors body scrollLeft via JS
   - .cal-head        grid with the same column template as .cal-grid
   - .cal-head__cell  same look as the old in-column title
   - .cal-body-scroller  overflow-x:auto, the only horizontal scroll container
   - .cal-grid        grid only (no overflow), columns set inline by JS
*/
.cal-schedule { position: relative; }

.cal-head-row {
  position: sticky;
  top: 78px;                       /* sits flush under the collapsed topbar */
  z-index: 50;                     /* below the topbar (100) but above all cards */
  background: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  transition: backdrop-filter 200ms ease;
}
body.is-scrolled .cal-head-row {
  -webkit-backdrop-filter: blur(60px) saturate(200%);
  backdrop-filter: blur(60px) saturate(200%);
}
.cal-head-row__viewport {
  overflow: hidden;
}
.cal-head {
  display: grid;
  gap: 12px;
  padding: 10px 0 12px;
}
.cal-head__cell {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
  padding: 0 4px;
}
.cal-head__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--ink-900);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  flex: 0 1 auto;
}
.cal-head__count {
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-500);
  letter-spacing: 0.01em;
  flex: 0 0 auto;
}

.cal-body-scroller {
  overflow-x: auto;
  overflow-y: visible;
  scrollbar-width: thin;
}
.cal-grid {
  display: grid;
  gap: 12px;
}
.cal-day {
  min-width: 0;
  display: flex;
  flex-direction: column;
}
/* Day column header — sticks below the (collapsed) topbar when the column
   scrolls. Mirrors the topbar's scroll behaviour: transparent + no blur at
   rest, frosted blur only when the page is scrolled. The `top` accounts
   for the collapsed topbar height (~80 px) plus a small breathing gap. */
/* Venue column header — sticks flush against the collapsed topbar so no
   transparent gap appears above the venue name when scrolling. */
.cal-day__header {
  position: sticky;
  top: 64px;                  /* collapsed topbar bottom, no breathing gap */
  z-index: 6;
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 10px 4px 10px;
  margin-bottom: 12px;
  background: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border: 0;
  color: var(--ink-900);
  border-radius: 0;
  box-shadow: none;
  transition: backdrop-filter 200ms ease;
}
body.is-scrolled .cal-day__header {
  -webkit-backdrop-filter: blur(60px) saturate(200%);
  backdrop-filter: blur(60px) saturate(200%);
}
.cal-day__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--ink-900);
}
.cal-day__count {
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-500);
  letter-spacing: 0.01em;
}
.cal-day__list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* Time-bucket eyebrow inside each calendar column. Same amber-gradient
   vocabulary as list view, scaled down for the narrower column. Only
   renders when the bucket has sessions (suppressed in JS otherwise). */
.cal-bucket {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 10px 0 2px;
  padding: 0;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-900);
}
.cal-bucket::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg,
    var(--amber-500) 0%,
    rgba(239, 141, 56, 0.6) 35%,
    rgba(239, 141, 56, 0.18) 65%,
    rgba(239, 141, 56, 0) 100%);
}
.cal-day__list > .cal-bucket:first-child { margin-top: 2px; }
.cal-day__empty {
  font-size: 12px;
  color: var(--ink-400);
  text-align: center;
  padding: 16px 8px;
  border: 1px dashed var(--hairline);
  border-radius: 12px;
}

/* Calendar event card — collapsed by default */
.cal-event {
  position: relative;
  background: var(--glass-65);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 10px 12px;
  cursor: pointer;
  outline: none;
  transition: background-color 160ms ease, border-color 160ms ease, transform 160ms ease, box-shadow 200ms ease;
}
.cal-event:hover {
  background: var(--glass-80);
  border-color: rgba(15, 27, 45, 0.12);
  transform: translateY(-1px);
  box-shadow: var(--shadow-1);
}
.cal-event:focus-visible {
  outline: 3px solid var(--steel-500);
  outline-offset: 2px;
}
.cal-event__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
}
.cal-event__time {
  font-size: 15px;                /* matches list view's .event__time-start */
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--ink-900);
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}
.cal-event__title {
  margin: 4px 0 0;
  font-size: 15px;                /* same as .cal-event__time and list view */
  font-weight: 700;
  letter-spacing: 0.01em;
  line-height: 1.3;
  color: var(--ink-900);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Expanded calendar card */
.cal-event.is-expanded {
  background: rgba(255, 255, 255, 0.92);
  border-color: rgba(15, 27, 45, 0.14);
  box-shadow: var(--shadow-2);
}
.cal-event.is-expanded .cal-event__title {
  -webkit-line-clamp: unset;
  overflow: visible;
}
.cal-event__details {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--hairline);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* "Learn more →" — sits on the meta row, pushed to the right edge so it
   sits opposite the category circles + venue. Placeholder link
   (no destination wired yet; click handled inline). */
.cal-event__learn-more {
  margin-left: auto;
  font-size: 12px;
  font-weight: 600;
  color: var(--steel-700);
  text-decoration: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  transition: color 160ms ease, transform 160ms ease;
}
.cal-event__learn-more:hover {
  color: var(--ink-900);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.cal-event__learn-more span {
  display: inline-block;
  text-decoration: none;       /* arrow never underlined, only the text */
  transition: transform 160ms ease;
}
.cal-event__learn-more:hover span { transform: translateX(2px); }
/* "until 5:30 PM" — sits next to the start time on the left, only visible
   when the card is expanded. Star stays on the right. */
.cal-event__time-end {
  display: none;
  margin-left: 6px;
  font-size: 12px;               /* matches list view's .event__time-end */
  font-weight: 500;
  color: var(--ink-500);
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
}
.cal-event.is-expanded .cal-event__time-end {
  display: inline;
}
.cal-event__head {
  /* time + time-end huddle on the left */
  justify-content: flex-start;
}
.cal-event__desc {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-500);
}
/* Cap calendar-card teaser at 4 lines. The modal's about-card uses the
   same class for prose paragraphs — those reset to natural flow below. */
.cal-event__details > .cal-event__desc {
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Modal about paragraphs override the clamp — they need full flow. */
.modal__about-card .cal-event__desc {
  display: block;
  -webkit-line-clamp: unset;
  overflow: visible;
}
.cal-event__cats { display: inline-flex; gap: 6px; }

/* Combined meta row: category circles + venue on the same line. */
.cal-event__meta-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  font-size: 12px;
  color: var(--ink-500);
}
.cal-event__meta-row svg { flex: 0 0 auto; color: var(--ink-400); }
.cal-event__meta-row .event__cat svg { color: currentColor; }   /* keep circle icon coloured per category */
/* Type meta — always visible in both collapsed and expanded states */
.cal-event__type {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  row-gap: 4px;
  margin-top: 6px;
  font-size: 12px;
  color: var(--ink-500);
}
.cal-event__type-icon { display: inline-flex; align-items: center; color: var(--ink-700); flex: 0 0 auto; }
.cal-event__type-icon svg { width: 14px; height: 14px; flex: 0 0 auto; }
.cal-event__type-label { color: var(--ink-900); font-weight: 700; }

/* Venue row — only inside details (expanded state) */
.cal-event__venue-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--ink-500);
}
.cal-event__venue-row svg { flex: 0 0 14px; width: 14px; height: 14px; color: var(--ink-400); }
.cal-event__venue { color: var(--ink-500); }

/* Tablet / narrow desktop: keep the same min-width minmax so column widths
   stay readable. Excess venues scroll horizontally via .cal-grid overflow-x. */
@media (max-width: 720px) {
  /* Sticky day headers sit under the topbar. They're only visibly relevant
     once the page (and topbar) is scrolled, so this targets the collapsed
     scrolled topbar height (~56-60px) rather than the full ~116px bar. */
  .cal-day__header { top: 60px; }
}

/* ----------------------------------------------------------------------------
   Session detail modal
   --------------------------------------------------------------------------- */

.modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.modal[hidden] { display: none; }
.modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(15, 27, 45, 0.45);
  -webkit-backdrop-filter: blur(8px) saturate(180%);
  backdrop-filter: blur(8px) saturate(180%);
  animation: modalBackdropIn 200ms ease-out;
}
.modal__panel {
  position: relative;
  background: rgb(var(--modal-surface-rgb));
  border: 1px solid var(--hairline);
  border-radius: 20px;
  box-shadow: var(--shadow-2);
  width: 100%;
  max-width: 1200px;               /* extra-wide modal */
  min-height: min(80vh, 720px);
  min-height: min(80dvh, 720px);
  max-height: min(92vh, 960px);
  max-height: min(92dvh, 960px);
  overflow: hidden;                 /* panel never scrolls; only right column does */
  outline: 0;
  animation: modalPanelIn 220ms cubic-bezier(0.32, 0.72, 0, 1);
  display: flex;
  flex-direction: column;
}
.modal__layout {
  flex: 1 1 auto;
  min-height: 0;                    /* allow children with overflow to shrink */
}
.modal__content-scroll {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;                 /* children own their scroll; panel height stays viewport-bound */
}
/* Dividers + speakers stay pinned; only the About body absorbs overflow. */
.modal__content-scroll > .modal__divider,
.modal__content-scroll > .modal__speakers { flex: 0 0 auto; }
/* About body shrinks and scrolls when its content is long, so a heavy
   programme description never pushes the modal past the viewport and the
   Impact Architects row below it stays visible. */
.modal__content-scroll > .modal__body {
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
}
/* Short viewports: there isn't enough vertical room to pin the Impact Architects
   row AND keep the programme narrative readable — pinning would squeeze the About
   section to nothing. Below this height the modal becomes ONE scroll: the hero /
   facts column and the content column scroll together as a single unit (rather than
   each scrolling independently). The whole panel is the scroller; the inner scroll
   regions are turned off so nothing is clipped and the About text is never lost. */
@media (max-height: 640px) {
  .modal__panel { overflow-y: auto; }                 /* the panel is the single scroller */
  .modal__layout { min-height: min-content; }         /* grid grows to content → panel scrolls */
  .modal__content-scroll { flex: 0 0 auto; overflow: visible; }
  .modal__content-scroll > .modal__body { flex: 0 0 auto; overflow-y: visible; }
  .modal__facts { overflow: visible; }                /* no separate left-column scroll */
  .speaker-modal__image-col { overflow: visible; }    /* panel is the single scroller */
  /* Keep the title bar (with star / close) pinned while the hero + content
     scroll together beneath it, so the modal stays closable mid-scroll. */
  .modal__top {
    position: sticky;
    top: 0;
    z-index: 4;
    background: rgb(var(--modal-surface-rgb));
  }
}
/* Portrait phones: the hero/facts block and the content column stack
   vertically (see the max-width:720 layout block further down), so combined
   content height regularly exceeds the panel's viewport-bound max-height even
   when the viewport itself is tall. Same fix as the max-height:640 case above:
   make the panel the single scroller instead of a fixed-height box that clips
   most of the content. */
@media (max-width: 720px) {
  .modal__panel { overflow-y: auto; }                 /* the panel is the single scroller */
  .modal__layout { min-height: min-content; }         /* grid grows to content → panel scrolls */
  .modal__content-scroll { flex: 0 0 auto; overflow: visible; }
  .modal__content-scroll > .modal__body { flex: 0 0 auto; overflow-y: visible; }
  .modal__facts { overflow: visible; }                /* no separate left-column scroll */
  .speaker-modal__image-col { overflow: visible; }    /* panel is the single scroller */
  /* The title bar sits mid-content once the hero stacks above it, so sticky
     positioning (used at max-height:640) can't keep it pinned here. Instead
     float the close button at the panel's top-right corner — .modal__panel
     is the nearest positioned ancestor — so it's visible the instant the
     modal opens. It scrolls away as the user reads; backdrop click and Esc
     remain available throughout. */
  .modal__panel .modal__close {
    position: absolute;
    top: 12px;
    right: 12px;
    z-index: 30;
    background: rgba(255, 255, 255, 0.92);
    border-radius: 50%;
    box-shadow: var(--shadow-2, 0 2px 8px rgba(15, 27, 45, 0.18));
  }
  /* The base hover/focus washes are 6–10% dark over transparent — over the
     hero image that erases the pill. Keep the pill opaque in those states. */
  .modal__panel .modal__close:hover { background: #fff; }
  .modal__panel .modal__close:focus-visible { background: #fff; }
}
.modal__layout--with-image .modal__content-col,
.modal__layout--plain .modal__content-col {
  min-height: 0;
}

/* Brand accent: thin amber gradient stripe pinned to the panel top edge.
   Sticky so it stays visible when modal content scrolls. Same vocabulary
   as the dark prototype's modal accent. */
.modal__panel::before {
  content: "";
  position: sticky;
  top: 0;
  display: block;
  width: 100%;
  height: 1px;
  margin-bottom: -1px;
  background: linear-gradient(90deg,
    var(--amber-500) 0%,
    rgba(246, 165, 92, 0.65) 35%,
    rgba(246, 165, 92, 0.18) 65%,
    transparent 100%);
  z-index: 5;
  pointer-events: none;
}
@keyframes modalBackdropIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes modalPanelIn {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .modal__backdrop, .modal__panel { animation: none; }
}

/* Modal layout — reuses calendar card classes throughout. Only modal-specific
   chrome lives here. */

/* Top region: title + meta. */
.modal__top { display: block; }
.modal__top-content { display: block; }

/* With-image variant: 2-column grid wraps the entire modal content. Image
   occupies a tall left column; the right column carries every section
   (title block, body, dividers, speakers, footer). The grid sets a min
   height so the image has presence; the no-image variant stays compact. */
.modal__layout--with-image {
  display: grid;
  grid-template-columns: minmax(280px, 34%) 1fr;
  align-items: stretch;
}

.modal__layout--with-image .modal__content-col {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* Right column uses the same 32px section padding as the plain variant
   so ABOUT / SPEAKERS / Register align across both modals. */

/* Anchor the Register button to the bottom of the column so it sits flush
   with the image's bottom edge when content is shorter than the panel. */
.modal__layout--with-image .modal__footer,
.modal__layout--plain .modal__footer {
  margin-top: auto;
}

/* Plain variant: 2-col grid like the with-image variant, but the left
   column hosts a "facts" panel (type + date/time + venue + categories)
   instead of an image. Same 38/62 split → visual parity. */
.modal__layout--plain {
  display: grid;
  grid-template-columns: minmax(280px, 34%) 1fr;
  align-items: stretch;
}
.modal__layout--plain .modal__content-col {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* Facts column — vertical info stack on the left. Tinted surface +
   right hairline so it reads as a defined aside, not floating text. */
.modal__facts {
  display: flex;
  flex-direction: column;
  padding: 26px 22px 24px 28px;
  gap: 21px;
  min-width: 0;
  background: rgba(15, 27, 45, 0.025);
  border-right: 1px solid var(--hairline-soft);
}
/* Compact type header — horizontal icon + label with a thin amber divider. */
.modal__facts-head {
  display: flex;
  align-items: center;
  gap: 9px;
  padding-bottom: 12px;
  position: relative;
}
.modal__facts-head::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  height: 1px;
  width: 100%;
  background: linear-gradient(90deg,
    var(--amber-500) 0%,
    rgba(246, 165, 92, 0.5) 38%,
    transparent 88%);
}
.modal__facts-head-icon {
  width: 18px;
  height: 18px;
  flex: 0 0 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--steel-700);
}
.modal__facts-head-icon svg { width: 18px; height: 18px; }
.modal__facts-head-label {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-900);
}
/* Enlarged date anchor — big date line + time on its own line below. */
.modal__facts-date-main {
  font-family: var(--font-display);
  font-size: 23px;
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.01em;
  color: var(--ink-900);
}
.modal__facts-date-time {
  font-size: 15px;
  font-weight: 500;
  color: var(--ink-500);
  margin-top: 5px;
  font-variant-numeric: tabular-nums;
}
/* Labeled section block + small uppercase label (categories has no label). */
.modal__facts-block {
  display: flex;
  flex-direction: column;
}
.modal__facts-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-400);
  margin-bottom: 7px;
}
/* Compact value rows (venue keeps a pin; language is text). */
.modal__facts-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14.5px;
  line-height: 1.35;
  color: var(--ink-700);
}
.modal__facts-row svg {
  width: 15px;
  height: 15px;
  flex: 0 0 15px;
  color: var(--ink-400);
}
/* Categories as wrapping colored chips (icon + full name); no leading label. */
.modal__facts-cats {
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.modal__facts-cat {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  line-height: 1.25;
  color: var(--ink-700);
}
.modal__facts-cat svg {
  width: 16px;
  height: 16px;
  flex: 0 0 16px;
}
/* Tint the category icon per type to match the event-card color language
   (eco amber, infra green, living steel). Text stays neutral. */
.modal__facts-cat--eco svg    { color: var(--amber-500); }
.modal__facts-cat--infra svg  { color: #10654B; }
.modal__facts-cat--living svg { color: var(--steel-700); }

/* Partner logo — enlarged in the compact layout. */
.modal__facts-partner {
  display: flex;
  align-items: center;
  min-height: 58px;
}
.modal__facts-partner img {
  max-height: 58px;
  max-width: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
}
/* Footer cluster — share icons + Register, pinned to the bottom. */
.modal__facts-foot {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: 13px;
}
/* Text fallback when a partner has no logo file. */
.modal__facts-partner-name {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--steel-700);
  max-width: 100%;
}

/* Partner crossfade carousel — each page of logos occupies the same grid cell
   (so the viewport sizes to the tallest page) and dissolves in place between
   swaps. JS (initPartnerCarousel) builds the pages and toggles .is-active. */
.modal__partners-viewport { overflow: hidden; }
.modal__partners-row { display: grid; }
.modal__partners-page {
  grid-area: 1 / 1;            /* stack every page in one cell */
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  opacity: 0;
  transition: opacity 600ms ease;
}
.modal__partners-page.is-active { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .modal__partners-page { transition: none; }
}

/* Secondary CTA anchored to the bottom of the facts column. Mirrors the
   Register button's bottom anchor in the content column. */
.modal__facts-cta {
  /* Pinned via .modal__facts-foot now; no own top spacing. */
}
.btn-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 12px 22px;
  background: transparent;
  color: var(--ink-800);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.01em;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  cursor: pointer;
  transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
}
.btn-ghost:hover {
  background: rgba(15, 27, 45, 0.04);
  border-color: var(--ink-300);
  color: var(--ink-900);
}
.btn-ghost:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px rgba(239, 141, 56, 0.45);
}
.btn-ghost svg { color: var(--steel-700); flex: 0 0 14px; }

/* Inline ghost variant for the footer — auto width, light grey border,
   text colour matches the plain variant's Add to Calendar. */
.btn-ghost--soft {
  width: auto;
  color: var(--ink-800);
  border-color: var(--ink-300);
}
.btn-ghost--soft:hover {
  background: rgba(15, 27, 45, 0.04);
  border-color: var(--ink-500);
  color: var(--ink-900);
}
.btn-ghost--soft svg { color: var(--steel-700); }

/* ---- Share section — sits inside the facts column, so it inherits the
   facts column's 40px left padding. No additional padding override here. ---- */
.modal__share-icons {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
  margin-bottom: 12px;
}
.modal__share-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--ink-700);
  text-decoration: none;
  transition: background-color 160ms ease, color 160ms ease;
}
.modal__share-btn:hover {
  background: rgba(15, 27, 45, 0.04);
  color: var(--ink-900);
}
.modal__share-btn:focus-visible {
  outline: none;
  background: rgba(239, 141, 56, 0.10);
  box-shadow: 0 0 0 2px rgba(239, 141, 56, 0.45);
}

/* Click-to-copy link pill — the entire pill is the button. Shows the full
   session URL; click copies it and flashes the amber "copied" state. */
.modal__share-link {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 14px;
  background: rgba(15, 27, 45, 0.03);
  border: 1px solid var(--hairline);
  border-radius: 999px;
  color: var(--ink-700);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
}
.modal__share-link:hover {
  background: rgba(15, 27, 45, 0.06);
  border-color: var(--ink-300);
  color: var(--ink-900);
}
.modal__share-link.is-copied {
  background: rgba(239, 141, 56, 0.10);
  border-color: var(--amber-500);
  color: var(--ink-900);
}
.modal__share-link:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px rgba(239, 141, 56, 0.45);
}
.modal__share-link svg {
  flex: 0 0 14px;
  width: 14px;
  height: 14px;
  color: var(--steel-700);
}
.modal__share-link-text {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
/* Default shows the link + trailing copy icons; on copy, swap link→check and
   drop the trailing copy icon (the text reads "Copied to clipboard"). */
.modal__share-link-check { display: none; }
.modal__share-link.is-copied .modal__share-link-ico,
.modal__share-link.is-copied .modal__share-link-copy { display: none; }
.modal__share-link.is-copied .modal__share-link-check { display: block; color: var(--amber-600); }

/* On-image overrides — share lives inside the facts column overlay. */
/* Partner logos over the dark hero scrim: sit each on a white chip so any
   logo (incl. dark-ink marks) stays legible. */
.modal__facts--on-image .modal__facts-partner {
  background: #fff;
  border-radius: 8px;
  padding: 6px 10px;
}
.modal__facts--on-image .modal__share-btn {
  color: rgba(255, 255, 255, 0.85);
}
.modal__facts--on-image .modal__share-btn:hover {
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
}
/* Copy-link pill over the dark hero: white glass so the URL stays legible. */
.modal__facts--on-image .modal__share-link {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.28);
  color: rgba(255, 255, 255, 0.92);
}
.modal__facts--on-image .modal__share-link svg { color: rgba(255, 255, 255, 0.85); }
.modal__facts--on-image .modal__share-link:hover {
  background: rgba(255, 255, 255, 0.20);
  border-color: rgba(255, 255, 255, 0.45);
  color: #fff;
}
.modal__facts--on-image .modal__share-link.is-copied {
  background: rgba(239, 141, 56, 0.28);
  border-color: var(--amber-500);
  color: #fff;
}
.modal__facts--on-image .modal__share-link.is-copied .modal__share-link-check { color: #fff; }

/* --- Facts overlay on a hero image: white text, transparent surface --- */
.modal__facts--on-image {
  background: transparent;
  border-right: 0;
}
.modal__facts--on-image .modal__facts-head-icon { color: #fff; }
.modal__facts--on-image .modal__facts-head-label,
.modal__facts--on-image .modal__facts-date-main,
.modal__facts--on-image .modal__facts-row,
.modal__facts--on-image .modal__facts-cat {
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.modal__facts--on-image .modal__facts-date-time {
  color: rgba(255, 255, 255, 0.82);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.modal__facts--on-image .modal__facts-label {
  color: rgba(255, 255, 255, 0.78);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.modal__facts--on-image .modal__facts-row svg {
  color: rgba(255, 255, 255, 0.8);
}
/* Category icons keep a (lifted) colour for legibility on the dark hero. */
.modal__facts--on-image .modal__facts-cat--eco svg    { color: #FFC489; }
.modal__facts--on-image .modal__facts-cat--infra svg  { color: #7FD9BC; }
.modal__facts--on-image .modal__facts-cat--living svg { color: #AFC3E0; }

/* Ghost button on a dark image background — uses a translucent dark fill
   plus backdrop blur so contrast holds even where the scrim fades. */
.btn-ghost--on-image {
  color: #fff;
  border-color: rgba(255, 255, 255, 0.5);
  background: rgba(15, 27, 45, 0.35);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.btn-ghost--on-image:hover {
  background: rgba(15, 27, 45, 0.55);
  border-color: rgba(255, 255, 255, 0.75);
  color: #fff;
}
.btn-ghost--on-image svg { color: rgba(255, 255, 255, 0.9); }

/* Plain variant: header padding-top to match with-image rhythm. */
.modal__layout--plain .modal__header { padding-top: 32px; }
.modal__layout--plain .modal__divider { margin-top: 22px; margin-bottom: 22px; }

/* A touch more vertical breathing now that the panel runs taller. */
.modal__layout--with-image .modal__header { padding-top: 32px; }
.modal__layout--with-image .modal__divider { margin-top: 22px; margin-bottom: 22px; }

/* ---- Speaker bio modal ----------------------------------------------------
   Same envelope as the event modal (panel size, amber stripe, border).
   2-column grid: square portrait + sessions on the left, bio on the right. */
.modal__layout--speaker {
  display: grid;
  grid-template-columns: minmax(320px, 38%) 1fr;
  align-items: stretch;
  gap: 0;
}
.speaker-modal__image-col {
  padding: 40px 24px 24px 40px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  min-width: 0;
  /* The session-card list can outgrow the fixed-height panel — let this
     column scroll on its own (desktop; stacked mobile layouts override). */
  min-height: 0;
  overflow-y: auto;
}
.speaker-modal__portrait {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 16px;
  overflow: hidden;
  background: rgba(15, 27, 45, 0.04);
  /* The image column scrolls; without this the portrait is a shrinkable
     flex item and gets squashed flat by a long session list. */
  flex: 0 0 auto;
}
.speaker-modal__portrait-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  display: block;
}
.speaker-modal__portrait-img--initials {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: 48px;
  font-weight: 700;
  color: var(--steel-700);
  background: var(--glass-65);
}
.speaker-modal__sessions { display: block; }
/* Sessions render twice (image column / after the bio); desktop shows the
   image-column copy, stacked mobile shows the in-body copy. */
.speaker-modal__sessions--mobile { display: none; }
/* Session cards — same design as the static speaker pages: amber date chip
   + clamped title in a glass box. */
.speaker-modal__session-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 10px;
}
.speaker-modal__session-link {
  display: block;
  border: 1px solid var(--hairline);
  border-radius: 16px;
  padding: 14px 16px;
  background: var(--glass-65);
  text-decoration: none;
  transition: background-color 150ms ease;
}
.speaker-modal__session-link:hover { background: var(--glass-80); }
.speaker-modal__session-when {
  display: block;
  font-size: 11px;
  letter-spacing: .1em;
  font-weight: 700;
  color: var(--amber-600);
  text-transform: uppercase;
  margin-bottom: 6px;
}
.speaker-modal__session-title {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  color: var(--ink-900);
  font-weight: 600;
  font-size: 14.5px;
  line-height: 1.4;
}

.modal__layout--speaker .modal__content-col {
  padding-top: 8px;
}
.modal__layout--speaker .modal__header { padding-top: 8px; padding-bottom: 4px; }
.modal__layout--speaker .speaker-modal__role-eyebrow {
  padding: 32px 32px 0;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-400);
  margin-bottom: 4px;
}
.modal__layout--speaker .modal__top-content > .speaker-modal__affil {
  padding: 0 32px 0;
  font-size: 16px;
  color: var(--steel-500);
  margin-top: 2px;
}
.modal__layout--speaker .modal__body { padding-top: 24px; }
.modal__layout--speaker .modal__footer { margin-top: auto; }

/* Back link — sits in the speaker-modal top bar (Back left, Close right),
   above the eyebrow, so it no longer offsets the title's left alignment.
   Plain underlined text, not a pill. */
.modal__back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0;
  background: transparent;
  border: none;
  color: var(--ink-700);
  font-size: 13px;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  transition: color 160ms ease;
  flex: 0 0 auto;
}
.modal__back:hover {
  color: var(--ink-900);
}
.modal__back:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px rgba(239, 141, 56, 0.45);
}

/* Speaker-modal top bar: Back (left) + Close (right). Keeps the eyebrow, name
   and affiliation below sharing one left edge — Back no longer sits inline with
   the title. Scoped to the modal because the bar element only exists here (the
   routed profile page uses its own .speaker-page__bar instead). */
.speaker-modal__bar {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: 18px 18px 0 32px;
}
.speaker-modal__bar .modal__close { margin-left: auto; }
.modal__top-content > .speaker-modal__bar + .speaker-modal__role-eyebrow {
  padding-top: 10px;
}

/* Hero image — fills the left column at full panel height. Background
   matches the modal surface so the masked image dissolves cleanly into it
   (no fallback gradient peeking through the transparent edge). */
.modal__hero {
  position: relative;
  background: rgb(var(--modal-surface-rgb));
  overflow: hidden;
}
.modal__hero > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
  z-index: 0;
}

.modal__layout--with-image .modal__hero {
  height: 100%;
  border-radius: 20px 0 0 20px;
}

/* Dark gradient scrim on the image so overlaid white text reads against
   varying tones. Holds darkness across the left two-thirds (where facts
   content sits), then fades fast so the image's right-edge mask can still
   dissolve into the modal surface. */
.modal__layout--with-image .modal__hero::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
    rgba(15, 27, 45, 0.58) 0%,
    rgba(15, 27, 45, 0.50) 45%,
    rgba(15, 27, 45, 0.22) 75%,
    rgba(15, 27, 45, 0) 95%);
  z-index: 1;
  pointer-events: none;
}

/* Facts overlay sits above the image + scrim. Uses min-height (not height) so a
   tall facts column (e.g. with categories + partners) grows the hero — and hence
   the modal — instead of clipping Share / Register; still fills the column when
   the content side is taller. */
.modal__layout--with-image .modal__hero .modal__facts {
  position: relative;
  z-index: 2;
  width: 100%;
  min-height: 100%;
}

/* The image ends on a crisp edge against the modal surface (the previous
   right-edge dissolve mask was removed by request); the dark scrim above
   stays so the white facts text remains legible. */

.modal__header {
  padding: 24px 32px 6px;
  display: flex;
  align-items: flex-start;
  gap: 16px;
}
.modal__title {
  flex: 1 1 0%;
  margin: 0;
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--ink-900);
}
.modal__actions {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
  margin-top: 2px;
}
.modal__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 50%;
  color: var(--ink-500);
  cursor: pointer;
  transition: background-color 160ms ease, color 160ms ease;
}
.modal__close:hover { background: rgba(15, 27, 45, 0.06); color: var(--ink-900); }
/* Subtle focus ring only — no glaring blue circle. Matches the hover wash. */
.modal__close:focus-visible {
  outline: none;
  background: rgba(15, 27, 45, 0.10);
  color: var(--ink-900);
}
/* Row 1: type icon + bold label — IDENTICAL to calendar card vocabulary
   (no background circle around the icon, mixed-case bold label). */
.modal .cal-event__type.modal__type-row {
  margin: 0;
  padding: 4px 32px 8px;
  gap: 8px;
}
.modal .cal-event__type-icon {
  width: 22px;
  height: 22px;
  border-radius: 0;
  background: transparent;
  color: var(--ink-700);
}
.modal .cal-event__type-icon svg { width: 16px; height: 16px; }
.modal__type-label {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.005em;
  text-transform: none;
  color: var(--ink-900);
}

/* Row 2: date · time · venue, mid-dot separators */
.modal__meta-line {
  padding: 0 32px 4px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  color: var(--ink-600, var(--ink-500));
  color: var(--ink-500);
  line-height: 1.5;
}
.modal__date { color: var(--ink-700); font-weight: 600; }
.modal__time { color: var(--ink-700); font-weight: 600; font-variant-numeric: tabular-nums; }
.modal__mid-dot {
  display: inline-block;
  color: var(--ink-400);
  font-weight: 600;
}
.modal__meta-line .cal-event__venue {
  color: var(--ink-700);
  font-weight: 500;
}
.modal__meta-line svg {
  flex: 0 0 14px;
  width: 14px;
  height: 14px;
  color: var(--ink-400);
  margin-right: 4px;
  vertical-align: -2px;
}

/* Body */
.modal__body {
  padding: 24px 32px 4px;
}
.modal__body--cats {
  padding-top: 4px;
  padding-bottom: 4px;
}
.modal__about-card {
  background: transparent;
  border: 0;
  padding: 0;
  margin-bottom: 14px;
}
.modal__about-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-400);
  margin-bottom: 12px;
}
.modal__body .cal-event__desc {
  font-size: 15px;
  line-height: 1.65;
  margin: 0 0 12px;
  color: var(--ink-700);
}
.modal__about-card .cal-event__desc:last-child { margin-bottom: 0; }
.modal__body .cal-event__desc:last-of-type { margin-bottom: 14px; }
.modal__body .cal-event__meta-row { margin-top: 6px; gap: 10px; }
.modal__body .event__cat { width: 30px; height: 30px; }

.modal__divider {
  height: 1px;
  background: var(--hairline);
  margin: 18px 32px;
}

/* Speakers — 3-column grid (max 3 per row); wraps to next row beyond 3. */
.modal__speakers {
  padding: 0 32px 22px;
}
/* When MODERATOR and IMPACT ARCHITECTS sections stack, the second one
   keeps its padding-bottom but doesn't double the gap above it. */
.modal__speakers--secondary {
  padding-top: 4px;
}
.modal__speakers-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-400);
  margin-bottom: 14px;
}
.modal__speakers-overflow {
  margin-left: 8px;
  letter-spacing: 0.04em;
  font-weight: 600;
  color: var(--ink-500);
  text-transform: none;
}
/* Single horizontal row of card-less speaker tiles. JS adds .is-carousel when
   the row should scroll (>3 speakers or it overflows the viewport), which turns
   on overflow-x scrolling, the grab cursor, and an edge fade. */
.modal__speakers-viewport {
  overflow-x: hidden;
  scrollbar-width: none;
}
.modal__speakers-viewport::-webkit-scrollbar { display: none; }
.modal__speakers-viewport.is-carousel {
  overflow-x: auto;
  cursor: grab;
  -webkit-mask: linear-gradient(90deg, transparent 0, #000 4%, #000 96%, transparent 100%);
  mask: linear-gradient(90deg, transparent 0, #000 4%, #000 96%, transparent 100%);
}
.modal__speakers-viewport.is-dragging { cursor: grabbing; scroll-behavior: auto; }
.modal__speakers-viewport.is-dragging .modal__speaker { pointer-events: none; }
.modal__speakers-row {
  display: flex;
  gap: 16px;
  align-items: flex-start;
}
.modal__speakers-viewport:not(.is-carousel) .modal__speakers-row { justify-content: flex-start; }
.modal__speakers-live {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}
.modal__speaker {
  flex: 0 0 auto;
  width: clamp(190px, 40%, 240px);   /* ~3 fit the content column; >3 or narrow viewport → scroll */
  display: flex;
  align-items: flex-start;            /* top-align so a long multi-line affiliation sits under the name */
  gap: 12px;
  margin: 0;
  min-width: 0;
  position: relative;
  isolation: isolate;                 /* contain the ::before highlight layer behind the content */
  /* Card-less tile button — transparent; the highlight is painted by ::before. */
  appearance: none;
  background: transparent;
  border: 0;
  padding: 20px 8px 8px;              /* uniform top reserve so every name shares one baseline
                                         and the absolutely-placed role badge has headroom */
  border-radius: 10px;
  cursor: pointer;
  text-align: left;
}
/* Hover/focus highlight lives on a pseudo-element so it can wrap the role badge
   on moderator/facilitator tiles (inset:0) yet skip the empty top reserve on
   badge-less tiles (inset top) — so no big empty band lights up on hover while
   names stay aligned. */
.modal__speaker::before {
  content: "";
  position: absolute;
  inset: 12px 0 0;
  z-index: -1;
  border-radius: 10px;
  background: transparent;
  transition: background-color 160ms ease;
}
.modal__speaker:has(.modal__speaker-role)::before { inset: 0; }
.modal__speaker:hover::before { background: rgba(15, 27, 45, 0.04); }
.modal__speaker:focus-visible { outline: none; }
.modal__speaker:focus-visible::before {
  background: rgba(239, 141, 56, 0.10);
  box-shadow: 0 0 0 2px rgba(239, 141, 56, 0.45);
}
@media (max-width: 720px) {
  /* Wider tiles on small screens so the row overflows and becomes swipeable. */
  .modal__speaker { width: clamp(200px, 78%, 280px); }
}
.modal__speaker-avatar {
  flex: 0 0 44px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  object-fit: cover;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.modal__speaker-avatar--initials {
  background: rgba(15, 27, 45, 0.06);
  color: var(--ink-900);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.04em;
}
.modal__speaker-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  position: relative;                 /* anchor the role badge above the name */
}
.modal__speaker-role {
  position: absolute;
  bottom: 100%;                       /* float above the name, out of flow, so every name shares
                                         one baseline; the tile's ::before highlight (inset:0 when
                                         a badge is present) still wraps it */
  left: 0;
  margin-bottom: 2px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #8E2A1F;
  line-height: 1.2;
  white-space: nowrap;
}
.modal__speaker-name {
  font-size: 15px;
  font-weight: 700;
  color: var(--ink-900);
  line-height: 1.3;
}
.modal__speaker-affil {
  font-size: 14px;
  font-weight: 400;
  color: var(--ink-500);
  line-height: 1.3;
  /* Full wrap, never truncate — long company names grow the tile vertically,
     and the row (hence the whole speakers section) sizes to the tallest one. */
  overflow-wrap: anywhere;
}

/* Modal footer with Register CTA */
.modal__footer {
  padding: 14px 32px 28px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  border-top: 0;
  margin-top: 4px;
}
.btn-register {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 12px 22px;
  background: var(--ink-900);
  color: #fff;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.01em;
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  transition: background-color 180ms ease, transform 180ms ease, box-shadow 220ms ease;
  box-shadow: 0 6px 16px rgba(15, 27, 45, 0.22);
}
.btn-register:hover {
  background: var(--ink-800, #1F2C45);
  transform: translateY(-1px);
  box-shadow: 0 8px 22px rgba(15, 27, 45, 0.28);
}
.btn-register:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(239, 141, 56, 0.45), 0 6px 16px rgba(15, 27, 45, 0.22);
}
.btn-register svg { width: 14px; height: 14px; }

@media (max-width: 720px) {
  .modal { padding: 12px; align-items: flex-end; }
  .modal {
    padding-left: max(12px, env(safe-area-inset-left));
    padding-right: max(12px, env(safe-area-inset-right));
    padding-top: max(12px, env(safe-area-inset-top));
    padding-bottom: max(12px, env(safe-area-inset-bottom));
  }
  .modal__panel { max-height: 92vh; max-height: 92dvh; }
  /* Collapse the 2-column image layout. Image becomes a top banner; the
     facts panel stacks below it. Facts keeps its white-on-dark styling
     (unchanged from desktop) but needs its own dark card here, since the
     panel surface below the banner is light and the facts column is much
     taller than the banner — without a card, most of the white text would
     land on the light panel background. */
  .modal__layout--with-image { display: block; }
  .modal__layout--with-image .modal__hero {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: auto;
    border-radius: 0;
    overflow: visible;
  }
  .modal__layout--with-image .modal__hero > img {
    position: relative;
    aspect-ratio: 16 / 9;
    height: auto;
    object-position: center;
    border-radius: 20px 20px 0 0;
    -webkit-mask-image: none;
    mask-image: none;
  }
  .modal__layout--with-image .modal__hero::after { display: none; }
  /* Dark card for the facts panel — butts directly against the banner
     image above (no gap) so image + card read as one continuous block.
     Keeps the same white-text styling the base --on-image rules already
     set up; this just gives that styling a dark surface to sit on. */
  .modal__layout--with-image .modal__facts--on-image {
    background: var(--ink-900);
    border-right: 0;
  }
  .btn-ghost--on-image {
    color: var(--ink-800);
    border-color: var(--hairline);
    background: transparent;
  }
  .btn-ghost--on-image:hover {
    background: rgba(15, 27, 45, 0.04);
    border-color: var(--ink-300);
    color: var(--ink-900);
  }
  .btn-ghost--on-image svg { color: var(--steel-700); }
  .modal__header { padding: 20px 22px 6px; }
  .modal .cal-event__type.modal__type-row { padding-left: 22px; padding-right: 22px; }
  .modal__meta-line { padding-left: 22px; padding-right: 22px; }
  .modal__body     { padding: 14px 22px 4px; }
  .modal__divider  { margin: 16px 22px; }
  .modal__speakers { padding: 0 22px 16px; }
  .modal__footer { padding: 14px 22px 22px; }
  .modal__title { font-size: 22px; }

  /* Plain event modal — collapse the 2-col grid; facts above content.
     Drop the aside background/border since the side-by-side metaphor
     no longer applies in stacked mode. */
  .modal__layout--plain { display: block; }
  .modal__facts {
    padding: 20px 22px 4px;
    gap: 16px;
    background: transparent;
    border-right: 0;
    border-bottom: 1px solid var(--hairline-soft);
  }
  .modal__facts-cal-day { font-size: 36px; }

  /* Speaker modal — collapse the 2-col grid to a stacked layout, portrait
     at the top, content below. */
  .modal__layout--speaker { display: block; }
  .speaker-modal__image-col { padding: 20px 22px 0; flex-direction: column; }
  .speaker-modal__portrait { width: 100%; aspect-ratio: 1 / 1; flex: 0 0 auto; border-radius: 12px; }
  /* Stacked layout: hide the image-column session list (it would cram into a
     narrow strip beside the portrait and push the name below the fold) and
     show the full-width copy after the bio instead. */
  .speaker-modal__sessions--desktop { display: none; }
  .speaker-modal__sessions--mobile { display: block; margin-top: 20px; }
  .modal__layout--speaker .modal__top-content > .speaker-modal__affil {
    padding-left: 22px; padding-right: 22px;
  }
  .speaker-modal__bar { padding-left: 22px; padding-right: 14px; }
}

/* ----------------------------------------------------------------------------
   Scroll-to-top button
   --------------------------------------------------------------------------- */

.scroll-top {
  position: fixed;
  right: 24px;
  bottom: 24px;
  right: max(24px, env(safe-area-inset-right));
  bottom: max(24px, env(safe-area-inset-bottom));
  z-index: 200;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--hairline);
  background: var(--glass-80);
  color: var(--ink-900);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-1);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  backdrop-filter: blur(20px) saturate(180%);
  transition: background-color 180ms ease, transform 200ms ease, box-shadow 220ms ease;
}
.scroll-top:hover {
  background: #fff;
  transform: translateY(-2px);
  box-shadow: var(--shadow-2);
}

/* ----------------------------------------------------------------------------
   Focus rings (consistent across components)
   --------------------------------------------------------------------------- */

.chip:focus-visible,
.day-pill:focus-visible,
.btn-clear:focus-visible,
.scroll-top:focus-visible,
.search:focus-within {
  outline: 3px solid var(--steel-500);
  outline-offset: 2px;
}
.search:focus-within { outline: 0; }   /* search uses its own inner ring */

/* ----------------------------------------------------------------------------
   Reduced motion
   --------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0ms !important;
    animation-duration: 0ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* ----------------------------------------------------------------------------
   Mobile tightening
   --------------------------------------------------------------------------- */

@media (max-width: 720px) {
  /* Masthead: theme default (atone-style.min.css) renders the logo at
     140px wide with 20px top/bottom margin (~120px) plus #masthead>div
     padding, landing around 127px tall. Shrink the logo and its margin
     so the burger + logo stay intact but the header collapses to ~76px. */
  #masthead .site-branding img {
    width: 88px !important;
    margin: 12px 0 !important;
  }
  #masthead > div { padding-top: 0; padding-bottom: 0; }
  .header-menu-burger { margin-bottom: 0 !important; }

  /* Programme tabs: trim the outer margin (tabs themselves already shrink
     via the 540px breakpoint above). */
  .programme-tabs { margin: 10px auto 6px; }

  .topbar__title { font-size: 32px; }
  .topbar__inner { padding: 8px var(--s-4) 8px; }
  .topbar__inner {
    padding-left: max(var(--s-4), env(safe-area-inset-left));
    padding-right: max(var(--s-4), env(safe-area-inset-right));
  }
  .topbar__row { gap: var(--s-2); }
  .topbar__controls { width: 100%; margin-left: 0; }
  /* Sidebar's 4px scrollbar inset made the venue dropdown 4px narrower than
     its calendar-view twin in the topbar; the mobile sidebar never scrolls
     internally, so drop it for identical widths. */
  .sidebar { padding-right: 0; }
  /* 12px below the venue dropdown = what calendar view has between its
     topbar-hosted dropdown and the filters (8px topbar padding + 4px layout
     top) — keeps the accordions at the same y in both views. */
  #venueGroup { margin-bottom: 12px; }
  .search { flex: 1 1 auto; min-width: 0; }
  .search input { width: 100%; min-width: 0; }
  .view-toggle { flex: 0 0 auto; }
  /* Tap targets: 32x32 buttons measure under the 44px guideline on touch.
     Grow to 40x40 (pill grows to ~86-90px, absorbed by the search's fluid
     flex-basis above — still leaves >170px for the search input at 320px). */
  .view-toggle__btn { width: 40px; height: 40px; }
  /* 4px top (+ topbar's 8px bottom) = 12px above the sidebar's venue
     dropdown — the same 12px the stacked calendar topbar puts between the
     search row and the relocated dropdown, so switching views doesn't shift
     the rhythm. */
  .layout { padding: 4px var(--s-4) var(--s-4); gap: var(--s-4); }
  .layout {
    padding-left: max(var(--s-4), env(safe-area-inset-left));
    padding-right: max(var(--s-4), env(safe-area-inset-right));
  }
  /* Calendar view's desktop-oriented 40px side padding (line ~628) beats this
     block on specificity — override explicitly so mobile calendar matches
     list view's 16px side inset instead of wasting 24px/side. */
  body.view-calendar .layout { padding-left: var(--s-4); padding-right: var(--s-4); }
  body.view-calendar .layout {
    padding-left: max(var(--s-4), env(safe-area-inset-left));
    padding-right: max(var(--s-4), env(safe-area-inset-right));
  }
  /* Bucket eyebrow ("Morning"/"Afternoon"...) is 10px on desktop, which reads
     too small on mobile — bump legibility while keeping the uppercase/tracking
     treatment proportionate. */
  .cal-bucket { font-size: 12px; }

  /* Day pills: single-row horizontal scroller instead of wrapping to a
     second row (was costing ~53px of extra height). Bleed to the viewport
     edge so pills aren't clipped mid-capsule at rest, while the first pill
     still lines up with the 16px content inset via matching padding. */
  .day-nav {
    flex: 1 1 100%;
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    margin: 0 calc(-1 * var(--s-4));
    padding: 0 var(--s-4);
    scrollbar-width: none;
  }
  .day-nav::-webkit-scrollbar { display: none; }
  .day-pill {
    flex: 0 0 auto;
    scroll-snap-align: start;
  }

  /* Scrolled state: collapse the topbar to a slim day-pill row only.
     Controls (search + view toggle) collapse via max-height/opacity so the
     change animates smoothly instead of snapping; pills shrink modestly but
     stay >=40px tall for tap targets. Desktop is-scrolled behaviour (defined
     above, outside this media query) is untouched. */
  .topbar__controls {
    overflow: hidden;
    max-height: 60px;
    opacity: 1;
    transition: max-height 200ms ease, opacity 200ms ease, margin-top 200ms ease;
  }
  body.is-scrolled .topbar__inner {
    padding-top: 6px;
    padding-bottom: 6px;
  }
  body.is-scrolled .topbar__controls {
    max-height: 0;
    opacity: 0;
    margin-top: 0;
  }
  body.is-scrolled .day-pill {
    min-width: 76px;
    padding: 4px 20px 6px;
    transition: background-color 180ms ease, border-color 180ms ease, transform 180ms ease,
      padding 200ms ease;
  }
  body.is-scrolled .day-pill__num { font-size: 17px; }


  .event {
    flex-wrap: wrap;
    padding: 14px 16px;
    gap: var(--s-3);
    /* Card stacks here (text wraps below the time row), so let the height
       follow content instead of pinning it — no trailing white space arises. */
    height: auto;
    overflow: visible;
  }
  .event__time {
    flex: 1 1 auto;
    width: auto;
    flex-direction: row;
    align-items: baseline;
    gap: 6px;
    padding-top: 0;
    padding-left: 0;
  }
  .event__time-pill { flex-direction: row; align-items: baseline; gap: 6px; }
  .event__time-end::before { content: "·"; margin-right: 6px; color: var(--ink-400); }
  .event__content {
    flex: 1 1 100%;
  }
  /* Cards with a key visual become banner tiles: the KV spans the full card
     width; the time pill and category circles overlay its bottom edge
     (pulled up over the image with a negative margin); title/desc/meta sit
     below on a clean text zone. */
  .event.has-kv {
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    padding: 0;
    gap: 0;
    overflow: hidden;
  }
  .event.has-kv .event__kv {
    flex: none;
    width: 100%;
    height: auto;
    aspect-ratio: 21 / 8;
    align-self: auto;
    border: 0;
    border-radius: 0;
  }
  /* Zero-height overlay row: takes no space in the card's flow (so the text
     zone below the image keeps its full padding); the pill and circles are
     lifted up onto the image with a translate instead of a negative margin. */
  .event.has-kv .event__time {
    flex: none;
    width: 100%;
    box-sizing: border-box;
    height: 0;
    margin: 0;
    padding: 0 10px;
    position: relative;
    z-index: 1;
    align-items: flex-start;
    justify-content: space-between;
    overflow: visible;
  }
  .event.has-kv .event__time-pill,
  .event.has-kv .event__cat-row {
    transform: translateY(calc(-100% - 10px));
  }
  .event.has-kv .event__time-pill {
    background: rgba(255, 255, 255, 0.92);
    border-radius: 999px;
    padding: 5px 12px;
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
  }
  .event.has-kv .event__cat-row { margin: 0; max-width: none; flex-wrap: nowrap; }
  .event.has-kv .event__cat { background: #fff; }
  .event.has-kv .event__content { padding: 16px 18px 16px; gap: 8px; }

  /* Calendar view topbar was 962px tall on mobile: it carried the full
     desktop-oriented #catFilterRow chip strip (whose 2-col chip grids
     overflow their row and clip off-screen) plus a hidden sidebar sitting
     unused below. Drop the chip strip from the topbar and restore the
     sidebar's accordion filters instead — that's the pattern list view
     already uses on mobile, so calendar view now matches it. */
  body.view-calendar #catFilterRow { display: none; }

  /* Undo the desktop/tablet `display:none` on .sidebar (line ~1537) so the
     accordion filters (#forumParent/#festivalParent) render above the
     schedule, stacked single-column same as list view. .layout is already
     single-column here (grid-template-columns: 1fr set at line ~1538 and
     ~630), so the sidebar simply stacks above .schedule with no grid work
     needed. */
  body.view-calendar .sidebar { display: block; }

  /* Sidebar's venue group is an empty shell in calendar view — #venueSelect
     is reparented into the topbar's .cal-venue-slot by applyView() in
     app.js, leaving a dangling "VENUE" header behind. Hide the shell. */
  body.view-calendar .filter-group--venue { display: none; }

  /* Footer links (theme default ~16px text, 20px line-height) sit well
     under the 40px tap-height guideline. Vertical padding + inline-block
     reads as generous line spacing in the stacked widget lists, which
     matches the footer's existing rhythm. */
  #site-footer a {
    display: inline-block;
    padding: 12px 0;
  }
}

/* ----------------------------------------------------------------------------
   Responsive: mobile + tablet fallbacks (DRD v2)
   Breakpoint tiers: xs <640, sm 640-1023, md 1024-1439, lg >=1440
   --------------------------------------------------------------------------- */

/* Mobile venue line on calendar cards: hidden on desktop. */
.cal-event__venue-mobile { display: none; }

/* sm + xs (< 1024px): swimlanes collapse. Hide sticky venue head row.
   Force the inline grid-template-columns from JS to a single column. */
@media (max-width: 1023px) {
  .cal-head-row { display: none; }
  .cal-grid { grid-template-columns: 1fr !important; gap: 8px; }
  .cal-day__list { gap: 6px; }

  .cal-event__venue-mobile {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 12px;
    color: var(--ink-500);
    padding: 0 2px 4px;
  }
  .cal-event__venue-mobile svg {
    width: 12px;
    height: 12px;
    color: var(--ink-400);
    flex: 0 0 12px;
  }

  /* Filter cards: 2 columns (eco | infra), with Partner-led + China Track as a
     full-width row of two cards below. */
  body.view-calendar .cat-filter-row { grid-template-columns: 1fr 1fr; }
  body.view-calendar .cat-filter-row__side { grid-column: 1 / -1; flex-direction: row; }
}

/* < 1024px: calendar topbar flattens to a single full-width stack — pills,
   then search + toggle, then venue, then the category cards. One layout for
   everything below desktop, so nothing reflows or jumps mid-range (the old
   desktop grid used to persist down to 783px, wrapping the pills and leaving
   the venue bar centered at partial width). */
@media (max-width: 1023px) {
  body.view-calendar .topbar__row {
    display: flex;
    flex-direction: column;
    /* Base row is flex-wrap:wrap; a wrapping COLUMN container doesn't stretch
       items to the container width, which let the nowrap day-pill strip spill
       to its content width instead of matching list view. */
    flex-wrap: nowrap;
    align-items: stretch;
    /* Same 8px the list-view topbar row uses, so the search row lands at the
       identical y in both views (was 12px → 4px drift on toggle). */
    gap: var(--s-2);
  }
  /* day-nav deliberately NOT width:100% — list view lets the pill row bleed
     edge-to-edge; forcing a width here made the pills shift on toggle.
     min-width:0 lets the nowrap pill row stretch-fit instead of spilling to
     its 544px min-content width. */
  body.view-calendar .day-nav { min-width: 0; }
  body.view-calendar .topbar__controls,
  body.view-calendar .cal-venue-slot,
  body.view-calendar .cat-filter-row {
    width: 100%;
    justify-content: flex-start;
  }
  /* Keep search + view toggle at the top (right under the day pills), same
     spot as list view — otherwise DOM order sinks them below the venue
     filter when the row goes column. */
  body.view-calendar .day-nav          { order: -2; }
  body.view-calendar .topbar__controls { order: -1; }
  body.view-calendar .cal-venue-slot {
    width: 100%;
    max-width: none;
    /* 8px row gap + 4px = the 12px list view shows between the controls row
       and the sidebar's venue dropdown (measured). */
    margin-top: 4px;
  }
  body.view-calendar .btn-clear--cal:not([hidden]) {
    margin-left: 0;
    align-self: flex-start;
    /* Same order tier as the day pills (DOM puts it right after them), so it
       stays anchored at the top next to the dates instead of sinking below
       the venue filter — matching where list view shows it. */
    order: -2;
  }
  /* Search + toggle take a full-width row in BOTH views (not just calendar),
     so the search bar goes full width at the same 1023px moment the venue
     bar does (list view's sidebar stacks — and its venue dropdown becomes
     full width — at this same breakpoint). */
  .topbar__controls { width: 100%; margin-left: 0; }
  .search { flex: 1 1 auto; min-width: 0; }
  .search input { width: 100%; min-width: 0; }
  /* Once the venue bar is full width its "Venue" heading is dropped — the
     dropdown's "Select venue" placeholder says it, and the calendar view
     (which hosts the dropdown in the topbar) never shows a heading. */
  #venueGroup .filter-group__head { display: none; }
  /* Same 12px between the search row and the venue bar as ≤720 (8px topbar
     bottom + 4px layout top) — the base 12px + 20px pair left a visibly
     taller gap in the 721–1023 range. */
  .topbar__inner { padding-bottom: 8px; }
  .layout { padding-top: 4px; }
  /* Filter cards stay 2 columns (rule above); they're hidden ≤720 where the
     sidebar accordions take over. */
  body.view-calendar .cat-filter-row .filter-group--living .cat-filter-row__chips { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 720px) {
  .venue-select__panel { max-height: 60vh; max-height: 60dvh; overflow-y: auto; }
}

/* ----------------------------------------------------------------------------
/* ============================================================================
   Speakers section — independent Architects/Practitioners tabs, alphabetical
   paged grid (5×2 desktop → fewer cols → mobile swipe), and the routed profile
   page (#speaker/<slug>). Mirrors the .programme-tab / speaker-modal patterns.
   ========================================================================== */
.speakers-section {
  /* Match the schedule content box exactly: 1280 grid + 2×24 padding = 1328.
     Arrows overlay the grid edges (absolute), so they don't widen this box. */
  max-width: 1328px;
  margin: 0 auto;
  padding: var(--s-8) var(--s-6);
}
.speakers-tabs {
  display: flex;
  align-items: center;
  gap: 4px;
  width: max-content;
  max-width: calc(100% - var(--s-6) * 2);
  margin: 0 auto var(--s-6);
  padding: 4px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--glass-65);
  -webkit-backdrop-filter: blur(10px) saturate(160%);
  backdrop-filter: blur(10px) saturate(160%);
}
/* `display:flex` above beats the [hidden] UA rule, so hiding the tabs in search
   mode needs this explicit, higher-specificity rule. */
.speakers-tabs[hidden] { display: none; }
.speakers-tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 11px;
  padding: 14px 28px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--ink-900);
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1.15;
  transition: background-color 160ms ease, color 160ms ease;
}
.speakers-tab__icon { flex: 0 0 24px; width: 24px; height: 24px; color: currentColor; }
.speakers-tab:hover:not(.is-active) { background: var(--glass-80); }
.speakers-tab.is-active {
  background: var(--ink-900);
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.speakers-tab:focus-visible { outline: 2px solid var(--steel-500); outline-offset: 2px; }

.speakers-deck {
  position: relative;
  margin: 0 auto;
}
/* Arrows overlay the grid edges (tucked ~16px onto the edge cards), vertically
   centred on the 2-row grid so they sit on the row gap, not over a face. They
   are absolute, so the grid keeps the full 1280 schedule width. */
.speakers-deck__arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  border: 1px solid var(--hairline);
  background: var(--glass-80);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  box-shadow: 0 2px 10px rgba(15, 27, 45, 0.10);
  color: var(--ink-900);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0.45;                     /* semi-transparent at rest — same whether or not a page exists */
  transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease, opacity 160ms ease;
}
.speakers-deck__arrow--prev { left: -16px; }
.speakers-deck__arrow--next { right: -16px; }
.speakers-deck__arrow[hidden] { display: none; }
/* Only an arrow that leads to another page lights up on hover (dark fill, white
   arrow). A dead-end arrow (disabled) keeps the resting translucent look on
   hover, signalling there are no more speakers that way. */
.speakers-deck__arrow:hover:not(:disabled) {
  opacity: 1;
  background: var(--ink-900);
  border-color: var(--ink-900);
  color: #fff;
}
.speakers-deck__arrow:disabled { cursor: default; }
.speakers-deck__arrow:focus-visible { outline: 2px solid var(--steel-500); outline-offset: 2px; }

.speakers-grid {
  width: 100%;
  min-width: 0;
  display: grid;
  grid-template-columns: repeat(var(--cols, 5), minmax(0, 1fr));
  gap: var(--s-4);
}
.speakers-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--s-2);
  /* One uniform padding value on all four sides — an even ring around the
     portrait that cannot drift out of proportion with itself, at any width.
     Mildly fluid (5% of the card) between fixed floors/caps. */
  padding: clamp(10px, 7%, 16px);
  /* Size container: children below size text/gaps in cqw (1cqw = 1% of card
     width), so the text block shrinks in step with the 1:1 portrait and the
     card's overall proportions hold at every width. */
  container-type: inline-size;
  border: 1px solid var(--hairline);
  border-radius: 14px;
  background: var(--glass-55);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: inherit;
  text-decoration: none;
  transition: background-color 160ms ease, border-color 160ms ease, transform 160ms ease;
}
.speakers-card:hover {
  background: var(--glass-80);
  border-color: rgba(15, 27, 45, 0.14);
  transform: translateY(-2px);
}
.speakers-card:focus-visible { outline: 2px solid var(--steel-500); outline-offset: 2px; }
.speakers-card__avatar {
  /* Always the full content width of the card — the portrait scales in
     direct proportion to the card at every size, no fixed cap to drift
     out of sync with. */
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 16px;
  object-fit: cover;
  object-position: top center;
  background: rgba(15, 27, 45, 0.05);
  display: block;
}
.speakers-card__avatar--initials {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: 700;
  color: var(--steel-700);
  background: var(--glass-65);
}
.speakers-card__name {
  margin-top: clamp(2px, 1.75cqw, 4px);
  font-family: var(--font-display);
  /* cqw values reproduce the old fixed sizes on a full-width ~230px card
     (15/12/12px) and scale down with the card; clamp floors keep tiny cards
     legible. */
  font-size: 15px;
  font-weight: 600;
  line-height: 1.25;
  color: var(--ink-900);
}
.speakers-card__org  { font-size: 13px; line-height: 1.3; color: var(--ink-500); }
/* Avatar wrapper so the live-feed badge can overlay the photo. */
.speakers-card__media {
  position: relative;
  width: 100%;
  max-width: 200px;
  line-height: 0;
}
/* "Live feed" pill overlaid on the bottom of a remote speaker's photo. */
.speakers-card__live {
  position: absolute;
  bottom: 8px;
  left: 50%;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 9px;
  border-radius: 999px;
  background: linear-gradient(135deg, var(--amber-500) 0%, var(--amber-600) 100%);
  color: #fff;
  font-size: 10.5px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.02em;
  white-space: nowrap;
  box-shadow: 0 1px 4px rgba(15, 27, 45, 0.25);
}
.speakers-card__live svg { width: 12px; height: 12px; }
/* Same status on the speaker profile / pop-up, below the affiliation. */
.speaker-modal__live {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  padding: 5px 12px;
  border-radius: 999px;
  background: linear-gradient(135deg, var(--amber-500) 0%, var(--amber-600) 100%);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.speaker-modal__live svg { width: 14px; height: 14px; }
.speakers-empty {
  grid-column: 1 / -1;
  margin: 0;
  padding: var(--s-7);
  text-align: center;
  font-size: 15px;
  color: var(--ink-500);
}

.speakers-dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  margin-top: var(--s-5);
}
.speakers-dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--ink-300);
  cursor: pointer;
  transition: background-color 160ms ease, transform 160ms ease;
}
.speakers-dot:hover { background: var(--ink-400); }
.speakers-dot.is-active { background: var(--ink-900); transform: scale(1.15); }
.speakers-dot:focus-visible { outline: 2px solid var(--steel-500); outline-offset: 2px; }

/* Speaker search — matches the sessions search box (.search): rounded-rect glass
   field, same padding / focus ring / icon / type. A non-empty query swaps the
   paged deck for a flat grid of matches (.speakers-grid--flat); see app.js §13c. */
.speakers-search {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  width: 100%;
  max-width: 520px;
  margin: 0 auto var(--s-5);
  padding: 8px 14px 8px 12px;
  border: 1px solid var(--hairline);
  border-radius: 18px;
  background: var(--glass-65);
  color: var(--ink-500);
  transition: background-color 180ms ease, border-color 180ms ease, box-shadow 180ms ease;
}
.speakers-search:focus-within {
  background: var(--glass-80);
  border-color: var(--steel-500);
  box-shadow: 0 0 0 3px rgba(95, 127, 162, 0.18);
}
.speakers-search__icon { flex: 0 0 16px; width: 16px; height: 16px; color: currentColor; }
/* Descendant `input` selector (not the BEM class) so it matches the sessions
   `.search input` specificity and, like it, wins over the WordPress theme's
   `input[type="search"]` reset by loading later. */
.speakers-search input {
  flex: 1 1 140px;
  min-width: 100px;
  border: 0;
  outline: 0;
  padding: 0;
  background: transparent;
  font-size: 14px;
  color: var(--ink-900);
}
.speakers-search input::placeholder { color: var(--ink-400); }
/* Hide the native search "×" so only our clear button shows. */
.speakers-search input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.speakers-search__count { flex: 0 0 auto; font-size: 13px; font-weight: 600; color: var(--ink-500); white-space: nowrap; }
.speakers-search__clear {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(15, 27, 45, 0.08);
  color: var(--ink-700);
  cursor: pointer;
  transition: background-color 160ms ease;
}
.speakers-search__clear:hover { background: rgba(15, 27, 45, 0.16); }
.speakers-search__clear svg { width: 14px; height: 14px; }
.speakers-search__clear[hidden] { display: none; }
.speakers-search__clear:focus-visible { outline: 2px solid var(--steel-500); outline-offset: 2px; }

/* Flat results grid (search mode): auto-fill instead of the deck's fixed --cols. */
.speakers-grid--flat { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }

@media (max-width: 540px) {
  .speakers-section { padding: var(--s-6) var(--s-4) var(--s-7); }
  /* Smaller arrows so they overhang the 2-col cards less on a phone. */
  .speakers-deck__arrow { width: 40px; height: 40px; }
  .speakers-deck__arrow svg { width: 20px; height: 20px; }
  .speakers-tab { padding: 11px 20px; gap: 9px; font-size: 15px; }
  .speakers-tab__icon { flex-basis: 20px; width: 20px; height: 20px; }
  .speakers-search { max-width: 100%; }
}

@media (max-width: 360px) {
  /* Two tabs ("Impact Architects" / "Impact Practitioners") must fit
     side by side in ~288px of content width. Compress padding/font,
     tighten letter-spacing, and drop the icons — the text label is the
     essential part. */
  .speakers-tabs { width: 100%; max-width: 100%; gap: 2px; padding: 3px; }
  .speakers-tab {
    flex: 1 1 0;
    padding: 14px 6px;
    gap: 0;
    font-size: 11.5px;
    letter-spacing: -0.01em;
    white-space: nowrap;
  }
  .speakers-tab__icon { display: none; }
}

/* ---- Routed speaker profile page (#speaker/<slug>) ---- */
.speaker-page {
  max-width: 1280px;
  margin: var(--s-6) auto var(--s-8);
  padding: 0 var(--s-6);
}
.speaker-page__back {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 0;
  border: none;
  background: transparent;
  color: var(--ink-900);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  transition: color 160ms ease;
}
.speaker-page__back:hover { color: var(--ink-700); }
.speaker-page__back:focus-visible { outline: 2px solid var(--steel-500); outline-offset: 2px; }
.speaker-page__panel {
  border: 1px solid var(--hairline);
  border-radius: 20px;
  background: var(--glass-65);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  backdrop-filter: blur(14px) saturate(160%);
  overflow: hidden;
}
.speaker-page__panel .modal__content-scroll { overflow: visible; }

/* On a speaker route, hide the calendar chrome (header + footer stay for context). */
body.is-speaker-route .programme-tabs,
body.is-speaker-route .topbar,
body.is-speaker-route .layout,
body.is-speaker-route #speakersSection { display: none !important; }

/* ----------------------------------------------------------------------------
   Register CTA — "Get ImpactKey" (steel mist)
   Rendered by openRegisterCta() into the session modal panel. Navy takeover:
   benefits left, light steel-wash download rail right (QR + stacked badges).
   --------------------------------------------------------------------------- */
/* While the CTA is showing, the panel shrinks from the extra-wide session
   layout to a compact card that hugs the CTA's own size. */
.modal__panel--ik {
  max-width: 720px;
  min-height: 0;
  height: auto;
}
.ik-cta {
  position: relative;
  display: flex;
  width: 100%;
  min-height: 420px;
  background: linear-gradient(160deg, #12213A, #0F1B2D);
  color: #fff;
}
.ik-cta__close {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 5;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  /* Sits over the LIGHT steel-mist rail on desktop → dark ink glyph. The
     ≤720 stack puts it over the navy area instead → flipped to white below. */
  background: rgba(15, 27, 45, 0.08);
  border: 1px solid rgba(15, 27, 45, 0.18);
  color: var(--ink-900);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.ik-cta__close:hover { background: rgba(15, 27, 45, 0.16); }
.ik-cta__close svg { width: 20px; height: 20px; flex: 0 0 20px; }
.ik-cta__close:focus-visible { outline: 2px solid var(--amber-500); outline-offset: 2px; }
.ik-cta__icn { width: 15px; height: 15px; flex: 0 0 15px; }
.ik-cta__main {
  flex: 1 1 auto;
  min-width: 0;
  padding: 38px 40px 30px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
/* Session chip — wraps to max 2 lines then ellipsizes; squares off from a
   pill when it wraps (long programme names). */
.ik-cta__chip {
  display: inline-flex;
  align-items: flex-start;
  gap: 7px;
  max-width: 100%;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.35;
  border-radius: 14px;
  padding: 6px 12px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.16);
  color: rgba(255, 255, 255, 0.85);
}
.ik-cta__chip .ik-cta__icn { margin-top: 1px; }
.ik-cta__chip-txt {
  min-width: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ik-cta__title {
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.01em;
  margin: 18px 0 8px;
  text-wrap: balance;
}
.ik-cta__sub {
  font-size: 14px;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.72);
  margin: 0 0 20px;
  max-width: 44ch;
}
.ik-cta__benefits { display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px; }
.ik-cta__benefit {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  font-size: 13.5px;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.88);
}
.ik-cta__benefit .ik-cta__icn { color: #FFC48A; margin-top: 2px; }
.ik-cta__back {
  margin-top: auto;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  text-decoration: underline;
  color: rgba(255, 255, 255, 0.75);
  cursor: pointer;
}
.ik-cta__back:hover { color: #fff; }
.ik-cta__back:focus-visible { outline: 2px solid var(--amber-500); outline-offset: 2px; }
/* Steel-mist download rail. */
.ik-cta__rail {
  flex: 0 0 232px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 13px;
  padding: 30px 22px;
  text-align: center;
  color: var(--ink-900);
  background:
    radial-gradient(110% 80% at 75% 12%, rgba(95, 127, 162, 0.26), transparent 62%),
    radial-gradient(120% 90% at 25% 92%, rgba(159, 192, 222, 0.30), transparent 65%),
    #F4F8FC;
}
.ik-cta__label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--steel-700);
}
.ik-cta__qr {
  background: #fff;
  border: 1px solid rgba(15, 27, 45, 0.10);
  border-radius: 14px;
  padding: 10px;
  display: inline-block;
}
.ik-cta__qr img { width: 128px; height: 128px; display: block; }
.ik-cta__badges { display: flex; flex-direction: column; gap: 9px; align-items: center; }
.ik-cta__badges img { height: 40px; display: block; }
.ik-cta__badges a:focus-visible { outline: 2px solid var(--amber-500); outline-offset: 2px; }
.ik-cta__hint { font-size: 11.5px; line-height: 1.45; max-width: 20ch; color: rgba(15, 27, 45, 0.6); }

@media (max-width: 720px) {
  /* Stack: copy first, rail below. On a phone the badges matter more than
     the QR (you're already on the device), so the QR steps back a little. */
  .ik-cta { flex-direction: column; min-height: 0; }
  /* Stacked layout: the close button now sits over the navy copy area. */
  .ik-cta__close {
    background: rgba(255, 255, 255, 0.14);
    border-color: rgba(255, 255, 255, 0.25);
    color: #fff;
  }
  .ik-cta__close:hover { background: rgba(255, 255, 255, 0.24); }
  .ik-cta__main { padding: 26px 22px 20px; }
  .ik-cta__title { font-size: 22px; }
  .ik-cta__back { margin-top: 4px; }
  .ik-cta__rail { flex: none; padding: 24px 20px calc(24px + env(safe-area-inset-bottom)); }
  .ik-cta__qr img { width: 104px; height: 104px; }
}
