/* === CSS Variables (Light) === */
:root {
  --bg: #f8fafc; --bg-card: #ffffff; --bg-card-hover: #f1f5f9;
  --text: #1e293b; --text-muted: #64748b; --text-light: #94a3b8;
  --border: #e2e8f0; --border-light: #f1f5f9;
  /* Indigo accent — unified with marketing surfaces (landing/login/join). */
  --primary: #4f46e5; --primary-hover: #3730a3; --primary-bg: #e0e7ff;
  --accent: #10b981; --accent-hover: #059669;
  --danger: #ef4444; --danger-bg: #fef2f2; --danger-border: #fecaca;
  --warn: #f59e0b; --warn-bg: #fffbeb; --warn-border: #fed7aa;
  --done-bg: #f0fdf4; --done-text: #16a34a; --done-border: #bbf7d0;
  --upcoming-bg: #e0e7ff; --upcoming-text: #4f46e5;
  --cancel-bg: #fef2f2; --cancel-text: #dc2626;
  --input-bg: #ffffff; --input-border: #d1d5db;
  --shadow: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
  --shadow-hover: 0 4px 12px rgba(0,0,0,0.08), 0 2px 4px rgba(0,0,0,0.04);
  --navbar-bg: #ffffff; --navbar-border: #e2e8f0;
  --stat-bg: #ffffff;
  --table-header: #f8fafc; --table-stripe: #f8fafc;
  --pill-bg: #e2e8f0; --pill-text: #475569;
  --slot-bg: #ffffff; --slot-border: #e2e8f0;
  --break-summer-bg: #fef9c3; --break-summer-border: #facc15; --break-summer-text: #854d0e;
  --break-xmas-bg: rgba(16,185,129,0.12); --break-xmas-border: rgba(16,185,129,0.4); --break-xmas-text: #065f46;
  /* Break-banner gradient stops (Builder calendar summer/xmas markers).
     Seasonal identity is intentional — warm amber for summer,
     emerald for xmas. Routed through tokens so dark mode can soften
     them without touching markup. */
  --break-summer-start: #fbbf24; --break-summer-end: #f59e0b;
  --break-xmas-start: #34d399; --break-xmas-end: #059669;
  /* Purple accent — used today only for the AI Rota beta button.
     Promoted to a token so dark mode adjusts and future AI/beta
     surfaces can share the hue. */
  --purple: #8b5cf6; --purple-soft: rgba(139,92,246,0.12);
  /* Saturated red for explicitly destructive controls (Clear All).
     Distinct from --danger which covers errors + cancellations. */
  --danger-strong: #dc2626;
  /* Drop-target glow used on the Builder slot drag-over state.
     Tinted indigo to match --primary; softer in dark mode. */
  --primary-glow: rgba(37,99,235,0.15);
  /* Indigo — shared with join.hpbteaching.app, used for all dropdowns & focus rings */
  --indigo: #4f46e5; --indigo-dark: #3730a3; --indigo-light: #e0e7ff;
  /* Audit-pack tile icon swatches (Compliance & Audit page).
     Distinct from --warn-bg / --done-bg which are full-alert chips —
     these are softer ~12% washes for the icon backgrounds. Introduced
     15 May 2026 to lift hardcoded rgba(...) values out of the
     inline template <style> block (audit action 4.4, R21 dark mode). */
  --audit-warn-bg: rgba(245,158,11,0.12); --audit-warn-fg: #b45309;
  --audit-good-bg: rgba(16,185,129,0.12); --audit-good-fg: #15803d;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0f172a; --bg-card: #1e293b; --bg-card-hover: #334155;
    --text: #e2e8f0; --text-muted: #94a3b8; --text-light: #64748b;
    --border: #334155; --border-light: #1e293b;
    /* Indigo accent — unified with marketing surfaces (landing/login/join). */
    --primary: #a5b4fc; --primary-hover: #818cf8; --primary-bg: rgba(79,70,229,0.18);
    --accent: #34d399; --accent-hover: #10b981;
    --danger: #f87171; --danger-bg: rgba(239,68,68,0.15); --danger-border: rgba(239,68,68,0.3);
    --warn: #fbbf24; --warn-bg: rgba(245,158,11,0.15); --warn-border: rgba(245,158,11,0.3);
    --done-bg: rgba(16,185,129,0.15); --done-text: #34d399; --done-border: rgba(16,185,129,0.3);
    --upcoming-bg: rgba(79,70,229,0.18); --upcoming-text: #a5b4fc;
    --cancel-bg: rgba(239,68,68,0.15); --cancel-text: #f87171;
    --input-bg: #1e293b; --input-border: #475569;
    --shadow: 0 1px 3px rgba(0,0,0,0.25), 0 1px 2px rgba(0,0,0,0.15);
    --shadow-hover: 0 4px 16px rgba(0,0,0,0.3), 0 2px 6px rgba(0,0,0,0.2);
    --navbar-bg: #1e293b; --navbar-border: #334155;
    --stat-bg: #1e293b;
    --table-header: #1e293b; --table-stripe: rgba(255,255,255,0.03);
    --pill-bg: #334155; --pill-text: #cbd5e1;
    --slot-bg: #1e293b; --slot-border: #334155;
    --break-summer-bg: rgba(250,204,21,0.15); --break-summer-border: #a16207; --break-summer-text: #fde047;
    --break-xmas-bg: rgba(16,185,129,0.15); --break-xmas-border: #059669; --break-xmas-text: #6ee7b7;
    --break-summer-start: #d97706; --break-summer-end: #b45309;
    --break-xmas-start: #059669; --break-xmas-end: #047857;
    --purple: #c4b5fd; --purple-soft: rgba(139,92,246,0.18);
    --danger-strong: #f87171;
    --primary-glow: rgba(165,180,252,0.22);
    --indigo-light: rgba(79,70,229,0.22); --indigo-dark: #a5b4fc;
    /* Audit-pack tile icon swatches — slightly brighter in dark mode
       so the swatch still reads against the darker --bg-card surface. */
    --audit-warn-bg: rgba(245,158,11,0.18); --audit-warn-fg: #fbbf24;
    --audit-good-bg: rgba(16,185,129,0.18); --audit-good-fg: #34d399;
  }
}

/* === Reset & Base === */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { font-size: 16px; -webkit-font-smoothing: antialiased; }
/* min-height uses 100dvh (dynamic viewport) so iOS Safari's
   collapsing toolbar doesn't make the body shrink unexpectedly.
   Falls back to 100vh on browsers that don't support dvh. */
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: var(--text); background: var(--bg); line-height: 1.6; min-height: 100vh; min-height: 100dvh; }
a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }

/* === Layout === */
.container { max-width: 1280px; margin: 0 auto; padding: 24px; }

/* === Navbar ===
   Slice 3: brings the navbar in line with the marketing typographic
   rhythm. Lighter border, slightly more padding, subtle frosted-glass
   on scroll, indigo logo with soft shadow, bolder active state. */
.navbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 28px;
    background: var(--navbar-bg);
    border-bottom: 1px solid var(--navbar-border);
    position: sticky;
    top: 0;
    z-index: 100;
    flex-wrap: wrap;
    gap: 12px;
    /* Soft frosted glass — only kicks in if user scrolls navbar over content. */
    backdrop-filter: saturate(140%) blur(8px);
    -webkit-backdrop-filter: saturate(140%) blur(8px);
}
.nav-brand a { display: flex; align-items: center; gap: 8px; text-decoration: none; color: var(--text); }
.brand-icon { font-size: 1.5rem; }
.brand-icon-img { display: none; }
.brand-text { font-size: 1rem; font-weight: 700; letter-spacing: -0.01em; }
/* Universal logo mark — uses the indigo accent var so it follows light/dark. */
.nav-logo { display: flex; align-items: center; gap: 10px; text-decoration: none; font-weight: 700; font-size: 1rem; color: var(--text); transition: opacity 0.15s; }
.nav-logo:hover { opacity: 0.85; text-decoration: none; }
.nav-logo-dot {
    width: 36px;
    height: 36px;
    background: var(--primary);
    border-radius: 9px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-weight: 800;
    font-size: 0.88rem;
    letter-spacing: -0.5px;
    box-shadow: 0 1px 3px rgba(79,70,229,0.3), inset 0 1px 0 rgba(255,255,255,0.15);
}
.nav-links { display: flex; gap: 2px; }
.nav-links a {
    padding: 8px 14px;
    border-radius: 8px;
    color: var(--text-muted);
    font-weight: 500;
    font-size: 0.9rem;
    transition: background 0.15s, color 0.15s;
    white-space: nowrap;
}
.nav-links a:hover { background: var(--primary-bg); color: var(--primary); text-decoration: none; }
.nav-links a.active { background: var(--primary-bg); color: var(--primary); font-weight: 600; text-decoration: none; }
.nav-user { display: flex; align-items: center; gap: 12px; }

/* Floating font size controls — desktop: bottom-left, mobile: above feedback FAB */
.font-size-controls { position: fixed; bottom: 24px; left: 24px; z-index: 150; display: flex; flex-direction: column; gap: 6px; opacity: 0.35; transition: opacity 0.2s; }
.font-size-controls:hover { opacity: 1; }
.fs-btn { width: 34px; height: 34px; border-radius: 50%; border: none; background: var(--bg-card); color: var(--text-muted); cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0,0,0,0.15); transition: all 0.15s; }
.fs-btn:hover { background: var(--primary); color: white; transform: scale(1.1); }
.fs-btn svg { pointer-events: none; }

/* === Buttons === */
.btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: 8px; font-weight: 600; font-size: 0.875rem; line-height: 1.25; font-family: inherit; box-sizing: border-box; border: none; cursor: pointer; transition: all 0.15s; text-decoration: none; }
.btn:hover { text-decoration: none; }
/* Press feedback — every .btn across the app gets a tactile
   <100ms response on :active (Doherty / R17). The released state
   inherits the .15s `transition: all` above for the back-up motion.
   Transform is theme-independent so no dark-mode adjustment needed.
   Documented in docs/design-system/patterns.md ("Button press
   feedback"), audit ref builder.md action 5.7. */
.btn:active { transform: scale(0.98); transition: transform 0.05s ease-in; }
.btn-primary { background: var(--primary); color: white; }
.btn-primary:hover { background: var(--primary-hover); }
.btn-accent { background: #10b981; color: white; }
.btn-accent:hover { background: #059669; }
.btn-danger { background: #ef4444; color: white; }
.btn-danger:hover { background: #dc2626; }
.btn-outline { background: transparent; border: 1px solid var(--input-border); color: var(--pill-text); }
.btn-outline { transition: all 0.15s ease; }
.btn-outline:hover { background: var(--primary-bg, #eff6ff); border-color: var(--primary); color: var(--primary); }
.btn-sm { padding: 4px 10px; font-size: 0.8rem; }
.btn-block { width: 100%; justify-content: center; }

/* === Cards === */
.card { background: var(--bg-card); border-radius: 12px; padding: 20px; box-shadow: var(--shadow); margin-bottom: 16px; transition: box-shadow 0.2s ease, transform 0.2s ease; }
.card h3 { margin-bottom: 12px; font-size: 1.1rem; }

/* === Forms === */
.form-group { margin-bottom: 16px; }
.form-group label { display: block; font-weight: 600; margin-bottom: 4px; font-size: 0.875rem; }
input[type="text"], input[type="email"], input[type="password"], input[type="url"],
select, textarea { padding: 8px 12px; border: 1px solid var(--input-border); border-radius: 8px; font-size: 0.9rem; width: 100%; font-family: inherit; transition: border-color 0.15s; background: var(--input-bg); color: var(--text); }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--indigo); box-shadow: 0 0 0 3px rgba(79,70,229,0.1); }
.form-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.form-row select { width: auto; }
.form-inline { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.input-flex { flex: 1; min-width: 200px; }
.input-md { width: 200px; }
.input-full { width: 100%; }
.input-search { max-width: 300px; }
.inline-form { display: inline; }
.checkbox-label { display: flex; align-items: center; gap: 6px; font-size: 0.875rem; cursor: pointer; white-space: nowrap; }

/* === Tables === */
.table-responsive { overflow-x: auto; }
.data-table { width: 100%; border-collapse: collapse; background: var(--bg-card); border-radius: 12px; overflow: hidden; box-shadow: var(--shadow); }
.data-table th { background: var(--border-light, #f1f5f9); padding: 11px 16px; text-align: left; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); font-weight: 600; }
.data-table td { padding: 12px 16px; border-top: 1px solid var(--border-light, #f1f5f9); font-size: 0.9rem; }
.data-table tbody tr { cursor: pointer; transition: background 0.15s ease; }
/* Indigo-tinted hover so the table feels on-brand instead of grey-on-grey. */
.data-table tbody tr:hover { background: rgba(79, 70, 229, 0.05); }
.row-past { color: var(--text-muted); transition: color 0.15s ease; }
.row-past:hover { color: var(--text); }
/* Completed rows: muted text only, NO background tint (Nov 2026 fix).
   The previous green-tinted background combined with the green
   "done" status pill was redundant encoding AND inverted the
   visual hierarchy — completed sessions were the loudest rows in
   the table when users almost always care more about what's coming.
   Now `done` rows fade into the background; upcoming/next rows
   carry the visual weight. The .status-done pill on its own is
   sufficient signal that a session is completed. */
.row-done { color: var(--text-muted); }
.row-done:hover { color: var(--text); background: rgba(79, 70, 229, 0.04); }
.row-done .status-badge { opacity: 0.85; }
.row-cancelled { background: var(--cancel-bg) !important; text-decoration: line-through; }
.row-cancelled:hover { background: color-mix(in srgb, var(--cancel-bg) 55%, var(--bg-card-hover) 45%) !important; }
.row-danger { background: var(--danger-bg) !important; }
.row-danger:hover { background: color-mix(in srgb, var(--danger-bg) 55%, var(--bg-card-hover) 45%) !important; }
/* Next-upcoming row — Nov 2026 mock-up alignment. Final shape:
   flat indigo tint at rest (no gradient) PLUS a strong 3px primary-
   indigo left bar that anchors the row visually as "the one that's
   next." The bar is the load-bearing signal; the tint reinforces.

   The user's mock-up showed both: a tinted row with a clear left
   accent. Removing the gradient last commit was right; dropping
   the bar was overcorrection. Restored here matching the mock. */
tr.row-next-upcoming {
  background: rgba(79, 70, 229, 0.05) !important;
  box-shadow: inset 3px 0 0 var(--primary);
}
tr.row-next-upcoming:hover {
  background: rgba(79, 70, 229, 0.10) !important;
}
/* Relative-time subtitle on the next row ("in 2 weeks") — replaces
   the weekday for this one row only. Slight indigo tint to tie it
   to the row's other accents; same size/weight as the weekday it
   replaces so the row layout stays unchanged. */
.row-next-relative {
  color: var(--primary);
  font-weight: 600;
}
.col-date { white-space: nowrap; }
.col-topic { max-width: 400px; }

/* === Status & Category Badges === */
.status-badge { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 0.75rem; font-weight: 600; }
.status-upcoming { background: #dbeafe; color: #1e40af; }
.status-done { background: #dcfce7; color: #166534; }
.status-cancelled { background: #fee2e2; color: #991b1b; }
/* Lifecycle states added in malleable-programme slice 2 — see
   ui-design.md §2.1. Amber = "speaker said done, awaiting coord
   sign-off"; grey = "skipped / superseded". Distinguishable from
   done (green) and cancelled (red) at-a-glance per principle A.6
   (colour = state). */
.status-completed { background: #fef3c7; color: #92400e; }
.status-skipped { background: #e2e8f0; color: #475569; }
/* "Unlocked" — session was signed off and has been temporarily
   reopened by the coordinator for correction. Distinct from
   .status-completed ("Awaiting sign-off" — never signed off);
   colour = state per principle A.6. Desaturated indigo ties the
   pill to the design system's primary accent (--indigo) so it
   reads as an active corrective state rather than an alert.
   The accompanying .slot-unlock-overlay banner uses amber for
   the call-to-action (relock); the pill stays calm-indigo so
   the two affordances do not visually compete.
   Bug fix 2026-05-19. */
.status-unlocked { background: #e0e7ff; color: #3730a3; }
.status-pending { background: #fef3c7; color: #92400e; }
.status-accepted { background: #dcfce7; color: #166534; }
.status-rejected { background: #fee2e2; color: #991b1b; }
.category-tag { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 0.75rem; font-weight: 600; white-space: nowrap; }
.cat-HPB { background: #dbeafe; color: #1e40af; }
.cat-Liver\/Bile, .cat-Liver-Bile { background: #dcfce7; color: #166534; }
.cat-HPB-General { background: #dbeafe; color: #1e40af; }
.cat-OLT { background: #fde8d8; color: #7c2d12; }
.cat-Pancreas { background: #fef3c7; color: #92400e; }
.cat-Journal-Club { background: #f3e8ff; color: #6b21a8; }

/* === User Badge === */
.user-badge { padding: 4px 12px; border-radius: 20px; font-size: 0.8rem; font-weight: 600; }
.role-coordinator { background: #dbeafe; color: #1e40af; }
.role-trainee { background: #dcfce7; color: #166534; }
.role-guest { background: #f3e8ff; color: #6b21a8; }
.role-badge { display: inline-block; padding: 2px 8px; border-radius: 8px; font-size: 0.75rem; text-transform: uppercase; }
.role-external { background: #fef3c7; color: #92400e; }
.role-consultant { background: #e0e7ff; color: #3730a3; }
.role-jcf { background: #dbeafe; color: #1e40af; }
.role-pa { background: #fce7f3; color: #9d174d; }
.role-cns { background: #d1fae5; color: #065f46; }
.role-affiliated { background: #d1fae5; color: #065f46; }
.role-student { background: #fef9c3; color: #713f12; }
.role-scf { background: #ede9fe; color: #4c1d95; }

/* === Page Header ===
   Shared shell for every authenticated page. Brings the marketing-surface
   typography rhythm (eyebrow + bold heading + subtitle) into the product
   surface, with actions docked on the right. Stacks on mobile. */
.page-header {
    display: flex;
    flex-direction: row;
    align-items: flex-end;
    justify-content: space-between;
    gap: 16px;
    margin: 4px 0 28px;
    flex-wrap: wrap;
}
.page-header-title { flex: 1; min-width: 0; }
.page-header-eyebrow {
    display: block;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--primary);
    margin: 0 0 6px;
}
.page-header h1 {
    font-size: clamp(1.5rem, 2.4vw, 1.85rem);
    font-weight: 800;
    letter-spacing: -0.015em;
    line-height: 1.15;
    color: var(--text);
    margin: 0;
}
.page-header-subtitle, .page-subtitle {
    font-size: 0.92rem;
    line-height: 1.5;
    color: var(--text-muted);
    margin: 6px 0 0;
}
.header-actions {
    display: flex;
    gap: 8px;
    align-items: center;
    flex-wrap: wrap;
    flex-shrink: 0;
}
/* Desktop: show full labels. Mobile rule below swaps to .btn-label-short. */
.btn-label-short { display: none; }
.btn-label-full { display: inline; }
@media (max-width: 720px) {
    .page-header { flex-direction: column; align-items: flex-start; gap: 10px; margin: 4px 0 18px; }
    .page-header h1 { font-size: 1.4rem; line-height: 1.2; }
    /* eyebrow + subtitle scale with the title so the rhythm survives on phones */
    .page-header-eyebrow { font-size: 0.66rem; letter-spacing: 0.09em; margin-bottom: 4px; }
    .page-header-subtitle, .page-subtitle { font-size: 0.85rem; margin-top: 4px; }
    /* Header actions: 3-up segmented strip on mobile (M9 — was icon-only
       in M8 but the user reported the three icons looked left-stacked
       and unbalanced). Each button now flex:1 and shows a SHORT label
       below/beside the icon, so the row reads as a designed action bar
       evenly spanning the page width. */
    .header-actions {
        width: 100%; gap: 6px;
        flex-wrap: nowrap; flex-direction: row;
    }
    .page-header .header-actions .btn {
        flex: 1; min-width: 0;          /* equal share, allow shrink */
        gap: 5px; padding: 8px 6px;
        font-size: 0.78rem; min-height: 44px;
        white-space: nowrap; overflow: hidden;
        margin-right: 0 !important;     /* kill the inline margin */
        justify-content: center;
    }
    /* Long labels live on desktop; short labels surface on mobile. */
    .page-header .btn-label-full { display: none; }
    .page-header .btn-label-short { display: inline; }
}
@keyframes spin { to { transform: rotate(360deg); } }
/* View toggle — was two full pill buttons (Table / Timeline) sitting
   in the same visual weight class as primary CTAs. Round 2 close-out
   (2026-05-10) shrinks them to a tight segmented icon-control: a
   single rounded box with two icon halves, active state filled.
   Same affordance, much less visual weight. */
.view-toggle {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg-card);
}
.view-toggle-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px; height: 32px;
  color: var(--text-muted);
  background: transparent;
  border: none;
  cursor: pointer;
  text-decoration: none;
  transition: all 0.15s ease;
}
.view-toggle-btn + .view-toggle-btn { border-left: 1px solid var(--border); }
.view-toggle-btn:hover { color: var(--text); background: var(--bg-card-hover, var(--bg)); text-decoration: none; }
.view-toggle-btn.is-active {
  color: white;
  background: var(--primary);
}
.view-toggle-btn.is-active:hover {
  color: white;
  background: var(--primary-hover, #4338ca);
}

/* === Login === */
.login-container { display: flex; justify-content: center; align-items: center; min-height: 80vh; min-height: 80dvh; }
.login-card { background: var(--bg-card); border-radius: 16px; padding: 40px; box-shadow: 0 4px 24px rgba(0,0,0,0.15); width: 100%; max-width: 400px; }
.login-header { text-align: center; margin-bottom: 24px; }
.login-header h1 { font-size: 1.75rem; color: var(--text); }
.login-subtitle { color: var(--text-muted); font-size: 0.9rem; margin-top: 4px; }
.login-form .form-group { margin-bottom: 16px; }
.login-footer { text-align: center; margin-top: 16px; font-size: 0.8rem; color: var(--text-light); }

/* === Filters ===
   Slice 5: subtle card chrome so the filter row reads as a designed
   block, not naked inputs floating between the hero and the table. */
.filters-bar {
    margin-bottom: 24px;
    padding: 12px 14px;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 12px;
}
.filters-form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.filters-form select { width: auto; }
.filters-secondary { display: contents; }  /* desktop: pretend the wrapper isn't there */
.filters-toggle { display: none; }          /* desktop: no toggle button */
.filters-toggle-count {
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--primary); color: #fff; border-radius: 999px;
    font-size: 0.65rem; font-weight: 700; min-width: 16px; height: 16px;
    padding: 0 4px; margin-left: 4px;
}

/* === Calendar View === */
.calendar-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
.calendar-slot { background: var(--bg-card); border-radius: 12px; padding: 16px; box-shadow: var(--shadow); transition: all 0.2s ease; text-decoration: none; color: inherit; border-left: 4px solid var(--primary); }
.calendar-slot:hover { box-shadow: var(--shadow-hover); transform: translateY(-1px); }
.calendar-slot:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.12); text-decoration: none; }
.calendar-slot.slot-past { opacity: 0.5; border-left-color: var(--text-light); }
.calendar-slot.slot-done { border-left-color: #10b981; }
.calendar-slot.slot-cancelled { border-left-color: #ef4444; }
.slot-date { font-weight: 700; font-size: 0.95rem; }
.slot-day { font-size: 0.8rem; color: var(--text-muted); }
.slot-topic { font-size: 0.85rem; margin-top: 8px; }
.slot-empty { color: var(--text-light); font-style: italic; }
.slot-speaker { font-size: 0.8rem; color: var(--text-muted); margin-top: 4px; }
.calendar-break { background: linear-gradient(135deg, #fbbf24, #f59e0b); color: white; text-align: center; padding: 20px; border-radius: 12px; font-weight: 700; font-size: 1.1rem; grid-column: 1 / -1; }

/* === Break Row === */
.break-row td { background: transparent; padding: 0 !important; border: none !important; }
.break-row:hover { background: transparent !important; }
.break-cell { padding: 12px 20px !important; text-align: center; font-weight: 500; font-size: 0.85rem; letter-spacing: 0.03em; border: none !important; }
.break-cell-summer { background: #fefce8 !important; color: #a16207; border-top: 1px dashed #e5d68a !important; border-bottom: 1px dashed #e5d68a !important; transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease; }
.break-row:hover .break-cell-summer { background: #fef08a !important; color: #713f12 !important; border-top-color: #ca8a04 !important; border-bottom-color: #ca8a04 !important; }
.break-cell-xmas { background: rgba(16,185,129,0.08) !important; color: #065f46; border-top: 1px dashed rgba(16,185,129,0.4) !important; border-bottom: 1px dashed rgba(16,185,129,0.4) !important; transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease; }
.break-row:hover .break-cell-xmas { background: rgba(16,185,129,0.22) !important; color: #064e3b !important; border-top-color: rgba(16,185,129,0.7) !important; border-bottom-color: rgba(16,185,129,0.7) !important; }

/* === Session Detail === */
.session-grid { display: grid; grid-template-columns: 1fr 350px; gap: 24px; }
/* .session-info-card border-left removed — was creating an awkward
   "bracket" effect on a dark hero, especially because the hero
   itself ALSO had a left-strip. Status is now carried entirely by
   the pill on the hero (.status-badge). */
.session-info-card.card-cancelled .session-meta .meta-item { opacity: 0.65; }
.session-main { min-width: 0; }
.session-main .card h2 { font-size: 1.35rem; font-weight: 700; line-height: 1.4; margin-bottom: 4px; overflow-wrap: break-word; word-break: break-word; }
.session-meta { display: grid; grid-template-columns: 1fr 1fr; gap: 16px 28px; margin-top: 0; }
.meta-item { font-size: 0.95rem; overflow-wrap: break-word; word-break: break-word; min-width: 0; line-height: 1.5; }
.meta-item-wide { grid-column: 1 / -1; }

/* === Session hero — plain left-aligned text on the page bg.
   Nothing fancy: eyebrow, big bold title, pills. No bracket, no
   gradient strip, no centering. Multiple iterations confirmed
   anything more elaborate competes with the actual content below
   instead of supporting it. */
.session-hero {
    margin: 4px 0 28px;
}
.session-hero.session-hero-cancelled .session-hero-title { text-decoration: line-through; opacity: 0.7; }
.session-hero-eyebrow {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--primary);
    margin: 0 0 10px;
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
}
.session-hero-eyebrow .session-hero-sep {
    color: var(--text-muted);
    opacity: 0.5;
}
.session-hero-title {
    font-size: clamp(1.5rem, 3vw, 2rem);
    font-weight: 800;
    letter-spacing: -0.015em;
    line-height: 1.2;
    color: var(--text);
    margin: 0 0 12px;
}
.session-hero-title em { font-style: normal; color: var(--text-muted); font-weight: 600; }
.session-hero-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
}
@media (max-width: 720px) {
    /* Same rhythm logic as .page-header on mobile: tighter eyebrow
       letterspacing, smaller bottom margin, pill gap reduced so the
       date/time/category row reads as one tight block above the title. */
    .session-hero { margin: 4px 0 18px; }
    .session-hero-eyebrow { font-size: 0.66rem; letter-spacing: 0.08em; gap: 6px; margin-bottom: 6px; }
    .session-hero-eyebrow .session-hero-sep { display: inline; }
    .session-hero-title { font-size: 1.4rem; line-height: 1.25; margin-bottom: 10px; }
    .session-hero-pills { gap: 6px; }
}

/* Declaration-of-interest card — refined to match the platform's
   rounded-card visual language (full soft-tinted border instead of
   heavy left stripe; small icon + eyebrow + body in line with the
   hero panel rhythm). */
.declaration-card {
    margin-top: 20px;
    padding: 16px 20px;
    border-radius: 12px;
    border: 1px solid;
    background: linear-gradient(135deg, var(--bg-card) 0%, transparent 100%);
    font-size: 0.92rem;
    line-height: 1.5;
    color: var(--text);
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 14px;
    align-items: start;
}
.declaration-card-icon {
    flex-shrink: 0;
    width: 32px; height: 32px;
    border-radius: 8px;
    display: inline-flex; align-items: center; justify-content: center;
}
.declaration-card-icon svg {
    width: 18px; height: 18px;
    stroke-width: 2.4;
}
.declaration-card-body { min-width: 0; }
.declaration-card-eyebrow {
    display: block;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    margin-bottom: 4px;
}
.declaration-card.declaration-none {
    border-color: rgba(16, 185, 129, 0.32);
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(16, 185, 129, 0.06) 100%);
}
.declaration-card.declaration-none .declaration-card-icon {
    background: rgba(16, 185, 129, 0.12);
    color: #047857;
}
.declaration-card.declaration-none .declaration-card-eyebrow { color: #047857; }
.declaration-card.declaration-has {
    border-color: rgba(245, 158, 11, 0.40);
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(245, 158, 11, 0.07) 100%);
}
.declaration-card.declaration-has .declaration-card-icon {
    background: rgba(245, 158, 11, 0.15);
    color: #b45309;
}
.declaration-card.declaration-has .declaration-card-eyebrow { color: #b45309; }
@media (prefers-color-scheme: dark) {
    .declaration-card.declaration-none {
        border-color: rgba(16, 185, 129, 0.4);
        background: linear-gradient(135deg, var(--bg-card) 0%, rgba(16, 185, 129, 0.10) 100%);
    }
    .declaration-card.declaration-none .declaration-card-eyebrow,
    .declaration-card.declaration-none .declaration-card-icon { color: #6ee7b7; }
    .declaration-card.declaration-has {
        border-color: rgba(245, 158, 11, 0.5);
        background: linear-gradient(135deg, var(--bg-card) 0%, rgba(245, 158, 11, 0.10) 100%);
    }
    .declaration-card.declaration-has .declaration-card-eyebrow,
    .declaration-card.declaration-has .declaration-card-icon { color: #fcd34d; }
}

/* Primary-action highlight inside the action-row — the row was
   previously 8 outline buttons with no visual hierarchy. Mark the
   one status-appropriate primary action and let the rest fall back. */
.action-row .btn-primary { box-shadow: 0 1px 3px rgba(79, 70, 229, 0.25); }

/* Card-header utility — replaces the recurring inline pattern
   `style="display:flex; justify-content:space-between; align-items:center;
   flex-wrap:wrap; gap:8px; margin-bottom:8px"` used at the top of
   most cards (heading on the left, action button(s) on the right). */
.card-header-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 12px;
}

/* Reusable section eyebrow (muted variant) — used inside cards to
   group sub-sections like "PORTAL LINKS" / "AUTOMATED EMAILS" /
   "SHARE & COPY" without resorting to inline styles every time. */
.eyebrow-muted {
    display: block;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
    margin: 0 0 10px;
}

/* Portal/utility-link row — used inside the Coordinator Tools panel
   on session_detail to render speaker / supervisor / attendance /
   feedback URL rows. Consolidates ~15 lines of per-row inline styles
   into one component. */
.tool-link {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 8px;
    font-size: 0.85rem;
    margin-bottom: 6px;
    transition: border-color 0.15s;
}
.tool-link:hover { border-color: var(--primary); }
.tool-link-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tool-link-label strong { font-weight: 600; }
.tool-link-label-sub { color: var(--text-muted); margin-left: 4px; }
.tool-link-actions { display: flex; gap: 4px; flex-shrink: 0; }
.tool-link-actions .btn { font-size: 0.72rem; padding: 4px 10px; }

/* Status row — used for the Automated Emails compact list inside
   Coordinator Tools. Replaces the previous <table> with inline-styled
   <tr> rows. */
.status-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    border-bottom: 1px solid var(--border-light, var(--border));
    font-size: 0.85rem;
}
.status-row:last-child { border-bottom: 0; }
.status-row-icon { width: 18px; flex-shrink: 0; line-height: 1; }
.status-row-icon-sent { color: var(--accent); }
.status-row-icon-pending { color: var(--text-muted); }
.status-row-label { flex: 1; min-width: 0; }
.status-row-label strong { font-weight: 600; }
.status-row-when { color: var(--text-muted); font-weight: 400; margin-left: 4px; }
.status-row-target { color: var(--text-muted); font-size: 0.78rem; flex-shrink: 0; text-align: right; }

/* Compact form inputs used inside the coordinator edit-mode panel
   on session_detail. The same `border + radius + padding + bg + color
   + font-size` declaration was repeated 6 times inline. */
.edit-input {
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 3px 6px;
    background: var(--bg);
    color: var(--text);
    font-size: 0.9rem;
}
.edit-input-flex {
    flex: 1;
    min-width: 0;
    width: 100%;
    box-sizing: border-box;
}

/* Edit-mode container for the inline session editor (toggled via
   #session-edit). Was inline-styled with a 2px dashed accent border. */
.session-edit-box {
    border: 2px dashed var(--accent);
    border-radius: 8px;
    padding: 12px;
    margin-top: 8px;
}

/* Attendance table — was inline-styled per <td>. Same pattern reused
   on the post-attendance manual-entry helper. */
.attendance-table {
    width: 100%;
    font-size: 0.85rem;
    border-collapse: collapse;
}
.attendance-table thead tr { border-bottom: 2px solid var(--border); text-align: left; }
.attendance-table th { padding: 6px 8px; }
.attendance-table tbody tr { border-bottom: 1px solid var(--border-light, var(--border)); }
.attendance-table td { padding: 6px 8px; color: var(--text-muted); }
.attendance-table td.att-name { color: var(--text); font-weight: 500; }
.attendance-table td.att-when { white-space: nowrap; }

/* Manual-attendance entry form — text inputs need consistent chrome
   (the global input rules are right, just reuse them). */
.manual-att-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 8px;
    margin-bottom: 8px;
}

/* Amber/warning button — used on the "Add Attendance" manual-entry
   button. Was an inline `style="background:#f59e0b; color:white;
   border:none"`. */
.btn-warn { background: var(--warn); color: white; border: none; }
.btn-warn:hover { background: #d97706; }

/* === Layout & typography utilities ===
   Tiny helpers to soak up the most-repeated per-element inline style
   patterns scattered across admin/stats and admin/speakers
   templates. These are intentionally narrow and named after the
   property they set, so the visual contract is unambiguous. */
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-right-bold { text-align: right; font-weight: 600; }
.fw-400 { font-weight: 400; }
.fw-600 { font-weight: 600; }
.fw-700 { font-weight: 700; }
.text-accent { color: var(--accent); }
.text-warn { color: var(--warn); }
.text-danger { color: var(--danger); }
.text-primary { color: var(--primary); }
.muted-italic { color: var(--text-muted); font-style: italic; }
.fs-65 { font-size: 0.65rem; }
.fs-75 { font-size: 0.75rem; }
.fs-80 { font-size: 0.8rem; }
.fs-85 { font-size: 0.85rem; }
.fs-90 { font-size: 0.9rem; }
.fs-130 { font-size: 1.3rem; }
.m-0 { margin: 0; }
.nowrap { white-space: nowrap; }
.mt-8 { margin-top: 8px; }
.mt-12 { margin-top: 12px; }
.mt-16 { margin-top: 16px; }
.mt-24 { margin-top: 24px; }
.mb-8 { margin-bottom: 8px; }
.mb-16 { margin-bottom: 16px; }
.flex-center-gap { display: flex; align-items: center; gap: 6px; }
.flex-row-gap-8 { display: flex; gap: 8px; flex-wrap: wrap; }
.flex-row-gap-12 { display: flex; gap: 12px; flex-wrap: wrap; }
.flex-row-spread { display: flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap; }
.chart-box { height: 150px; }
.chart-box-tall { height: 220px; }

/* Sign-off status pill (consultant approval state). Replaces inline-
   styled green/amber/red badges in the slides-and-materials card. */
.signoff-pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 10px;
    border-radius: 12px;
    font-size: 0.75rem;
    font-weight: 600;
    border: 1px solid;
}
.signoff-pill-approved { background: var(--done-bg); color: var(--done-text); border-color: var(--done-border); }
.signoff-pill-changes  { background: var(--cancel-bg); color: var(--cancel-text); border-color: var(--danger-border); }
.signoff-pill-pending  { background: var(--warn-bg); color: var(--warn); border-color: var(--warn-border); }
/* Label above value — uppercase muted small caps */
.meta-item strong { display: block; font-size: 0.68rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin-bottom: 3px; }
/* Presenter / Supervisor name links → /speaker/<id>. Quiet by
   default (inherits text colour, no underline) so the meta-grid
   stays calm; tiny ↗ arrow signals "this opens a sub-page" without
   shouting. On hover the arrow translates up-right slightly to
   reinforce the destination, and the name picks up the primary
   colour. Inline-flex pulls the arrow tightly against the name. */
.meta-person-link {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    color: var(--text);
    text-decoration: none;
    transition: color 0.15s;
}
.meta-person-link:hover { color: var(--primary); text-decoration: none; }
.meta-person-arrow {
    color: var(--text-muted);
    flex-shrink: 0;
    transition: color 0.15s, transform 0.15s;
}
.meta-person-link:hover .meta-person-arrow {
    color: var(--primary);
    transform: translate(1px, -1px);
}
.admin-actions { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--border); }
.action-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.whatsapp-preview { margin-top: 12px; padding: 12px; background: var(--done-bg); border-radius: 8px; }

/* === Rating === */
.rating { color: #f59e0b; }
.star-filled { color: #f59e0b; }
.star-empty { color: var(--border); }
.star-rating { display: flex; flex-direction: row-reverse; justify-content: flex-end; gap: 2px; }
.star-rating input { display: none; }
.star-rating label { cursor: pointer; font-size: 1.5rem; color: var(--border); transition: color 0.1s; }
.star-rating input:checked ~ label, .star-rating label:hover, .star-rating label:hover ~ label { color: #f59e0b; }
.rating-input { margin-bottom: 12px; }
.rating-stars { color: #f59e0b; }

/* === Feedback === */
.feedback-form { margin-bottom: 24px; }
.feedback-list { margin-top: 16px; }
.feedback-item { padding: 12px; border-bottom: 1px solid var(--border-light, #f1f5f9); }
.feedback-item:last-child { border-bottom: none; }
.feedback-rating { margin-bottom: 4px; }
.feedback-comment { font-size: 0.9rem; margin: 4px 0; }
.feedback-meta { color: var(--text-light); }

/* === Uploads === */
.upload-list { list-style: none; padding: 0; }
.upload-item { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--border-light, #f1f5f9); min-width: 0; flex-wrap: wrap; }
.upload-item:last-child { border-bottom: none; }
.upload-link { display: flex; align-items: center; gap: 8px; font-weight: 500; font-size: 0.875rem; min-width: 0; max-width: 100%; flex: 1; color: var(--primary); }
.upload-link:hover { text-decoration: underline; }
.upload-filename { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }
.upload-type-badge { font-size: 0.68rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); background: var(--bg); border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; flex-shrink: 0; }
.file-icon { flex-shrink: 0; color: var(--primary); opacity: 0.7; }
.upload-meta { font-size: 0.75rem; color: var(--text-light); white-space: nowrap; flex-shrink: 0; }
.upload-form { display: flex; align-items: center; gap: 8px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); flex-wrap: wrap; }

/* === Suggestion Box === */
.suggestion-box { margin-top: 32px; background: var(--bg-card); border-radius: 12px; padding: 20px; box-shadow: var(--shadow); }
.suggestion-form .form-row { gap: 8px; }

/* === Stats === */
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 24px; }
.stat-card { background: var(--bg-card); border-radius: 12px; padding: 20px; text-align: center; box-shadow: var(--shadow); }
.stat-value { font-size: 2rem; font-weight: 700; color: var(--primary); font-variant-numeric: tabular-nums; }
.stat-label { font-size: 0.85rem; color: var(--text-muted); margin-top: 4px; }
.charts-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }

/* === Coverage === */
.coverage-grid { display: grid; gap: 12px; }
.coverage-item { display: flex; align-items: center; gap: 12px; }
.coverage-label { width: 120px; font-weight: 600; font-size: 0.9rem; }
.coverage-bar-bg { flex: 1; background: var(--border-light, #f1f5f9); border-radius: 8px; height: 28px; overflow: hidden; }
.coverage-bar { background: var(--primary); height: 100%; border-radius: 8px; display: flex; align-items: center; padding: 0 12px; color: white; font-weight: 600; font-size: 0.8rem; min-width: 30px; transition: width 0.5s; }

/* === Fairness === */
.fairness-list { display: grid; gap: 8px; }
.fairness-item { display: flex; align-items: center; gap: 12px; position: relative; padding: 8px 12px; background: var(--bg, #f8fafc); border-radius: 8px; }
.fairness-rank { font-weight: 700; color: var(--primary); width: 30px; }
.fairness-name { font-weight: 600; width: 120px; }
.fairness-count { font-size: 0.85rem; color: var(--text-muted); width: 100px; }
.fairness-bar { position: absolute; left: 0; top: 0; height: 100%; background: #dbeafe; border-radius: 8px; z-index: -1; transition: width 0.5s; }

/* === Date Chips (Rota) === */
.date-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.date-chip { display: inline-block; padding: 4px 12px; border-radius: 20px; background: #eff6ff; color: var(--primary); font-size: 0.85rem; font-weight: 500; }
.chip-past { background: var(--border-light, #f1f5f9); color: var(--text-light); }

/* === Alerts === */
.flash-messages { margin-bottom: 16px; }
.alert { padding: 12px 16px; border-radius: 8px; margin-bottom: 8px; font-size: 0.9rem; }
.alert-success { background: #dcfce7; color: #166534; border: 1px solid #bbf7d0; }
.alert-error { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
.alert-info { background: #dbeafe; color: #1e40af; border: 1px solid #bfdbfe; }
.text-muted { color: var(--text-light); }

/* === HTMX Indicator === */
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { display: block; }

/* === Feedback Metrics === */
.feedback-metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 16px; }
.metric-item { text-align: center; }
.metric-value { font-size: 1.8rem; font-weight: 700; color: var(--primary); font-variant-numeric: tabular-nums; }
.metric-value.metric-positive { color: #10b981; }
.metric-label { font-size: 0.8rem; color: var(--text-muted); margin-top: 2px; }
.metric-sub { font-size: 0.75rem; color: var(--text-light); margin-top: 2px; }
.metric-bar { height: 6px; background: var(--border-light, #f1f5f9); border-radius: 3px; margin-top: 6px; overflow: hidden; }
.metric-fill { height: 100%; background: var(--primary); border-radius: 3px; transition: width 0.5s; }
.feedback-stars-grid { display: grid; gap: 6px; }
.feedback-row { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.feedback-row > label { font-size: 0.8rem; font-weight: 500; min-width: 100px; flex-shrink: 0; line-height: 1.2; }
.feedback-row .star-rating { flex-shrink: 0; }
.feedback-row .star-rating label { font-size: 1.2rem; min-width: auto; cursor: pointer; }
.feedback-scores { display: flex; gap: 8px; font-size: 0.8rem; font-weight: 600; color: var(--pill-text); }
.feedback-scores span { background: var(--border-light, #f1f5f9); padding: 2px 6px; border-radius: 4px; }

/* Coordinator-side hint that a person has opted out of bulk emails.
   Subtle grey pill — informational, not alarming. Hover for the
   reminder that operational 1:1 emails still send. */
.ppl-unsub-pill {
    display:inline-block;
    margin-left:6px; padding:1px 7px;
    border-radius:8px;
    font-size:0.66rem; font-weight:600;
    background:rgba(100,116,139,0.12); color:var(--text-muted);
    border:1px solid var(--border);
    text-transform:uppercase; letter-spacing:0.04em;
    cursor:help;
}

/* Passkey badge — neutral grey key glyph rendered next to the
   speaker name when they have at least one Credential row. Sits
   to the right of the (green) ORCID badge. Deliberately neutral:
   making it green would imply email-code users are "less safe",
   which isn't true — email-code is still strong auth. The glyph
   says "extra hardened", not "verified good". 14px target box,
   centred via inline-flex so it baselines cleanly next to the
   ORCID glyph. */
.passkey-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: 5px;
    vertical-align: -2px;
    width: 14px; height: 14px;
    color: var(--text-muted);
    opacity: 0.7;
    cursor: help;
    transition: opacity 0.15s ease, color 0.15s ease;
}
.passkey-badge:hover {
    color: var(--text);
    opacity: 1;
}

/* Temporal columns (Last seen, Last logged in) — right-aligned so
   the dates form a clean vertical stack the eye can scan. The
   absolute date sits on top in normal weight; the relative phrase
   + source label sits on a small subtitle line below. */
.col-temporal {
    text-align: right;
    white-space: nowrap;
}
.col-temporal .ts-abs {
    font-size: 0.85rem;
    color: var(--text);
    line-height: 1.2;
}
.col-temporal .ts-sub {
    font-size: 0.68rem;
    color: var(--text-muted);
    margin-top: 2px;
    line-height: 1.2;
}

/* Email column — tightened typography. The column is information-
   dense but low-importance (you don't pivot decisions on the
   domain), so it reads as metadata not headline. Slightly smaller
   font + muted colour pulls visual weight back toward the Name
   column. truncate on overflow keeps long addresses from pushing
   the table out. */
.speaker-table .col-email {
    font-size: 0.82rem;
    color: var(--text-muted);
    max-width: 220px;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* === Admin People tab strip === */
/* Shared between /admin/speakers (Active/External/Inactive tabs, JS-
   switched in-page) and /admin/registrations (the 'Registrations'
   tab, which is page-navigation since it's a different route). Kept
   here in style.css rather than inline so both templates render the
   identical strip without duplication. */
.ppl-tabs { display:flex; gap:6px; flex-wrap:wrap; margin-bottom:18px; }
.ppl-tab {
    padding:7px 14px; border-radius:999px; font-size:0.82rem; font-weight:600;
    text-decoration:none; color:var(--text-muted); border:1px solid var(--border);
    background:transparent; transition:all 0.15s; cursor:pointer;
    display:inline-flex; align-items:center; gap:6px;
    font-family:inherit;
}
.ppl-tab:hover { color:var(--text); border-color:var(--text-muted); text-decoration:none; }
.ppl-tab.active { background:var(--primary); color:white; border-color:var(--primary); }
.ppl-tab .ppl-count {
    font-weight:500; opacity:0.85;
    background:rgba(0,0,0,0.05); padding:1px 7px; border-radius:10px;
    font-size:0.72rem;
}
.ppl-tab.active .ppl-count { background:rgba(255,255,255,0.2); opacity:1; }

/* === Topic Explorer === */
/* Explore cards — updated 2026-05 to share the EXACT card chrome used by
   the mobile programme cards (.prog-card). Same surface, radius, border,
   transition, hover lift — so the Explore tab and the Programme tab feel
   like one design system rather than two visual dialects.
   Inner pieces (.explore-title, .explore-hook, .explore-why,
   .explore-paper-row) are content-specific but use the same design tokens
   (--bg-card, --border, --primary, --text-*) so dark-mode + light-mode
   automatically work. */
.explore-card {
    display: block;
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(79,70,229,0.04) 100%);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 14px 16px;
    margin-bottom: 10px;
    color: var(--text);
    transition: background 0.15s, border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}
.explore-card:hover {
    border-color: rgba(79,70,229,0.3);
    box-shadow: 0 4px 12px rgba(79,70,229,0.08);
}
.explore-results-meta {
    font-size: 0.8rem;
    color: var(--text-muted);
    margin-bottom: 12px;
}
.explore-card-tags { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-bottom: 8px; }
.explore-title {
    font-weight: 600; font-size: 0.95rem; line-height: 1.35;
    color: var(--text);
    margin-bottom: 8px;
    word-break: break-word;
}
/* Hook — punchy AI-generated one-liner. Indigo italic flags it as
   "the model's pitch" rather than copy from the paper. */
.explore-hook {
    font-size: 0.85rem; color: var(--primary);
    font-style: italic; line-height: 1.4;
    margin-bottom: 10px;
}
/* "Why this matters" callout. Subtle amber tint (same family as the
   today/attention colour) so it reads as a "worth pausing on" note
   without the old hardcoded #fefce8/#92400e palette that broke in dark
   mode. */
.explore-why {
    background: rgba(234, 179, 8, 0.06);
    border: 1px solid rgba(234, 179, 8, 0.2);
    border-radius: 8px;
    padding: 10px 12px;
    margin-bottom: 12px;
    font-size: 0.83rem;
    color: var(--text);
    line-height: 1.5;
}
/* Paper rows — small list of linked PubMed citations */
.explore-papers { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; }
.explore-paper-row { display: flex; align-items: flex-start; gap: 8px; font-size: 0.82rem; min-width: 0; }
.explore-paper-bullet {
    flex-shrink: 0; margin-top: 7px;
    width: 5px; height: 5px; border-radius: 50%;
    background: var(--primary);
    opacity: 0.6;
}
.explore-paper-content { min-width: 0; flex: 1; }
.explore-paper-link {
    color: var(--primary); text-decoration: none; font-weight: 500;
    line-height: 1.4; display: block;
    word-break: break-word; overflow-wrap: break-word;
}
.explore-paper-link:hover { text-decoration: underline; }
.explore-paper-meta {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: 6px; margin-top: 3px;
    font-size: 0.75rem; color: var(--text-light);
}
.explore-paper-pubmed {
    display: inline-block;
    padding: 1px 6px; font-size: 0.7rem;
    color: var(--primary); text-decoration: none;
    border: 1px solid rgba(79,70,229,0.3); border-radius: 4px;
    white-space: nowrap;
    transition: background 0.15s, border-color 0.15s;
}
.explore-paper-pubmed:hover {
    background: rgba(79,70,229,0.08); border-color: var(--primary);
    text-decoration: none;
}
@media (max-width: 768px) {
    .explore-card { padding: 12px 14px; }
    .explore-title { font-size: 0.92rem; }
    .explore-hook { font-size: 0.82rem; }
    .explore-why { font-size: 0.8rem; padding: 9px 11px; }
}

/* === Year Pills === */
/* Section header used above the programme table/timeline — gives
   the data block a small narrative break (eyebrow + label + count)
   matching the marketing-page rhythm. */
.section-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin: 8px 0 12px;
    flex-wrap: wrap;
}
.section-header-eyebrow {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--primary);
}
.section-header-eyebrow .section-header-count {
    color: var(--text-muted);
    font-weight: 500;
    margin-left: 8px;
    letter-spacing: 0.04em;
}
@media (max-width: 720px) {
    /* M9: section-header hidden on mobile entirely. The page-header
       subtitle above already says "Fortnightly HPB sessions · 2026/2027
       academic year" — repeating it here as "ALL SESSIONS · 2026/2027
       · N sessions" was redundant and stacked-left. The session count
       is genuinely useful but cheap; surface it elsewhere if needed. */
    .section-header { display: none; }
}

/* Empty-state — shown when client-side filters reduce the table to
   zero visible rows. JS toggles display via pfFilterRows. */
.programme-empty-state {
    display: none;
    text-align: center;
    padding: 48px 24px;
    background: var(--bg-card);
    border: 1px dashed var(--border);
    border-radius: 12px;
    margin: 16px 0;
}
.programme-empty-state.is-visible { display: block; }
.programme-empty-state-icon {
    width: 48px;
    height: 48px;
    margin: 0 auto 12px;
    color: var(--text-muted);
    opacity: 0.5;
}
.programme-empty-state-title {
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
    margin: 0 0 4px;
}
.programme-empty-state-body {
    font-size: 0.9rem;
    color: var(--text-muted);
    margin: 0 0 14px;
}
@media (max-width: 720px) {
    .programme-empty-state { padding: 28px 16px; margin: 12px 0; border-radius: 10px; }
    .programme-empty-state-icon { width: 40px; height: 40px; margin-bottom: 8px; }
    .programme-empty-state-title { font-size: 0.95rem; }
    .programme-empty-state-body { font-size: 0.82rem; margin-bottom: 10px; }
}

/* Year-pill polish (slice B from /programme audit) — bigger active
   state with indigo glow so the current year visually anchors the
   pill row, slightly tighter spacing, transparent inactive state
   that feels less "filled grey" and more "tab nav". */
/* Year selector — Nov 2026 (revised after user mock-up). Original
   shipped with a segmented-control "track" wrapper around the year
   pills; user preferred a calmer treatment with NO track — just
   individual pills floating in the row, with the active one filled-
   purple and "All years" as plain text spaced afterwards. The semantic
   distinction (specific year vs filter override) is still preserved
   through styling weight: years are pills, "All years" is plain text. */
.year-selector {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

/* Year selector — proper segmented control / slider track (May 2026
   rebuild). Previously rendered as three separate rounded pills with
   gaps between them; that's a "tab strip", not a "slider". A slider
   is a single track with one filled pill INSIDE moving across the
   year choices.

   Layout:
     • .year-segmented = the track itself — one rounded container
       with a subtle border and small padding so the inner pill has
       breathing room.
     • .year-seg = individual segments INSIDE the track. No own
       border. Inactive = transparent + muted text. Active = filled-
       indigo pill with subtle lift.
     • .year-seg-all = the "All years" segment. Same shape as numeric
       segments, slightly fainter colour at rest to signal "different
       kind of choice" (mutually exclusive year vs. drop-the-filter).
       When active it gets the full indigo treatment — there's no
       ambiguity once selected. */
.year-segmented {
  display: inline-flex;
  align-items: stretch;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px;
  gap: 2px;
}
.year-seg {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 7px 20px;
  border-radius: 999px;
  font-size: 0.92rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  text-decoration: none;
  background: transparent;
  border: none;
  transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
  min-width: 64px;
}
.year-seg:hover {
  color: var(--text);
  background: rgba(79, 70, 229, 0.06);
  text-decoration: none;
}
.year-seg.is-active {
  background: var(--primary);
  color: white;
  box-shadow: 0 1px 3px rgba(79, 70, 229, 0.30);
}
.year-seg.is-active:hover {
  background: var(--primary);
  color: white;
}

/* "All years" segment — a different KIND of choice. Numeric segments
   pick one year (mutually exclusive scope); "All years" drops the
   year filter entirely. Same track to keep the affordance unified,
   but renders ~15% fainter at rest so the eye reads it as "secondary
   option" without leaving the slider. Active state is the full
   indigo pill — once chosen there's no need for visual hedging. */
.year-seg-all {
  min-width: 96px;
  color: var(--text-light);
  font-weight: 500;
  opacity: 0.85;
}
.year-seg-all:hover {
  color: var(--text-muted);
  opacity: 1;
}
.year-seg-all.is-active {
  color: white;
  font-weight: 600;
  opacity: 1;
}
.year-seg-all.is-active:hover { color: white; }

/* Legacy .year-pills retained — still used by the speaker_bio and
   archive pages. Don't delete until those are migrated to the
   segmented pattern. */
.year-pills { display: flex; align-items: center; gap: 4px; margin-bottom: 20px; flex-wrap: wrap; }
.year-pill {
    padding: 7px 18px;
    border-radius: 999px;
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--text-muted);
    background: transparent;
    border: 1px solid transparent;
    text-decoration: none;
    transition: background 0.15s, color 0.15s, border-color 0.15s, box-shadow 0.15s;
    font-variant-numeric: tabular-nums;
}
.year-pill:hover {
    background: var(--primary-bg);
    color: var(--primary);
    text-decoration: none;
}
.year-pill.active {
    background: var(--primary);
    color: white;
    box-shadow: 0 2px 6px rgba(79, 70, 229, 0.3);
}
.year-pill.active:hover { background: var(--primary-hover); color: white; }
.year-pill-archive {
    background: transparent;
    color: var(--text-muted);
    border: 1px dashed var(--input-border);
}
.year-pill-archive:hover {
    border-color: var(--primary);
    color: var(--primary);
    background: transparent;
}

/* === Archive === */
.archive-year-row td { background: var(--border-light, #f1f5f9); padding: 8px 14px !important; font-size: 1rem; color: var(--primary); }

/* === Rota === */
.rota-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 4px; }
.rota-activity { display: inline-block; padding: 2px 8px; border-radius: 6px; font-size: 0.8rem; font-weight: 600; white-space: nowrap; }
.rota-activity-red { background: var(--danger-bg); color: var(--danger); }
.rota-activity-orange { background: var(--warn-bg); color: var(--warn); }
.rota-activity-green { background: var(--done-bg); color: var(--done-text); }
.rota-row-red { background: var(--danger-bg); }
.rota-row-orange { background: var(--warn-bg); }
/* Status badges */
.rota-badge { display: inline-block; padding: 2px 8px; border-radius: 20px; font-size: 0.75rem; font-weight: 700; white-space: nowrap; }
.rota-badge-red { background: var(--danger-bg); color: var(--danger); }
.rota-badge-orange { background: var(--warn-bg); color: var(--warn); }
.rota-badge-green { background: var(--done-bg); color: var(--done-text); }
/* Eye-icon popup (details/summary) */
.rota-details { position: relative; display: inline-block; }
.rota-details summary { list-style: none; cursor: pointer; color: var(--text-muted); padding: 4px; border-radius: 4px; display: flex; }
.rota-details summary::-webkit-details-marker { display: none; }
.rota-details summary:hover { color: var(--primary); background: rgba(79,70,229,0.06); }
.rota-details[open] summary { color: var(--primary); }
.rota-popup { position: absolute; right: 28px; top: calc(100% + 6px); background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; box-shadow: 0 6px 20px rgba(0,0,0,0.12); z-index: 100; min-width: 260px; max-height: 280px; overflow-y: auto; }
.rota-popup-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 4px 0; font-size: 0.82rem; border-bottom: 1px solid var(--border-light, #f1f5f9); }
.rota-popup-row:last-child { border-bottom: none; }
.rota-table td { vertical-align: middle; }
.alert-warning { background: var(--warn-bg); color: var(--warn); border: 1px solid var(--warn-border); }


/* === Swap suggestion buttons === */
.swap-btn {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 3px 8px;
    border: 1px solid rgba(22,163,74,0.3);
    background: rgba(22,163,74,0.06);
    cursor: pointer; border-radius: 5px;
    font-size: 0.72rem; color: var(--text);
    font-family: inherit;
    transition: background 0.15s, border-color 0.15s, transform 0.15s, box-shadow 0.15s;
    text-align: left; width: 100%;
}
.swap-btn:hover {
    background: rgba(22,163,74,0.14);
    border-color: rgba(22,163,74,0.7);
    transform: translateX(3px);
    box-shadow: -3px 0 0 0 #16a34a;
    color: var(--text);
}
.swap-btn:hover .swap-icon {
    transform: rotate(180deg);
}
.swap-icon {
    transition: transform 0.3s ease;
    flex-shrink: 0;
}
.swap-btn:active { transform: translateX(1px); }

/* === Footer === */
.footer { text-align: center; padding: 24px; margin-top: 48px; color: var(--text-light); font-size: 0.8rem; border-top: 1px solid var(--border); background: var(--bg); }

/* === Programme Mobile Cards === */
.programme-cards-view { display: none; }

/* Mobile session cards — updated 2026-05 to match the hero's new
   colour system. The old harsh 3px solid left-border idiom is gone;
   each state now uses a subtle tinted background + a 1px coloured
   border that match the hero variants:
     upcoming  → indigo tint  (matches hero upcoming)
     today     → yellow-orange tint (matches hero today)
     done      → green tint   (matches hero feedback)
     cancelled → red tint
     past      → muted grey (existing dim behaviour kept)
   Chevron on the right replaces the left-stripe as the "this is a
   tappable card" affordance. */
.prog-card {
    display: flex; align-items: stretch; gap: 12px;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 12px; padding: 13px 14px;
    margin-bottom: 8px;
    text-decoration: none; color: var(--text);
    transition: background 0.15s, border-color 0.15s, box-shadow 0.15s, transform 0.15s;
    position: relative;
}
.prog-card:active { transform: scale(0.99); }
.prog-card:hover {
    border-color: rgba(79,70,229,0.3);
    box-shadow: 0 4px 12px rgba(79,70,229,0.08);
    text-decoration: none;
}
/* Today — yellow-orange tint + matching border. Visually pops out of
   the list so attendees spot the today/this-week card immediately. */
.prog-card-today {
    border-color: rgba(234,88,12,0.3);
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(234,88,12,0.06) 100%);
}
.prog-card-today:hover {
    border-color: rgba(234,88,12,0.5);
    box-shadow: 0 4px 12px rgba(234,88,12,0.1);
}
/* Done — green tint matches the hero feedback colour. */
.prog-card-done {
    border-color: rgba(16,185,129,0.25);
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(16,185,129,0.04) 100%);
}
/* Cancelled — red tint, dimmed. */
.prog-card-cancelled {
    border-color: rgba(220,38,38,0.25);
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(220,38,38,0.04) 100%);
    opacity: 0.7;
}
/* Past sessions (not done, not today) — softly muted. */
.prog-card-past { opacity: 0.7; }
.prog-card-bank-holiday { opacity: 0.5; cursor: default; }

.prog-card-date {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    min-width: 42px; flex-shrink: 0;
}
.prog-card-day { font-size: 1.3rem; font-weight: 700; line-height: 1.1; color: var(--text); font-variant-numeric: tabular-nums; }
.prog-card-month { font-size: 0.7rem; font-weight: 600; text-transform: uppercase; color: var(--text-muted); }
.prog-card-weekday { font-size: 0.6rem; color: var(--text-light); margin-top: 2px; }

.prog-card-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }

.prog-card-top-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* Topic-as-title — matches the desktop hierarchy where the topic is
   the dominant column. Bumped weight to 600 + size to 0.92rem so the
   card foregrounds the SUBJECT, not the date.
   M6: clamp to 2 lines max — long topic names were eating the whole
   card on phones. Uses standard line-clamp (also Webkit prefix for
   older iOS). */
.prog-card-title {
    font-size: 0.92rem; font-weight: 600; line-height: 1.35; color: var(--text);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    gap: 4px;
}

.prog-card-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 0.78rem; color: var(--text-muted); }
.prog-card-speaker { display: inline-flex; align-items: center; gap: 3px; }
.prog-card-supervisor { display: inline-flex; align-items: center; gap: 3px; color: var(--text-light); }
.prog-card-files { display: inline-flex; align-items: center; gap: 3px; color: var(--primary); }

.prog-card-chevron { display: flex; align-items: center; flex-shrink: 0; }

/* === Responsive === */
/* === Mobile Responsive === */
@media (max-width: 768px) {
    /* Show cards, hide table on mobile */
    .programme-cards-view { display: block; }
    .programme-table-view { display: none; }
    /* Container — full width on mobile */
    .container { padding: 8px; }

    /* Navbar: single row — brand on left, user on right.
       The middle .nav-links strip is REPLACED by the bottom tabbar
       on mobile (slice M5), so it hides here. */
    .navbar { flex-wrap: wrap; padding: 0; gap: 0; }
    .navbar > .nav-brand { order: 1; padding: 8px 12px; }
    .navbar > .nav-user { order: 2; padding: 8px 12px; margin-left: auto; flex-shrink: 0; }
    .navbar > .nav-links { display: none; }
    .nav-brand a { gap: 6px; }
    .nav-logo-dot { width: 28px; height: 28px; font-size: 0.75rem; }
    .nav-links a { padding: 10px 18px; font-size: 1.4rem; font-weight: 700; border-radius: 6px; min-height: 44px; display: inline-flex; align-items: center; }
    .nav-user { gap: 6px; }
    .user-badge { font-size: 0.7rem; padding: 2px 8px; }
    .nav-user .btn-sm { padding: 4px 10px; font-size: 0.7rem; min-height: 32px; }

    /* Font size controls: bottom-left on mobile, more visible */
    .font-size-controls { bottom: 24px; left: 12px; right: auto; opacity: 0.7; }
    .fs-btn { width: 40px; height: 40px; box-shadow: 0 2px 10px rgba(0,0,0,0.25); background: var(--bg-card); color: var(--text); }

    /* Page headers (h1 sizing handled by .page-header @media block above) */
    .page-header h2 { font-size: 1.15rem; }
    .header-actions { margin-left: 0; width: 100%; display: flex; flex-wrap: wrap; gap: 6px; }

    /* Cards */
    .card { padding: 14px; margin-bottom: 12px; border-radius: 10px; }

    /* Stats grid: 2 columns on mobile */
    .stats-grid { grid-template-columns: 1fr 1fr !important; gap: 8px; }
    .stat-card { padding: 10px !important; }
    .stat-value { font-size: 1.3rem !important; }
    .stat-label { font-size: 0.7rem; }

    /* Feedback metrics grid */
    .feedback-metrics-grid { grid-template-columns: 1fr 1fr !important; gap: 8px; }

    /* Charts */
    .charts-grid { grid-template-columns: 1fr; }

    /* Programme table — full width, minimal padding on mobile */
    .data-table { font-size: 0.88rem; width: 100%; table-layout: auto; }
    .data-table th { padding: 8px 4px; font-size: 0.72rem; }
    .data-table td { padding: 12px 4px; font-size: 0.88rem; vertical-align: top; }
    .data-table tbody tr { min-height: 48px; }

    /* Hide columns on mobile: supervisor, rating, category, files, status */
    .col-supervisor, .col-rating, .col-category, .col-files { display: none; }
    .data-table th:nth-child(5), .data-table td:nth-child(5) { display: none; }

    /* Date: tight */
    .col-date { white-space: nowrap; font-size: 0.82rem; font-weight: 600; }
    .col-date small { display: none; }

    /* Topic: dominant column */
    .col-topic { font-size: 0.9rem; line-height: 1.35; word-break: break-word; }

    /* Category badge on mobile: tiny, inline */
    .category-tag { padding: 1px 5px; font-size: 0.58rem; white-space: nowrap; }

    /* Presenter */
    .col-presenter { font-size: 0.82rem; white-space: nowrap; }

    /* Status: compact */
    .col-status { white-space: nowrap; }
    .status-badge { font-size: 0.65rem; padding: 2px 6px; }

    /* Year pills — touch-target compliant and aligned to mobile card
       chrome. The right-edge fade hint surfaces the Archive pill on
       scroll (was hidden in M9, restored when Archive was removed
       from the user dropdown — single source of truth in year-pills). */
    .year-pills {
        gap: 6px !important; overflow-x: auto; flex-wrap: nowrap;
        -webkit-overflow-scrolling: touch;
        padding-bottom: 2px; margin-bottom: 0;
        min-width: 0; flex: 1;
        /* Fade the right edge so users see "scroll for more years" */
        mask-image: linear-gradient(to right, black 0, black calc(100% - 16px), transparent 100%);
        -webkit-mask-image: linear-gradient(to right, black 0, black calc(100% - 16px), transparent 100%);
    }
    .year-pills.scrolled-end {
        mask-image: none; -webkit-mask-image: none;
    }
    .year-pill { flex-shrink: 0; padding: 8px 14px; font-size: 0.82rem; min-height: 40px; display: inline-flex; align-items: center; border-radius: 10px; }
    .year-pills .btn, .year-pill-archive { padding: 8px 12px !important; font-size: 0.78rem !important; min-height: 40px; }
    /* The .flex-between wrapper around year-pills + view-toggle: keep
       both on one line, view-toggle compact + pinned right. */
    .year-pills + .view-toggle, .flex-between > .view-toggle {
        flex-shrink: 0;
    }

    /* Programme title + buttons row */
    .page-title-row { flex-direction: column; gap: 8px; align-items: flex-start !important; }
    .page-title-row .btn { font-size: 0.78rem; padding: 6px 10px; }

    /* Full width table — no card padding eating space */
    .table-responsive { overflow-x: hidden; margin: 0 -4px; }
    .card { padding: 10px 6px; }

    /* Filters (M8): collapsed by default, search + toggle on one row.
       Tap "Filters" → secondary combos slide into view stacked below.
       Pre-expanded if any combo is set (Jinja sets is-expanded class). */
    .filters-bar { padding: 10px; margin-bottom: 16px; border-radius: 10px; }
    .filters-form { flex-direction: row; gap: 8px; flex-wrap: wrap; align-items: center; }
    .filters-form .input-search {
        flex: 1; min-width: 0; max-width: 100%;
        min-height: 44px; font-size: 16px;
    }
    .filters-toggle {
        display: inline-flex; flex-shrink: 0;
        min-height: 44px; padding: 8px 12px; gap: 6px;
    }
    .filters-secondary {
        display: none;
        width: 100%;
        flex-direction: column;
        gap: 8px;
    }
    .filters-form.is-expanded .filters-secondary { display: flex; }
    .filters-secondary .combo-wrap { width: 100% !important; }
    .filters-secondary input, .filters-secondary select { width: 100%; min-height: 44px; font-size: 16px; }
    .filters-secondary .btn { width: 100%; min-height: 44px; justify-content: center; }

    /* View toggle — compact icon+label on mobile (M8) */
    .view-toggle { gap: 2px; }
    .view-toggle .btn { font-size: 0.7rem; padding: 6px 10px; min-height: 36px; }

    /* Session detail: stack main + sidebar */
    .session-grid { grid-template-columns: 1fr; gap: 12px; }
    .session-sidebar { order: 2; }
    .session-main { order: 1; min-width: 0; }
    .session-meta { grid-template-columns: 1fr; gap: 10px; }
    .session-main .card h2 { font-size: 1.1rem; }
    .upload-item { flex-wrap: nowrap; }

    /* Prevent edit-mode inputs from overflowing */
    .session-meta input[type="text"],
    .session-meta input[type="time"],
    .session-meta select { min-width: 0 !important; max-width: 100%; box-sizing: border-box; }
    #session-edit .meta-item form { flex-wrap: wrap; }

    /* Coordinator Tools portal link rows: stack copy/open buttons on very small screens */
    .coord-link-row { flex-wrap: wrap; gap: 4px; }

    /* Action row: wrap nicely on mobile */
    .action-row { gap: 6px; }
    .action-row .btn, .action-row select { min-height: 44px; font-size: 0.8rem; }
    .action-row form { width: 100%; }
    .action-row form select { width: 100%; }

    /* Calendar view */
    .calendar-grid { grid-template-columns: 1fr; gap: 8px; }
    .calendar-slot { padding: 12px; }

    /* Explore page: stack to single column */
    .explore-grid { grid-template-columns: 1fr !important; }

    /* Builder (coordinator mobile) — slice M4.
       At desktop the topic bank is a sticky 320px sidebar. Stacked
       on mobile, position:sticky pins it to the top of the viewport
       while the user scrolls the slot list below — the sidebar then
       eats permanent screen real estate AND the slot grid is mostly
       offscreen. Drop the sticky on mobile. Also trim the topic
       scroll list from ~7 rows to ~4 — drag-and-drop is awkward on
       touch anyway, and the slot's per-row "+ topic" action is the
       real mobile workflow. */
    .builder-layout { grid-template-columns: 1fr; }
    .topic-bank { position: static; padding: 12px; }
    .topic-list-scroll { max-height: calc(4 * 62px); }
    .suggestions-scroll { max-height: calc(2 * 52px); }

    /* Charts (admin stats page): tighten chart canvas height so
       Chart.js's auto-allocated axes + legend leave readable plot
       area on a phone. */
    .chart-box { height: 120px; }
    .chart-box-tall { height: 180px; }

    /* Login */
    .login-card { padding: 24px 20px; margin: 12px; }
    .login-header h1 { font-size: 1.35rem; }

    /* Feedback FAB */
    .feedback-fab { bottom: 16px !important; right: 16px !important; width: 48px !important; height: 48px !important; }
    .feedback-modal-content { width: 95% !important; max-height: 90vh !important; margin: 5vh auto; padding: 16px !important; }

    /* Break banners */
    .break-banner { padding: 10px; font-size: 0.8rem; }
    .break-banner .break-dates { display: none; }
    .break-cell { font-size: 0.75rem !important; padding: 8px !important; }

    /* Teams pill */
    .teams-pill { font-size: 0.6rem; padding: 1px 6px; }
    .teams-join-btn { padding: 6px 12px; font-size: 0.75rem; }

    /* Archive */
    .archive-year-divider { font-size: 0.9rem; }

    /* Buttons — touch-friendly min sizes */
    .btn { padding: 8px 14px; font-size: 0.8rem; min-height: 44px; display: inline-flex; align-items: center; }
    .btn-sm { padding: 6px 10px; font-size: 0.75rem; min-height: 36px; }
    .btn-block { width: 100%; justify-content: center; }

    /* Upload form responsive */
    .upload-form { flex-direction: column; align-items: stretch !important; gap: 10px; }
    .upload-form select, .upload-form input[type="file"], .upload-form .btn { width: 100%; min-height: 44px; }
    .upload-item { flex-wrap: nowrap; min-width: 0; width: 100%; }
    .upload-link { width: 100%; min-width: 0; }
    .upload-filename { max-width: 100%; }
    .upload-meta { display: none; } /* hide size/uploader on mobile — space is premium */

    /* Session detail: hide email column in attendance table */
    .att-col-email { display: none; }

    /* Coverage grid on mobile */
    .coverage-item { flex-direction: column; align-items: flex-start; gap: 4px; }
    .coverage-label { width: auto; }

    /* Fairness list on mobile */
    .fairness-item { flex-wrap: wrap; }
    .fairness-name { width: auto; }
    .fairness-count { width: auto; }

    /* Activity dropdown for mobile */
    #activity-dropdown { width: 90vw !important; max-width: 360px; right: -40px !important; }

    /* Footer */
    .footer { padding: 16px 12px; font-size: 0.7rem; }
}

/* === Tablet (769–1024px) === slice M2.
   The desktop layout assumes ≥1100px of useful viewport. Between
   the mobile cliff (768) and ~1100, a few rules pinch:
     - .session-grid (1fr 350px) leaves the main column too narrow
     - the 7-col programme table compresses topic + presenter cells
   This block fixes both without touching desktop or mobile rules. */
@media (min-width: 769px) and (max-width: 1024px) {
    /* Session detail: stack until we have real desktop room. The
       sidebar reads better at full width than wedged into 350px
       beside a 430px main column. */
    .session-grid { grid-template-columns: 1fr; gap: 20px; }
    .session-sidebar { order: 2; }
    .session-main { order: 1; min-width: 0; }

    /* Programme table: hide the bulkiest secondary columns so topic
       and presenter get reading room. Keep rating + category visible
       (they're compact and high-signal). */
    .col-supervisor, .col-files { display: none; }

    /* Hero card: a touch of breathing room reduction so the action
       buttons don't float far from the title at narrow widths. */
    .hero-card { padding: 24px 28px; gap: 20px; }
}

/* Small phones (375px and below) */
@media (max-width: 400px) {
    .navbar { padding: 0; }
    .nav-links a { padding: 6px 8px; font-size: 0.75rem; }
    .container { padding: 8px; }
    .stats-grid { grid-template-columns: 1fr 1fr; }
    .data-table td { padding: 8px 6px; font-size: 0.75rem; }
    .data-table th { padding: 6px; font-size: 0.65rem; }

    /* Hide status column on very small screens */
    .data-table th:nth-child(6),
    .data-table td:nth-child(6) { display: none; }

    /* Compact hero card */
    .hero-date { font-size: 1rem; }
    .hero-topic { font-size: 0.85rem; }
    .hero-meta { font-size: 0.75rem; }

    /* Compact year pills — keep 44px tap target on tiny phones too,
       just trim horizontal padding to fit more pills in a row. */
    .year-pill { padding: 10px 12px; font-size: 0.78rem; min-height: 44px; }

    /* Feedback form on small screens */
    .feedback-row { flex-direction: column; align-items: flex-start; gap: 2px; }
    .feedback-row > label { min-width: auto; }
}

/* === Action Buttons (slots & topics) === */
.slot-action-btn, .slot-action-btn-sm { background: none; border: none; cursor: pointer; padding: 3px; border-radius: 4px; display: inline-flex; align-items: center; opacity: 0.7; transition: opacity 0.15s, background 0.15s; }
.slot-action-btn:hover, .slot-action-btn-sm:hover { opacity: 1; background: var(--border-light, #f1f5f9); }
.topic-card .topic-actions { opacity: 1; }

/* === Timeline View === */
.timeline-container { position: relative; padding: 0 12px 20px; margin-top: 12px; }
.timeline-months { display: flex; position: relative; height: 24px; margin-bottom: 4px; }
.timeline-month { position: absolute; text-align: center; font-size: 0.7rem; font-weight: 600; color: var(--text-muted); border-left: 1px solid var(--border); padding-left: 4px; }

/* School holiday bands */
.timeline-school-band { position: absolute; top: 28px; height: 80px; background: repeating-linear-gradient(45deg, rgba(251,191,36,0.2), rgba(251,191,36,0.2) 3px, rgba(251,191,36,0.08) 3px, rgba(251,191,36,0.08) 6px); border-left: 2px solid rgba(251,191,36,0.7); border-right: 2px solid rgba(251,191,36,0.7); z-index: 3; min-width: 20px; border-radius: 3px; pointer-events: none; }
.timeline-school-label { position: absolute; top: -13px; left: 0; font-size: 0.55rem; color: #b45309; font-weight: 600; white-space: nowrap; }

/* Track */
.timeline-track { position: relative; height: 80px; margin-top: 4px; background: var(--bg, #f8fafc); border-radius: 8px; border: 1px solid var(--border); }

/* Break zones */
.timeline-break-zone { position: absolute; top: 0; height: 100%; background: repeating-linear-gradient(45deg, transparent, transparent 4px, rgba(148,163,184,0.1) 4px, rgba(148,163,184,0.1) 8px); border-left: 1px dashed var(--input-border); border-right: 1px dashed var(--input-border); z-index: 1; }
.timeline-break-label { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 0.6rem; color: var(--text-light); font-weight: 500; white-space: nowrap; }

/* Today marker */
.timeline-today { position: absolute; top: -4px; z-index: 10; transform: translateX(-50%); }
.timeline-today-line { width: 2px; height: 88px; background: #ef4444; }
.timeline-today-label { position: absolute; top: -14px; left: 50%; transform: translateX(-50%); font-size: 0.6rem; font-weight: 700; color: #ef4444; white-space: nowrap; }

/* Bank holiday markers */
.timeline-bank-holiday { position: absolute; top: 0; height: 100%; z-index: 6; transform: translateX(-50%); }
.timeline-bh-marker { width: 3px; height: 100%; background: rgba(239, 68, 68, 0.35); border-radius: 1px; }
.timeline-bank-holiday:hover .timeline-bh-marker { background: rgba(239, 68, 68, 0.7); }
.timeline-bh-tooltip { display: none; position: absolute; bottom: calc(100% + 4px); left: 50%; transform: translateX(-50%); background: #1e293b; color: white; padding: 4px 8px; border-radius: 6px; font-size: 0.7rem; white-space: nowrap; z-index: 30; line-height: 1.3; text-align: center; }
.timeline-bank-holiday:hover .timeline-bh-tooltip { display: block; }

/* Instant tooltips (no browser delay) */
.instant-tip { position: relative; display: inline-block; }
.instant-tip-text { display: none; position: absolute; bottom: calc(100% + 4px); left: 50%; transform: translateX(-50%); background: #1e293b; color: white; padding: 4px 8px; border-radius: 6px; font-size: 0.7rem; font-weight: 400; white-space: nowrap; z-index: 30; line-height: 1.3; }
.instant-tip:hover .instant-tip-text { display: block; }

/* Session dots */
.timeline-dot { position: absolute; top: 50%; transform: translate(-50%, -50%); width: 16px; height: 16px; border-radius: 50%; z-index: 5; cursor: pointer; transition: all 0.15s; border: 2px solid white; box-shadow: 0 1px 3px rgba(0,0,0,0.15); text-decoration: none; }
.timeline-dot:hover { transform: translate(-50%, -50%) scale(1.4); z-index: 20; box-shadow: 0 2px 8px rgba(0,0,0,0.2); }

/* Category colors */
.dot-hpb { background: #3b82f6; }
.dot-liver\/bile, .dot-liver-bile { background: #10b981; }
.dot-hpb-general { background: #3b82f6; }
.dot-olt { background: #9a3412; }
.dot-pancreas { background: #f59e0b; }
.dot-journal-club { background: #8b5cf6; }
.dot-none { background: var(--input-border); }

/* Hero Card — slice 4 polish: solid 1px border (was dashed),
   slightly larger radius and padding so it reads as a designed
   moment rather than an "outline only" placeholder. */
.hero-card {
  /* 3 × 3 grid event-card layout. Strict horizontal alignment:
        Row 1: eyebrow pill   ↔  category pill
        Row 2: BIG date       ↔  TITLE
        Row 3: time chip      ↔  speaker / location
     Column 3 holds the action tiles, spanning all three rows.

     row-gap bumped from 8px → 14px — pills now feel like a top
     'masthead' band sitting above the title row rather than
     crammed close to the big text. Card top padding tightened
     from 24px → 18px so the pills sit closer to the card's top
     edge, not vertically centred on the whole height.

     align-items: center per row vertically centres each cell
     within its row's auto-height, so different content heights
     don't break the alignment.

     Vertical column-separator: removed once, re-added Nov 2026 at
     much lower contrast (rgba 0.16) than the original. Implemented
     as a grid-placed ::before pseudo-element below — see the
     .hero-card::before block under the grid-cell definitions. The
     left-to-right gradient still does the primary visual leading
     toward the action cluster; the separator is a faint structural
     hint, not a divider. */
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: auto auto auto;
  align-items: center;
  column-gap: 36px;
  row-gap: 14px;
  padding: 18px 32px 22px;
  background: var(--bg-card); border: 1px solid var(--border); border-radius: 18px;
  margin-bottom: 24px;
  position: relative; overflow: hidden;
  transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease, background 0.25s ease; cursor: default;
}
.hero-card:hover { transform: translateY(-3px); box-shadow: 0 12px 32px rgba(0,0,0,0.10); }
/* No ::before stripe by default — the green stripe-on-upcoming-card
   read as "complete/success" semantics which is the opposite of
   "coming up". today/live/feedback/break still get a coloured stripe
   because there it carries genuine meaning (urgency / status). */
/* Color scheme — "temperature progression" from cool future to warm
   present, then cool resolution. Each mode is visually distinct so
   the page state is readable at a glance:
     upcoming  → indigo (calm, future)
     today     → yellow-orange (soon, attention-getting but not panicked)
     live      → red (urgent — handled by inline override on .hero-label)
     feedback  → green (completed / "done" cue) */
/* Hero state styling — Nov 2026 calm-down. Gradients removed across
   all hero variants (today/feedback/upcoming/break). Per the
   simplification arc: gradients add visual chrome that no longer fits
   the calmer design language we've been condensing toward. State is
   now signalled purely through (a) the border-color tint and (b) the
   eyebrow label colour (.hero-label). The card surface itself stays
   flat var(--bg-card) — same as every other card on the platform.

   Why this works: in the busier earlier design the gradient led the
   eye toward the action cluster on the right. With the simplified
   button hierarchy (time-aware primary, ghost secondaries) the
   primary CTA already pulls focus through colour weight; the
   gradient was reinforcing what the buttons now do alone. */
.hero-card.hero-today {
  border-color: rgba(234,88,12,0.35);
  background: var(--bg-card);
}
.hero-card.hero-feedback {
  border-color: rgba(16,185,129,0.3);
  background: var(--bg-card);
}
.hero-card:not(.hero-today):not(.hero-feedback):not(.hero-break-summer):not(.hero-break-xmas) {
  border-color: rgba(79,70,229,0.2);
  background: var(--bg-card);
}
/* Legacy class kept so any old call sites still compile. The
   three-column rework moved meta info into .hero-info-col and
   the date into .hero-date-col below. Safe to remove once no
   template references remain. */
.hero-left { flex: 1; min-width: 0; padding-left: 0; }

/* ── Hero grid cells — placed via grid-column / grid-row ──
   Each .hero-cell is a single grid item. The .hero-r{1,2,3} class
   marks which row, and -date / -info marks which column. Tiles
   live in column 3 spanning all three rows (.hero-actions). */
.hero-cell { min-width: 0; }
.hero-r1-date { grid-column: 1; grid-row: 1; }
.hero-r1-info { grid-column: 2; grid-row: 1; }
.hero-r2-date { grid-column: 1; grid-row: 2; }
.hero-r2-info { grid-column: 2; grid-row: 2; }
.hero-r3-date { grid-column: 1; grid-row: 3; }
.hero-r3-info { grid-column: 2; grid-row: 3; }

/* Faint vertical separator between the date column (1) and the info
   column (2) — Nov 2026 re-add. Was removed previously per the
   "the gradient leads the eye" comment in .hero-card; the user has
   asked for it back at much lower contrast. Implemented as a grid-
   placed pseudo-element so it works regardless of date-column width
   and stays as a single continuous line (border-right on individual
   date cells would have been broken by the row-gap). The negative
   margin pulls the line halfway into the column-gap (which is 36px)
   so it sits visually centred in the gutter. Hidden when the hero
   collapses to a single column on small screens via the responsive
   block at line ~1944. */
.hero-card::before {
  content: '';
  grid-column: 2;
  grid-row: 1 / -1;
  width: 1px;
  background: rgba(79, 70, 229, 0.16);
  justify-self: start;
  align-self: stretch;
  margin-left: -18px;
  pointer-events: none;
}

/* Stacked date container — day-of-week tiny on top, big day+month
   below. flex-column lets the two children stack cleanly with a
   tight gap, mimicking the layout of a desk-calendar page. */
.hero-date {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  line-height: 1;
}
/* Day-of-week — small uppercase letter-spaced label above the big
   date. Visual weight matches the pill above so it doesn't fight
   for attention with the load-bearing big date below. */
.hero-date-dow {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
/* Big date — paired with the topic title in row 2. Same scale
   (clamp 1.35–1.6rem) and weight (800) so the two columns look
   like equal-weight siblings sharing row 2. */
.hero-date-big {
  font-size: clamp(1.35rem, 1.7vw, 1.6rem);
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1.15;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Time chip — sits in row 3 col 1, aligned with the speaker line
   on the right. Visual weight is a small pill — supporting info,
   not load-bearing. */
.hero-time {
  display: inline-block;
  font-weight: 500;
  color: var(--text-muted);
  font-size: 0.82rem;
  padding: 3px 10px;
  background: var(--bg);
  border-radius: 6px;
  font-variant-numeric: tabular-nums;
}

/* Pill-height parity — the "in 2 weeks" pill (.hero-when) and the
   category-tag pill ("Pancreas") sit in the same row 1 and need
   to look the same height for visual balance. Override both with
   a shared geometry: same font-size, same padding, same radius.
   Background colours stay distinct (hero-when uses the indigo
   tint pulled from the brand; category-tag uses its category
   colour map) — the SHAPE matches, the COLOUR carries meaning. */
.hero-r1-info .category-tag,
.hero-r1-date .hero-when {
  font-size: 0.72rem;
  padding: 3px 10px;
  border-radius: 12px;
  line-height: 1.4;
}
/* Eyebrow moved from green --accent to indigo --primary so the colour
   matches the rest of the brand and doesn't suggest "completed". The
   relative-time chip sits inline next to it. */
.hero-label {
  font-size: 0.72rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.1em; color: var(--primary); margin-bottom: 8px;
  display: inline-flex; align-items: center; gap: 8px;
}
.hero-label .hero-when {
  /* Relative-time chip: "in 4 days" / "tomorrow" / "in 2 weeks" — answers
     the silent question every visitor has about the COMING UP date. */
  font-weight: 500; font-size: 0.7rem; text-transform: none;
  letter-spacing: 0.02em; color: var(--text-muted);
  background: rgba(79,70,229,0.08); padding: 1px 8px; border-radius: 10px;
}
.hero-today .hero-label { color: #ea580c; }
.hero-feedback .hero-label { color: #10b981; }
.hero-feedback .hero-label .hero-when {
  background: rgba(16,185,129,0.12);
}
/* Hero typography polished — slice A from /programme audit. Was a
   modest 1.15rem date + 0.95rem topic; now scales up with viewport
   so the today-card actually feels like a "moment of polish" and
   the topic title sits prominently in the eye-line. */
/* Old .hero-date / .hero-time rules removed in the 3-column rework.
   The date now lives in .hero-date-col and is styled by the more
   specific rules above (.hero-date-dow, .hero-date-big, .hero-date-col
   .hero-time). The single inline-row .hero-date pattern is gone. */
/* Topic title is the load-bearing typography on the hero card —
   the one thing every visitor wants to read at a glance ("what's
   the next session about?"). Bumped from 1.05rem to clamp 1.35–
   1.6rem so it scales up with viewport and reads as a real title,
   not as a body-text label. Tighter line-height (1.3) + slight
   negative letter-spacing keeps it readable when wrapping to two
   lines. The category-tag stays inline at its original size — it's
   the eyebrow-style pre-pill, deliberately not competing with the
   title for visual weight. */
.hero-topic {
  font-size: clamp(1.35rem, 1.7vw, 1.6rem);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text);
  line-height: 1.3;
}
/* (Old inline-with-title category-tag rule removed — category pill
   now lives in row 1 col 2 as its own grid cell, height-matched to
   the relative-time pill on the left.) */
/* Speaker line — first child of row 3 col 2, so no top margin
   (grid cell positions it; the 8px margin-top from the old
   2-column layout would have pushed it off the row baseline). */
.hero-meta { font-size: 0.85rem; color: var(--text-muted); }
/* Bumped from --text-light to --text-muted (one step darker) so the
   location pin + text actually register against the lavender card tint. */
.hero-location { font-size: 0.82rem; color: var(--text-muted); margin-top: 6px; display: flex; align-items: center; gap: 6px; }
.hero-location svg { color: var(--primary); opacity: 0.7; }
/* Break hero variants */
.hero-card.hero-break-summer { border-color: rgba(251,191,36,0.4); background: var(--bg-card); }
.hero-break-summer .hero-label { color: #d97706; }
.hero-break-summer .hero-date { color: #92400e; }
.hero-card.hero-break-xmas { border-color: rgba(16,185,129,0.4); background: var(--bg-card); }
.hero-break-xmas .hero-label { color: #059669; }
.hero-break-xmas .hero-date { color: #065f46; }
.hero-online-only { display: inline-flex; align-items: center; gap: 4px; color: #f59e0b; font-weight: 600; background: rgba(245,158,11,0.12); padding: 2px 8px; border-radius: 6px; border: 1px solid rgba(245,158,11,0.3); }
/* Hero action tiles — column 3 of the grid, spanning all three
   rows. Flex row with fixed-width tiles so the cluster's intrinsic
   width is naturally N × 84px + gaps. align-self: center centres
   the cluster vertically against the card's three-row height. */
.hero-actions {
  grid-column: 3;
  grid-row: 1 / span 3;
  align-self: center;
  display: flex;
  flex-direction: row;
  gap: 8px;
  flex-shrink: 0;
}
/* Each tile: square footprint, vertically-stacked icon + label,
   centred contents. Visual hierarchy is carried by COLOUR alone
   (Teams = filled indigo), not by size — every tile is the same
   84×84 footprint so the row reads as a balanced grid. */
.hero-btn {
  width: 84px;
  height: 84px;
  flex-shrink: 0;  /* don't squash when parent flex tries to fit */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border-radius: 12px;
  padding: 8px 6px;
  font-weight: 600;
  font-size: 0.72rem;
  line-height: 1.15;
  text-decoration: none;
  cursor: pointer;
  transition: all 0.15s;
  text-align: center;
  white-space: nowrap;
}
.hero-btn svg { flex-shrink: 0; }
/* Secondary tiles — quiet ghost treatment. Icon picks up text
   colour so it reads as neutral against the hero card bg, then
   the whole tile lifts to primary indigo on hover. */
.hero-btn-secondary {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
}
.hero-btn-secondary:hover {
  background: var(--bg);
  border-color: var(--primary);
  color: var(--primary);
  transform: translateY(-1px);
}
.hero-btn-secondary svg { opacity: 0.7; transition: opacity 0.15s; }
.hero-btn-secondary:hover svg { opacity: 1; }

/* Primary tile marker class — actual fill comes from .btn-teams or
   .btn-accent below; .hero-btn-primary stays empty so future tile-
   specific overrides have a hook without disturbing the brand-fill
   classes (which are reused elsewhere in the codebase). */
.hero-btn-primary { /* hierarchy comes from .btn-teams / .btn-accent below */ }

.btn-teams { background: #5b5fc7; color: white; border: 1px solid #5b5fc7; box-shadow: 0 2px 6px rgba(91,95,199,0.25); }
.btn-teams:hover { background: #4b4fb7; border-color: #4b4fb7; color: white; box-shadow: 0 6px 16px rgba(91,95,199,0.4); transform: translateY(-1px); }

/* Calendar-as-primary fill (Nov 2026 audit fix #3 — time-aware
   primary action). When the hero session is more than a day away,
   "Add to my calendar" is the right primary CTA, not "Join Teams".
   This class is applied to the Calendar tile in that window so it
   carries the same filled-primary visual weight Teams gets when
   imminent. Brand-indigo to differentiate from Teams' purple-grey,
   reinforcing that this is the "add to your calendar" action not
   the "join the meeting" action. */
.btn-cal-primary { background: var(--primary); color: white; border: 1px solid var(--primary); box-shadow: 0 2px 6px rgba(79,70,229,0.25); }
.btn-cal-primary:hover { background: var(--primary-hover, #4338ca); border-color: var(--primary-hover, #4338ca); color: white; box-shadow: 0 6px 16px rgba(79,70,229,0.4); transform: translateY(-1px); }
.btn-accent { background: #f59e0b; color: white; border: 1px solid #f59e0b; box-shadow: 0 2px 6px rgba(245,158,11,0.25); }
.btn-accent:hover { background: #d97706; border-color: #d97706; color: white; box-shadow: 0 6px 16px rgba(245,158,11,0.4); transform: translateY(-1px); }

/* Legacy class kept so any straggler call sites still compile —
   the new .hero-btn-secondary covers the same role with sharper
   styling. Safe to remove after the codebase is grep-cleaned. */
.hero-detail { font-size: 0.75rem; text-align: center; padding: 6px 12px; }
@keyframes pulse-glow { 0%, 100% { box-shadow: 0 0 0 0 rgba(91,95,199,0.4); } 50% { box-shadow: 0 0 0 10px rgba(91,95,199,0); } }
.pulse-glow { animation: pulse-glow 2s ease-in-out infinite; }

/* === Hero card: dark mode lift effect === */
@media (prefers-color-scheme: dark) {
  /* Nov 2026: gradients dropped here too (matching the light-mode
     calm-down). Just slightly stronger border tints so the card
     edge reads on the dark page; surface stays flat var(--bg-card).

     2026-07-15 hover-blink fix: rest state is now a gradient too
     (with 0% alpha on the tint stop) so the hover transition
     interpolates a single alpha value 0 → 0.14 smoothly. Previously
     rest was flat colour and hover was a gradient — CSS can't
     interpolate between the two, so the browser briefly showed the
     underlying page bg before snapping to the target gradient
     ("goes dark first, then colour change"). Same angle + same
     stops in both ends is the key; only the tint alpha differs. */
  .hero-card.hero-feedback {
    border-color: rgba(245,158,11,0.38);
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(245,158,11,0) 100%);
  }
  .hero-card.hero-today {
    border-color: rgba(79,70,229,0.4);
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(79,70,229,0) 100%);
  }
  .hero-card:not(.hero-today):not(.hero-feedback):not(.hero-break-summer):not(.hero-break-xmas) {
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(79,70,229,0) 100%);
  }
  .hero-card.hero-break-summer {
    border-color: rgba(251,191,36,0.45);
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(251,191,36,0) 100%);
  }
  .hero-card.hero-break-xmas {
    border-color: rgba(16,185,129,0.45);
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(16,185,129,0) 100%);
  }
  /* Break hero TEXT colours — at rest in dark mode. The light-mode
     defaults (#d97706 / #92400e / #059669 / #065f46) are dark amber/
     green designed for white cards. On a slate card with amber/green
     bg tint they're nearly unreadable, and on hover when the bg gets
     more saturated they vanish entirely — the user perceived this as
     "the inside got darker on hover" but it was actually the text
     contrast collapsing. Override with light shades that read on
     dark slate AND brighten further on hover. */
  .hero-break-summer .hero-label { color: #fcd34d; }   /* amber-300 */
  .hero-break-summer .hero-date  { color: #fde68a; }   /* amber-200 */
  .hero-break-xmas   .hero-label { color: #6ee7b7; }   /* emerald-300 */
  .hero-break-xmas   .hero-date  { color: #a7f3d0; }   /* emerald-200 */
  /* On hover: bump text one step brighter so it visibly "lights up"
     in sync with the background glow. */
  .hero-card.hero-break-summer:hover .hero-label { color: #fde68a; }
  .hero-card.hero-break-summer:hover .hero-date  { color: #fef3c7; }   /* amber-100, near-white */
  .hero-card.hero-break-xmas:hover   .hero-label { color: #a7f3d0; }
  .hero-card.hero-break-xmas:hover   .hero-date  { color: #d1fae5; }   /* emerald-100, near-white */
  /* On hover: strong shadow + colour glow replaces the near-invisible light-mode shadow */
  .hero-card:hover {
    box-shadow: 0 10px 32px rgba(0,0,0,0.55), 0 3px 8px rgba(0,0,0,0.4);
  }
  .hero-card.hero-feedback:hover {
    box-shadow: 0 10px 32px rgba(0,0,0,0.5), 0 0 0 1px rgba(245,158,11,0.3);
    border-color: rgba(245,158,11,0.6);
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(245,158,11,0.14) 100%);
  }
  .hero-card.hero-today:hover {
    box-shadow: 0 10px 32px rgba(0,0,0,0.5), 0 0 0 1px rgba(79,70,229,0.35);
    border-color: rgba(79,70,229,0.6);
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(79,70,229,0.16) 100%);
  }
  /* Break variants on hover: actually GLOW UP (outer coloured shadow
     radiates light beyond the card edges, interior bg jumps from a
     subtle 0.13 to a saturated 0.32, dark drop shadow toned down so
     the colour wins). Previous version was too subtle on slate. */
  .hero-card.hero-break-summer:hover {
    box-shadow:
      0 0 32px rgba(251,191,36,0.35),         /* OUTER amber glow */
      0 10px 32px rgba(0,0,0,0.45),            /* dark drop / lift */
      0 0 0 1px rgba(251,191,36,0.6);          /* inner amber edge */
    border-color: rgba(251,191,36,0.8);
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(251,191,36,0.32) 100%);
  }
  .hero-card.hero-break-xmas:hover {
    box-shadow:
      0 0 32px rgba(16,185,129,0.35),          /* OUTER green glow */
      0 10px 32px rgba(0,0,0,0.45),
      0 0 0 1px rgba(16,185,129,0.6);
    border-color: rgba(16,185,129,0.8);
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(16,185,129,0.32) 100%);
  }
  .hero-card:not(.hero-today):not(.hero-feedback):not(.hero-break-summer):not(.hero-break-xmas):hover {
    box-shadow: 0 10px 32px rgba(0,0,0,0.5), 0 0 0 1px rgba(79,70,229,0.25);
    border-color: rgba(79,70,229,0.45);
    background: linear-gradient(135deg, var(--bg-card) 0%, rgba(79,70,229,0.14) 100%);
  }
}

@media (max-width: 600px) {
  /* Hero on phones: typography scales down so the date+topic+meta stack
     reads as a tight unit, the spine rail wraps the full left edge
     instead of a half-rounded stub, and the action buttons sit as a
     full-width column underneath. Padding matches the rest of mobile
     card chrome (14–16px) — was a desktop hold-over. */
  /* Mobile: collapse the 3×3 grid into a stacked layout.
     Each grid cell takes the full width on its own row, in this
     order:
       row 1: eyebrow row (date side then info side)
       row 2: big date
       row 3: title
       row 4: time chip
       row 5: speaker / location
       row 6: tile cluster
     We use grid-template-columns: 1fr and place each cell into
     consecutive auto rows in source-order. */
  .hero-card {
    grid-template-columns: 1fr;
    grid-template-rows: none;
    grid-auto-rows: auto;
    column-gap: 0;
    row-gap: 6px;
  }
  /* Hide the vertical separator on mobile — there's only one column,
     so a vertical line through the middle of stacked cells would be
     visual noise without the column structure to anchor it. */
  .hero-card::before { display: none; }
  .hero-card {
    padding: 16px 16px;
    border-radius: 14px;
  }
  /* Reset explicit grid placement so cells flow auto in source order. */
  .hero-r1-date, .hero-r1-info,
  .hero-r2-date, .hero-r2-info,
  .hero-r3-date, .hero-r3-info {
    grid-column: 1;
    grid-row: auto;
  }
  .hero-actions {
    grid-column: 1;
    grid-row: auto;
    margin-top: 8px;
    width: 100%;
    gap: 6px;
  }
  .hero-card::before { border-radius: 14px 0 0 14px; width: 3px; }
  .hero-label { font-size: 0.66rem; margin-bottom: 0; }
  .hero-date { font-size: 1.3rem; }
  .hero-time { font-size: 0.78rem; padding: 2px 8px; }
  .hero-topic { font-size: 1.1rem; line-height: 1.35; }
  .hero-meta { font-size: 0.78rem; }
  .hero-location { font-size: 0.76rem; }
  .hero-btn { width: auto; height: auto; min-height: 68px; font-size: 0.68rem; padding: 8px 4px; flex: 1 1 0; }
  .hero-detail { width: 100%; text-align: center; }
}

/* Page-header overflow menu [···] — Round 2 (2026-05-10).
   Replaces the previous 3 always-visible icon-buttons (PDF +
   Calendar + Suggest). Toggled open/closed by adding .open to
   .prog-overflow on the trigger button click. Closes on
   document-level click via the script in programme.html. */
.prog-overflow { position: relative; display: inline-block; }
.prog-overflow-btn {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 8px;
  width: 36px; height: 36px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-muted);
  cursor: pointer;
  transition: all 0.15s ease;
}
.prog-overflow-btn:hover {
  color: var(--text);
  border-color: var(--text-muted);
  background: var(--bg-card-hover, var(--bg));
}
.prog-overflow-menu {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 240px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  z-index: 100;
  padding: 6px 0;
}
.prog-overflow.open .prog-overflow-menu { display: block; }
.prog-overflow-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 14px;
  color: var(--text);
  text-decoration: none;
  font-size: 0.85rem;
  font-family: inherit;
  font-weight: 400;
  line-height: 1.4;
  transition: background 0.15s ease;
  /* Reset browser <button> defaults so anchor + button items render
     identically. Without this, the Invite button (a <button> because
     it triggers JS, not a navigation) inherits browser font + padding
     and reads visibly different from the PDF item (an <a>). */
  background: transparent;
  border: none;
  width: 100%;
  text-align: left;
  cursor: pointer;
  box-sizing: border-box;
}
.prog-overflow-item:hover {
  background: rgba(79, 70, 229, 0.08);
  text-decoration: none;
  color: var(--text);
}
.prog-overflow-item.is-disabled {
  color: var(--text-muted);
  cursor: not-allowed;
  opacity: 0.6;
}
.prog-overflow-item.is-disabled:hover { background: transparent; }
.prog-overflow-when {
  font-size: 0.72rem;
  color: var(--text-muted);
  margin-left: 4px;
  font-weight: 500;
}

/* Consolidated programme controls row — Round 2 refinement
   (2026-05-10 PM). Replaces three separate rows (year pills row,
   'Take HPB Teaching with you' strip, filters bar) with one
   unified flex container. Layout:

     [year pills]   [search ────────────]   [Subscribe]   [≡|▦]

   Wraps gracefully on narrow viewports — year pills first row,
   search next row, action buttons last row. */
.programme-controls {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 18px;
  flex-wrap: wrap;
}

/* Search — flex-grow so it absorbs whatever width is left after the
   other controls. Min-width keeps it usable when wrapped to its own
   line on mobile. Icon sits inside the input via a pseudo-position. */
.programme-controls-search {
  position: relative;
  display: flex;
  align-items: center;
  flex: 1 1 240px;
  min-width: 200px;
  margin: 0;
}
.programme-controls-search-icon {
  position: absolute;
  left: 12px;
  color: var(--text-muted);
  pointer-events: none;
}
.programme-controls-search .input-search {
  width: 100%;
  padding: 8px 12px 8px 34px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.88rem;
  background: var(--bg-card);
  color: var(--text);
  font-family: inherit;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.programme-controls-search .input-search:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.12);
}

/* Subscribe button — Nov 2026 state-aware variants.
   Three modes:
     1. .btn-subscribe (default)         — outlined-ghost, anonymous
                                           visitors / generic feed
     2. .btn-subscribe-loud (NEW)        — filled primary + 2-line
                                           label, members WITHOUT a
                                           personal-feed token. The
                                           power feature is the most
                                           important undiscovered
                                           affordance for them.
     3. .btn-subscribe-synced (NEW)      — calm green checkmark pill,
                                           members who already have
                                           the token. They've done it;
                                           the button just lets them
                                           re-copy the URL if needed.
*/
.btn-subscribe {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 14px;
  background: transparent;
  color: var(--primary);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
  transition: all 0.15s ease;
  white-space: nowrap;
}
.btn-subscribe:hover {
  background: rgba(79, 70, 229, 0.08);
  border-color: var(--primary);
  color: var(--primary);
}

/* LOUD variant — filled primary, two-line label with subtitle.
   This is the "discover the power feature" state for authed
   members who haven't subscribed yet. Larger padding + filled
   indigo + box-shadow draw the eye in a way that says "this is
   the important action you're missing." */
.btn-subscribe.btn-subscribe-loud {
  background: var(--primary);
  color: white;
  border-color: var(--primary);
  padding: 9px 16px;
  box-shadow: 0 1px 3px rgba(79, 70, 229, 0.30), 0 1px 2px rgba(0, 0, 0, 0.05);
  align-items: center;
}
.btn-subscribe.btn-subscribe-loud:hover {
  background: #4338ca;
  color: white;
  border-color: #4338ca;
  box-shadow: 0 4px 12px rgba(79, 70, 229, 0.35);
  transform: translateY(-1px);
}
.btn-subscribe-label-stack {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1.15;
}
.btn-subscribe-label-main {
  font-size: 0.85rem;
  font-weight: 700;
}
.btn-subscribe-label-sub {
  font-size: 0.68rem;
  font-weight: 500;
  opacity: 0.85;
  letter-spacing: 0.01em;
  margin-top: 1px;
}

/* ──────────────────────────────────────────────────────────────
   Calendar recruitment strip — slim banner between hero and filters
   for members who haven't subscribed and haven't dismissed (Nov 2026).
   Soft indigo tinted background, no left-border accent (per design
   feedback), single-row layout. Real estate without color
   saturation — present but not loud.
   ────────────────────────────────────────────────────────────── */
.ical-strip {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 11px 16px;
    margin: 0 0 18px;
    border-radius: 12px;
    background: linear-gradient(135deg,
                rgba(79, 70, 229, 0.05) 0%,
                rgba(79, 70, 229, 0.03) 60%,
                transparent 100%),
                var(--bg-card);
    border: 1px solid rgba(79, 70, 229, 0.18);
    font-size: 0.85rem;
    color: var(--text);
}
.ical-strip-icon {
    position: relative;
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    border-radius: 9px;
    background: rgba(79, 70, 229, 0.10);
    color: var(--primary);
    display: flex;
    align-items: center;
    justify-content: center;
}
.ical-strip-icon-dot {
    position: absolute;
    top: 5px;
    right: 5px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #10b981;
    box-shadow: 0 0 0 2px var(--bg-card);
}
.ical-strip-body {
    display: flex;
    align-items: baseline;
    gap: 6px;
    flex-wrap: wrap;
    min-width: 0;
    flex: 1 1 auto;
}
.ical-strip-title {
    font-weight: 700;
    color: var(--text);
}
.ical-strip-meta {
    color: var(--text-muted);
    font-size: 0.82rem;
}
.ical-strip-features {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    align-items: center;
    gap: 14px;
    flex-shrink: 0;
}
.ical-strip-features li {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.78rem;
    color: var(--text-muted);
    white-space: nowrap;
}
.ical-strip-features svg {
    color: #10b981;
    flex-shrink: 0;
}
.ical-strip-action {
    margin: 0;
    flex-shrink: 0;
}
.ical-strip-subscribe {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: var(--primary);
    color: white;
    border: 1px solid var(--primary);
    border-radius: 8px;
    padding: 8px 14px;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    font-family: inherit;
    transition: background 0.15s ease, transform 0.1s ease;
}
.ical-strip-subscribe:hover {
    background: #4338ca;
    border-color: #4338ca;
}
.ical-strip-subscribe:active { transform: translateY(1px); }
.ical-strip-dismiss-form { margin: 0; flex-shrink: 0; }
.ical-strip-dismiss {
    background: transparent;
    color: var(--text-muted);
    border: none;
    width: 28px;
    height: 28px;
    border-radius: 6px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s ease, color 0.15s ease;
}
.ical-strip-dismiss:hover {
    background: rgba(15, 23, 42, 0.06);
    color: var(--text);
}

/* Mobile: collapse to a 2-row stack */
@media (max-width: 640px) {
    .ical-strip {
        flex-wrap: wrap;
        padding: 10px 12px;
        gap: 8px 12px;
    }
    .ical-strip-body {
        flex: 1 1 calc(100% - 80px);
    }
    .ical-strip-features {
        order: 4;
        flex-basis: 100%;
        gap: 10px;
        margin-top: 2px;
    }
    .ical-strip-features li { font-size: 0.74rem; }
    .ical-strip-action { order: 5; flex: 1 1 auto; }
    .ical-strip-subscribe { width: 100%; justify-content: center; }
    .ical-strip-dismiss-form { order: 3; }
}

/* ──────────────────────────────────────────────────────────────
   Stale calendar banner (Nov 2026) — sibling of .ical-strip; shown
   when the user IS subscribed but their calendar app stopped polling
   (>21 days). Amber palette signals "needs attention but not urgent".
   Shape mirrors .ical-strip so the page rhythm is consistent
   regardless of which (mutually-exclusive) state is active.
   ────────────────────────────────────────────────────────────── */
.ical-stale-banner {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 10px 16px;
    margin: 14px 0;
    background: rgba(251, 191, 36, 0.08);
    border: 1px solid rgba(251, 191, 36, 0.30);
    border-radius: 10px;
    font-size: 0.88rem;
}
.ical-stale-icon {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    border-radius: 8px;
    background: rgba(251, 191, 36, 0.18);
    color: #b45309;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.ical-stale-body {
    flex: 1;
    display: flex;
    flex-wrap: wrap;
    gap: 4px 10px;
    align-items: baseline;
    line-height: 1.4;
}
.ical-stale-body strong { color: var(--text); font-weight: 600; }
.ical-stale-meta {
    color: var(--text-muted);
    font-size: 0.83rem;
}
.ical-stale-action {
    flex-shrink: 0;
    padding: 7px 14px;
    border-radius: 8px;
    border: 1px solid rgba(251, 191, 36, 0.55);
    background: white;
    color: #b45309;
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
    font-family: inherit;
    transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.ical-stale-action:hover {
    background: rgba(251, 191, 36, 0.10);
    border-color: rgba(251, 191, 36, 0.75);
}
.ical-stale-action:active { transform: translateY(1px); }

@media (max-width: 640px) {
    .ical-stale-banner {
        flex-wrap: wrap;
        padding: 10px 12px;
    }
    .ical-stale-body { flex-basis: calc(100% - 48px); }
    .ical-stale-action { width: 100%; text-align: center; }
}

/* ──────────────────────────────────────────────────────────────
   Instant tooltip pattern (Nov 2026) — replaces title= attributes
   that have a 1-2s browser-default delay. Use data-tooltip="..."
   on any element; this CSS shows it instantly on hover/focus, fades
   in over 80ms.

   Touch devices: data-tooltip won't trigger via :hover reliably;
   use this for desktop hover affordance, fall back to a normal
   title= or visible label for the mobile case if the message is
   essential.
   ────────────────────────────────────────────────────────────── */
[data-tooltip] {
    position: relative;
}
[data-tooltip]::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: calc(100% + 6px);
    left: 50%;
    transform: translateX(-50%);
    background: rgba(15, 23, 42, 0.92);
    color: white;
    font-size: 0.72rem;
    font-weight: 500;
    padding: 5px 9px;
    border-radius: 6px;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.08s ease;
    z-index: 100;
    letter-spacing: 0.01em;
}
[data-tooltip]:hover::after,
[data-tooltip]:focus-visible::after {
    opacity: 1;
}

/* SYNCED variant — green checkmark pill. Members who've already
   subscribed AND whose calendar app is actively polling. */
.btn-subscribe.btn-subscribe-synced {
  background: rgba(16, 185, 129, 0.08);
  border-color: rgba(16, 185, 129, 0.30);
  color: #059669;
}
.btn-subscribe.btn-subscribe-synced:hover {
  background: rgba(16, 185, 129, 0.14);
  border-color: rgba(16, 185, 129, 0.50);
  color: #047857;
}

/* STALE variant — token exists but no calendar-app fetch in 21+
   days. Likely removed from the calendar app, or the user moved
   to a new device. Amber instead of green: not broken, but the
   user should know to check / re-add. */
.btn-subscribe.btn-subscribe-stale {
  background: rgba(217, 119, 6, 0.08);
  border-color: rgba(217, 119, 6, 0.30);
  color: #b45309;
}
.btn-subscribe.btn-subscribe-stale:hover {
  background: rgba(217, 119, 6, 0.14);
  border-color: rgba(217, 119, 6, 0.50);
  color: #92400e;
}

@media (max-width: 540px) {
  /* On narrow screens, hide the "Subscribe" text to save space —
     icon alone (with title tooltip) is enough. The loud variant's
     subtitle also collapses on narrow screens to avoid wrapping. */
  .btn-subscribe-label { display: none; }
  .btn-subscribe { padding: 7px 9px; }
  .btn-subscribe.btn-subscribe-loud { padding: 9px 12px; }
  .btn-subscribe-label-sub { display: none; }
}

/* Feedback prompt — was a standalone banner (.feedback-nudge) above
   the session list. Member-UX-Roadmap Round 2 (2026-05-10) moved it
   inline INTO the session row that needs feedback. The .feedback-
   nudge / .nudge-* classes are gone with the banner. The two new
   classes below cover the table-row variant and the mobile-card
   variant. */
.feedback-inline-prompt {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-left: 10px;
  padding: 2px 9px;
  font-size: 0.74rem;
  font-weight: 600;
  border-radius: 999px;
  background: rgba(245,158,11,0.10);
  color: #b45309;
  border: 1px solid rgba(245,158,11,0.30);
  text-decoration: none;
  vertical-align: middle;
  transition: all 0.15s ease;
  white-space: nowrap;
}
.feedback-inline-prompt:hover {
  background: rgba(245,158,11,0.18);
  border-color: rgba(245,158,11,0.50);
  text-decoration: none;
}
.btn-ghost { background: none; border: none; color: var(--text-muted); cursor: pointer; padding: 4px 8px; font-size: 0.8rem; border-radius: 6px; }
.btn-ghost:hover { background: var(--bg-card-hover); }

/* Mobile-card variant of the inline feedback prompt — sits under
   the topic title, full-width pill so it's tap-friendly on a phone. */
.prog-card-feedback-prompt {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin-top: 8px;
  padding: 6px 10px;
  font-size: 0.8rem;
  font-weight: 600;
  border-radius: 8px;
  background: rgba(245,158,11,0.10);
  color: #b45309;
  border: 1px solid rgba(245,158,11,0.30);
  cursor: pointer;
  transition: all 0.15s ease;
}
.prog-card-feedback-prompt:hover {
  background: rgba(245,158,11,0.18);
  border-color: rgba(245,158,11,0.50);
}

/* States */
.dot-past { opacity: 0.65; }
.dot-cancelled { background: #fca5a5 !important; border: 2px dashed #ef4444; opacity: 0.5; }
.dot-next { opacity: 1 !important; box-shadow: 0 0 0 4px rgba(79,70,229,0.3); animation: pulse-dot 2s ease-in-out infinite; }
@keyframes pulse-dot { 0%, 100% { box-shadow: 0 0 0 4px rgba(79,70,229,0.3); } 50% { box-shadow: 0 0 0 8px rgba(79,70,229,0.1); } }

/* Tooltip */
.timeline-tooltip { display: none; position: absolute; bottom: 24px; left: 50%; transform: translateX(-50%); background: var(--bg-card); border-radius: 8px; padding: 10px 12px; box-shadow: 0 4px 16px rgba(0,0,0,0.15); border: 1px solid var(--border); min-width: 180px; max-width: 240px; z-index: 50; font-size: 0.8rem; line-height: 1.4; text-decoration: none; color: var(--text); }
.timeline-dot:hover .timeline-tooltip { display: block; }
.tt-date { font-weight: 600; font-size: 0.75rem; color: var(--text-muted); margin-bottom: 4px; }
.tt-topic { font-weight: 500; margin-bottom: 2px; }
.tt-speaker { font-size: 0.75rem; color: var(--text-muted); }
.tt-status { margin-top: 4px; }
.tt-cat { margin-bottom: 2px; }

/* Legend */
.timeline-legend { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 12px; padding-top: 8px; border-top: 1px solid var(--border); }
.legend-item { display: flex; align-items: center; gap: 4px; font-size: 0.7rem; color: var(--text-muted); }
.legend-dot { width: 10px; height: 10px; border-radius: 50%; border: 1px solid rgba(0,0,0,0.1); }
.legend-bh { width: 10px; height: 10px; background: rgba(239, 68, 68, 0.25); border: 1px solid #ef4444; }
.legend-school { width: 16px; height: 10px; background: rgba(251, 191, 36, 0.15); border: 1px dashed #f59e0b; border-radius: 2px; }

/* Mobile timeline */
@media (max-width: 768px) {
    .timeline-track { height: 60px; }
    .timeline-dot { width: 12px; height: 12px; }
    .timeline-today-line { height: 68px; }
    .timeline-school-band { height: 60px; top: 28px; }
    .timeline-school-label { display: none; }
    .timeline-break-label { font-size: 0.5rem; }
    .timeline-tooltip { min-width: 150px; font-size: 0.75rem; }
    .timeline-legend { gap: 8px; }
}

/* === Break Banners === */
.break-banner { text-align: center; padding: 10px 16px; margin: 4px 0; border-radius: 8px; font-weight: 600; font-size: 0.82rem; border: 1px dashed; display: flex; align-items: center; justify-content: center; gap: 6px; transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease; }
.break-banner .break-icon { font-size: 1rem; }
.break-banner .break-dates { font-weight: 400; font-size: 0.75rem; margin-left: 6px; opacity: 0.7; }
/* Neutral closure banner for data-driven / ad-hoc breaks (ADR-012).
   Uses theme tokens so it works in light + dark with no override. */
.break-generic { background: var(--bg-card-hover); border-color: var(--border); color: var(--text-muted); }
.break-row:hover .break-generic,
.break-band:hover .break-generic { border-color: var(--text-light); color: var(--text); }

/* Single data-driven band at the top of #slot-grid (ADR-012). Empty
   when no closures recorded for the year — no chrome, no gap. */
.slot-break-band:empty { display: none; }

/* === Builder floating action group (ADR-013, repositioned 2026-05-20) ===
   Round icon-only quick-add FABs anchored to the VERTICAL MIDDLE of the
   right edge — clear of the bottom-right help "?" FAB they used to
   collide with (dogfood fix). All secondary (outline) so the page
   Publish stays the sole primary CTA (R1). Disabled members read muted
   and keep their tooltip explanation (R23). */
.builder-fab-group {
    position: fixed; right: 24px; top: 50%; transform: translateY(-50%);
    z-index: 40;
    display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.builder-fab {
    width: 48px; height: 48px; padding: 0;
    border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    box-shadow: 0 4px 14px rgba(0,0,0,0.12);
    background: var(--bg-card);
}
.builder-fab[disabled], .builder-fab[aria-disabled="true"] {
    opacity: 0.55; cursor: not-allowed; box-shadow: none;
}
@media (max-width: 640px) {
    /* Keep clear of the bottom-right help FAB on mobile too: stay on the
       right edge, nudged up from centre, smaller targets that still meet
       the 44px minimum (R6). */
    .builder-fab-group { right: 12px; top: 42%; gap: 8px; }
    .builder-fab { width: 44px; height: 44px; }
}

/* === Break-management modal body (ADR-013) === */
.break-modal-list-wrap { margin: 4px 0 16px; max-height: 320px; overflow-y: auto; }
.break-modal-empty { color: var(--text-muted); font-size: 0.85rem; padding: 12px 4px; }
.break-modal-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.break-modal-row {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px;
    background: var(--bg-card); font-size: 0.85rem;
}
.break-modal-row .break-reason { font-weight: 600; color: var(--text); }
.break-modal-row .break-dates { color: var(--text-muted); font-size: 0.78rem; }
.break-add-form { border-top: 1px solid var(--border); padding-top: 14px; }
.break-add-form-row { display: flex; gap: 12px; }
.break-add-form-row .builder-modal-field { flex: 1; }

.break-summer { background: rgba(251,191,36,0.1); border-color: rgba(251,191,36,0.4); color: #a16207; }
.break-row:hover .break-summer { border-color: #f59e0b; color: #78350f; }
.break-xmas { background: rgba(16,185,129,0.1); border-color: rgba(16,185,129,0.4); color: #065f46; }
.break-row:hover .break-xmas { border-color: #10b981; color: #064e3b; }

/* Dark-mode break banners — light text, bg brightens on hover instead
   of going darker. The light-mode rules above set rest text to
   amber-700/emerald-900 and HOVER text to amber-900/emerald-950 —
   correct for white card bg, totally backwards on slate (text gets
   darker as you hover, on top of an already low-contrast rest state).
   Override both rest and hover with light shades that read on the
   dark page AND brighten further on hover. */
@media (prefers-color-scheme: dark) {
  .break-summer { color: #fde68a; }                              /* amber-200 light */
  .break-row:hover .break-summer {
    background: rgba(251,191,36,0.22);                            /* bg lights up */
    border-color: #fcd34d;
    color: #fef3c7;                                                /* near-white amber */
  }
  .break-xmas { color: #a7f3d0; }                                /* emerald-200 light */
  .break-row:hover .break-xmas {
    background: rgba(16,185,129,0.22);                            /* bg lights up */
    border-color: #6ee7b7;
    color: #d1fae5;                                                /* near-white emerald */
  }
}

/* Rota clash indicators (builder inline chips) */
.rota-clash { display: inline-flex; align-items: center; gap: 4px; padding: 1px 8px; border-radius: 4px; margin-left: auto; }
.rota-red { background: var(--danger-bg); border: 1px solid #fecaca; }
.rota-orange { background: var(--warn-bg); border: 1px solid #fed7aa; }

/* Teams join button */
.teams-join-btn { display: inline-flex; align-items: center; gap: 4px; background: #464EB8; color: white !important; padding: 3px 10px; border-radius: 4px; font-size: 0.8rem; font-weight: 500; text-decoration: none !important; }
.teams-join-btn:hover { background: #3b41a0; }
.teams-pill { display: inline-block; background: #464EB8; color: white !important; padding: 1px 8px; border-radius: 10px; font-size: 0.65rem; font-weight: 600; text-decoration: none !important; margin-left: 4px; vertical-align: middle; letter-spacing: 0.02em; }
.teams-pill:hover { background: #3b41a0; text-decoration: none !important; }

/* ——— programme.html utility classes ——— */
/* SVG icons inline with text */
.icon-sm  { width:1.1em; height:1.1em; vertical-align:-0.15em; }
.icon-btn { vertical-align:-2px; margin-right:3px; }
.icon-mr  { margin-right:6px; flex-shrink:0; }
/* Layout helpers */
.flex-between { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:8px; }
.dl-stack { display:inline-flex; flex-direction:column; align-items:center; gap:2px; }
/* Calendar / generic modal */
.modal-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5); z-index:200; justify-content:center; align-items:center; }
.modal-card { max-width:500px; width:90%; margin:20px; position:relative; }
.modal-close { position:absolute; right:12px; top:12px; background:none; border:none; font-size:1.2rem; cursor:pointer; color:var(--text-muted); }
.modal-btn-list { display:flex; flex-direction:column; gap:8px; }
.btn-icon-left { justify-content:flex-start; }

/* ——— Combo dropdown — exact style from join.hpbteaching.app ——— */
.combo-wrap { position: relative; }
.combo-input-row { position: relative; }
.combo-input { width: 100%; border: 1px solid var(--border); border-radius: 8px; padding: 9px 36px 9px 12px; font-size: 0.9rem; font-family: inherit; color: var(--text); background: var(--bg-card); transition: border-color 0.15s; cursor: pointer; }
.combo-input::placeholder { color: #94a3b8; }
.combo-input:focus { outline: none; border-color: var(--indigo); box-shadow: 0 0 0 3px rgba(79,70,229,0.1); }
.combo-input.selected { color: var(--text); font-weight: 500; }
.combo-chevron { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); pointer-events: none; color: var(--text-muted); transition: transform 0.2s; }
.combo-wrap.open .combo-chevron { transform: translateY(-50%) rotate(180deg); }
.combo-dropdown { position: absolute; top: calc(100% + 4px); left: 0; right: 0; background: var(--bg-card); border: 1px solid var(--indigo); border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.12); z-index: 300; max-height: 280px; overflow: hidden; display: none; flex-direction: column; }
.combo-wrap.open .combo-dropdown { display: flex; }
.combo-search-row { padding: 8px 10px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
.combo-search { width: 100%; border: 1px solid var(--border); border-radius: 6px; padding: 6px 10px; font-size: 0.85rem; font-family: inherit; color: var(--text); background: var(--bg); }
.combo-search:focus { outline: none; border-color: var(--indigo); }
.combo-list { overflow-y: auto; flex: 1; padding: 4px 0; }
.combo-group-label { padding: 6px 12px 2px; font-size: 0.68rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--indigo); background: var(--bg); border-top: 1px solid var(--border); margin-top: 2px; }
.combo-group-label:first-child { border-top: none; margin-top: 0; }
.combo-option { padding: 7px 14px; font-size: 0.875rem; color: var(--text); cursor: pointer; transition: background 0.1s; }
.combo-option:hover, .combo-option.focused { background: var(--indigo-light); color: var(--indigo-dark); }
.combo-option.selected-opt { /* selection shown in input above — no extra highlight in list */ }
.combo-empty { padding: 12px 14px; font-size: 0.875rem; color: var(--text-muted); text-align: center; }

/* === Mobile bottom-tab navigation (slice M5) ===
   Replaces the top .nav-links strip on phones with an app-style
   bottom tab bar. The top navbar stays (brand + user dropdown) so
   profile/logout remain reachable; primary navigation moves to the
   bottom where the thumb naturally sits. */
.mobile-tabbar { display: none; }
.mobile-sheet, .mobile-sheet-backdrop { display: none; }

@media (max-width: 768px) {
    .mobile-tabbar {
        display: flex;
        position: fixed;
        bottom: 0; left: 0; right: 0;
        z-index: 100;
        background: var(--bg-card);
        border-top: 1px solid var(--border);
        box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.06);
        /* iPhone home-indicator safe-area inset */
        padding-bottom: env(safe-area-inset-bottom, 0);
        height: calc(60px + env(safe-area-inset-bottom, 0));
    }
    .mtab {
        flex: 1;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 3px;
        padding: 8px 4px;
        color: var(--text-muted);
        text-decoration: none;
        font-size: 0.65rem;
        font-weight: 600;
        letter-spacing: 0.01em;
        background: none;
        border: none;
        cursor: pointer;
        font-family: inherit;
        transition: color 0.15s;
        position: relative;
        min-height: 44px;
    }
    .mtab svg {
        width: 22px;
        height: 22px;
        transition: transform 0.15s;
    }
    .mtab:active svg { transform: scale(0.92); }
    .mtab:hover, .mtab:focus { color: var(--text); text-decoration: none; }
    .mtab-active { color: var(--primary); }
    .mtab-active::before {
        content: "";
        position: absolute;
        top: 0; left: 50%;
        transform: translateX(-50%);
        width: 28px; height: 2px;
        background: var(--primary);
        border-radius: 0 0 2px 2px;
    }

    /* Body needs bottom space so fixed tabbar doesn't cover content.
       Override the existing 8px container padding only at the bottom. */
    main.container { padding-bottom: calc(72px + env(safe-area-inset-bottom, 0)); }
    /* Footer also needs to clear the tabbar */
    .footer { margin-bottom: calc(60px + env(safe-area-inset-bottom, 0)); }
    /* Font-size FAB sits ABOVE the tabbar instead of beside it */
    .font-size-controls { bottom: calc(76px + env(safe-area-inset-bottom, 0)) !important; }

    /* === Coordinator "More" sheet === */
    .mobile-sheet-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        background: rgba(0, 0, 0, 0.4);
        z-index: 200;
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.2s;
    }
    .mobile-sheet-backdrop.is-open { opacity: 1; pointer-events: auto; }

    .mobile-sheet {
        display: block;
        position: fixed;
        bottom: 0; left: 0; right: 0;
        z-index: 201;
        background: var(--bg-card);
        border-radius: 16px 16px 0 0;
        padding: 12px 16px calc(20px + env(safe-area-inset-bottom, 0));
        box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.18);
        transform: translateY(100%);
        transition: transform 0.25s cubic-bezier(0.32, 0.72, 0, 1);
    }
    .mobile-sheet.is-open { transform: translateY(0); }
    /* Grabber: visible 36×4 bar but a 44×24 invisible touch zone
       (M7 — drag-to-dismiss target). The padding extends the
       hit area without changing the visual. cursor:grab gives a
       hint on devices with a pointer. */
    .mobile-sheet-grabber {
        width: 36px; height: 4px;
        background: var(--border);
        border-radius: 2px;
        margin: 0 auto;
        padding: 0;
        position: relative;
    }
    .mobile-sheet-grabber::before {
        content: "";
        position: absolute;
        inset: -10px -20px;  /* expands hit area to 76×24 */
        cursor: grab;
    }
    .mobile-sheet-grabber:active::before { cursor: grabbing; }
    .mobile-sheet-title {
        font-size: 0.7rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.1em;
        color: var(--text-muted);
        text-align: center;
        margin: 12px 0 8px;
        padding: 4px 0;
        cursor: grab;          /* desktop pointer hint — sheet is draggable */
        user-select: none;     /* prevent text selection during drag */
    }
    .mobile-sheet-link {
        display: flex;
        align-items: center;
        gap: 14px;
        padding: 14px 12px;
        color: var(--text);
        text-decoration: none;
        font-size: 0.95rem;
        font-weight: 500;
        border-radius: 10px;
        min-height: 48px;
        transition: background 0.15s;
    }
    .mobile-sheet-link:hover, .mobile-sheet-link:focus {
        background: var(--bg);
        text-decoration: none;
    }
    .mobile-sheet-link.is-active { color: var(--primary); background: rgba(79, 70, 229, 0.08); }
    .mobile-sheet-link svg { width: 22px; height: 22px; flex-shrink: 0; color: var(--text-muted); }
    .mobile-sheet-link.is-active svg { color: var(--primary); }
    .mobile-sheet-close {
        display: block;
        width: 100%;
        margin-top: 8px;
        padding: 12px;
        background: var(--bg);
        border: 1px solid var(--border);
        border-radius: 10px;
        font-size: 0.9rem;
        font-weight: 600;
        color: var(--text);
        cursor: pointer;
        font-family: inherit;
        min-height: 44px;
    }
    .mobile-sheet-close:active { background: var(--bg-card); }
}

@media (prefers-color-scheme: dark) {
    @media (max-width: 768px) {
        .mobile-tabbar { box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.4); }
        .mobile-sheet { box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.5); }
        .mobile-sheet-link.is-active { background: rgba(99, 102, 241, 0.18); }
    }
}

/* === Reduced motion (slice M6) ===
   Users with vestibular sensitivity or motion-sickness can ask the OS
   to reduce motion. Respect that across our animation surfaces — the
   sheet still opens (just instantly), tabs still respond to taps
   (just no scale), hover/focus states still work. We're disabling
   animation, not interactivity. */
/* === Auto-wrapped table scroller (slice M7) ===
   Inline-styled <table> elements across admin pages overflow narrow
   viewports. JS in base.html wraps each table (not already wrapped)
   in a .table-x-scroll div on DOM ready; this CSS gives that wrapper
   a clean horizontal scroll on mobile + a faint right-edge fade so
   users notice there's more content. Desktop: invisible passthrough. */
.table-x-scroll { width: 100%; }
@media (max-width: 768px) {
    .table-x-scroll {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        /* Right-edge fade to hint at horizontal overflow */
        mask-image: linear-gradient(to right, black 0, black calc(100% - 24px), transparent 100%);
        -webkit-mask-image: linear-gradient(to right, black 0, black calc(100% - 24px), transparent 100%);
    }
    .table-x-scroll > table { min-width: max-content; }
    /* When fully scrolled to the right, drop the fade */
    .table-x-scroll.scrolled-end {
        mask-image: none;
        -webkit-mask-image: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .mobile-sheet, .mobile-sheet-backdrop { transition: none; }
    .mtab svg, .mtab:active svg { transition: none; transform: none; }
    .hero-card, .hero-card:hover, .prog-card, .prog-card:active,
    .help-fab, .help-panel { transition: none; animation: none; }
    .pulse-glow, .pulse-dot, .help-fab.bounce { animation: none; }
    /* Generic catch-all for transitions across the design system —
       any rule with transition: all or transition: transform shrinks
       to zero so reduced-motion is honoured even on elements not
       explicitly listed above. */
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
}

/* ============================================================
   === Utilities ===
   ============================================================
   Small, single-purpose classes extracted from the most-repeated
   inline-style patterns in templates. Namespaced with `u-` so
   they're easy to grep and clearly distinguishable from
   component classes.

   Why: an audit found ~1,926 inline `style="..."` attributes
   across 70 templates, with the same patterns ("text-muted",
   "margin-top:8px", "font-weight:600") repeated dozens of times.
   This module replaces those copies with named classes so:
     • mass design changes hit one definition, not 50 inline edits
     • visual consistency is enforced by the single source of truth
     • templates become readable (intent-named classes vs. CSS soup)

   Usage rule: prefer classes for *repeated* patterns. One-off
   layout tweaks can still use inline styles — the goal isn't to
   eliminate inline CSS, it's to eliminate the *repetition*.

   Placed at the END of the stylesheet so utility classes win the
   cascade against component-level rules — same convention as
   Tailwind, Bulma, Bootstrap utilities. ============================ */

/* ── Text colour ── */
.u-muted        { color: var(--text-muted); }
.u-primary      { color: var(--primary); }
.u-success      { color: #16a34a; }
.u-danger       { color: #dc2626; }
.u-warn         { color: #d97706; }

/* ── Text size (seven-step scale matching the actual values used
       across the templates audit). Sizes shifted slightly from the
       initial library to match the most-common template values
       (0.75rem & 0.8rem instead of 0.72/0.78). ── */
.u-text-2xs     { font-size: 0.7rem; }
.u-text-xs      { font-size: 0.75rem; }
.u-text-sm      { font-size: 0.8rem; }
.u-text-md      { font-size: 0.85rem; }
.u-text-base    { font-size: 0.95rem; }
.u-text-lg      { font-size: 1.05rem; }
.u-text-xl      { font-size: 1.4rem; }

/* ── Combined "supporting text" pattern ──
   12 instances of font-size:0.8rem + color:var(--text-muted) became
   one class. Reads as "this is a meta line, smaller and quieter." */
.u-meta         { font-size: 0.8rem; color: var(--text-muted); }
.u-meta-sm      { font-size: 0.75rem; color: var(--text-muted); }
.u-meta-md      { font-size: 0.85rem; color: var(--text-muted); }

/* ── Weight ── */
.u-bold         { font-weight: 600; }
.u-bolder       { font-weight: 700; }
.u-bold-heavy   { font-weight: 800; }

/* ── Alignment ── */
.u-center       { text-align: center; }
.u-right        { text-align: right; }
.u-left         { text-align: left; }

/* ── Margin (small token set; covers 90% of the inline margin
       values seen in the audit: 4/8/12/16/20/24px) ── */
.u-mt-0         { margin-top: 0; }
.u-mt-1         { margin-top: 4px; }
.u-mt-2         { margin-top: 8px; }
.u-mt-3         { margin-top: 12px; }
.u-mt-4         { margin-top: 16px; }
.u-mt-5         { margin-top: 20px; }
.u-mt-6         { margin-top: 24px; }
.u-mb-0         { margin-bottom: 0; }
.u-mb-1         { margin-bottom: 4px; }
.u-mb-2         { margin-bottom: 8px; }
.u-mb-3         { margin-bottom: 12px; }
.u-mb-4         { margin-bottom: 16px; }
.u-mb-5         { margin-bottom: 20px; }
.u-mb-6         { margin-bottom: 24px; }
.u-m-0          { margin: 0; }

/* ── Display ── */
.u-hidden       { display: none; }
.u-inline       { display: inline; }
.u-inline-block { display: inline-block; }
.u-inline-flex  { display: inline-flex; align-items: center; }
.u-block        { display: block; }
.u-flex         { display: flex; }
.u-flex-shrink-0 { flex-shrink: 0; }

/* ── Inline SVG icon utilities ──
   Three patterns dominate the inline-style sprawl for icons:
     1. Em-sized icons (1em × 1em) for icons that should match text size
     2. Vertical-alignment offsets so the icon's optical center aligns
        with the text x-height (-1px for ~14px icons, -2px for ~16px,
        -3px for ~20px+).
     3. Icon-prefix patterns: vertical-align tweak + right margin so
        the icon sits flush against following text ("📅 Calendar"). */
.u-icon-em        { width: 1em; height: 1em; vertical-align: -0.1em; }
.u-icon-svg       { vertical-align: -2px; flex-shrink: 0; }
.u-icon-svg-sm    { vertical-align: -1px; flex-shrink: 0; }
.u-icon-v1        { vertical-align: -1px; }
.u-icon-v2        { vertical-align: -2px; }
.u-icon-v3        { vertical-align: -3px; }
.u-icon-prefix-1  { vertical-align: -1px; margin-right: 3px; }
.u-icon-prefix-2  { vertical-align: -2px; margin-right: 3px; }
.u-icon-prefix-2w { vertical-align: -2px; margin-right: 4px; }

/* ── Position / cursor / interaction ── */
.u-relative      { position: relative; }
.u-pointer       { cursor: pointer; }
.u-no-underline  { text-decoration: none; }

/* ── Flex ── */
.u-flex-1        { flex: 1; }
.u-flex-grow     { flex: 1; min-width: 0; }
.u-flex-row      { display: flex; align-items: center; gap: 6px; }

/* ── Padding (table cell pattern + small set) ──
   .u-pad-cell is the most common — appears 30+ times across
   the table-heavy admin templates as `padding: 6px 8px`. */
.u-pad-cell      { padding: 6px 8px; }
.u-pad-cell-tall { padding: 8px 4px; }

/* ── Whitespace ── */
.u-pre          { white-space: pre-wrap; }
.u-nowrap       { white-space: nowrap; }


/* ═════════════════════════════════════════════════════════════════
   Files browser — /admin/files
   Visual feel borrowed from the React TreeView the coordinator
   referenced: chevron rotation, indentation lines via padding,
   hover state, smooth folder expansion via the native <details>
   element. No JS framework, no Tailwind, no shadcn.
   ═══════════════════════════════════════════════════════════════ */
.files-page {
    max-width: 1100px;
    margin: 24px auto;
    padding: 0 16px 80px;
}

.ft-root {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 12px;
    margin: 18px 0;
    overflow: hidden;
}
.ft-root.ft-root-platform {
    border-left: 3px solid #f59e0b;
}

.ft-root-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    padding: 16px 20px 12px;
    border-bottom: 1px solid var(--border);
    background: linear-gradient(180deg, var(--bg-card) 0%, var(--bg-page) 100%);
}
.ft-root-head h2 {
    font-size: 0.96rem;
    margin: 0 0 4px;
    color: var(--text);
}
.ft-root-desc {
    font-size: 0.8rem;
    color: var(--text-muted);
    margin: 0;
    max-width: 720px;
    line-height: 1.45;
}
.ft-root-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}
.ft-scope-tag {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 8px;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.06em;
}
.ft-scope-tag.programme {
    background: var(--primary-bg);
    color: var(--primary);
}
.ft-scope-tag.platform {
    background: rgba(245, 158, 11, 0.12);
    color: #92400e;
}
.ft-readonly {
    font-size: 0.66rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 600;
}

.ft-empty {
    padding: 14px 20px;
    color: var(--text-muted);
    font-size: 0.86rem;
    font-style: italic;
    margin: 0;
}

.ft-tree {
    padding: 6px 8px 10px;
}

/* A row is either a folder summary or a file line. Same vertical
   rhythm so hover state and indentation lines feel uniform. */
.ft-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 8px;
    min-height: 28px;
    border-radius: 6px;
    cursor: pointer;
    font-size: 0.86rem;
    line-height: 1.3;
    user-select: none;
    transition: background-color 0.12s;
}
.ft-row:hover {
    background: var(--accent-bg, rgba(0, 0, 0, 0.04));
}

.ft-folder > summary {
    list-style: none;
}
.ft-folder > summary::-webkit-details-marker { display: none; }

/* Chevron rotates 90deg when the parent <details> is open. */
.ft-chevron {
    display: inline-block;
    width: 12px;
    color: var(--text-muted);
    font-size: 0.72rem;
    flex-shrink: 0;
    transition: transform 0.18s ease;
}
.ft-folder[open] > summary > .ft-chevron {
    transform: rotate(90deg);
}
.ft-chevron-blank {
    visibility: hidden;
}

.ft-icon {
    flex-shrink: 0;
    font-size: 0.92rem;
}

.ft-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text);
}
.ft-file-row .ft-label {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.82rem;
}

.ft-meta {
    flex-shrink: 0;
    font-size: 0.74rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.ft-actions {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
    opacity: 0;
    transition: opacity 0.12s;
}
.ft-row:hover .ft-actions {
    opacity: 1;
}
.ft-actions form { margin: 0; }

.ft-action {
    display: inline-block;
    padding: 3px 8px;
    border-radius: 6px;
    font-size: 0.72rem;
    font-weight: 600;
    text-decoration: none;
    color: var(--primary);
    background: transparent;
    border: 1px solid transparent;
    cursor: pointer;
    font-family: inherit;
}
.ft-action:hover {
    background: var(--primary-bg);
    border-color: var(--primary);
    text-decoration: none;
}
.ft-action-danger {
    color: #b91c1c;
}
.ft-action-danger:hover {
    background: rgba(220, 38, 38, 0.08);
    border-color: #b91c1c;
}

/* Children sit slightly inset under the parent summary for visual
   hierarchy. Padding is computed per-node in the template so we
   don't need a CSS variable per depth. */
.ft-children {
    border-left: 1px dashed var(--border);
    margin-left: 14px;
    padding-left: 4px;
}

/* ═══════════════════════════════════════════════════════════════════
   Drawer pattern
   ───────────────────────────────────────────────────────────────────
   Right- (or left-) side slide-in panel with dimmed overlay.
   Shared chrome for Balance + Rota drawers on /admin/builder; ready
   for reuse anywhere a side panel is the right shape.

   HTML/Jinja: see `app/templates/admin/_drawer.html` (macro).
   JS hook  : `openDrawer(id)` / `closeDrawer(id)` in builder.js.
   ═══════════════════════════════════════════════════════════════════ */
.drawer-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(15,23,42,0.32);
    z-index: 1000;
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
}
.drawer {
    display: none;
    position: fixed;
    top: 0;
    height: 100vh;
    width: min(var(--drawer-width, 520px), 92vw);
    background: var(--bg-card);
    z-index: 1001;
    overflow-y: auto;
    padding: 24px 22px 32px;
}
.drawer-right {
    right: 0;
    border-left: 1px solid var(--border);
    box-shadow: -12px 0 32px rgba(0,0,0,0.18);
}
.drawer-left {
    left: 0;
    border-right: 1px solid var(--border);
    box-shadow: 12px 0 32px rgba(0,0,0,0.18);
}
.drawer-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    margin-bottom: 18px;
}
.drawer-title {
    margin: 0 0 4px;
    font-size: 1.15rem;
}
.drawer-subtitle {
    margin: 0;
    font-size: 0.82rem;
}
.drawer-close {
    padding: 4px 10px;
}

/* ═══════════════════════════════════════════════════════════════════
   Builder page
   ───────────────────────────────────────────────────────────────────
   Moved from the inline <style> in app/templates/admin/builder.html
   (May 2026 cleanup, audit ref docs/design-system/audits/builder.md
   action 5.4). Hardcoded colours promoted to CSS variables so dark
   mode adapts automatically.
   ═══════════════════════════════════════════════════════════════════ */
.builder-layout { display: grid; grid-template-columns: 320px 1fr; gap: 24px; min-height: 70vh; }
.topic-bank { background: var(--bg); border: 1px solid var(--border); border-radius: 12px; padding: 16px; position: sticky; top: 80px; }
.topic-list-scroll { overflow-y: auto; max-height: calc(7 * 62px); }
.suggestions-inbox { background: var(--bg); border: 1px solid var(--border); border-radius: 12px; padding: 16px; margin-bottom: 12px; }
.suggestions-scroll { overflow-y: auto; max-height: calc(3 * 52px); }
.suggestion-item { display:flex; justify-content:space-between; align-items:center; padding:8px 10px; border:1px solid var(--border); border-radius:8px; margin-bottom:6px; font-size:0.85rem; background:var(--bg-card); }
.suggestion-item .suggestion-info { flex:1; min-width:0; }
.suggestion-item .suggestion-title { font-weight:500; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.suggestion-item .suggestion-meta { font-size:0.75rem; color:var(--text-muted); }
.suggestion-item .suggestion-notes { font-size:0.72rem; color:var(--text-muted); font-style:italic; margin-top:2px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:160px; }
.suggestion-actions { display:flex; gap:4px; flex-shrink:0; margin-left:8px; }
.topic-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; margin-bottom: 6px; cursor: grab; transition: all 0.2s; font-size: 0.85rem; }
.topic-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.15); transform: translateY(-1px); }
.topic-card.done { border-left: 3px solid var(--accent); cursor: default; color: var(--text-muted); }
.topic-card .badge { font-size: 0.6rem; padding: 1px 5px; border-radius: 4px; background: var(--accent); color: white; }
.slot-grid { display: grid; gap: 10px; }
.session-slot { background: var(--bg-card); border: 2px dashed var(--input-border); border-radius: 10px; padding: 14px; min-height: 60px; transition: all 0.15s; }
.session-slot.has-topic { border-style: solid; border-color: var(--accent); background: var(--done-bg); }
.session-slot.is-past { color: var(--text-muted); border-color: var(--border); }
/* Done = immutable from the Builder (policy 14 May 2026 PM). Status-
   based, not date-based, so it can fire on a future session the
   coordinator deliberately closed early. The cursor + filter signal
   "this card is archival"; the action cluster is dropped in markup,
   not just hidden, so there's no half-disabled-button awkwardness. */
.session-slot.is-done { color: var(--text-muted); border-color: var(--border); border-style: solid; background: var(--bg); cursor: not-allowed; }
.session-slot.is-done .slot-topic-title { color: var(--text); }
.slot-locked-pill { display: inline-flex; align-items: center; gap: 3px; font-size: 0.65rem; font-weight: 700; color: var(--text-muted); background: var(--bg); border: 1px solid var(--border); border-radius: 4px; padding: 1px 6px; letter-spacing: 0.02em; text-transform: uppercase; }
/* Clickable variant (malleable-programme slice 2 — opens unlock
   modal). When rendered as a <button>, reset native button chrome
   so it visually matches the original <span>, plus add a hover
   affordance so it doesn't read as "decorative pill". */
button.slot-locked-pill, .slot-locked-pill-clickable { cursor: pointer; font-family: inherit; }
.slot-locked-pill-clickable:hover { background: var(--bg-card-hover); border-color: var(--primary); color: var(--primary); }
.slot-locked-pill-clickable:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
/* Clickable date span (malleable-programme slice 2 — opens move
   modal). Subtle underline-on-hover affordance per
   docs/design-system/rules.md R17 (click feedback <100ms). The
   default state stays visually identical to the static span so
   we don't churn the rest of the Builder. */
.slot-date-clickable { cursor: pointer; }
.slot-date-clickable:hover { text-decoration: underline; text-decoration-color: var(--primary); text-underline-offset: 3px; }
.slot-date-clickable:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 2px; }
.slot-detail-link { display: inline-flex; align-items: center; gap: 4px; margin-top: 4px; font-size: 0.72rem; color: var(--primary); text-decoration: none; align-self: flex-start; }
.slot-detail-link:hover { text-decoration: underline; }
.session-slot.slot-cancelled { border-color: var(--danger); border-style: solid; background: var(--danger-bg); }
.session-slot.slot-amber { border-color: var(--warn); }
.session-slot.drag-over { border-color: var(--primary); background: var(--primary-bg); box-shadow: 0 0 0 3px var(--primary-glow); }
.slot-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
.slot-date { font-weight: 700; color: var(--text); font-size: 0.9rem; }
.slot-actions { display: flex; gap: 4px; align-items: center; }
.slot-action-btn { background: none; border: none; cursor: pointer; padding: 3px; border-radius: 4px; display: flex; align-items: center; opacity: 0.5; transition: opacity 0.15s; }
.slot-action-btn:hover { opacity: 1; background: var(--bg-card-hover); }
.slot-action-btn-sm { background: none; border: none; cursor: pointer; padding: 2px; opacity: 0.4; }
.slot-action-btn-sm:hover { opacity: 1; }
.slot-topic-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.slot-topic-title { font-weight: 600; font-size: 0.88rem; }
.slot-select { width: auto; font-size: 0.8rem; padding: 5px 32px 5px 8px; }

/* Done-slot read-only display field — visually mirrors the upcoming
   slot's <select> dimensions so locked sessions and upcoming
   sessions read as the same component in two states (rather than
   two different layouts). Same border + font + padding as
   .slot-select but with no chevron, no hover ring, default cursor.
   The lock pill in the header + absence of edit affordances signal
   immutability; the visual mirror keeps the eye anchored. */
.slot-select.slot-readonly {
    padding: 5px 10px 5px 8px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text);
    cursor: default;
    display: inline-flex;
    align-items: center;
    min-height: 26px;
    line-height: 1;
}

/* Conflict badge (ui-design.md §2.6) — surfaces when another
   session in the same academic year shares this date. Visual
   register: scaled-down sibling of the rota-clash banner —
   tonal red, small red dot + brief caption — sits inline next
   to the date span so it's the first thing read after the
   date but doesn't compete with the modal-trigger affordance.
   Wired up May 2026 to close the service/UI gap (slice 7 set
   the contract; the Builder template wasn't reading it). */
.slot-conflict-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-left: 6px;
    font-size: 0.7rem;
    font-weight: 600;
    color: #dc2626;
    background: #fef2f2;
    border: 1px solid rgba(220, 38, 38, 0.2);
    padding: 1px 6px;
    border-radius: 4px;
    vertical-align: 2px;
    cursor: help;
}
.slot-conflict-badge-dot {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #dc2626;
}

/* "Online Only" read-only pill for done sessions — same visual
   register as the upcoming Online-Only checkbox-label but stripped
   of the interactive affordance. Sits inline with the two readonly
   boxes so the row composition matches the upcoming layout. */
.slot-online-only-pill {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 0.7rem;
    color: var(--text-muted);
    padding: 2px 6px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--bg);
}
.slot-content { font-size: 0.88rem; }
.break-banner { background: linear-gradient(135deg, var(--break-summer-start), var(--break-summer-end)); color: white; text-align: center; padding: 12px; border-radius: 10px; font-weight: 700; }
.break-banner.break-xmas { background: linear-gradient(135deg, var(--break-xmas-start), var(--break-xmas-end)); }
.insert-slot-btn { display: flex; align-items: center; justify-content: center; gap: 4px; padding: 6px; border: 2px dashed var(--input-border); border-radius: 8px; background: transparent; cursor: pointer; color: var(--text-muted); font-size: 0.8rem; width: 100%; transition: all 0.15s; }
.insert-slot-btn:hover { border-color: var(--primary); color: var(--primary); background: var(--primary-bg); }
.undo-toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); background: var(--bg-card); color: var(--text); border: 1px solid var(--border); padding: 10px 20px; border-radius: 8px; font-size: 0.85rem; display: none; z-index: 100; box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
.undo-toast a { color: var(--primary); cursor: pointer; text-decoration: underline; margin-left: 8px; }

/* Header indicator: amber count of unpublished changes. */
.builder-unpublished-count { font-size: 0.8rem; color: var(--warn); font-weight: 600; }

/* Per-object draft marker — 3px amber left-border reusing the existing
   --warn token. Coordinator-approved D7 decision (2026-07-15): pure
   visual signal, no text, no icon, no new palette entry. Applied to
   session cards (step 05-01), break banners + modal rows (step 05-02).

   Specificity note (regression fix 2026-07-18, staging dogfood): rules
   like `.session-slot.has-topic { border-color: var(--accent) }` at
   line 3611 are specificity 0,2,0 and would beat a bare `.is-draft`
   (0,1,0) on the shared `border-left-color` property — leaving the
   card's original border colour and no visible amber. Explicit
   two-class selectors match that specificity; because these rules
   come LATER in the file, tie-break goes to `.is-draft`. */
.session-slot.is-draft,
.break-banner.is-draft,
.break-modal-row.is-draft { border-left: 3px solid var(--warn); }

/* Header buttons that retain a distinct hue beyond btn-primary/outline:
   - AI Rota: purple/violet (beta indicator).
   - Clear All: saturated red (extra-destructive). */
.btn-ai-rota { border-color: var(--purple); color: var(--purple); }
.btn-clear-all { border-color: var(--danger-strong); color: var(--danger-strong); }

/* Rota drawer's two-input upload grid — XLSX + Google Docs side by
   side, stacks under 600px. */
.rota-upload-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 600px) {
    .rota-upload-grid { grid-template-columns: 1fr; }
}

/* Cancel-session modal — centred overlay; only one on this page so
   the styling lives with the Builder section for now. Will lift to a
   shared .modal pattern once a second modal earns the slot.

   Fade-in (audit 5.7-rest): the modal was a hard display:block snap;
   now display:flex is permanent + opacity transitions from 0 to 1
   over 150ms. `visibility: hidden` ensures the dimming layer doesn't
   intercept clicks while closed. The `is-open` class is toggled by
   showCancelModal/closeCancelModal in builder.js — no markup change
   needed at the call sites. */
.builder-modal-overlay {
    display: flex;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.4);
    z-index: 200;
    align-items: center;
    justify-content: center;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.15s ease-out, visibility 0s linear 0.15s;
}
.builder-modal-overlay.is-open {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.15s ease-out, visibility 0s linear 0s;
}
.builder-modal { background: var(--bg-card); border-radius: 12px; padding: 24px; max-width: 400px; width: 90%; box-shadow: 0 8px 32px rgba(0,0,0,0.2); }
.builder-modal-subtitle { font-size: 0.85rem; color: var(--text-muted); margin-bottom: 12px; }
.cancel-reason-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
.cancel-reason-label { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 6px; cursor: pointer; font-size: 0.85rem; border: 1px solid var(--border); transition: background 0.1s; }
.cancel-reason-label:hover { background: var(--bg); }
.cancel-reason-label input[type="radio"] { accent-color: var(--danger-strong); }
.cancel-modal-actions { display: flex; gap: 8px; justify-content: flex-end; }
.btn-danger-strong { background: var(--danger-strong); color: white; border: none; }

/* Move / Unlock modal chrome (step 03-03). Reuses the
   .builder-modal-overlay + .builder-modal classes from the cancel
   modal above. The `hidden` attribute is the open/closed signal
   from JS; the .is-open class drives the opacity fade. We make the
   hidden attribute work cleanly with the permanent display:flex
   overlay by hiding via `display: none` when hidden. */
.builder-modal-overlay[hidden] { display: none; }
.builder-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}
.builder-modal-header .u-mb-0 { margin: 0; }
.builder-modal-close {
    background: none;
    border: none;
    font-size: 1.4rem;
    line-height: 1;
    color: var(--text-muted);
    cursor: pointer;
    padding: 4px 8px;
    border-radius: 6px;
}
.builder-modal-close:hover { background: var(--bg); color: var(--text); }
.builder-modal-field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 12px;
    font-size: 0.85rem;
}
.builder-modal-field > span {
    font-weight: 600;
    color: var(--text);
}
.builder-modal-field input,
.builder-modal-field textarea {
    padding: 6px 10px;
    font-size: 0.85rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--bg);
    color: var(--text);
    font-family: inherit;
}
.builder-modal-footer {
    display: flex;
    gap: 8px;
    justify-content: flex-end;
    margin-top: 16px;
}
/* Inline error surface inside a builder modal — populated by
   the JS submit handler with the plain-text response body when
   the route returns 4xx/5xx (ADR-007). Hidden by default;
   shown via removing the [hidden] attribute. Tinted danger so
   it reads as "this submission failed" without yelling.
   Bug fix 2026-05-19. */
.builder-modal-error {
    margin-top: 12px;
    padding: 8px 10px;
    background: rgba(220,38,38,0.08);
    border: 1px solid rgba(220,38,38,0.30);
    border-radius: 6px;
    color: #991b1b;
    font-size: 0.82rem;
    line-height: 1.35;
}
body.builder-modal-open { overflow: hidden; }

/* Rota drawer body — uploads grid, AI toggle row, loading spinner,
   result-meta strip — colours all routed through tokens. */
.rota-ai-toggle { display: flex; align-items: center; gap: 10px; padding: 10px 14px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; margin-bottom: 14px; flex-wrap: wrap; }
.rota-ai-toggle-label { display: inline-flex; align-items: center; gap: 6px; font-size: 0.82rem; cursor: pointer; }
.rota-ai-toggle-hint { font-size: 0.75rem; color: var(--text-muted); }
.rota-loading { padding: 12px; text-align: center; color: var(--text-muted); font-size: 0.85rem; }
.rota-loading-spinner { animation: spin 1s linear infinite; margin-right: 6px; vertical-align: -2px; }
.rota-result-meta { display: flex; align-items: center; gap: 6px; margin-bottom: 10px; font-size: 0.78rem; color: var(--text-muted); flex-wrap: wrap; }
.rota-parser-badge { display: inline-flex; align-items: center; gap: 3px; padding: 1px 7px; border-radius: 10px; font-size: 0.7rem; font-weight: 700; }
.rota-parser-badge-ai { background: var(--primary-bg); color: var(--primary); }
.rota-parser-badge-legacy { background: var(--bg); color: var(--text-muted); border: 1px solid var(--border); }

/* Topic-proposals shortcut pill in the topic-bank header — amber
   when there are pending submissions, neutral otherwise. */
.topic-proposals-link { font-size: 0.78rem; font-weight: 700; text-decoration: none; display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 14px; color: var(--indigo); border: 1px solid transparent; }
.topic-proposals-link.has-pending { background: var(--warn-bg); color: var(--warn); border-color: var(--warn-border); }
.topic-proposals-badge { background: var(--danger-strong); color: white; font-size: 0.65rem; font-weight: 700; padding: 1px 7px; border-radius: 10px; margin-left: 2px; }

/* ─── Header action restraint (audit 5.1 + Commit 4) ──────────────
   Commit 2 (Nov→May 2026) collapsed eight identical-weight buttons
   into a validation cluster + Publish + overflow. Commit 4 (May
   2026) tightened further: header carries ONLY the change→save→
   publish narrative steps (Save + Publish), the rest moves into the
   overflow. The page now reads left-to-right as a workflow band:
   "Save" (auto-save confirmation), "Publish" (commit to members),
   "…" (everything else). Compact gap, no decorative seam — two
   buttons stand on their own (Gestalt: proximity is for grouping
   3+ items; for pairs, just put them next to each other). */
.builder-header-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* Save button — always visible, always clickable, muted at rest
   (it's a confirmation affordance, not the next click). The .btn
   .btn-outline base supplies the chrome; we override colour to muted
   so Save fades into the background of the squint test and Publish
   wins the eye. The flashing state takes over with a green
   checkmark — higher specificity than the resting rule. */
.builder-save-btn {
    color: var(--text-muted);
    border-color: var(--input-border);
}
.builder-save-btn:hover {
    color: var(--text);
    border-color: var(--border);
}
.builder-save-btn.is-flashing {
    color: var(--accent);
    border-color: var(--accent);
}

/* Primary Publish button. Two states:
   - .has-pending  (unpublished_count > 0): glow + lift on hover.
   - .is-disabled  (unpublished_count == 0): same shape so the
     workflow doesn't visually rearrange, but opacity 0.5 and
     not-allowed cursor signal that the action is unavailable. The
     button stays in the tab order with aria-disabled so screen
     readers announce its state; the inline `type="button"` keeps it
     out of any accidental form submits. We keep the .btn-primary
     background tinted (rather than going neutral) so the workflow
     band still shows "Publish lives here" at a glance. */
.builder-publish-btn.has-pending { box-shadow: 0 0 0 4px var(--primary-glow); }
.builder-publish-btn.has-pending:hover { transform: translateY(-1px); box-shadow: 0 0 0 4px var(--primary-glow), 0 4px 12px rgba(79,70,229,0.25); }
.builder-publish-btn.is-disabled {
    opacity: 0.5;
    cursor: not-allowed;
    box-shadow: none;
    pointer-events: none; /* swallow clicks completely; aria-disabled handles AT */
}
.builder-publish-btn.is-disabled:hover { transform: none; box-shadow: none; }

/* The .builder-editor-mode "Editor mode — changes auto-save as
   drafts…" prose band lived briefly here (Commit 4) and was
   removed 14 May 2026 as scope-creep — explanatory prose belongs
   in tooltips, not in ambient UI chrome. See principles.md →
   "Tooltips beat prose" and rules.md R23. */

/* ─── Overflow action menu (audit 5.1, patterns.md) ──────────────
   Native <details>/<summary> for free keyboard accessibility +
   zero-JS open/close. The default disclosure triangle is hidden;
   our own three-dot icon lives inside the <summary>. The menu
   itself is absolutely positioned beneath the summary, with a
   small shadow + border so it reads as a popover. Closes on
   outside-click (handler in builder.js) and on Esc (delegated to
   the same body keydown handler that closes drawers). */
.builder-overflow { position: relative; display: inline-block; }
.builder-overflow > summary { list-style: none; cursor: pointer; padding: 8px 10px; }
.builder-overflow > summary::-webkit-details-marker { display: none; }
.builder-overflow > summary::marker { content: ''; }
.builder-overflow-toggle { /* Reuses .btn + .btn-outline; overrides width to be square-ish for an icon-only button */ min-width: 0; }
.builder-overflow[open] > summary { background: var(--primary-bg, #eff6ff); border-color: var(--primary); color: var(--primary); }
.builder-overflow-menu {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    min-width: 220px;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.18);
    padding: 6px;
    z-index: 50;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.builder-overflow-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 12px;
    border: none;
    background: transparent;
    color: var(--text);
    font-size: 0.85rem;
    font-weight: 500;
    text-align: left;
    text-decoration: none;
    border-radius: 6px;
    cursor: pointer;
    font-family: inherit;
    transition: background 0.1s;
}
.builder-overflow-item:hover, .builder-overflow-item:focus {
    background: var(--primary-bg, #eff6ff);
    color: var(--primary);
    outline: none;
}
.builder-overflow-item svg { flex-shrink: 0; opacity: 0.75; }
.builder-overflow-item:hover svg { opacity: 1; }
.builder-overflow-item.builder-overflow-ai-rota { color: var(--purple); }
.builder-overflow-item.builder-overflow-ai-rota:hover { background: var(--purple-soft); color: var(--purple); }
.builder-overflow-item.builder-overflow-clear-all { color: var(--danger-strong); }
.builder-overflow-item.builder-overflow-clear-all:hover { background: rgba(220,38,38,0.08); color: var(--danger-strong); }
.builder-overflow-tag {
    margin-left: auto;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    padding: 1px 6px;
    border-radius: 8px;
    background: var(--purple-soft);
    color: var(--purple);
}

/* Per-card kebab variant (feat-card-actions-kebab — 2nd use of the
   overflow pattern). Compact ~28px summary sits in the slot header
   next to the status pill; the menu reuses .builder-overflow-menu
   as-is (right-aligned, 220px, z-index 50 — .session-slot has no
   overflow clipping). Danger entries mirror the header's
   .builder-overflow-clear-all treatment; no new colour tokens. */
.slot-overflow > summary {
    padding: 6px;
    border-radius: 6px;
    border: 1px solid transparent;
    color: var(--text-muted);
    display: inline-flex;
    align-items: center;
    line-height: 0;
}
.slot-overflow > summary:hover { background: var(--bg); border-color: var(--border); color: var(--text); }
.slot-overflow[open] > summary { background: var(--primary-bg, #eff6ff); border-color: var(--primary); color: var(--primary); }
.slot-overflow-divider { height: 1px; background: var(--border); margin: 4px 6px; }
.builder-overflow-item.slot-overflow-danger { color: var(--danger-strong); }
.builder-overflow-item.slot-overflow-danger:hover,
.builder-overflow-item.slot-overflow-danger:focus { background: rgba(220,38,38,0.08); color: var(--danger-strong); }

/* Mobile (Commit 4): header carries the workflow pair (Save +
   Publish) and the overflow icon. Save's label is suppressed and
   the icon stands alone so the row fits two buttons + the …
   without wrapping. Publish keeps its label (it's the primary
   action and the count is informative). The unpublished-count pill
   wraps below the row when present — acceptable at this width. */
@media (max-width: 768px) {
    .builder-save-btn span,
    .builder-save-btn { /* keep the button; the label fades out */ }
    .builder-save-btn { font-size: 0; padding: 8px 10px; }
    .builder-save-btn svg { font-size: 14px; /* preserve icon size */ }
    .builder-save-btn.is-flashing { font-size: 0.78rem; padding: 8px 12px; } /* show the "Saved!" label during flash so the feedback is legible */
}

/* ─── Sticky publish bar (audit 5.6, patterns.md) ────────────────
   Fixed to the viewport bottom; rendered server-side only when
   unpublished_count > 0. Hidden by default (translated 100% down);
   toggled by builder.js's IntersectionObserver on the header
   Publish button. Esc dismisses (sets data-dismissed). z-index 50
   sits BELOW drawers (1000+) and modals (200) so it never covers
   in-flight tasks. */
.builder-sticky-publish {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    background: var(--bg-card);
    border-top: 1px solid var(--border);
    box-shadow: 0 -4px 16px rgba(0,0,0,0.08);
    z-index: 50;
    transform: translateY(100%);
    transition: transform 0.25s ease-out;
    pointer-events: none;
}
.builder-sticky-publish.is-visible {
    transform: translateY(0);
    pointer-events: auto;
}
.builder-sticky-publish-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    max-width: 1200px;
    margin: 0 auto;
    padding: 14px 24px;
}
.builder-sticky-publish-summary {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    font-size: 0.9rem;
    color: var(--text);
}
.builder-sticky-publish-summary strong { color: var(--warn); }
.builder-sticky-publish-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--warn);
    box-shadow: 0 0 0 4px var(--warn-bg, rgba(245,158,11,0.18));
}
.builder-sticky-publish-btn { padding: 10px 20px; box-shadow: 0 0 0 4px var(--primary-glow); }
@media (max-width: 600px) {
    .builder-sticky-publish-inner { padding: 12px 14px; gap: 8px; }
    .builder-sticky-publish-summary { font-size: 0.82rem; }
}

/* ─── Empty states (audit 5.5, R10) ─────────────────────────────────
   Three regions on the Builder may be empty: topic bank, suggestions
   inbox, slot grid. All three honour the same shape — icon + headline
   + next-action — but in two sizes.

   .builder-empty-inline — slots inside an existing list region (topic
       bank, suggestions inbox). Compact, vertically centred in
       whatever max-height the parent caps to.
   .builder-empty-large — stands in for a whole missing grid (the
       fresh-academic-year case). Large CTA button included.

   Text uses var(--text-muted) so dark mode is automatic. SVG inherits
   currentColor, also muted. The inline pattern uses padding to fill
   ~70% of the parent's max-height when the parent is the
   .topic-list-scroll (max-height calc(7*62px) ≈ 434px). */
.builder-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    color: var(--text-muted);
    gap: 8px;
}
.builder-empty svg { opacity: 0.6; }
.builder-empty-headline { font-weight: 600; font-size: 0.92rem; color: var(--text); }
.builder-empty-body { font-size: 0.82rem; line-height: 1.45; max-width: 32ch; }
.builder-empty-body em { color: var(--primary); font-style: normal; font-weight: 500; }
.builder-empty-inline { padding: 32px 12px; }
.builder-empty-large { padding: 48px 24px; margin-bottom: 16px; }
.builder-empty-large svg { opacity: 0.55; margin-bottom: 4px; }
.builder-empty-large .builder-empty-headline { font-size: 1.05rem; }
.builder-empty-large .builder-empty-body { font-size: 0.9rem; max-width: 40ch; }
.builder-empty-action { margin-top: 8px; }

/* When the topic-list-scroll holds only the inline empty state, kill
   the scroll cap so the message centres naturally rather than getting
   pinned to the top with empty space below it. */
.topic-list-scroll.is-empty { max-height: none; overflow: visible; }

/* ─── Topic-bank goal-gradient (audit 5.9) ───────────────────────────
   A two-line caption + thin progress bar above the topic-list-scroll.
   Renders server-side, refreshes on next page load — no live updates
   during drag (would require a JS recompute hook; not worth the
   complexity for a quiet motivational signal). */
.topic-bank-progress-row { margin: 4px 0 10px; }
.topic-bank-progress-caption {
    display: block;
    font-size: 0.75rem;
    color: var(--text-muted);
    line-height: 1.4;
}
.topic-bank-progress-caption strong { color: var(--text); font-weight: 600; }
.topic-bank-progress-remaining { color: var(--text-muted); }
.topic-bank-progress {
    height: 3px;
    background: var(--border);
    border-radius: 2px;
    margin: 4px 0 0;
    overflow: hidden;
}
.topic-bank-progress-bar {
    height: 100%;
    background: var(--primary);
    border-radius: 2px;
    transition: width 0.3s ease-out;
}

/* ─── Drag-drop tactile polish (audit 5.7-rest) ─────────────────────
   `is-dragging` is added by builder.js on dragstart and removed on
   dragend. The `cursor: grabbing` is the 100ms-feedback signal that
   the card is actively moving. (R17 / Doherty.)

   `just-dropped` runs a 400ms flash + scale on the receiving slot.
   The same primary-glow + colour palette as the .drag-over state
   means the user reads "the action you intended landed here" — no
   new visual vocabulary needed. */
.topic-card.is-dragging,
.suggestion-item.is-dragging,
.session-slot.is-dragging { cursor: grabbing; }

@keyframes builder-drop-into-slot {
    0% {
        background: var(--primary-bg);
        box-shadow: 0 0 0 3px var(--primary-glow);
        transform: scale(1.02);
    }
    50% {
        background: var(--primary-bg);
        box-shadow: 0 0 0 3px var(--primary-glow);
        transform: scale(1.02);
    }
    100% {
        background: transparent;
        box-shadow: none;
        transform: scale(1);
    }
}
.session-slot.just-dropped { animation: builder-drop-into-slot 0.4s ease-out; }

/* Respect prefers-reduced-motion — the animation is decorative; the
   colour/border change from `.has-topic` is the load-bearing signal
   that the drop succeeded. */
@media (prefers-reduced-motion: reduce) {
    .session-slot.just-dropped { animation: none; }
}

/* ─── Celebration toast (audit 5.8 — Peak-end on Publish) ───────────
   Centre-top, fixed, NOT bottom (would conflict with the sticky
   Publish bar even though we hide it before showing this).
   Background uses `--accent` (green/success) plus a small drop
   shadow to lift it visually — the moment IS a small celebration.

   The toast is injected by JS just before the reload, so it carries
   no server-side markup. role=status + aria-live=polite ensure
   screen-reader users hear "Published N changes" without being
   interrupted. */
.builder-celebration-toast {
    position: fixed;
    top: 96px;
    left: 50%;
    transform: translateX(-50%) translateY(-12px);
    background: var(--bg-card);
    color: var(--text);
    border: 1px solid var(--accent);
    border-radius: 999px;
    padding: 12px 22px;
    display: inline-flex;
    align-items: center;
    gap: 10px;
    font-size: 0.98rem;
    box-shadow: 0 8px 28px rgba(16,185,129,0.18), 0 0 0 4px rgba(16,185,129,0.08);
    z-index: 300;
    opacity: 0;
    transition: opacity 0.18s ease-out, transform 0.18s ease-out;
    pointer-events: none;
}
.builder-celebration-toast.is-visible {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}
.builder-celebration-toast svg { color: var(--accent); flex-shrink: 0; }
.builder-celebration-toast strong { color: var(--accent); }
@media (max-width: 600px) {
    .builder-celebration-toast { font-size: 0.88rem; padding: 10px 16px; top: 72px; }
}

/* ─────────────────────────────────────────────────────────────────────
   Custom audit packs (Dispatch C, docs/design/custom-compliance-packs.md)

   Two distinct surfaces:
     1. .cp-grid + .cp-pack-tile{,--new}     — pack tiles on /admin/governance
     2. .cp-wizard{,-overlay,-step,...}      — the 3-step centre modal

   Both surfaces use existing design tokens — no new --colour variables.
   Dark mode is automatic (tokens flip in the prefers-color-scheme dark
   block at the top of this file). Match the .gv-tile / .gv-section-head
   chrome so the two pack rows feel like one component family (R20).
   ───────────────────────────────────────────────────────────────────── */

.cp-grid {
    display: grid;
    gap: 12px;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    margin-bottom: 8px;
}

.cp-pack-tile {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-height: 160px;
    transition: border-color 0.15s, box-shadow 0.15s;
    text-decoration: none;
    color: inherit;
}
.cp-pack-tile:hover {
    border-color: var(--primary);
    box-shadow: 0 2px 8px rgba(79, 70, 229, 0.06);
    text-decoration: none;
}

.cp-pack-tile-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 8px;
}
.cp-pack-tile-title {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text);
    line-height: 1.3;
    flex: 1;
    min-width: 0;
    word-break: break-word;
}

/* Overflow menu — native <details>. Summary is the ⋯ trigger; body is
   absolutely positioned so it doesn't push the tile chrome around when
   open. */
.cp-pack-tile-menu {
    position: relative;
    flex-shrink: 0;
}
.cp-pack-tile-menu > summary {
    list-style: none;
    cursor: pointer;
    padding: 2px 8px;
    border-radius: 6px;
    color: var(--text-muted);
    font-size: 1.05rem;
    line-height: 1;
    user-select: none;
    transition: background 0.1s;
}
.cp-pack-tile-menu > summary::-webkit-details-marker { display: none; }
.cp-pack-tile-menu > summary:hover { background: var(--bg-card-hover); color: var(--text); }
.cp-pack-tile-menu[open] > summary { background: var(--bg-card-hover); }
.cp-pack-tile-menu-body {
    position: absolute;
    right: 0;
    top: calc(100% + 2px);
    z-index: 50;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
    min-width: 140px;
    padding: 4px;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.cp-pack-tile-menu-body button,
.cp-pack-tile-menu-body form button {
    display: block;
    width: 100%;
    text-align: left;
    padding: 6px 10px;
    border: none;
    background: transparent;
    color: var(--text);
    font-size: 0.85rem;
    font-family: inherit;
    cursor: pointer;
    border-radius: 4px;
    transition: background 0.1s, color 0.1s;
}
.cp-pack-tile-menu-body button:hover { background: var(--primary-bg); color: var(--primary); }
.cp-pack-tile-menu-danger { color: #b91c1c !important; }
.cp-pack-tile-menu-danger:hover { background: rgba(220, 38, 38, 0.08) !important; color: #b91c1c !important; }

.cp-pack-tile-desc {
    margin: 0;
    font-size: 0.82rem;
    color: var(--text-muted);
    line-height: 1.4;
}
.cp-pack-tile-meta {
    margin: 0;
    font-size: 0.78rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.cp-pack-tile-lastrun {
    margin: 0;
    font-size: 0.75rem;
    color: var(--text-light);
    cursor: help;
}

.cp-pack-tile-download {
    margin: 0;
    margin-top: auto;
    display: flex;
    gap: 8px;
    align-items: center;
    flex-wrap: wrap;
}
/* The per-tile <select name="year"> was removed in the 15 May 2026
   audit (action 4.2 — single page-level year selector). The tile now
   carries a <input type="hidden" name="year">; no styles needed for
   that. .cp-pack-tile-year{,-label} selectors deleted. */

/* The dashed "create slot" tile — last child of .cp-grid. Mirrors the
   design-system "Empty state with action" pattern (patterns.md) so the
   discovery affordance is obvious whether the grid is empty or not. */
.cp-pack-tile--new {
    border-style: dashed;
    border-color: var(--border);
    background: transparent;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
    cursor: pointer;
    min-height: 160px;
    gap: 10px;
}
.cp-pack-tile--new:hover {
    border-color: var(--primary);
    color: var(--primary);
    background: var(--primary-bg);
}
.cp-pack-tile--new svg {
    opacity: 0.85;
}
.cp-pack-tile-cta {
    font-size: 0.9rem;
    font-weight: 600;
}

/* .cp-empty-caption — deleted 15 May 2026 (audit action 4.1 + 4.3).
   The orphan "Create your first audit pack..." caption was both R23
   prose AND R10 stale (rendered even when packs existed). The
   "+ New audit pack" tile in the unified grid is now the only
   discovery affordance. */

/* ── Sprint 1 (Audit Packs evolution) ─────────────────────────────────
   - Top-left icon swatch on every pack tile (replaces the old fixed-
     tile coloured square — same visual position, same 34×34 footprint).
   - Small lock overlay for system packs (is_system=True) so coordinators
     spot at a glance which tiles are locked.
   - Icon picker strip in the wizard's step 1 — a horizontal scroll of
     ~16 swatches, each a clickable radio. Selected swatch shows a
     primary-coloured border + checkmark badge (Von Restorff: one of
     them is unmistakably "the one"). */
.cp-pack-tile-icon {
    position: relative;
    width: 34px;
    height: 34px;
    border-radius: 8px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    background: var(--primary-bg);
    color: var(--primary);
}
.cp-pack-tile--system .cp-pack-tile-icon {
    background: rgba(245, 158, 11, 0.12);
    color: #b45309;
}
.cp-pack-tile-lock {
    position: absolute;
    right: -4px;
    bottom: -4px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--bg-card);
    border: 1px solid var(--border);
    color: var(--text-muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.cp-pack-tile--system {
    /* No visual lock per row — the icon-lock badge is enough; tile
       chrome stays uniform so the grid reads as one component family. */
}

/* Icon picker — step 1 of the wizard. Native radios hidden; styled
   swatches show the icon glyph + a checkmark badge when checked. */
.cp-wizard-icon-picker {
    border: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.cp-wizard-icon-picker legend {
    font-weight: 600;
    color: var(--text);
    font-size: 0.82rem;
    padding: 0;
    margin: 0;
}
/* Visually-hide content for sighted users while keeping it available
   to keyboards + screen readers. Used by icon-picker swatch labels
   (the picker is tooltip-driven for sighted users — the label text
   stays accessible to AT). Was missing globally; this is the
   canonical SR-only helper. */
.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;
}

.cp-icon-strip {
    /* Grid layout so 16 swatches wrap onto 2 rows of ~8 — much
       easier to scan than a horizontal scroll strip, and the wizard
       modal isn't a horizontal-scroll affordance anywhere else. */
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
    gap: 8px;
    padding: 4px 2px;
    max-width: 100%;
}
.cp-icon-swatch {
    position: relative;
    /* width/flex dropped — grid template handles sizing now */
    aspect-ratio: 1 / 1;
    min-height: 40px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg-card);
    color: var(--text-muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    overflow: hidden;  /* belt-and-braces — defensive against any future label leak */
    transition: border-color 0.12s, color 0.12s, background 0.12s,
                box-shadow 0.12s;
}
.cp-icon-swatch:hover {
    border-color: var(--primary);
    color: var(--primary);
}
.cp-icon-swatch input[type="radio"] {
    /* Visually hidden but kept accessible to keyboards + screen readers. */
    position: absolute;
    opacity: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    cursor: pointer;
}
.cp-icon-swatch input[type="radio"]:focus-visible + .cp-icon-swatch-glyph {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
    border-radius: 6px;
}
.cp-icon-swatch:has(input[type="radio"]:checked) {
    border-color: var(--primary);
    color: var(--primary);
    background: var(--primary-bg);
    box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.12);
}
.cp-icon-swatch-glyph {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}
.cp-icon-swatch-check {
    position: absolute;
    top: -4px;
    right: -4px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--primary);
    color: #fff;
    display: none;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}
.cp-icon-swatch:has(input[type="radio"]:checked) .cp-icon-swatch-check {
    display: inline-flex;
}

/* ── Wizard centre-modal ──────────────────────────────────────────── */
.cp-wizard-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 800;
}
.cp-wizard {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(640px, 92vw);
    max-height: 90vh;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 14px;
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.22);
    z-index: 900;
    padding: 20px 22px;
    display: flex;
    flex-direction: column;
    gap: 14px;
    overflow: hidden;
}

.cp-wizard-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}
.cp-wizard-head h2 {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--text);
}
.cp-wizard-close {
    background: none;
    border: none;
    font-size: 1.4rem;
    line-height: 1;
    color: var(--text-muted);
    cursor: pointer;
    padding: 4px 8px;
    border-radius: 6px;
    transition: background 0.1s, color 0.1s;
}
.cp-wizard-close:hover { background: var(--bg-card-hover); color: var(--text); }

.cp-wizard-steps {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    gap: 6px;
    align-items: center;
    font-size: 0.72rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 600;
    border-bottom: 1px solid var(--border-light);
    padding-bottom: 10px;
}
.cp-wizard-steps li {
    padding: 4px 10px;
    border-radius: 12px;
    background: var(--bg);
    transition: background 0.1s, color 0.1s;
}
.cp-wizard-steps li.is-active {
    background: var(--primary-bg);
    color: var(--primary);
}
.cp-wizard-steps li.is-done {
    color: var(--text);
}

.cp-wizard-step {
    border: none;
    padding: 4px 2px;
    margin: 0;
    display: none;
    flex-direction: column;
    gap: 12px;
    overflow-y: auto;
    flex: 1;
    min-height: 0;
}
.cp-wizard-step.is-active { display: flex; }

.cp-wizard-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
    font-size: 0.82rem;
    color: var(--text);
}
.cp-wizard-field > span {
    font-weight: 600;
    color: var(--text);
}
.cp-required { color: #b91c1c; font-weight: 700; }
.cp-wizard-input {
    width: 100%;
    padding: 9px 11px;
    border: 1px solid var(--border);
    border-radius: 8px;
    font-size: 0.9rem;
    font-family: inherit;
    color: var(--text);
    background: var(--bg-card);
    transition: border-color 0.15s, box-shadow 0.15s;
}
.cp-wizard-input:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.12);
}
.cp-wizard-textarea {
    resize: vertical;
    min-height: 70px;
}

.cp-wizard-category {
    border-top: 1px solid var(--border-light);
    padding-top: 10px;
}
.cp-wizard-category:first-child {
    border-top: none;
    padding-top: 0;
}
.cp-wizard-category h3 {
    margin: 0 0 6px;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    font-weight: 700;
}
.cp-wizard-resource {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 8px;
    border-radius: 6px;
    cursor: pointer;
    font-size: 0.88rem;
    color: var(--text);
    transition: background 0.1s;
}
.cp-wizard-resource:hover { background: var(--bg-card-hover); }
.cp-wizard-resource input[type="checkbox"] { cursor: pointer; }
.cp-wizard-selected-count {
    margin: 6px 0 0;
    font-size: 0.78rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.cp-wizard-output-summary {
    margin: 0;
    font-size: 0.82rem;
    color: var(--text-muted);
}
.cp-wizard-output-option {
    display: flex;
    gap: 10px;
    padding: 12px;
    border: 1px solid var(--border);
    border-radius: 10px;
    cursor: pointer;
    transition: border-color 0.15s, background 0.1s;
}
.cp-wizard-output-option:hover { border-color: var(--primary); }
.cp-wizard-output-option strong { color: var(--text); font-size: 0.92rem; }
.cp-wizard-output-option p {
    margin: 4px 0 0;
    font-size: 0.78rem;
    color: var(--text-muted);
    line-height: 1.4;
}
.cp-wizard-output-option input[type="radio"] {
    margin-top: 3px;
    cursor: pointer;
}
.cp-wizard-output-disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.cp-wizard-output-disabled:hover { border-color: var(--border); }
.cp-wizard-output-disabled input[type="radio"] { cursor: not-allowed; }

.cp-wizard-step-error {
    margin: 6px 0 0;
    font-size: 0.8rem;
    color: #b91c1c;
    font-weight: 600;
}

.cp-wizard-foot {
    display: flex;
    align-items: center;
    gap: 10px;
    border-top: 1px solid var(--border-light);
    padding-top: 12px;
}
.cp-wizard-foot-spacer { flex: 1; }

@media (max-width: 640px) {
    .cp-wizard { padding: 16px; width: 96vw; max-height: 95vh; }
    .cp-wizard-steps { font-size: 0.66rem; gap: 4px; flex-wrap: wrap; }
    .cp-pack-tile-download { gap: 6px; }
}


/* ─────────────────────────────────────────────────────────────────────
   Compliance & Audit page (/admin/governance)

   Lifted out of the inline <style> block in app/templates/admin/
   governance.html on 15 May 2026 per the audit at docs/design-system/
   audits/compliance.md (action 4.4 — structural cleanup, R20). The
   template now carries zero inline CSS.

   Class taxonomy:
     .section-head / .section-controls
         Shared "section heading + controls row" pattern (audit
         action 4.5 — R20 promotion). Lifted from the page-specific
         `.gv-section-head` / `.gv-controls`. Future pages with the
         same shape (one h2 + a small filter strip on the right or
         below) can reuse these classes — see patterns.md
         "Section head + controls row".

     .gv-summary, .gv-status*, .gv-note-pill, .gv-row-*,
     .gv-snapshot-*, .gv-empty
         Governance-list-specific chrome. Stays page-scoped because
         these are tightly bound to the per-session RAG row layout —
         not reusable as-is on other surfaces.

   Hardcoded amber / green rgba(...) values replaced with new
   --audit-warn-* / --audit-good-* tokens defined at the top of this
   file (dark-mode variants in the prefers-color-scheme block). R21.
   ───────────────────────────────────────────────────────────────────── */

/* Section head + controls — shared pattern, two-use threshold (R20).
   `.section-head` is the heading container; pair with
   `.section-head--spaced` when the section is the 2nd / 3rd on a page
   and needs ~32px of breathing room above. `.section-controls` is
   the filter / action row that sits under (or beside) the heading. */
.section-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin: 24px 0 10px;
    padding: 0 2px;
}
.section-head:first-of-type { margin-top: 8px; }
.section-head--spaced { margin-top: 32px; }
.section-head h2 {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text);
}
.section-controls {
    display: flex;
    gap: 10px;
    align-items: center;
    flex-wrap: wrap;
}
.section-controls select {
    padding: 7px 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    font-size: 0.85rem;
    background: var(--bg-card);
    color: var(--text);
}
.section-controls-label {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
}

/* Compact summary strip — three counts on one line, low visual weight,
   so it doesn't overshadow the actual session list below. Replaces the
   earlier full-width tiled grid which dominated the page. */
.gv-summary {
    display: flex;
    gap: 14px;
    align-items: center;
    flex-wrap: wrap;
    padding: 10px 14px;
    margin-bottom: 16px;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 10px;
}
.gv-summary-item { display: inline-flex; align-items: baseline; gap: 6px; }
.gv-summary-num {
    font-size: 1rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--text);
}
.gv-summary-label {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
}
.gv-summary-item.is-complete .gv-summary-num { color: var(--audit-good-fg); }
.gv-summary-item.is-partial  .gv-summary-num { color: #854d0e; }
.gv-summary-item.is-missing  .gv-summary-num { color: #b91c1c; }
.gv-summary-divider { width: 1px; height: 18px; background: var(--border); }

/* Status badge — uses the same shape + colour family as the site's
   .status-badge (defined further up in style.css) but with
   governance-specific text colours that match the RAG bands. */
.gv-status {
    display: inline-block;
    padding: 2px 10px;
    border-radius: 12px;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
}
.gv-status-complete { background: rgba(16,185,129,0.15); color: var(--audit-good-fg); }
.gv-status-partial  { background: rgba(234,179,8,0.15); color: #854d0e; }
.gv-status-missing  { background: rgba(220,38,38,0.15); color: #b91c1c; }

/* Coordinator note pill — neutral grey, distinguishable from RAG.
   Used most often for the 'pre-platform' annotation but can hold any
   coordinator-set note. Tooltip on hover shows the full text. */
.gv-note-pill {
    display: inline-block;
    padding: 2px 9px;
    border-radius: 12px;
    font-size: 0.7rem;
    font-weight: 600;
    background: var(--bg);
    color: var(--text-muted);
    border: 1px solid var(--border);
    white-space: nowrap;
    cursor: help;
}

/* Per-row RAG left-border modifier on top of .prog-card. The hover
   tint + base chrome come from .prog-card itself — we only paint a
   coloured stripe on the left so the eye groups rows by status. */
.gv-row-complete { border-left: 3px solid rgba(16,185,129,0.6); }
.gv-row-partial  { border-left: 3px solid rgba(234,179,8,0.6); }
.gv-row-missing  { border-left: 3px solid rgba(220,38,38,0.6); }

/* Compact actions column on the right of each row. Uses .btn /
   .btn-sm / .btn-outline vocabulary so the buttons match the rest
   of the site. */
.gv-row-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    padding-left: 8px;
}
.gv-row-actions form { margin: 0; }
.gv-row-tier-select {
    padding: 5px 8px;
    border-radius: 6px;
    font-size: 0.72rem;
    font-weight: 600;
    background: var(--bg);
    color: var(--text-muted);
    border: 1px solid var(--border);
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s;
}
.gv-row-tier-select:hover { border-color: var(--primary); color: var(--text); }
.gv-row-reset {
    padding: 4px 10px;
    border-radius: 6px;
    font-size: 0.78rem;
    font-weight: 600;
    background: transparent;
    color: var(--text-muted);
    border: 1px solid var(--border);
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
    font-family: inherit;
}
.gv-row-reset:hover {
    background: rgba(220,38,38,0.06);
    border-color: rgba(220,38,38,0.4);
    color: #b91c1c;
}

.gv-empty {
    padding: 48px 24px;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.9rem;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 12px;
}

/* Programme snapshot card — upgraded from a plain text row to a
   small card matching the audit-pack tile visual register (audit
   action 4.6). Smaller than a pack tile: no description, no action
   cluster; just year + label pill + meta line + a hover-only delete
   button on the right (R8). */
.gv-snapshot-list { display: flex; flex-direction: column; gap: 8px; }
.gv-snapshot-card {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 10px;
    transition: border-color 0.15s, box-shadow 0.15s;
}
.gv-snapshot-card:hover {
    border-color: var(--primary);
    box-shadow: 0 2px 8px rgba(79,70,229,0.06);
}
.gv-snapshot-card-main {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}
.gv-snapshot-card-head {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}
.gv-snapshot-card-year {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.gv-snapshot-card-pill {
    display: inline-block;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--primary);
    background: var(--primary-bg);
    padding: 2px 9px;
    border-radius: 999px;
}
.gv-snapshot-card-meta {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    font-size: 0.82rem;
    color: var(--text-muted);
}
.gv-snapshot-card-dot { color: var(--text-light); }
.gv-snapshot-card-actions {
    margin: 0;
    flex-shrink: 0;
}
/* Delete is hover-only per R8 — the row reads calm at rest. Focus
   keeps it visible too so keyboard navigation is unaffected. */
.gv-snapshot-card-delete {
    opacity: 0;
    transition: opacity 0.15s;
}
.gv-snapshot-card:hover .gv-snapshot-card-delete,
.gv-snapshot-card-delete:focus,
.gv-snapshot-card-delete:focus-visible {
    opacity: 1;
}
/* Touch devices have no hover state — keep the action visible
   there so it remains reachable on tablets / phones. */
@media (hover: none) {
    .gv-snapshot-card-delete { opacity: 1; }
}

/* Hover-only row actions on per-session governance list (audit
   action 4.7, R8). 22 rows of always-visible chrome reads as noise
   when scanning; fade the action column in on hover. Focus-visible
   keeps keyboard access uncompromised. */
.gv-row-hover-actions .gv-row-actions {
    transition: opacity 0.15s;
}
@media (hover: hover) {
    .gv-row-hover-actions .gv-row-actions {
        opacity: 0.35;
    }
    .gv-row-hover-actions:hover .gv-row-actions,
    .gv-row-hover-actions:focus-within .gv-row-actions {
        opacity: 1;
    }
}

@media (max-width: 768px) {
    .gv-summary { padding: 8px 12px; gap: 10px; }
    .gv-summary-num { font-size: 0.92rem; }
    .gv-row-actions {
        flex-direction: column;
        align-items: stretch;
        gap: 4px;
        padding-left: 6px;
    }
    .gv-snapshot-row { gap: 8px; font-size: 0.82rem; }
}

/* ── Option 1 polish (15 May 2026) ─────────────────────────────────
   Compliance pulse hero · pre-platform toggle · collapsed snapshot
   one-liner. All colours via --audit-* tokens; no hardcoded rgba.
   ──────────────────────────────────────────────────────────────── */

/* Compliance-pulse hero: a single card-shaped block answering
   'am I OK?' in <200vh px. Two states (good / warn) carry the only
   visual difference; structural shape stays identical so the eye
   doesn't have to relearn the chrome on each visit. */
.gv-pulse {
    margin: 12px 0 16px;
    padding: 14px 18px;
    border-radius: 12px;
    border: 1px solid var(--border);
    background: var(--bg-card);
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.gv-pulse--good {
    background: var(--audit-good-bg);
    border-color: var(--audit-good-bg);
}
.gv-pulse--warn {
    background: var(--audit-warn-bg);
    border-color: var(--audit-warn-bg);
}
.gv-pulse-headline {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--text);
}
.gv-pulse--good .gv-pulse-headline { color: var(--audit-good-fg); }
.gv-pulse--warn .gv-pulse-headline { color: var(--audit-warn-fg); }
.gv-pulse-stats {
    font-size: 0.85rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.gv-pulse-jump {
    color: var(--audit-warn-fg);
    font-weight: 600;
    text-decoration: none;
}
.gv-pulse-jump:hover { text-decoration: underline; }

/* Collapsed snapshot one-liner — replaces the "Programme snapshots"
   peer section in the common single-Accredited-snapshot case.
   `Re-freeze` + `✕` live on the right, hover-revealed per R8. */
.gv-snapshot-line {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 8px 14px;
    margin: 0 0 16px;
    font-size: 0.85rem;
    color: var(--text-muted);
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg-card);
}
.gv-snapshot-line--empty { justify-content: flex-start; }
.gv-snapshot-line-text strong {
    color: var(--text);
    font-weight: 600;
}
.gv-snapshot-line-actions {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
}
.gv-snapshot-line-form { margin: 0; display: inline-flex; }
.gv-snapshot-line-refreeze {
    background: transparent;
    border: 1px solid transparent;
    color: var(--primary);
    font-size: 0.78rem;
    font-weight: 600;
    padding: 3px 8px;
    border-radius: 6px;
    cursor: pointer;
    font-family: inherit;
    transition: background 0.15s, border-color 0.15s;
    opacity: 0;
}
.gv-snapshot-line-refreeze:hover {
    background: var(--primary-bg);
    border-color: var(--primary);
}
.gv-snapshot-line-delete {
    background: transparent;
    border: 1px solid transparent;
    color: var(--text-light);
    font-size: 0.9rem;
    line-height: 1;
    padding: 3px 7px;
    border-radius: 6px;
    cursor: pointer;
    font-family: inherit;
    opacity: 0;
    transition: opacity 0.15s, color 0.15s, border-color 0.15s;
}
.gv-snapshot-line-delete:hover {
    color: #b91c1c;
    border-color: rgba(220,38,38,0.4);
}
.gv-snapshot-line:hover .gv-snapshot-line-refreeze,
.gv-snapshot-line:hover .gv-snapshot-line-delete,
.gv-snapshot-line-refreeze:focus-visible,
.gv-snapshot-line-delete:focus-visible {
    opacity: 1;
}
@media (hover: none) {
    .gv-snapshot-line-refreeze,
    .gv-snapshot-line-delete { opacity: 1; }
}

/* Pre-platform toggle bar. Lives between the .gv-summary stat strip
   and the per-session list. Link-style secondary button — the
   pre-platform reveal is a low-stakes opt-in, not a primary action. */
.gv-preplatform-bar {
    margin: -4px 0 12px;
    padding: 0 2px;
}
.gv-preplatform-toggle {
    background: transparent;
    border: none;
    padding: 4px 0;
    font-family: inherit;
    font-size: 0.82rem;
    color: var(--text-muted);
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: var(--border);
    text-underline-offset: 3px;
    transition: color 0.15s, text-decoration-color 0.15s;
}
.gv-preplatform-toggle:hover {
    color: var(--text);
    text-decoration-color: var(--text-muted);
}
/* Show one span / hide the other based on toggle state. */
.gv-preplatform-toggle .gv-preplatform-toggle-hide { display: none; }
.gv-preplatform-toggle[data-show-preplatform="1"] .gv-preplatform-toggle-show { display: none; }
.gv-preplatform-toggle[data-show-preplatform="1"] .gv-preplatform-toggle-hide { display: inline; }

/* List-level modifier that hides any row tagged `data-preplatform="1"`.
   Default state (rendered server-side when show_preplatform is falsy)
   keeps pre-platform rows out of the way; the toggle button removes
   the modifier client-side without a route round-trip. */
.gv-list--hide-preplatform [data-preplatform="1"] { display: none; }

/* Audit-pack tile icon swatches — lifted from the inline template
   block (hardcoded rgba 0.12 values) onto the --audit-* tokens above.
   The data-c attribute is set by `app/services/compliance_icons.py`
   when an icon is in the warn / good colour family. */
.cp-pack-tile-icon[data-c="amber"] {
    background: var(--audit-warn-bg);
    color: var(--audit-warn-fg);
}
.cp-pack-tile-icon[data-c="green"] {
    background: var(--audit-good-bg);
    color: var(--audit-good-fg);
}

/* ═══════════════════════════════════════════════════════════════════
   Malleable Programme (slice 2-3) — session-slot affordances
   ───────────────────────────────────────────────────────────────────
   Added by step 03-02: clickable history icon, unlock-overlay
   badge, drawer-body history list. The trigger functions
   (openMoveModal / openUnlockModal / openDrawer / openRelockConfirm)
   land in step 03-03 as JS handlers; the markup + visual treatment
   live here.
   ═══════════════════════════════════════════════════════════════════ */

/* History icon — small clock icon at the bottom-right of every
   session card. ui-design.md §2.5. Low visual weight; reveals
   its intent on hover (per docs/design-system/rules.md R8 —
   per-row controls fade in on hover). On touch it stays
   visible (handled by the lack of a hover-only selector). */
.slot-footer-actions {
    display: flex;
    justify-content: flex-end;
    margin-top: 8px;
    gap: 6px;
}
.slot-history-icon {
    background: none;
    border: 1px solid transparent;
    border-radius: 4px;
    padding: 3px 5px;
    cursor: pointer;
    color: var(--text-muted);
    opacity: 0.55;
    transition: opacity 0.15s, color 0.15s, border-color 0.15s;
    display: inline-flex;
    align-items: center;
}
.slot-history-icon:hover {
    opacity: 1;
    color: var(--primary);
    border-color: var(--border);
}
.slot-history-icon:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 1px;
    opacity: 1;
}

/* Unlock-overlay badge — visible only when a done session has
   been unlocked for correction and not yet re-locked. Amber
   chrome flags the transient "needs attention" state per
   principle A.6 (colour = state) and rule R2 (state of the
   world above the fold — this is the card-local equivalent). */
.slot-unlock-overlay {
    margin-top: 8px;
    padding: 6px 10px;
    background: rgba(245,158,11,0.10);
    border: 1px solid rgba(245,158,11,0.30);
    border-radius: 6px;
    color: #92400e;
    font-size: 0.72rem;
    font-weight: 600;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 6px;
}
.slot-unlock-overlay:hover {
    background: rgba(245,158,11,0.18);
}

/* Session-history drawer body — monospace timestamps line up
   under each other, one entry per line. Matches the calm
   density of the other admin drawers (rota, balance). */
.session-history-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.session-history-entry {
    padding: 8px 10px;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--bg);
    display: grid;
    grid-template-columns: auto 1fr auto;
    gap: 4px 10px;
    align-items: baseline;
    font-size: 0.82rem;
}
.session-history-entry .event {
    font-weight: 600;
    color: var(--text);
    grid-column: 1;
}
.session-history-entry .actor {
    color: var(--text-muted);
    grid-column: 2;
    font-size: 0.78rem;
}
.session-history-entry time {
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: 0.72rem;
    color: var(--text-muted);
    grid-column: 3;
    text-align: right;
}
.session-history-entry .dates {
    grid-column: 1 / -1;
    font-size: 0.74rem;
    color: var(--text-muted);
}
.session-history-entry .reason {
    grid-column: 1 / -1;
    margin: 2px 0 0;
    font-size: 0.78rem;
    color: var(--text);
    font-style: italic;
}
.drawer-empty {
    color: var(--text-muted);
    font-style: italic;
    font-size: 0.85rem;
    margin: 8px 0 0;
}

/* ── Year-setup wizard modal (feat-year-setup-wizard slice 02) ──────
   Additive members of the builder-modal family — same tokens, no new
   colours (R7 discipline). The modal is wider than the 400px default
   because it hosts the read-only preview list next to nothing else on
   screen; the preview pane scrolls so 50 dates never push the footer
   off-viewport. */
.builder-modal-year-setup { max-width: 560px; }
.year-setup-extent-row input[type="radio"] {
    width: auto;
    margin-right: 4px;
    vertical-align: -1px;
}
.year-setup-preview-wrap {
    margin-top: 14px;
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 10px 12px;
    max-height: 260px;
    overflow-y: auto;
    background: var(--bg);
    font-size: 0.85rem;
    line-height: 1.5;
}
.year-setup-preview-placeholder {
    color: var(--text-muted);
    font-style: italic;
    margin: 0;
}
.year-setup-summary { margin: 0 0 8px; }
.year-setup-note {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin: 0 0 8px;
    padding: 6px 8px;
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text-muted);
}
.year-setup-note-dismiss {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--text-muted);
    font-size: 1rem;
    line-height: 1;
    padding: 0 2px;
}
.year-setup-skips {
    margin: 0 0 8px;
    padding-left: 18px;
    color: var(--text-muted);
}
.year-setup-skip-reason { color: var(--text); font-weight: 600; }
.year-setup-date-list {
    margin: 0;
    padding-left: 22px;
}
.year-setup-date-list li { padding: 1px 0; }
.year-setup-bh-flag {
    color: var(--text-muted);
    font-size: 0.78rem;
    margin-left: 6px;
}
.year-setup-errors { margin-top: 0; }
.year-setup-error-list { margin: 0; padding-left: 16px; }
