/* Theme-aware component classes using CSS variables set by base.html */

:root {
  /* Mobile bottom-tab bar height — consumed by .bottom-tabs and any fixed bar
     pinned just above it (e.g. the emergency roll-call Submit bar) so the two
     can never drift. Centralized in the main :root (Gemini #1093). */
  --bottom-tabs-h: 4rem;
  --color-nav:           #4338ca;
  --color-nav-text:      #c7d2fe;
  --color-primary:       #4f46e5;
  --color-primary-dark:  #4338ca;
  --color-primary-light: #eef2ff;
  --color-ring:          rgba(99,102,241,0.15);
  /* Hairline borders. slate-200 (#e2e8f0) reads invisible against the
     off-white page on most screens — too "deferential" to do its job
     of containing a control. slate-300 stays understated but actually
     shows. Strong variant is for hover/focus accents. New code should
     use these tokens; existing slate-200 literals get swept over
     time. */
  --border-hairline:        #cbd5e1;  /* slate-300 */
  --border-hairline-strong: #94a3b8;  /* slate-400 */
}

/* Alpine.js x-cloak — hide elements until Alpine has hydrated, so
   toast/banner partials don't briefly flash on page load. */
[x-cloak] { display: none !important; }

.btn-primary {
  display: inline-flex;
  align-items: center;
  background-color: var(--color-primary);
  color: #ffffff;
  font-weight: 600;
  font-size: 0.875rem;
  padding: 0.5rem 1rem;
  border-radius: 0.5rem;
  border: none;
  cursor: pointer;
  text-decoration: none;
  transition: background-color 0.15s;
}
.btn-primary:hover { background-color: var(--color-primary-dark); }

.btn-secondary {
  display: inline-flex;
  align-items: center;
  background-color: #ffffff;
  color: #334155;
  font-weight: 600;
  font-size: 0.875rem;
  padding: 0.5rem 1rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-hairline);
  cursor: pointer;
  text-decoration: none;
  transition: background-color 0.15s, border-color 0.15s;
}
.btn-secondary:hover { background-color: #f8fafc; border-color: #94a3b8; }

.form-label {
  display: block;
  font-size: 0.8125rem;
  font-weight: 600;
  color: #475569;
  margin-bottom: 0.25rem;
}

.form-input {
  display: block;
  width: 100%;
  padding: 0.5rem 0.75rem;
  font-size: 0.875rem;
  border: 1px solid var(--border-hairline);
  border-radius: 0.5rem;
  background: #ffffff;
  color: #1e293b;
  transition: border-color 0.15s, box-shadow 0.15s;
  box-sizing: border-box;
}
.form-input:hover { border-color: var(--border-hairline-strong); }
.form-input:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-ring);
}
textarea.form-input { resize: vertical; }
select.form-input    { appearance: auto; }

.form-error {
  font-size: 0.75rem;
  color: #dc2626;
  margin-top: 0.2rem;
}

.line-clamp-2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ── Sprint 1 design-system tokens ─────────────────────────────────────────
 * Plain-CSS implementations (not @apply) so they render the same way under
 * both the dev CDN and the production Tailwind build of source.css. Mirrors
 * the spec in DESIGN_SPEC.md §1.
 * ─────────────────────────────────────────────────────────────────────── */

.badge {
  display: inline-flex;
  align-items: center;
  padding: 0.125rem 0.5rem;
  border-radius: 9999px;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1rem;
}
.badge-success    { background-color: #d1fae5; color: #065f46; }
.badge-warning    { background-color: #fef3c7; color: #92400e; }
.badge-danger     { background-color: #fee2e2; color: #991b1b; }
.badge-default    { background-color: #f1f5f9; color: #475569; }
.badge-info       { background-color: #dbeafe; color: #1e40af; }
.badge-published  { background-color: #d1fae5; color: #065f46; }
.badge-draft      { background-color: #fef9c3; color: #854d0e; }
.badge,
.badge-success,
.badge-warning,
.badge-danger,
.badge-default,
.badge-info,
.badge-published,
.badge-draft {
  display: inline-flex;
  align-items: center;
  padding: 0.125rem 0.5rem;
  border-radius: 9999px;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1rem;
}

.capacity-bar {
  height: 0.375rem;
  border-radius: 9999px;
  background-color: #f1f5f9;
  overflow: hidden;
}
.capacity-fill {
  height: 100%;
  border-radius: 9999px;
  transition: width 0.3s ease;
}

/* ── Module launcher cards ─────────────────────────────────
   Active modules get a solid colored icon chip + accent stat
   row + full-width primary CTA. Pending modules get a dashed
   border and a muted "future" treatment — clearly disabled
   without going fully grey/dead.
─────────────────────────────────────────────────────────── */
.module-card {
  background-color: #ffffff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.875rem;
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  transition: box-shadow 0.18s, transform 0.18s, border-color 0.18s;
}
.module-card.is-active:hover {
  box-shadow: 0 8px 24px -8px rgba(15, 23, 42, 0.12), 0 2px 4px -1px rgba(15, 23, 42, 0.04);
  transform: translateY(-2px);
}
.module-card.is-pending {
  border-style: dashed;
  border-color: #cbd5e1;
  background-color: #f8fafc;
}

.module-card .module-icon {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 0.625rem;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  background-color: #e2e8f0;
  color: #64748b;
}
.module-card .module-icon svg { width: 1.25rem; height: 1.25rem; }

/* Accent variants — solid colored bg, white icon */
.module-card.is-active.accent-indigo  .module-icon { background-color: #6366f1; color: #fff; }
.module-card.is-active.accent-emerald .module-icon { background-color: #10b981; color: #fff; }
.module-card.is-active.accent-amber   .module-icon { background-color: #f59e0b; color: #fff; }
.module-card.is-active.accent-rose    .module-icon { background-color: #f43f5e; color: #fff; }
.module-card.is-active.accent-sky     .module-icon { background-color: #0ea5e9; color: #fff; }
.module-card.is-active.accent-violet  .module-icon { background-color: #8b5cf6; color: #fff; }
.module-card.is-active.accent-teal    .module-icon { background-color: #0d9488; color: #fff; }

.module-card .module-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.5rem;
  margin-bottom: 1rem;
}
.module-card .module-stat {
  background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
  border: 1px solid var(--border-hairline);
  border-radius: 0.625rem;
  padding: 0.625rem 0.5rem;
  text-align: center;
  text-decoration: none;
  color: inherit;
  display: block;
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}
.module-card .module-stat.is-link { cursor: pointer; }
.module-card .module-stat.is-link:hover {
  border-color: #c7d2fe;
  box-shadow: 0 4px 10px -4px rgba(99, 102, 241, 0.18);
  transform: translateY(-1px);
}
.module-card .module-stat-value {
  font-size: 1.375rem;
  font-weight: 700;
  line-height: 1.1;
  color: #0f172a;
  font-variant-numeric: tabular-nums;
}
.module-card .module-stat-label {
  font-size: 0.6875rem;
  font-weight: 500;
  color: #64748b;
  margin-top: 0.125rem;
  letter-spacing: 0.02em;
}

.module-card.is-active.accent-indigo  .module-stat-value { color: #4f46e5; }
.module-card.is-active.accent-emerald .module-stat-value { color: #059669; }
.module-card.is-active.accent-amber   .module-stat-value { color: #d97706; }
.module-card.is-active.accent-violet  .module-stat-value { color: #7c3aed; }
.module-card.is-active.accent-rose    .module-stat-value { color: #e11d48; }
.module-card.is-active.accent-sky     .module-stat-value { color: #0284c7; }
.module-card.is-active.accent-teal    .module-stat-value { color: #0d9488; }

.module-card .module-pending-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 1.5rem 0.75rem;
}

.module-card .module-cta-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  width: 100%;
  padding: 0.625rem 1rem;
  border-radius: 0.5rem;
  font-size: 0.875rem;
  font-weight: 600;
  color: #fff;
  background-color: var(--color-primary);
  transition: background-color 0.15s;
  margin-bottom: 0.5rem;
  text-decoration: none;
}
.module-card .module-cta-primary:hover { background-color: var(--color-primary-dark); }

.module-card.is-active.accent-indigo  .module-cta-primary { background-color: #4f46e5; }
.module-card.is-active.accent-indigo  .module-cta-primary:hover { background-color: #4338ca; }
.module-card.is-active.accent-emerald .module-cta-primary { background-color: #059669; }
.module-card.is-active.accent-emerald .module-cta-primary:hover { background-color: #047857; }
.module-card.is-active.accent-violet  .module-cta-primary { background-color: #7c3aed; }
.module-card.is-active.accent-violet  .module-cta-primary:hover { background-color: #6d28d9; }
/* sky / amber / teal CTAs were falling back to var(--color-primary)
   (the school theme color, often gold) — pin them to fixed accents
   so the dashboard reads consistently across schools regardless of
   theme. */
.module-card.is-active.accent-sky    .module-cta-primary { background-color: #0284c7; }
.module-card.is-active.accent-sky    .module-cta-primary:hover { background-color: #0369a1; }
.module-card.is-active.accent-amber  .module-cta-primary { background-color: #d97706; }
.module-card.is-active.accent-amber  .module-cta-primary:hover { background-color: #b45309; }
.module-card.is-active.accent-teal   .module-cta-primary { background-color: #0d9488; }
.module-card.is-active.accent-teal   .module-cta-primary:hover { background-color: #0f766e; }

.module-card .module-cta-secondary-row { display: flex; gap: 0.5rem; }
.module-card .module-cta-secondary {
  flex: 1;
  text-align: center;
  padding: 0.5rem 0.75rem;
  font-size: 0.75rem;
  font-weight: 500;
  color: #475569;
  background-color: #ffffff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.5rem;
  transition: border-color 0.15s, background-color 0.15s, color 0.15s;
  text-decoration: none;
}
.module-card .module-cta-secondary:hover { border-color: #94a3b8; background-color: #f8fafc; color: #1e293b; }


/* ── Shared admin cards ────────────────────────────────────
   One row of cross-cutting administration links. Each gets a
   distinct solid-color icon chip; on hover the card lifts
   with a subtle shadow and the chevron slides right.
─────────────────────────────────────────────────────────── */
.shared-card {
  background-color: #ffffff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.75rem;
  padding: 0.875rem 1rem;
  display: flex;
  gap: 0.75rem;
  align-items: center;
  text-decoration: none;
  color: inherit;
  transition: box-shadow 0.18s, transform 0.18s, border-color 0.18s;
}
.shared-card:hover {
  box-shadow: 0 6px 18px -6px rgba(15, 23, 42, 0.10);
  transform: translateY(-1px);
  border-color: #cbd5e1;
}
.shared-card .shared-icon {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 0.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: #fff;
}
.shared-card .shared-icon svg { width: 1.125rem; height: 1.125rem; }

.shared-card.accent-blue   .shared-icon { background-color: #3b82f6; }
.shared-card.accent-violet .shared-icon { background-color: #8b5cf6; }
.shared-card.accent-sky    .shared-icon { background-color: #0ea5e9; }
.shared-card.accent-rose   .shared-icon { background-color: #f43f5e; }
.shared-card.accent-slate  .shared-icon { background-color: #64748b; }
/* amber + indigo rules were missing — Activity Log (amber) and
   Approval Routings (indigo) rendered with no chip background, which
   read as a missing icon. Defined here so all dashboard.shared_admin_links
   accents resolve. */
.shared-card.accent-amber  .shared-icon { background-color: #f59e0b; }
.shared-card.accent-indigo .shared-icon { background-color: #4f46e5; }
.shared-card.accent-emerald .shared-icon { background-color: #10b981; }

.shared-card .shared-chevron {
  width: 1rem;
  height: 1rem;
  color: #cbd5e1;
  flex-shrink: 0;
  transition: transform 0.15s, color 0.15s;
}
.shared-card:hover .shared-chevron { color: #64748b; transform: translateX(2px); }


.section-label {
  font-size: 0.75rem;
  font-weight: 700;
  color: #94a3b8;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* Collapse caret rotation for the admin-dashboard "Shared
   Administration" disclosure card. ``<details open>`` flips it. */
.admin-shared-card[open] .admin-shared-chevron { transform: rotate(180deg); }
.admin-shared-card > summary::-webkit-details-marker { display: none; }
.admin-shared-card > summary::marker              { display: none; }

.alerts-strip {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  background-color: #fffbeb;
  border: 1px solid #fde68a;
  border-radius: 0.625rem;
  font-size: 0.875rem;
  color: #78350f;
}


/* ── My Schedule — unified timeline ────────────────────────
   Single chronological view per event, role-aware rows.
   Date chip color tracks the event "accent" (amber for today,
   indigo for upcoming, slate for past). Vertical accent stripe
   on each row signals role (event accent for supervising,
   emerald for checked-in, slate for default registered).
─────────────────────────────────────────────────────────── */
.ms-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.25rem;
  margin-bottom: 1.75rem;
}
.ms-header-left { display: flex; align-items: center; gap: 0.875rem; }
.ms-header-right { display: flex; align-items: center; gap: 0.625rem; flex-wrap: wrap; }

.ms-avatar {
  width: 2.75rem; height: 2.75rem;
  border-radius: 50%;
  flex-shrink: 0;
  object-fit: cover;
  box-shadow: 0 0 0 2px #e0e7ff;
}
.ms-avatar-fallback {
  display: inline-flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
  color: #fff; font-weight: 700; font-size: 0.95rem;
}

.ms-filters {
  display: inline-flex;
  border: 1px solid var(--border-hairline);
  border-radius: 0.5rem;
  overflow: hidden;
  background-color: #fff;
}
.ms-filter {
  padding: 0.4rem 0.875rem;
  font-size: 0.8125rem;
  font-weight: 500;
  color: #475569;
  background-color: #fff;
  border: none;
  border-right: 1px solid #e2e8f0;
  cursor: pointer;
  transition: background-color 0.12s, color 0.12s;
}
.ms-filter:last-child { border-right: none; }
.ms-filter:hover { background-color: #f8fafc; }
.ms-filter.is-active {
  background-color: #0f172a;
  color: #fff;
  font-weight: 600;
}

.ms-subscribe { position: relative; }
.ms-subscribe-pop {
  position: absolute;
  right: 0; top: calc(100% + 0.375rem);
  width: 22rem;
  background: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.625rem;
  box-shadow: 0 12px 32px -8px rgba(15, 23, 42, 0.18);
  padding: 1rem;
  z-index: 50;
  display: none;
}
.ms-subscribe:hover .ms-subscribe-pop,
.ms-subscribe:focus-within .ms-subscribe-pop { display: block; }
.ms-subscribe-url {
  width: 100%;
  font-size: 0.6875rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: #f8fafc;
  border: 1px solid var(--border-hairline);
  border-radius: 0.375rem;
  padding: 0.4rem 0.5rem;
  color: #334155;
  cursor: pointer;
}

.ms-timeline { display: flex; flex-direction: column; gap: 1.125rem; }

/* ── Today hero — default landing view ─────────────────────────────── */
.ms-today {
  background: linear-gradient(140deg, #fef3c7 0%, #fffbeb 60%, #fff 100%);
  border: 1px solid #fde68a;
  border-radius: 1rem;
  padding: 1.25rem 1.25rem 1rem;
  margin-bottom: 1.25rem;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
}
/* Quiet variant — weekends / school-closed days. Drops the amber loud
   gradient + the heavy date-chip glow in favour of a near-silent slate
   card so the page lands on the upcoming list, not on a giant nothing. */
.ms-today.is-quiet {
  background: #fff;
  border-color: #e2e8f0;
  padding: 1rem 1.125rem 0.75rem;
}
.ms-today.is-quiet .ms-today-date {
  background: #f1f5f9;
  color: #475569;
  box-shadow: none;
  width: 2.25rem; height: 2.25rem;
}
.ms-today.is-quiet .ms-today-date-day { font-size: 0.9375rem; }
.ms-today.is-quiet .ms-today-eyebrow  { color: #94a3b8; }
.ms-today.is-quiet .ms-today-heading  { font-size: 1rem; }
.ms-today.is-quiet .ms-today-empty    { padding: 0.5rem 0 0.25rem; }
.ms-today-header {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  margin-bottom: 1rem;
}
.ms-today-date {
  width: 3rem; height: 3rem;
  border-radius: 0.625rem;
  background: linear-gradient(140deg, #fbbf24 0%, #d97706 100%);
  color: #fff;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  flex-shrink: 0;
  line-height: 1;
  box-shadow: 0 4px 10px -4px rgba(217, 119, 6, 0.5);
}
.ms-today-date-day { font-size: 1.125rem; font-weight: 800; }
.ms-today-date-mon { font-size: 0.625rem; font-weight: 700; opacity: 0.9; margin-top: 0.125rem; letter-spacing: 0.06em; }
.ms-today-eyebrow {
  font-size: 0.6875rem;
  font-weight: 700;
  color: #b45309;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.ms-today-heading {
  font-size: 1.125rem;
  font-weight: 700;
  color: #0f172a;
  line-height: 1.2;
  margin-top: 0.125rem;
}
.ms-today-event {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
  padding: 0 0.125rem;
}
.ms-today-empty {
  text-align: center;
  padding: 0.75rem 0 0.5rem;
}
.ms-today-empty-headline {
  font-size: 0.9375rem;
  font-weight: 600;
  color: #334155;
}

/* Bell schedule label tag in the hero header right side. */
.ms-today-bell-tag {
  display: inline-flex;
  align-items: center;
  padding: 0.25rem 0.625rem;
  border-radius: 9999px;
  font-size: 0.6875rem;
  font-weight: 600;
  color: #b45309;
  background: rgba(255, 255, 255, 0.7);
  border: 1px solid #fde68a;
  flex-shrink: 0;
}

/* Bell-strip — chips for each period; the live one gets the amber accent. */
.ms-bell-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  padding: 0.625rem 0.75rem;
  background: rgba(255, 255, 255, 0.6);
  border: 1px solid #fde68a;
  border-radius: 0.625rem;
  margin-bottom: 0.875rem;
}
.ms-bell-period {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.25rem 0.625rem;
  border-radius: 0.5rem;
  background: #f1f5f9;
  color: #475569;
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.1;
}
.ms-bell-period.is-past {
  opacity: 0.45;
}
.ms-bell-period.is-now {
  background: linear-gradient(140deg, #fbbf24 0%, #d97706 100%);
  color: #fff;
  box-shadow: 0 2px 6px -2px rgba(217, 119, 6, 0.5);
  font-weight: 600;
}
.ms-bell-dot {
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.8);
  flex-shrink: 0;
}
.ms-bell-name { font-weight: 600; }
.ms-bell-period.is-now .ms-bell-name { font-weight: 700; }
.ms-bell-time {
  font-variant-numeric: tabular-nums;
  opacity: 0.75;
  font-size: 0.6875rem;
}

/* "Now" / "Up next" callouts on the Today hero rows. */
.ms-row.is-now {
  background: linear-gradient(90deg, rgba(251, 191, 36, 0.12) 0%, rgba(251, 191, 36, 0) 60%);
  border-left: 3px solid #d97706;
  padding-left: calc(0.875rem - 3px);
}
.ms-row.is-up-next {
  background: rgba(99, 102, 241, 0.04);
  border-left: 3px solid #6366f1;
  padding-left: calc(0.875rem - 3px);
}
.ms-now-badge,
.ms-next-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.1875rem 0.5rem;
  border-radius: 0.375rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
.ms-now-badge {
  background: linear-gradient(140deg, #fbbf24 0%, #d97706 100%);
  color: #fff;
  box-shadow: 0 2px 6px -2px rgba(217, 119, 6, 0.45);
}
.ms-next-badge {
  background: #eef2ff;
  color: #4338ca;
  border: 1px solid #c7d2fe;
}

/* "School closed today" banner inside the Today hero. */
.ms-bell-closed {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.75rem 0.875rem;
  background: #fef2f2;
  border: 1px solid #fecaca;
  border-radius: 0.625rem;
  margin-bottom: 0.875rem;
}

.ms-event {
  background: transparent;
}
.ms-event-past { opacity: 0.7; }

/* Compact event header — date pill + title + counts on one row. */
.ms-event-header {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  margin-bottom: 0.5rem;
  padding: 0 0.125rem;
}
.ms-event-title {
  font-size: 0.9375rem;
  font-weight: 700;
  color: #0f172a;
  line-height: 1.2;
  letter-spacing: -0.005em;
}
.ms-event-sub {
  font-size: 0.75rem;
  color: #64748b;
  margin-top: 0.0625rem;
}

.ms-date-chip {
  width: 2.25rem; height: 2.25rem;
  border-radius: 0.5rem;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  flex-shrink: 0;
  color: #fff;
  line-height: 1;
}
.ms-date-chip.ms-accent-amber  { background: linear-gradient(140deg, #fbbf24 0%, #d97706 100%); }
.ms-date-chip.ms-accent-indigo { background: linear-gradient(140deg, #818cf8 0%, #4f46e5 100%); }
.ms-date-chip.ms-accent-slate  { background: #94a3b8; }
.ms-date-day { font-size: 0.8125rem; font-weight: 800; }
.ms-date-mon { font-size: 0.5rem; font-weight: 700; opacity: 0.85; margin-top: 0.0625rem; letter-spacing: 0.04em; }

.ms-event-counts {
  display: flex; gap: 0.25rem;
  flex-shrink: 0;
  margin-left: auto;
}

.ms-rows {
  background-color: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.5rem;
  overflow: hidden;
}

/* Two-column layout for events with 4+ workshops on desktops ≥1024px.
   Reads column-major (top-to-bottom in left col, then top-to-bottom in
   right col) so each column stays chronological. The shared border that
   .ms-row provides is replaced here with a grid gap + a vertical
   separator between columns; the row's own border-bottom stays so each
   row reads as a separate band. */
@media (min-width: 1024px) {
  .ms-rows.is-2col {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-auto-flow: column;
    grid-template-rows: repeat(var(--ms-rows-per-col, auto-fill), 1fr);
  }
  .ms-rows.is-2col .ms-row { border-right: 1px solid #f1f5f9; }
  .ms-rows.is-2col .ms-row:nth-child(2n) { border-right: none; }
}

/* Tight row — single visual "line" in most cases (title row + meta row),
   centered vertically so time + content align even with one-line meta. */
.ms-row {
  display: flex;
  gap: 0.625rem;
  align-items: center;
  padding: 0.4375rem 0.75rem;
  border-bottom: 1px solid #f1f5f9;
}
.ms-row:last-child { border-bottom: none; }
.ms-row.is-checked-in { background-color: #f0fdf4; }

.ms-row-body { display: flex; flex-direction: column; gap: 0.0625rem; min-width: 0; }
.ms-row-titleline { display: flex; align-items: center; gap: 0.4375rem; min-width: 0; }
.ms-row-title {
  font-size: 0.875rem;
  font-weight: 600;
  color: #0f172a;
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.ms-time {
  width: 3.25rem;
  flex-shrink: 0;
  text-align: center;
  line-height: 1;
}
.ms-time-start {
  font-size: 0.875rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.ms-time-end {
  font-size: 0.625rem;
  color: #94a3b8;
  margin-top: 0.125rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.ms-stripe {
  width: 3px;
  align-self: stretch;
  border-radius: 2px;
  flex-shrink: 0;
  min-height: 2rem;
}
.ms-stripe-amber   { background-color: #d97706; }
.ms-stripe-indigo  { background-color: #4f46e5; }
.ms-stripe-emerald { background-color: #10b981; }
.ms-stripe-slate   { background-color: #cbd5e1; }

.ms-row-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.125rem 0.625rem;
  font-size: 0.75rem;
  color: #64748b;
  line-height: 1.25;
}
.ms-meta-item { display: inline-flex; align-items: center; gap: 0.25rem; }
.ms-meta-supervisor {
  font-size: 0.6875rem;
  color: #4f46e5;
  font-weight: 500;
  white-space: nowrap;
}

.ms-session-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.0625rem 0.4375rem;
  border-radius: 9999px;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
  flex-shrink: 0;
}
.ms-session-pill.ms-pill-amber  { background: #fef3c7; color: #92400e; }
.ms-session-pill.ms-pill-indigo { background: #e0e7ff; color: #3730a3; }
.ms-session-pill.ms-pill-slate  { background: #f1f5f9; color: #475569; }

.ms-row-actions {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  flex-shrink: 0;
  margin-left: auto;
}
.ms-row-actions .btn-primary,
.ms-row-actions .btn-secondary {
  padding: 0.3125rem 0.625rem;
  font-size: 0.75rem;
}
.ms-cancel {
  font-size: 0.75rem;
  color: #ef4444;
  font-weight: 500;
  background: none; border: none;
  cursor: pointer; padding: 0;
}
.ms-cancel:hover { color: #b91c1c; text-decoration: underline; }

.ms-empty {
  text-align: center;
  padding: 3rem 1.5rem;
  border: 2px dashed #e2e8f0;
  border-radius: 0.875rem;
  background-color: #f8fafc;
}

.ms-past {
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid #e2e8f0;
}
.ms-past-summary {
  cursor: pointer;
  font-size: 0.875rem;
  font-weight: 600;
  color: #475569;
  list-style: none;
  display: inline-flex;
  align-items: center;
  user-select: none;
}
.ms-past-summary::before {
  content: '▸';
  display: inline-block;
  margin-right: 0.375rem;
  color: #94a3b8;
  transition: transform 0.15s;
}
.ms-past[open] .ms-past-summary::before { transform: rotate(90deg); }
.ms-past-summary::-webkit-details-marker { display: none; }

@media (max-width: 640px) {
  .ms-header-right { width: 100%; justify-content: space-between; }
  .ms-time { width: 3.25rem; }
  .ms-row-actions { width: 100%; margin-left: 0; padding-left: 4rem; }

  /* Today hero — tighter on narrow screens. The bell tag wraps under
     the heading instead of pushing it off-screen; padding shrinks; the
     bell-strip chips drop the time suffix to keep one row scannable. */
  .ms-today { padding: 1rem 0.875rem 0.75rem; border-radius: 0.875rem; }
  .ms-today-header { flex-wrap: wrap; gap: 0.625rem; row-gap: 0.5rem; }
  .ms-today-date { width: 2.5rem; height: 2.5rem; }
  .ms-today-date-day { font-size: 1rem; }
  .ms-today-heading { font-size: 1rem; }
  .ms-today-bell-tag { order: 3; flex-basis: 100%; justify-content: center; }
  .ms-bell-strip { padding: 0.5rem 0.5rem; gap: 0.25rem; }
  .ms-bell-period { padding: 0.1875rem 0.5rem; font-size: 0.6875rem; }
  .ms-bell-time { display: none; }
  .ms-bell-period.is-now .ms-bell-time { display: inline; }
}


/* ── Campaigns readiness grid ──────────────────────────────
   2D table: members × checklist items. Cells are buttons that
   toggle a completion via a tiny form. Sticky header + first
   column so scrolling stays grounded.
─────────────────────────────────────────────────────────── */
.grid-wrap {
  overflow-x: auto;
  border: 1px solid var(--border-hairline);
  border-radius: 0.875rem;
  background: #fff;
}
.grid-table {
  border-collapse: separate;
  border-spacing: 0;
  width: 100%;
  font-size: 0.8125rem;
}
.grid-table thead th {
  background: #f8fafc;
  border-bottom: 1px solid #e2e8f0;
  position: sticky;
  top: 0;
  z-index: 2;
}
.grid-th-name {
  text-align: left;
  padding: 0.625rem 0.75rem;
  min-width: 11rem;
  position: sticky;
  left: 0;
  background: #f8fafc;
  z-index: 3;
  font-weight: 600;
  color: #475569;
  border-right: 1px solid #e2e8f0;
}
.grid-th-progress {
  padding: 0.625rem 0.5rem;
  min-width: 5.5rem;
  text-align: center;
  font-weight: 600;
  color: #475569;
  border-right: 1px solid #e2e8f0;
}
.grid-th-item {
  padding: 0.625rem 0.5rem;
  min-width: 8rem;
  vertical-align: top;
  text-align: center;
  border-right: 1px solid #f1f5f9;
}
.grid-item-title {
  font-weight: 600;
  color: #334155;
  line-height: 1.2;
  margin-bottom: 0.125rem;
  white-space: normal;
}
.grid-item-meta {
  font-size: 0.6875rem;
  color: #94a3b8;
  font-weight: 500;
}
.grid-bulk-btn {
  display: inline-block;
  font-size: 0.6875rem;
  font-weight: 600;
  color: #4338ca;
  background: #eef2ff;
  border: 1px solid #c7d2fe;
  border-radius: 0.375rem;
  padding: 0.125rem 0.5rem;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
}
.grid-bulk-btn:hover { background: #c7d2fe; border-color: #818cf8; }

.grid-table tbody tr:nth-child(even) { background: #fafbfc; }
.grid-table tbody tr:hover { background: #f1f5f9; }
.grid-td-name {
  padding: 0.5rem 0.75rem;
  min-width: 11rem;
  position: sticky;
  left: 0;
  z-index: 1;
  background: inherit;
  border-right: 1px solid #e2e8f0;
  border-bottom: 1px solid #f1f5f9;
}
.grid-td-progress {
  padding: 0.5rem 0.5rem;
  min-width: 5.5rem;
  border-right: 1px solid #e2e8f0;
  border-bottom: 1px solid #f1f5f9;
  vertical-align: middle;
}
.grid-progress {
  height: 0.375rem;
  background: #f1f5f9;
  border-radius: 9999px;
  overflow: hidden;
}
.grid-progress-fill {
  height: 100%;
  border-radius: 9999px;
  transition: width 0.2s;
}
.grid-td-cell {
  padding: 0.25rem;
  text-align: center;
  border-right: 1px solid #f1f5f9;
  border-bottom: 1px solid #f1f5f9;
  vertical-align: middle;
}
.grid-cell-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 0.375rem;
  border: 1px solid var(--border-hairline);
  background: #fff;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, transform 0.1s;
}
.grid-cell-btn:hover { background: #f8fafc; border-color: #cbd5e1; transform: scale(1.06); }
.grid-cell-btn.is-done {
  background: #d1fae5;
  border-color: #10b981;
  color: #047857;
}
.grid-cell-btn.is-done:hover {
  background: #a7f3d0;
  border-color: #059669;
}
.grid-cell-btn svg { width: 0.875rem; height: 0.875rem; }
.grid-cell-dash { color: #cbd5e1; font-weight: 600; }

/* ── Account / Settings hub shell ──────────────────────────
   Sidebar on the left, content on the right. Sidebar collapses
   to a top tab strip on mobile.
─────────────────────────────────────────────────────────── */
.account-shell {
  display: grid;
  grid-template-columns: 14rem 1fr;
  gap: 1.5rem;
  align-items: start;
  /* One shared measure for every settings page — forms read better
     constrained than full-bleed (the table is the wide exception we accept). */
  max-width: 58rem;
}
@media (max-width: 768px) {
  .account-shell { grid-template-columns: 1fr; gap: 0.75rem; }
}
.account-sidebar {
  background: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.875rem;
  padding: 1rem 0.625rem;
  position: sticky;
  top: 1rem;
}
@media (max-width: 768px) {
  .account-sidebar { position: static; padding: 0.5rem; }
}
.account-sidebar-eyebrow {
  font-size: 0.6875rem;
  font-weight: 700;
  color: #94a3b8;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 0 0.625rem 0.5rem;
}
.account-nav {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}
@media (max-width: 768px) {
  .account-nav { flex-direction: row; flex-wrap: wrap; }
}
.account-nav-link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.625rem;
  border-radius: 0.5rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: #475569;
  text-decoration: none;
  transition: background-color 0.12s, color 0.12s;
}
.account-nav-link:hover { background: #f1f5f9; color: #1e293b; }
.account-nav-link.is-active {
  background: var(--brand-teal-soft);
  color: var(--brand-teal-dark, var(--brand-teal));
}
.account-nav-link svg { color: currentColor; }

/* Dark mode — the settings sub-menu is a dark panel (not a white card on
   the deep-navy page), and the active item uses the brand accent rather
   than the old hardcoded indigo, so it tracks the chosen palette. */
html.dark .account-sidebar {
  background: var(--brand-navy-2);
  border-color: rgba(255, 255, 255, 0.08);
}
html.dark .account-sidebar-eyebrow { color: rgba(236, 242, 255, 0.45); }
html.dark .account-nav-link { color: rgba(236, 242, 255, 0.72); }
html.dark .account-nav-link:hover { background: rgba(255, 255, 255, 0.06); color: #fff; }
html.dark .account-nav-link.is-active {
  background: color-mix(in srgb, var(--brand-teal) 16%, transparent);
  color: var(--brand-teal-bright, var(--brand-teal));
}

/* Breadcrumb rendered BELOW the section hero (account shell) instead of the
   base full-bleed slot: strip the strip chrome (its own padding / paper bg /
   bottom hairline / phone sticky) so it reads as a plain inline crumb under
   the hero rather than a misplaced bar. */
.crumb-inhero .crumb-row {
  padding: 0 0 0.5rem;
  background: transparent;
  border-bottom: 0;
  position: static;
}

.account-content { min-width: 0; }
.account-section-head { margin-bottom: 1rem; }
.account-section-title {
  font-size: 1.5rem;
  font-weight: 700;
  color: #0f172a;
  line-height: 1.2;
}
.account-section-sub {
  font-size: 0.8125rem;
  color: #64748b;
  margin-top: 0.25rem;
}

/* Dark mode — the settings / notifications content was authored for light;
   give its headings, labels and the prefs-table header their dark treatment
   so nothing reads dark-on-dark or as a white strip on the deep page. */
html.dark .account-section-title { color: #ECF2FF; }
html.dark .account-section-sub { color: rgba(236, 242, 255, 0.62); }
html.dark .appearance-legend { color: rgba(236, 242, 255, 0.82); }
html.dark .prefs-section-h { color: #ECF2FF; }
html.dark .prefs-th-name,
html.dark .prefs-th-channel {
  color: rgba(236, 242, 255, 0.55);
  background: rgba(255, 255, 255, 0.03);
  border-bottom-color: rgba(255, 255, 255, 0.08);
}

/* ── Appearance: palette swatches + light/dark picker ─────── */
.appearance-group { margin-bottom: 1.75rem; }
.appearance-legend {
  font-size: 0.8125rem; font-weight: 600; color: #334155; margin-bottom: 0.75rem;
}
.appearance-swatches {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.625rem;
}
@media (min-width: 640px) {
  .appearance-swatches { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.appearance-swatch { position: relative; display: block; cursor: pointer; }
.appearance-swatch input { position: absolute; opacity: 0; pointer-events: none; }
.appearance-swatch-card {
  display: flex; align-items: center; gap: 0.625rem;
  padding: 0.6875rem 0.75rem; border-radius: 0.75rem;
  border: 2px solid #e2e8f0; background: #fff;
  transition: border-color 0.12s, background-color 0.12s;
}
.appearance-swatch:hover .appearance-swatch-card { background: #f8fafc; }
.appearance-swatch input:checked + .appearance-swatch-card {
  border-color: var(--color-primary, #00B0B1);
  background: color-mix(in srgb, var(--color-primary, #00B0B1) 6%, #fff);
}
.appearance-swatch input:focus-visible + .appearance-swatch-card {
  outline: 2px solid var(--color-primary, #00B0B1); outline-offset: 2px;
}
.appearance-dot {
  width: 1.5rem; height: 1.5rem; border-radius: 999px; flex: 0 0 auto;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
}
.appearance-swatch-label { font-size: 0.875rem; font-weight: 600; color: #334155; }
/* Custom-accent picker — a round colour swatch like .appearance-dot. The
   high-specificity selector un-hides it (the rule above hides swatch inputs,
   which are radios). */
.appearance-swatch input.appearance-custom-input {
  position: static; opacity: 1; pointer-events: auto;
  width: 1.5rem; height: 1.5rem; flex: 0 0 auto;
  padding: 0; border: none; border-radius: 999px;
  background: none; cursor: pointer;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
}
.appearance-custom-input::-webkit-color-swatch-wrapper { padding: 0; }
.appearance-custom-input::-webkit-color-swatch { border: none; border-radius: 999px; }
.appearance-custom-input::-moz-color-swatch { border: none; border-radius: 999px; }
.appearance-swatch-default {
  margin-left: auto; font-size: 0.625rem; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: #94a3b8;
}
.appearance-modes { display: inline-flex; gap: 0.5rem; }
.appearance-mode-icon { width: 1.05rem; height: 1.05rem; flex: 0 0 auto; }
.appearance-mode { position: relative; cursor: pointer; }
.appearance-mode input { position: absolute; opacity: 0; pointer-events: none; }
.appearance-mode-card {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.5rem 1.125rem; border-radius: 0.625rem;
  border: 2px solid #e2e8f0; background: #fff;
  font-size: 0.875rem; font-weight: 600; color: #334155;
  transition: border-color 0.12s, background-color 0.12s;
}
.appearance-mode input:checked + .appearance-mode-card {
  border-color: var(--color-primary, #00B0B1);
  background: color-mix(in srgb, var(--color-primary, #00B0B1) 6%, #fff);
}
.appearance-mode input:focus-visible + .appearance-mode-card {
  outline: 2px solid var(--color-primary, #00B0B1); outline-offset: 2px;
}
.appearance-save {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.625rem 1.125rem; border-radius: 0.625rem;
  background: var(--color-primary, #00B0B1); color: #fff;
  font-size: 0.875rem; font-weight: 600; border: 0; cursor: pointer;
  transition: background-color 0.12s;
}
.appearance-save:hover { background: var(--color-primary-dark, #008687); }

/* Dark mode */
html.dark .appearance-legend { color: #cbd5e1; }
html.dark .appearance-swatch-card,
html.dark .appearance-mode-card { background: #1e293b; border-color: #334155; }
html.dark .appearance-swatch:hover .appearance-swatch-card { background: #263244; }
html.dark .appearance-swatch input:checked + .appearance-swatch-card,
html.dark .appearance-mode input:checked + .appearance-mode-card {
  background: color-mix(in srgb, var(--color-primary, #00B0B1) 16%, #1e293b);
}
html.dark .appearance-swatch-label { color: #e2e8f0; }
html.dark .appearance-mode-card { color: #e2e8f0; }

/* ── Notification preferences sections + table + toggles ─── */
.prefs-section {
  border-bottom: 1px solid #f1f5f9;
}
.prefs-section:last-of-type { border-bottom: 0; }
.prefs-section-h {
  margin: 0;
  padding: 1rem 1rem 0.5rem;
  font: 600 0.8125rem/1.3 system-ui, sans-serif;
  letter-spacing: -0.005em;
  color: #0f172a;
}
.prefs-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
}
.prefs-th-name {
  text-align: left;
  font-size: 0.6875rem;
  font-weight: 700;
  color: #64748b;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.625rem 1rem;
  background: #f8fafc;
  border-bottom: 1px solid #e2e8f0;
}
.prefs-th-channel {
  font-size: 0.6875rem;
  font-weight: 700;
  color: #64748b;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.625rem 1rem;
  background: #f8fafc;
  border-bottom: 1px solid #e2e8f0;
  width: 6rem;
  text-align: center;
}
.prefs-table tbody tr { border-bottom: 1px solid #f1f5f9; }
.prefs-table tbody tr:last-child { border-bottom: 0; }
.prefs-td-name {
  padding: 0.625rem 1rem;
  vertical-align: middle;
}
.prefs-td-channel {
  padding: 0.625rem 1rem;
  text-align: center;
  vertical-align: middle;
}

/* iOS-style switch (Syncc Notifications mock). On-state is brand aqua —
   was an off-brand indigo. Bigger, softer knob shadow, snappier ease. */
.prefs-toggle {
  position: relative;
  display: inline-block;
  width: 2.75rem;
  height: 1.625rem;
  cursor: pointer;
  flex: 0 0 auto;
}
.prefs-toggle input {
  opacity: 0;
  position: absolute;
  width: 0; height: 0;
}
.prefs-toggle-track {
  position: absolute;
  inset: 0;
  background: #d1d5db;
  border-radius: 9999px;
  transition: background-color 0.2s ease;
}
html.dark .prefs-toggle-track { background: rgba(255, 255, 255, 0.18); }
.prefs-toggle-track::before {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 1.375rem;
  height: 1.375rem;
  background: #fff;
  border-radius: 9999px;
  transition: transform 0.22s cubic-bezier(.4, 0, .2, 1);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.1);
}
.prefs-toggle input:checked + .prefs-toggle-track {
  background: var(--brand-teal-dark, #00b0b1);
}
html.dark .prefs-toggle input:checked + .prefs-toggle-track {
  background: var(--brand-teal-bright, #44d6d6);
}
.prefs-toggle input:checked + .prefs-toggle-track::before {
  transform: translateX(1.125rem);
}
.prefs-toggle input:focus-visible + .prefs-toggle-track {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-teal-dark, #00b0b1) 35%, transparent);
}

/* ── Notification bell + dropdown panel ────────────────────
   <details>/<summary>-driven dropdown so the open/close state
   needs no JS. The panel is absolutely positioned under the
   trigger; clicking outside closes via the native :focus-within
   pattern (helped along by a click-elsewhere shim only if needed
   later — for now native behaviour is good enough).
─────────────────────────────────────────────────────────── */
.notif-bell {
  position: relative;
}
.notif-bell > summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 0.5rem;
  color: var(--color-nav-text, #c7d2fe);
  transition: background-color 0.15s, color 0.15s;
  position: relative;
}
.notif-bell > summary::-webkit-details-marker { display: none; }
.notif-bell > summary:hover {
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
}
.notif-bell[open] > summary {
  background: rgba(255, 255, 255, 0.15);
  color: #fff;
}
.notif-bell-badge {
  position: absolute;
  top: -2px;
  right: -2px;
  min-width: 1rem;
  height: 1rem;
  padding: 0 0.25rem;
  border-radius: 9999px;
  background: #ef4444;
  color: #fff;
  font-size: 0.625rem;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--color-nav, #4338ca);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.notif-bell-panel {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  width: min(22rem, 90vw);
  /* iOS-Notification-Center vibe: translucent panel + saturated
   * blur. Falls back to opaque white via the @supports block
   * below for browsers without backdrop-filter. */
  background: rgba(255, 255, 255, 0.80);
  -webkit-backdrop-filter: blur(24px) saturate(180%);
  backdrop-filter: blur(24px) saturate(180%);
  border: 1px solid var(--border-hairline);
  border-radius: 0.875rem;
  box-shadow: 0 14px 32px -10px rgba(15, 23, 42, 0.22);
  z-index: 60;
  overflow: hidden;
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .notif-bell-panel { background: #fff; }
}
.notif-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.625rem 0.875rem;
  border-bottom: 1px solid #f1f5f9;
}
.notif-mark-all {
  font-size: 0.6875rem;
  font-weight: 600;
  color: #4f46e5;
  background: none;
  border: none;
  cursor: pointer;
}
.notif-mark-all:hover { color: #3730a3; text-decoration: underline; }

/* Push opt-in pill — shown only until the user grants notification
 * permission. push-init.js sets data-push-state="on" once we have a
 * server-side subscription, at which point CSS collapses the button. */
/* Push opt-in card — polished iMessage-clean banner that lives
 * at the top of the bell dropdown.  Whole card is the tap target
 * (44px+ tall) so the user doesn't have to aim for a 12 px
 * button.  Collapses when push is already on (or unavailable).
 * Maintainer May 16 ask: "more polish, rather than a standalone
 * button". */
.notif-push-card {
  display: flex;
  align-items: center;
  gap: 12px;
  width: calc(100% - 1rem);
  margin: 0.5rem 0.5rem;
  padding: 12px 14px;
  border-radius: 14px;
  border: 1px solid var(--brand-teal-line, rgba(26, 185, 148, 0.22));
  background: var(--brand-teal-soft, rgba(37, 214, 173, 0.08));
  color: var(--brand-teal-dark, #0F8A6B);
  font: inherit;
  cursor: pointer;
  text-align: left;
  min-height: 56px;
  transition: background 120ms ease;
}
.notif-push-card:hover {
  background: var(--brand-teal-soft, rgba(37, 214, 173, 0.14));
}
.notif-push-card[disabled] {
  cursor: default;
  opacity: 0.65;
}
.notif-push-card[data-push-state="on"] {
  /* Hide once subscribed — there's nothing actionable left.  Note
   * we deliberately do NOT hide ``state="unavailable"`` (Gemini
   * #797 r4): the unavailable copy ("Push not enabled on this
   * server" / "Push not supported on this browser") IS the value
   * the admin needs to see — hiding it would just reintroduce the
   * silent failure the polish was meant to eliminate. */
  display: none;
}

/* Debug zone (May 16) — "Send test push" button + inline diagnostic
 * line.  Hidden by default; the script that wires the polished
 * push card sets ``data-push-state="on"`` on the card when a
 * subscription is live, and we show the debug button only in that
 * state (a test push is meaningless before opt-in). */
/* Always visible — the "Send test push" diagnostic was previously
 * hidden until the polished push card flipped to
 * data-push-state="on", but that's circular: when push opt-in
 * fails (FCM 403, keypair mismatch, etc.) the card never flips
 * on, and the admin can't reach the diagnostic that would tell
 * them why.  The test endpoint already handles every state
 * (server_not_configured, no_subscription self-heal, real 403
 * reason inlined) so showing the button always is strictly more
 * useful.  Maintainer May 18: "I cant see send test push on
 * mobile views". */
.notif-push-debug {
  display: block;
  margin: 0 0.5rem 8px;
  padding: 0;
}
.notif-push-test {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 8px 12px;
  min-height: 36px;
  border-radius: 10px;
  border: 1px solid rgba(15, 23, 42, 0.10);
  background: rgba(15, 23, 42, 0.04);
  color: inherit;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.notif-push-test:hover { background: rgba(15, 23, 42, 0.08); }
.notif-push-test[disabled] { cursor: default; opacity: 0.65; }

.notif-push-test-result {
  margin: 6px 0 0;
  font-size: 12px;
  line-height: 1.4;
  color: var(--color-text-muted, #64748b);
}
.notif-push-test-result:empty { display: none; }
.notif-push-test-result[data-push-test-state="ok"]  { color: #0d9488; }
.notif-push-test-result[data-push-test-state="err"] { color: #b45309; }

html.dark .notif-push-test {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.10);
}
html.dark .notif-push-test:hover { background: rgba(255, 255, 255, 0.12); }

.notif-push-icon {
  flex: 0 0 32px;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: var(--brand-teal-soft, rgba(37, 214, 173, 0.16));
  color: var(--brand-teal-dark, #0F8A6B);
}
.notif-push-icon svg { width: 18px; height: 18px; }

.notif-push-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.notif-push-title {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.25;
  color: inherit;
}
.notif-push-sub {
  font-size: 12px;
  line-height: 1.35;
  color: var(--color-text-muted, #64748b);
  font-weight: 400;
}

.notif-push-chev {
  flex: 0 0 auto;
  color: var(--color-text-muted, #64748b);
  opacity: 0.6;
}
.notif-push-chev svg { width: 14px; height: 14px; display: block; }

html.dark .notif-push-card {
  background: rgba(37, 214, 173, 0.08);
  color: var(--brand-teal-bright, #25D6AD);
  border-color: rgba(37, 214, 173, 0.18);
}
html.dark .notif-push-card:hover { background: rgba(37, 214, 173, 0.14); }
html.dark .notif-push-icon {
  background: rgba(37, 214, 173, 0.16);
  color: var(--brand-teal-bright, #25D6AD);
}
html.dark .notif-push-sub  { color: rgba(226, 232, 240, 0.65); }
html.dark .notif-push-chev { color: rgba(226, 232, 240, 0.50); }

.notif-panel-footer {
  padding: 0.5rem 0.875rem;
  border-top: 1px solid #f1f5f9;
  background: #fafbfc;
  text-align: center;
}
.notif-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 24rem;
  overflow-y: auto;
}
.notif-empty {
  padding: 1.25rem;
  text-align: center;
}

/* Each row is a button-like form. Unread rows get a left accent. */
.notif-row {
  position: relative;
  display: flex;
  align-items: stretch;
  border-bottom: 1px solid #f1f5f9;
}
.notif-row:last-child { border-bottom: 0; }

/* When the row is ALSO swipeable (bell dropdown + inbox page),
 * `.notif-row`'s default flex layout fights the swipe pattern —
 * the absolutely-positioned `.notif-swipe-actions` ends up
 * rendering inline as a flex sibling instead of being clipped
 * behind `.notif-swipe-content`. Maintainer May 14 screenshot
 * showed the red DISMISS panel sitting next to the card.
 *
 * Switch the combined element to a block so the swipeable
 * pattern works: actions absolute-positioned at the right edge,
 * hidden behind the foreground until the user drags. */
.notif-row.notif-swipeable {
  display: block;
  align-items: initial;
}
.notif-row.is-unread {
  background: linear-gradient(90deg, rgba(99, 102, 241, 0.07) 0%, transparent 50%);
}
.notif-row.is-unread::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: #6366f1;
}
.notif-row-form { flex: 1; display: flex; }
.notif-row-btn {
  width: 100%;
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  padding: 0.625rem 0.875rem;
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
  color: inherit;
  font: inherit;
  transition: background-color 0.12s;
}
.notif-row-btn:hover { background: rgba(15, 23, 42, 0.04); }
html.dark .notif-row-btn:hover { background: rgba(255, 255, 255, 0.06); }
.notif-icon {
  flex-shrink: 0;
  width: 2rem;
  height: 2rem;
  border-radius: 0.5rem;
  background: #eef2ff;
  color: #4338ca;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.notif-icon svg { width: 1rem; height: 1rem; }
.notif-icon[data-kind="proposal_approved"],
.notif-icon[data-kind="registration_confirmed"] { background: #dcfce7; color: #15803d; }
.notif-icon[data-kind="proposal_rejected"],
.notif-icon[data-kind="registration_cancelled"] { background: #fee2e2; color: #b91c1c; }
.notif-icon[data-kind="waitlist_promoted"]      { background: #fef3c7; color: #92400e; }
.notif-icon[data-kind="event_tomorrow"]         { background: #fef3c7; color: #92400e; }
.notif-icon[data-kind="campaign_deadline"]      { background: #fef2f2; color: #be123c; }
.notif-icon[data-kind="supervisor_assigned"]    { background: #dbeafe; color: #1d4ed8; }

.notif-body {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  min-width: 0;
  flex: 1;
}
.notif-title {
  font-size: 0.8125rem;
  font-weight: 600;
  color: #0f172a;
  line-height: 1.25;
}
.notif-row.is-unread .notif-title { font-weight: 700; }
.notif-snippet {
  font-size: 0.75rem;
  color: #64748b;
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.notif-time {
  font-size: 0.6875rem;
  color: #94a3b8;
  margin-top: 0.125rem;
  font-variant-numeric: tabular-nums;
}
/* Bell row meta — intent dot + "Coordinate · Hall pass" path + relative
   time, mirroring the notifications center so the two surfaces read the
   same. The dot carries the module intent color. */
.notif-meta {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.6875rem;
  color: #94a3b8;
  margin-top: 0.1875rem;
}
.notif-meta-sep { color: #cbd5e1; }
.notif-pathdot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #94a3b8;
  flex: 0 0 6px;
}
.notif-pathdot.coord   { background: var(--brand-teal-dark, #00b0b1); }
.notif-pathdot.comm    { background: #0ea5e9; }
.notif-pathdot.plan    { background: #f59e0b; }
.notif-pathdot.people  { background: #10b981; }
.notif-pathdot.reports { background: #6366f1; }
.notif-pathdot.urgent  { background: #f43f5e; }
html.dark .notif-meta { color: rgba(255, 255, 255, 0.50); }
html.dark .notif-meta-sep { color: rgba(255, 255, 255, 0.22); }

/* Inbox-page rows — same component as the bell-dropdown rows but
 * inside a card container. Maintainer May 14: "full notifications
 * page is vague too, apply same styling." Switched to the
 * translucent recipe (matches the bell-panel + swipe-content) so
 * rows lift off the dark page aurora the same way. The actual
 * background lives on `.notif-swipe-content` (the foreground
 * card); the wrapper just contributes the rounded clipping shell.
 * Border kept as a hairline for definition; the unread accent is
 * handled by the existing left-rail rule above. */
.notif-row-page {
  background: transparent;
  border: 0;
  border-radius: 14px;
  padding-right: 0;
}
.notif-row-page.is-unread .notif-swipe-content {
  border-color: rgba(37, 214, 173, 0.40);
}
html.dark .notif-row-page.is-unread .notif-swipe-content {
  border-color: rgba(37, 214, 173, 0.45);
}
.notif-dismiss {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 0.375rem;
  background: none;
  border: none;
  color: #94a3b8;
  cursor: pointer;
  align-self: center;
  transition: background-color 0.12s, color 0.12s;
}
.notif-dismiss:hover { background: #f1f5f9; color: #334155; }

/* ── Campaign starter-template catalog tiles ───────────────
   3-column grid of tiles, one per template. Whole tile is a
   click target — hover lifts the card slightly.
─────────────────────────────────────────────────────────── */
.tmpl-card {
  background: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.875rem;
  padding: 1.125rem;
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
  text-decoration: none;
  color: inherit;
}
.tmpl-card:hover {
  border-color: #c7d2fe;
  box-shadow: 0 6px 18px -8px rgba(79, 70, 229, 0.18);
  transform: translateY(-2px);
}
.tmpl-card-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.125rem 0.5rem;
  border-radius: 9999px;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  background: #eef2ff;
  color: #4338ca;
  text-transform: uppercase;
}
.tmpl-card-title {
  font-size: 1rem;
  font-weight: 700;
  color: #0f172a;
  line-height: 1.25;
}
.tmpl-card-desc {
  font-size: 0.8125rem;
  color: #64748b;
  margin-top: 0.375rem;
  line-height: 1.5;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.tmpl-card-stats {
  display: flex;
  gap: 1.25rem;
  margin-top: 0.875rem;
  padding-top: 0.75rem;
  border-top: 1px solid #f1f5f9;
}
.tmpl-card-stats > div { display: flex; align-items: baseline; gap: 0.25rem; }
.tmpl-stat-value {
  font-size: 1.125rem;
  font-weight: 700;
  color: #4f46e5;
  font-variant-numeric: tabular-nums;
}
.tmpl-stat-label {
  font-size: 0.75rem;
  color: #64748b;
}
.tmpl-card-cta {
  margin-top: 0.625rem;
  font-size: 0.8125rem;
  font-weight: 600;
  color: #4f46e5;
}

/* ── Workshop Proposals — admin queue ──────────────────────
   Hero card with icon chip; KPI strip; status-stripe cards.
─────────────────────────────────────────────────────────── */
/* Brand-aligned hero — Apple-HIG-on-navy, matches the My Day
   hero (`.myday-hero` in my-day.css) so every admin command
   surface reads as the same primitive: navy base + radial
   teal aurora top-right + faint grid texture masked to upper
   right + white typography with teal-bright eyebrow. The
   maintainer's rule (CLAUDE.md "Hero gradient + glass pill"):
   any new command surface starts here, not from a flat tint. */
.prop-hero {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.875rem;
  /* Defensive fallback: --brand-navy is defined in brand.css
     at :root and loads before custom.css, but the fallback hex
     guards against load-order regressions and per-page CSS
     resets that might shadow the variable. */
  background: var(--brand-navy, #0A1628);
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 1rem;
  padding: 1.25rem;
  margin-bottom: 1.25rem;
  overflow: hidden;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
}
.prop-hero::before {
  content: '';
  position: absolute;
  inset: -40% -10% auto -10%;
  height: 380px;
  background:
    radial-gradient(ellipse at 70% 0%, rgba(26, 185, 148, 0.34) 0%, transparent 60%);
  filter: blur(24px);
  pointer-events: none;
}
.prop-hero::after {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, rgba(255, 255, 255, 0.04) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255, 255, 255, 0.04) 1px, transparent 1px);
  background-size: 56px 56px;
  -webkit-mask-image: radial-gradient(ellipse 70% 60% at 80% 30%, #000 0%, transparent 75%);
  mask-image: radial-gradient(ellipse 70% 60% at 80% 30%, #000 0%, transparent 75%);
  pointer-events: none;
}
.prop-hero > * { position: relative; z-index: 1; }
/* Stable hook for the action button row inside a .prop-hero header.
   Wraps to its own row on narrow screens so the title doesn't get
   squeezed by side-by-side buttons. */
.prop-hero-actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  flex-shrink: 0;
  margin-left: auto;
}
@media (max-width: 640px) {
  .prop-hero { padding: 1rem; }
  .prop-hero-title { font-size: 1.125rem; }
  .prop-hero-actions {
    flex-basis: 100%;
    margin-left: 0;
    margin-top: 0.25rem;
  }
  /* Make every action element stretch on tiny phones so the row
     reads as a stack of full-width tappable rectangles, not a half-
     overflowing hbox. Single-button heroes (cohorts/proposals) just
     get one full-width button. */
  .prop-hero-actions > * { flex: 1 1 auto; text-align: center; justify-content: center; }
}
.prop-hero-icon {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 0.625rem;
  background: linear-gradient(140deg,
      var(--brand-teal-bright, #25D6AD) 0%,
      var(--brand-teal-dark, #0F8A6B) 100%);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  /* Teal glow on navy reads as live brand light, not a drop
     shadow. Soft + diffuse so it doesn't look pasted on. */
  box-shadow: 0 6px 18px -6px rgba(26, 185, 148, 0.55);
}
.prop-hero-icon svg { width: 1.25rem; height: 1.25rem; }
.prop-hero-eyebrow {
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--brand-teal-bright, #25D6AD);
  text-transform: uppercase;
  letter-spacing: 0.18em;
}
.prop-hero-title {
  font-size: 1.375rem;
  font-weight: 700;
  color: #fff;
  line-height: 1.2;
  margin-top: 0.125rem;
}
.prop-hero-sub {
  font-size: 0.8125rem;
  color: rgba(255, 255, 255, 0.65);
  margin-top: 0.375rem;
  max-width: 60ch;
}

/* Glass-pill secondary actions on the hero — matches the
   `.myday-nextbell` translucency vocabulary so buttons read
   as the same surface family as the rest of the hero. The
   primary CTA stays bright teal (high-contrast call to
   action); the secondary buttons recede into glass. */
.prop-hero-actions .btn-secondary {
  background-color: rgba(255, 255, 255, 0.06);
  color: #fff;
  border-color: rgba(255, 255, 255, 0.16);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}
.prop-hero-actions .btn-secondary:hover {
  background-color: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.28);
}

.prop-kpi {
  background: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.75rem;
  padding: 0.75rem 0.875rem;
}
.prop-kpi-label {
  font-size: 0.6875rem;
  font-weight: 600;
  color: #64748b;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.prop-kpi-value {
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.1;
  margin-top: 0.25rem;
  font-variant-numeric: tabular-nums;
}

/* Proposal card with a left status stripe so the queue scans by color. */
.prop-card {
  display: flex;
  background: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.875rem;
  overflow: hidden;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.prop-card:hover {
  border-color: var(--border-hairline-strong);
  box-shadow: 0 4px 14px -8px rgba(15, 23, 42, 0.1);
}
.prop-card-stripe {
  width: 4px;
  flex-shrink: 0;
}
.prop-card-body {
  flex: 1;
  padding: 1rem 1.125rem;
  min-width: 0;
}
.prop-accent-amber   .prop-card-stripe { background: #f59e0b; }
.prop-accent-emerald .prop-card-stripe { background: #10b981; }
.prop-accent-rose    .prop-card-stripe { background: #f43f5e; }

.prop-card-title {
  font-size: 0.9375rem;
  font-weight: 700;
  color: #0f172a;
  line-height: 1.25;
}
.prop-card-meta {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  flex-wrap: wrap;
  font-size: 0.75rem;
  color: #64748b;
  margin-top: 0.125rem;
}
.prop-card-meta svg { display: inline-block; }
.prop-card-desc {
  font-size: 0.8125rem;
  color: #475569;
  line-height: 1.55;
  margin-top: 0.625rem;
}
.prop-card-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  margin-top: 0.625rem;
  font-size: 0.75rem;
}
.prop-tag { display: inline-flex; gap: 0.25rem; }

.prop-status-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.125rem 0.5rem;
  border-radius: 9999px;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.prop-status-amber   { background: #fef3c7; color: #92400e; }
.prop-status-emerald { background: #d1fae5; color: #065f46; }
.prop-status-rose    { background: #fee2e2; color: #991b1b; }

.prop-callout {
  margin-top: 0.625rem;
  padding: 0.5rem 0.75rem;
  border-radius: 0.5rem;
  font-size: 0.8125rem;
  line-height: 1.5;
}
.prop-callout-label {
  display: block;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 0.125rem;
  opacity: 0.7;
}
.prop-callout-slate  { background: #f8fafc; color: #475569; border: 1px solid #f1f5f9; }
.prop-callout-indigo { background: #eef2ff; color: #4338ca; border: 1px solid #c7d2fe; }
.prop-callout-rose   { background: #fef2f2; color: #991b1b; border: 1px solid #fecaca; }

.prop-btn-reject {
  font-size: 0.875rem;
  font-weight: 600;
  color: #be123c;
  padding: 0.5rem 0.875rem;
  border-radius: 0.5rem;
  border: 1px solid #fecdd3;
  background: #fff;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.prop-btn-reject:hover { background: #fff1f2; border-color: #fda4af; }

.prop-empty {
  text-align: center;
  padding: 4rem 1rem;
  background: #fff;
  border: 1px dashed #cbd5e1;
  border-radius: 0.875rem;
}

/* ── Analytics — event-scoped page ─────────────────────────
   Header with event selector, stat strip, charts row, and
   the heatmap (event rollup OR per-event workshop rows).
─────────────────────────────────────────────────────────── */
.an-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.75rem;
}
.an-header-actions {
  display: flex;
  gap: 0.625rem;
  align-items: center;
  flex-wrap: wrap;
}
.an-selector-form { margin: 0; }
.an-select {
  height: 2.25rem;
  padding: 0 0.625rem;
  border: 1px solid #cbd5e1;
  border-radius: 0.5rem;
  font-size: 0.875rem;
  color: #0f172a;
  background-color: #fff;
  min-width: 18rem;
  cursor: pointer;
  transition: border-color 0.15s;
}
.an-select:hover  { border-color: #94a3b8; }
.an-select:focus  { outline: none; border-color: var(--color-primary); box-shadow: 0 0 0 3px var(--color-ring); }

.an-stat {
  background-color: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.75rem;
  padding: 0.875rem 1rem;
}
.an-stat-label {
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #94a3b8;
}
.an-stat-value {
  font-size: 1.75rem;
  font-weight: 700;
  margin-top: 0.25rem;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

.an-card {
  background-color: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.75rem;
  overflow: hidden;
}
.an-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.75rem 1.25rem;
  border-bottom: 1px solid #f1f5f9;
}

.an-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.25rem 0.625rem;
  border-radius: 9999px;
  font-size: 0.75rem;
  border: 1px solid;
}
.an-pill-rose    { background: #fff1f2; color: #be123c; border-color: #fecdd3; }
.an-pill-amber   { background: #fffbeb; color: #b45309; border-color: #fde68a; }
.an-pill-emerald { background: #ecfdf5; color: #065f46; border-color: #a7f3d0; }

.an-fill-legend {
  display: inline-flex;
  align-items: center;
  gap: 0.875rem;
  font-size: 0.6875rem;
  color: #94a3b8;
}
.an-fill-legend > span { display: inline-flex; align-items: center; gap: 0.3125rem; }
.an-swatch { width: 0.625rem; height: 0.625rem; border-radius: 0.125rem; display: inline-block; }

.an-fill-cell {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  justify-content: flex-end;
}
.an-fill-bar {
  width: 4rem;
  height: 0.375rem;
  background-color: #f1f5f9;
  border-radius: 0.125rem;
  overflow: hidden;
}
.an-fill-fill {
  height: 100%;
  border-radius: 0.125rem;
  transition: width 0.3s ease;
}
.an-fill-rose    { background-color: #f87171; }
.an-fill-amber   { background-color: #fbbf24; }
.an-fill-emerald { background-color: #34d399; }
.an-fill-slate   { background-color: #cbd5e1; }
.an-fill-pct {
  font-size: 0.75rem;
  font-weight: 600;
  color: #475569;
  font-variant-numeric: tabular-nums;
  min-width: 2.75rem;
  text-align: right;
}

@media (max-width: 640px) {
  .an-select { min-width: 0; width: 100%; }
  .an-header-actions { width: 100%; }
}


/* ── Search input clear-× (May 15) ──────────────────────────
 * Companion CSS for `app/static/js/search-clear.js` which wraps
 * every search-y input in a `.search-clear-wrap` and injects a
 * × button. Visible only when the input has a value. Position
 * absolute against the wrapper so it floats over the right pad
 * of the input.
 *──────────────────────────────────────────────────────────── */
.search-clear-wrap {
  position: relative;
  display: inline-block;
  width: 100%;
}
.search-clear-wrap > input {
  /* Reserve room for the × so the user's text doesn't run under
   * it. 32px = button hit area + a touch of breathing room. */
  padding-right: 32px;
  /* Hide the native WebKit search-cancel button (Chrome/Safari)
   * so we don't render two ×s on top of each other. */
  -webkit-appearance: none;
  appearance: none;
}
.search-clear-wrap > input::-webkit-search-decoration,
.search-clear-wrap > input::-webkit-search-cancel-button,
.search-clear-wrap > input::-webkit-search-results-button,
.search-clear-wrap > input::-webkit-search-results-decoration {
  -webkit-appearance: none;
  display: none;
}
.search-clear {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  width: 22px; height: 22px;
  padding: 0;
  display: none;             /* hidden by default */
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: #94a3b8;
  cursor: pointer;
  transition: color 0.12s ease, transform 0.08s ease, background 0.12s ease;
}
.search-clear:hover { color: #475569; background: rgba(15, 23, 42, 0.06); }
.search-clear:active { transform: translateY(-50%) scale(0.9); }
.search-clear svg { width: 18px; height: 18px; }
.search-clear-wrap.has-value .search-clear { display: inline-flex; }
html.dark .search-clear { color: #94a3b8; }
html.dark .search-clear:hover { color: #e2e8f0; background: rgba(255, 255, 255, 0.10); }


/* ── Width primitive: `.page-centered` (May 15) ────────────
 * For single-column pages (forms, settings panels, single-stream
 * lists, reassurance heroes). Matches the admin dashboard's
 * center column width at every breakpoint so a settings page or
 * an edit form reads as wide as the *middle* of the dashboard
 * rather than stretching full-bleed.
 *
 * Formula on >= 1024px:
 *   max-width = 100vw - sidebar - rail - 3 * gap
 *   sidebar   = clamp(224px, 16vw, 280px)
 *   rail      = clamp(280px, 22vw, 360px)
 *   gap       = var(--cc-gap, 24px) — same value the dashboard
 *               shell uses between its grid tracks
 *
 * Below 1024px there's no sidebar/rail to subtract, so the page
 * flows full-width with the normal viewport gutter.
 *
 * Maintainer May 15 rule: "if only one column, keep them in
 * the center, width max as middle column size of the admin
 * dashboard. if multiple columns leave them a full width."
 * Multi-column pages (master/detail, calendar, modules grid,
 * etc.) DO NOT carry this class — they stay full-bleed.
 *────────────────────────────────────────────────────────────*/
.page-centered {
  width: 100%;
  max-width: 100%;
  margin-left: auto;
  margin-right: auto;
}
@media (min-width: 1024px) {
  /* Maintainer May 15 (post-merge): "most of single column pages
     are too wide, lets cap max as the navbar size." 80rem matches
     `.navbar-inner` max-width above — single-column pages now hit
     the same right edge as the navbar's content rather than
     stretching to the full viewport-minus-rails formula. */
  .page-centered {
    max-width: 80rem;
  }
}


/* ── Module landing chrome (ml-*) ──────────────────────────
   Reusable shape for any module's landing page (Events today,
   WBL / Clubs / etc. tomorrow). Components:
     .ml-hero        — title + icon + primary action
     .ml-kpis        — 4-up stat strip
     .ml-tools       — quick-access grid (tool cards)
     .ml-filter-tabs — chip-style filter tab strip
     .ml-event-row   — primary content row (events list today)
     .ml-empty       — filter-aware empty state card
   When refactor item #8 lands a shared partial, this block
   should map across with no class-name changes.
─────────────────────────────────────────────────────────── */
.ml-hero {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1.5rem;
}
.ml-hero-icon {
  width: 3rem; height: 3rem;
  border-radius: 0.75rem;
  /* Quick-dial soft-tint recipe (May 15 redesign) — 18% teal
     background + deep-tone stroke. Matches the dock + dashboard
     quick-action recipe so every module's landing reads with the
     same visual vocabulary. Per-module overrides via inline style
     (rooms.html sets purple) still win. */
  background: rgba(37, 214, 173, 0.18);
  color: #0F8A6B;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
html.dark .ml-hero-icon { color: #25D6AD; }
.ml-hero-icon svg { width: 1.5rem; height: 1.5rem; }
.ml-hero-cta {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.5rem 1rem;
  background-color: var(--color-primary);
  color: #fff;
  font-weight: 600;
  font-size: 0.875rem;
  border-radius: 0.625rem;
  transition: background-color 0.15s;
  text-decoration: none;
  flex-shrink: 0;
}
.ml-hero-cta:hover { background-color: var(--color-primary-dark); }

/* Icon-only variant of the hero CTA — used by the events landing
   "+" create button after the May 15 maintainer "make it just +"
   ask. Square aspect, same fill as the labeled variant. */
.ml-hero-cta--icon {
  padding: 0;
  width: 2.25rem;
  height: 2.25rem;
  justify-content: center;
}

/* .ml-hero-tool — compact icon button inline with the hero CTA
   cluster (May 15: tool tiles collapse from a full-width grid to
   icon-only buttons). Hover bumps the border + glows teal. */
.ml-hero-tool {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 0.625rem;
  border: 1px solid var(--border-hairline);
  color: var(--brand-ink-2, #2C3D52);
  background: transparent;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
  text-decoration: none;
  flex-shrink: 0;
}
.ml-hero-tool svg { width: 1.125rem; height: 1.125rem; }
.ml-hero-tool:hover {
  border-color: var(--brand-teal-line, rgba(26, 185, 148, 0.28));
  color: var(--brand-teal-dark, #0F8A6B);
  background: var(--brand-teal-soft, rgba(26, 185, 148, 0.10));
}
html.dark .ml-hero-tool {
  border-color: var(--brand-line-dk, rgba(255, 255, 255, 0.08));
  color: rgba(255, 255, 255, 0.78);
}
html.dark .ml-hero-tool:hover {
  border-color: rgba(37, 214, 173, 0.36);
  color: var(--brand-teal-bright, #25D6AD);
  background: rgba(37, 214, 173, 0.10);
}

.ml-kpis {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.625rem;
  margin-bottom: 2rem;
}
@media (min-width: 640px) {
  .ml-kpis { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.ml-kpi {
  /* Glass material recipe (May 15) — same as dashboard KPI strip.
   * Translucent fill + backdrop-blur instead of opaque white card. */
  background: rgba(255, 255, 255, 0.06);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  backdrop-filter: blur(14px) saturate(160%);
  border: 1px solid var(--border-hairline);
  border-radius: 0.75rem;
  padding: 0.875rem 1rem;
}
html.dark .ml-kpi { background: rgba(255, 255, 255, 0.04); }
.ml-kpi-label {
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #94a3b8;
}
.ml-kpi-value {
  font-size: 1.75rem;
  font-weight: 700;
  margin-top: 0.25rem;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

.ml-tools {
  display: grid;
  grid-template-columns: repeat(1, minmax(0, 1fr));
  gap: 0.625rem;
}
@media (min-width: 640px) { .ml-tools { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .ml-tools { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.ml-tool {
  background-color: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.75rem;
  padding: 0.875rem 1rem;
  display: flex; align-items: center; gap: 0.75rem;
  text-decoration: none;
  color: inherit;
  position: relative;
  transition: box-shadow 0.18s, transform 0.18s, border-color 0.18s;
}
.ml-tool:hover {
  box-shadow: 0 6px 18px -6px rgba(15, 23, 42, 0.10);
  transform: translateY(-1px);
  border-color: #cbd5e1;
}
.ml-tool .ml-tool-icon {
  width: 2.25rem; height: 2.25rem;
  border-radius: 0.5rem;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  /* Default soft-tint slate when no `.accent-<color>` modifier
   * supplied. Per-accent rules below replace this. */
  background: rgba(148, 163, 184, 0.18);
  color: #475569;
}
.ml-tool .ml-tool-icon svg { width: 1.125rem; height: 1.125rem; }

/* Quick-dial soft-tint recipe (May 15) — replaces the saturated
 * solid-color icon container with an 18% accent tint + deep-tone
 * stroke. Matches the dock + dashboard quick-actions + macOS dock
 * so every module landing speaks the same visual vocabulary. */
.ml-tool.accent-teal    .ml-tool-icon { background: rgba(37, 214, 173, 0.18); color: #0F8A6B; }
.ml-tool.accent-emerald .ml-tool-icon { background: rgba(52, 211, 153, 0.18); color: #047857; }
.ml-tool.accent-amber   .ml-tool-icon { background: rgba(251, 191, 36, 0.18); color: #b45309; }
.ml-tool.accent-violet  .ml-tool-icon { background: rgba(167, 139, 250, 0.18); color: #6d28d9; }
.ml-tool.accent-indigo  .ml-tool-icon { background: rgba(129, 140, 248, 0.18); color: #4f46e5; }
.ml-tool.accent-sky     .ml-tool-icon { background: rgba(56, 189, 248, 0.18); color: #0369a1; }
.ml-tool.accent-rose    .ml-tool-icon { background: rgba(251, 113, 133, 0.18); color: #be123c; }
.ml-tool.accent-blue    .ml-tool-icon { background: rgba(59, 130, 246, 0.18); color: #1d4ed8; }
.ml-tool.accent-cyan    .ml-tool-icon { background: rgba(34, 211, 238, 0.18); color: #0e7490; }
.ml-tool.accent-pink    .ml-tool-icon { background: rgba(236, 72, 153, 0.18); color: #be185d; }

/* Dark mode: stroke flips to the bright accent tone for legibility
 * on dark backgrounds; the 18% rgba tint stays the same. */
html.dark .ml-tool.accent-teal    .ml-tool-icon { color: #25D6AD; }
html.dark .ml-tool.accent-emerald .ml-tool-icon { color: #6ee7b7; }
html.dark .ml-tool.accent-amber   .ml-tool-icon { color: #fcd34d; }
html.dark .ml-tool.accent-violet  .ml-tool-icon { color: #c4b5fd; }
html.dark .ml-tool.accent-indigo  .ml-tool-icon { color: #a5b4fc; }
html.dark .ml-tool.accent-sky     .ml-tool-icon { color: #7dd3fc; }
html.dark .ml-tool.accent-rose    .ml-tool-icon { color: #fda4af; }
html.dark .ml-tool.accent-blue    .ml-tool-icon { color: #93c5fd; }
html.dark .ml-tool.accent-cyan    .ml-tool-icon { color: #67e8f9; }
html.dark .ml-tool.accent-pink    .ml-tool-icon { color: #f9a8d4; }
.ml-tool-badge {
  position: absolute;
  top: -0.375rem; right: -0.375rem;
  min-width: 1.25rem; height: 1.25rem; padding: 0 0.375rem;
  border-radius: 9999px;
  background-color: #f59e0b;
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
  border: 2px solid #fff;
  font-variant-numeric: tabular-nums;
}

.ml-filter-tabs {
  display: inline-flex;
  border: 1px solid var(--border-hairline);
  border-radius: 0.5rem;
  overflow: hidden;
  background-color: #fff;
}
.ml-filter-tab {
  padding: 0.4rem 0.75rem;
  font-size: 0.8125rem;
  font-weight: 500;
  color: #475569;
  background-color: #fff;
  border-right: 1px solid #e2e8f0;
  cursor: pointer;
  transition: background-color 0.12s, color 0.12s;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
}
.ml-filter-tab:last-child { border-right: none; }
.ml-filter-tab:hover { background-color: #f8fafc; }
.ml-filter-tab.is-active {
  background-color: #0f172a;
  color: #fff;
  font-weight: 600;
}
.ml-filter-count {
  font-size: 0.6875rem;
  font-weight: 600;
  color: #94a3b8;
  background-color: #f1f5f9;
  border-radius: 9999px;
  padding: 0 0.375rem;
  min-width: 1.25rem;
  text-align: center;
}
.ml-filter-tab.is-active .ml-filter-count {
  background-color: rgba(255,255,255,0.18);
  color: #fff;
}

.ml-event-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  background-color: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.75rem;
  padding: 1rem 1.25rem;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.ml-event-row:hover {
  border-color: #c7d2fe;
  box-shadow: 0 4px 14px -6px rgba(99, 102, 241, 0.18);
}
.ml-event-date {
  width: 3.5rem; height: 3.5rem;
  border-radius: 0.625rem;
  background: linear-gradient(140deg, #e0e7ff 0%, #c7d2fe 100%);
  color: #4338ca;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  flex-shrink: 0;
  line-height: 1;
}
.ml-event-day { font-size: 1.125rem; font-weight: 800; }
.ml-event-mon { font-size: 0.625rem; font-weight: 700; opacity: 0.85; margin-top: 0.125rem; letter-spacing: 0.04em; }

.ml-empty {
  text-align: center;
  padding: 3rem 1.5rem;
  border: 2px dashed #e2e8f0;
  border-radius: 0.875rem;
  background-color: #f8fafc;
}

@media (max-width: 640px) {
  .ml-hero-cta { padding: 0.4rem 0.75rem; font-size: 0.8125rem; }
  .ml-event-row { flex-wrap: wrap; }
}


/* ── Today banner ──────────────────────────────────────────
   Sits above the dashboard. Uses the same indigo family as
   the rest of the admin panel (date chips, hero icons,
   primary CTAs) — the prior amber/gold treatment clashed with
   the panel tone.
─────────────────────────────────────────────────────────── */
.today-banner {
  background: linear-gradient(135deg, #eef2ff 0%, #e0e7ff 70%, #c7d2fe 100%);
  border: 1px solid #c7d2fe;
  border-radius: 0.875rem;
  padding: 0.875rem 1rem 0.875rem 1.125rem;
  box-shadow: 0 4px 16px -8px rgba(99, 102, 241, 0.25);
  position: relative;
  overflow: hidden;
}
.today-banner::before {
  /* Subtle radial highlight on the left edge to draw the eye. */
  content: '';
  position: absolute;
  top: -2rem; left: -3rem;
  width: 12rem; height: 12rem;
  background: radial-gradient(circle, rgba(255,255,255,0.55) 0%, rgba(255,255,255,0) 60%);
  pointer-events: none;
}

.today-banner-header {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  margin-bottom: 0.625rem;
  position: relative;
}
.today-banner-icon {
  width: 1.5rem; height: 1.5rem;
  color: #4338ca;
  flex-shrink: 0;
}
.today-banner-eyebrow {
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #4338ca;
  opacity: 0.75;
}
.today-banner-title {
  font-size: 0.9375rem;
  font-weight: 700;
  color: #1e1b4b;
  line-height: 1.2;
  margin-top: 0.0625rem;
}

.today-banner-rows {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  position: relative;
}

.today-row {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  background-color: rgba(255, 255, 255, 0.85);
  border: 1px solid rgba(199, 210, 254, 0.7);
  border-radius: 0.625rem;
  padding: 0.625rem 0.875rem;
  transition: background-color 0.15s, transform 0.15s;
}
.today-row:hover {
  background-color: rgba(255, 255, 255, 0.97);
  transform: translateX(2px);
}

.today-row-time {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  flex-shrink: 0;
  width: 3.5rem;
  line-height: 1;
}
.today-row-time-start {
  font-size: 1.125rem;
  font-weight: 800;
  color: #4338ca;
  font-variant-numeric: tabular-nums;
}
.today-row-time-suffix {
  font-size: 0.625rem;
  font-weight: 700;
  color: #4338ca;
  opacity: 0.7;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-top: 0.125rem;
}

.today-row-title {
  font-size: 0.9375rem;
  font-weight: 600;
  color: #1f2937;
  line-height: 1.3;
}
.today-row-meta {
  font-size: 0.75rem;
  color: #4338ca;
  margin-top: 0.125rem;
  opacity: 0.85;
}
.today-row-dot { margin: 0 0.375rem; opacity: 0.5; }

.today-row-actions {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  flex-shrink: 0;
}
.today-row-action {
  font-size: 0.75rem;
  font-weight: 600;
  color: #4338ca;
  padding: 0.3125rem 0.625rem;
  border-radius: 0.375rem;
  text-decoration: none;
  transition: background-color 0.12s, color 0.12s;
  white-space: nowrap;
}
.today-row-action:hover { background-color: rgba(79, 70, 229, 0.08); }
.today-row-action-primary {
  background-color: var(--color-primary);
  color: #fff;
}
.today-row-action-primary:hover { background-color: var(--color-primary-dark); color: #fff; }

@media (max-width: 640px) {
  .today-row { flex-wrap: wrap; }
  .today-row-actions { width: 100%; justify-content: flex-end; padding-top: 0.25rem; }
}


/* ── Rooms admin (toolbar + grouped sections) ──────────────
   Builds on top of the .ml-* module-landing chrome. The
   toolbar is one row of: search, state-select, CSV import +
   Template + Export. Each category becomes its own card with
   an accent-colored header strip and a tight table inside.
─────────────────────────────────────────────────────────── */
.rooms-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}
.rooms-toolbar-spacer { flex: 1; min-width: 0.5rem; }

.rooms-filter-form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.rooms-search { position: relative; }
.rooms-search-icon {
  position: absolute;
  left: 0.625rem;
  top: 50%;
  transform: translateY(-50%);
  width: 0.95rem; height: 0.95rem;
  color: #94a3b8;
  pointer-events: none;
}
.rooms-search-input {
  height: 2.25rem;
  width: 16rem;
  padding: 0 0.625rem 0 2.125rem;
  border: 1px solid #cbd5e1;
  border-radius: 0.5rem;
  font-size: 0.875rem;
  color: #0f172a;
  background-color: #fff;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.rooms-search-input:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-ring);
}

.rooms-state-select {
  height: 2.25rem;
  padding: 0 0.625rem;
  border: 1px solid #cbd5e1;
  border-radius: 0.5rem;
  font-size: 0.8125rem;
  color: #0f172a;
  background-color: #fff;
  cursor: pointer;
}
.rooms-state-select:focus { outline: none; border-color: var(--color-primary); box-shadow: 0 0 0 3px var(--color-ring); }

.rooms-import-form {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  margin: 0;
}
.rooms-import-label {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  height: 2rem;
  padding: 0 0.75rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-hairline);
  font-size: 0.75rem;
  font-weight: 500;
  color: #475569;
  background-color: #fff;
  cursor: pointer;
  transition: border-color 0.15s, background-color 0.15s;
}
.rooms-import-label:hover { background-color: #f8fafc; border-color: #cbd5e1; }


.rooms-group {
  background-color: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.875rem;
  overflow: hidden;
}
.rooms-group-header {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.75rem 1rem;
  background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
  border-bottom: 1px solid #e2e8f0;
}
.rooms-group-icon {
  width: 1.75rem; height: 1.75rem;
  border-radius: 0.5rem;
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  color: #fff;
}
.rooms-group-icon svg { width: 1rem; height: 1rem; }
.rooms-group.accent-blue    .rooms-group-icon { background-color: #3b82f6; }
.rooms-group.accent-indigo  .rooms-group-icon { background-color: #6366f1; }
.rooms-group.accent-violet  .rooms-group-icon { background-color: #8b5cf6; }
.rooms-group.accent-emerald .rooms-group-icon { background-color: #10b981; }
.rooms-group.accent-amber   .rooms-group-icon { background-color: #f59e0b; }
.rooms-group.accent-sky     .rooms-group-icon { background-color: #0ea5e9; }
.rooms-group.accent-slate   .rooms-group-icon { background-color: #64748b; }
.rooms-group.accent-rose    .rooms-group-icon { background-color: #f43f5e; }

.rooms-group-title {
  font-size: 0.9375rem;
  font-weight: 700;
  color: #0f172a;
}
.rooms-group-count {
  font-size: 0.6875rem;
  font-weight: 600;
  color: #64748b;
  background-color: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 9999px;
  padding: 0.125rem 0.5rem;
  font-variant-numeric: tabular-nums;
}

.rooms-table-wrap { overflow-x: auto; }
.rooms-table {
  width: 100%;
  font-size: 0.875rem;
  border-collapse: collapse;
}
.rooms-th {
  text-align: left;
  font-size: 0.6875rem;
  font-weight: 700;
  color: #94a3b8;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.5rem 1rem;
  background-color: #fafbfc;
  border-bottom: 1px solid #f1f5f9;
}
.rooms-th-num     { text-align: right; }
.rooms-th-status  { width: 7rem; }
.rooms-th-actions { width: 4rem; }

.rooms-table tbody tr {
  border-bottom: 1px solid #f1f5f9;
  transition: background-color 0.12s;
}
.rooms-table tbody tr:last-child { border-bottom: none; }
.rooms-table tbody tr:hover { background-color: #f8fafc; }
.rooms-row-inactive td { opacity: 0.55; }

.rooms-td-name {
  padding: 0.625rem 1rem;
  font-weight: 600;
  color: #0f172a;
  font-variant-numeric: tabular-nums;
}
.rooms-td-num {
  padding: 0.625rem 1rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.rooms-td-location {
  padding: 0.625rem 1rem;
  color: #64748b;
}
.rooms-td-actions {
  padding: 0.625rem 1rem;
  text-align: right;
}

.rooms-add-location {
  font-size: 0.75rem;
  color: #6366f1;
  font-weight: 500;
  text-decoration: none;
  opacity: 0.7;
}
.rooms-add-location:hover { opacity: 1; text-decoration: underline; }

.rooms-edit-link {
  font-size: 0.75rem;
  color: #6366f1;
  font-weight: 600;
  text-decoration: none;
}
.rooms-edit-link:hover { text-decoration: underline; }

@media (max-width: 640px) {
  .rooms-search-input { width: 100%; }
  .rooms-toolbar { gap: 0.625rem; }
  .rooms-toolbar-spacer { display: none; }
}

/* ── Rooms <details> behaviour ────────────────────────────────
   Each category is a native <details> so the OS handles the
   open/close state — Apple HIG: "use system primitives". The
   default browser marker is hidden in favour of a chevron that
   rotates 90° when the section opens.
─────────────────────────────────────────────────────────── */
.rooms-group > summary {
  cursor: pointer;
  list-style: none;          /* Firefox */
  user-select: none;
}
.rooms-group > summary::-webkit-details-marker { display: none; }  /* Safari */
.rooms-group > summary::marker                  { display: none; }  /* Chrome */
.rooms-group-chevron {
  width: 0.875rem;
  height: 0.875rem;
  color: #94a3b8;
  flex-shrink: 0;
  margin-right: -0.125rem;
  transition: transform 0.15s ease;
}
.rooms-group[open] > summary .rooms-group-chevron {
  transform: rotate(90deg);
}
.rooms-group > summary:hover .rooms-group-chevron { color: #475569; }

/* ── Dark mode overrides for the rooms admin landing ─────────
   The rooms page predates the dark-mode pass; its bespoke
   classes (.rooms-*, the local .ml-filter-tab variant) all set
   their own white / slate-100 surfaces so they need explicit
   ``html.dark`` rules — the global utility overrides further
   down don't catch them.
─────────────────────────────────────────────────────────── */
html.dark .rooms-search-input,
html.dark .rooms-state-select,
html.dark .rooms-import-label {
  background-color: #1e293b;
  border-color: #475569;
  color: #f1f5f9;
}
html.dark .rooms-import-label { color: #cbd5e1; }
html.dark .rooms-import-label:hover {
  background-color: #334155;
  border-color: #64748b;
}
html.dark .rooms-search-icon { color: #64748b; }

html.dark .ml-filter-tabs {
  background-color: #1e293b;
  border-color: #475569;
}
html.dark .ml-filter-tab {
  background-color: transparent;
  color: #cbd5e1;
  border-right-color: #334155;
}
html.dark .ml-filter-tab:hover { background-color: #334155; }
html.dark .ml-filter-tab.is-active {
  background-color: #f1f5f9;
  color: #0f172a;
}
html.dark .ml-filter-count {
  background-color: #334155;
  color: #cbd5e1;
}
html.dark .ml-filter-tab.is-active .ml-filter-count {
  background-color: rgba(15, 23, 42, 0.18);
  color: #0f172a;
}

html.dark .rooms-group {
  background-color: #1e293b;
  border-color: #475569;
}
html.dark .rooms-group-header {
  background: linear-gradient(180deg, #1e293b 0%, #172033 100%);
  border-bottom-color: #334155;
}
html.dark .rooms-group-title { color: #f1f5f9; }
html.dark .rooms-group-count {
  background-color: #0f172a;
  border-color: #475569;
  color: #cbd5e1;
}
html.dark .rooms-group-chevron { color: #64748b; }
html.dark .rooms-group > summary:hover .rooms-group-chevron { color: #cbd5e1; }

html.dark .rooms-th {
  background-color: #172033;
  color: #94a3b8;
  border-bottom-color: #334155;
}
html.dark .rooms-table tbody tr { border-bottom-color: #334155; }
html.dark .rooms-table tbody tr:hover { background-color: #243044; }
html.dark .rooms-td-name { color: #f1f5f9; }
html.dark .rooms-td-num,
html.dark .rooms-td-location { color: #cbd5e1; }
html.dark .rooms-td-location .text-slate-300,
html.dark .rooms-table .text-slate-300 { color: #64748b !important; }
html.dark .rooms-add-location,
html.dark .rooms-edit-link { color: #a5b4fc; }


/* ── Top navbar + mobile bottom tabs ───────────────────────
   Desktop: icon + label nav links with an active underline +
   white pill-highlight when on that route.
   Mobile (<768px): brand text hides, links collapse, the
   bottom tab bar takes over primary navigation.
─────────────────────────────────────────────────────────── */
.navbar {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
  z-index: 50;
  box-shadow: 0 2px 6px -2px rgba(15, 23, 42, 0.18);
}

/* Page title — centered on phone widths so the user always sees
   what they're on (replaces the desktop nav-links which are hidden
   below md). Larger type for at-a-glance recognition; truncates
   long titles cleanly. */
.navbar-page-title {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  color: #fff;
  font-weight: 700;
  font-size: 1.25rem;       /* 20px — bumped per user feedback */
  letter-spacing: 0.01em;
  max-width: calc(100vw - 11rem);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  pointer-events: none;
  text-align: center;
}
@media (min-width: 768px) {
  .navbar-page-title { display: none; }
}
.navbar-inner {
  position: relative;        /* anchor for .navbar-page-title */
  max-width: 80rem;
  margin: 0 auto;
  padding: 0 1.25rem;
  height: 3.75rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem;
}
.navbar-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  flex-shrink: 0;
  text-decoration: none;
  color: #fff;
}
.navbar-logo {
  height: 2rem;
  width: 2rem;
  object-fit: contain;
  border-radius: 0.375rem;
}
.navbar-brand-text {
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: -0.01em;
  color: #fff;
  white-space: nowrap;
}

.navbar-links {
  display: none;
  align-items: center;
  gap: 0.25rem;
  flex: 1;
  justify-content: center;
}
.nav-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.875rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-nav-text);
  border-radius: 0.5rem;
  transition: background-color 0.15s, color 0.15s;
  text-decoration: none;
  position: relative;
}
.nav-link svg { width: 1.05rem; height: 1.05rem; opacity: 0.85; }
.nav-link:hover {
  background-color: rgba(255, 255, 255, 0.08);
  color: #fff;
}
.nav-link.is-active {
  color: #fff;
  background-color: rgba(255, 255, 255, 0.14);
}
.nav-link.is-active svg { opacity: 1; }
.nav-link.is-active::after {
  content: '';
  position: absolute;
  left: 0.875rem;
  right: 0.875rem;
  bottom: -0.125rem;
  height: 2px;
  background-color: #fff;
  border-radius: 1px;
}

.navbar-right {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  flex-shrink: 0;
}
.navbar-role-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.125rem 0.5rem;
  border-radius: 9999px;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}

/* Avatar + dropdown — uses native <details>/<summary> for menu state. */
.navbar-menu { position: relative; }
.navbar-menu-trigger {
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.25rem 0.5rem 0.25rem 0.25rem;
  border-radius: 9999px;
  cursor: pointer;
  transition: background-color 0.15s;
  user-select: none;
}
.navbar-menu-trigger::-webkit-details-marker { display: none; }
.navbar-menu-trigger:hover { background-color: rgba(255, 255, 255, 0.1); }
.navbar-avatar {
  width: 1.875rem;
  height: 1.875rem;
  border-radius: 50%;
  object-fit: cover;
  background-color: rgba(255, 255, 255, 0.15);
  flex-shrink: 0;
}
.navbar-avatar-fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 700;
  font-size: 0.75rem;
}
.navbar-username {
  display: none;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--color-nav-text);
}
.navbar-chevron { color: var(--color-nav-text); display: none; }

.navbar-menu[open] .navbar-menu-trigger { background-color: rgba(255, 255, 255, 0.14); }
.navbar-menu-pop {
  position: absolute;
  right: 0;
  top: calc(100% + 0.5rem);
  width: 14rem;
  background-color: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.625rem;
  box-shadow: 0 12px 28px -8px rgba(15, 23, 42, 0.22);
  z-index: 60;
  overflow: hidden;
}
.navbar-menu-header {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid #f1f5f9;
}
.navbar-menu-item {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.625rem 1rem;
  font-size: 0.8125rem;
  font-weight: 500;
  color: #334155;
  text-decoration: none;
  transition: background-color 0.12s;
}
.navbar-menu-item:hover { background-color: #f8fafc; color: #0f172a; }
.navbar-menu-item-danger { color: #dc2626; }
.navbar-menu-item-danger:hover { background-color: #fef2f2; color: #b91c1c; }
.navbar-menu-item svg { color: #64748b; flex-shrink: 0; }
.navbar-menu-item-danger svg { color: #ef4444; }

@media (min-width: 768px) {
  .navbar-links     { display: flex; }
  .navbar-username  { display: inline; }
  .navbar-chevron   { display: inline; }
}

/* Hide the brand text and role badge on small screens — logo + avatar
   are enough to anchor the top bar. */
@media (max-width: 480px) {
  .navbar-brand-text { display: none; }
  .navbar-role-badge { display: none; }
}

/* ── Mobile chrome strip (PR M.2) ────────────────────────────────
   Below 768 px the entire top navbar is removed from the visual
   layout: no background, no logo, no page title, no theme toggle
   in this row. The bell + avatar dropdown float in the top-right
   safe-area inset as two backdrop-blurred circular buttons. The
   navbar element itself stays in DOM (the markup graft is too
   surface-y to gut here) but reserves zero height + no longer
   intercepts pointer events.

   Bottom tabs detach from the screen edge into a floating pill,
   keeping labels (parents-who-log-in-once-a-week need them).
─────────────────────────────────────────────────────────────── */
@media (max-width: 767px) {

  /* 1. Strip navbar chrome — invisible, height-less, click-through.
     The bell + avatar (.navbar-right) opt back in via pointer-events
     below. */
  .navbar {
    background: transparent !important;
    box-shadow: none;
    pointer-events: none;
    /* keep position: sticky from the base rule but neutralise its
       visual + layout footprint so page content sits flush at the
       top edge */
  }
  .navbar-inner {
    height: 0;
    min-height: 0;
    padding: 0;
  }
  .navbar-brand,
  .navbar-page-title,
  .navbar-links,
  .navbar-theme-toggle {
    display: none !important;
  }

  /* 2. Float the right-side cluster in the top-right corner. The
     `position: fixed` lets it sit independent of the now-collapsed
     navbar; safe-area inset keeps it clear of iOS notches. */
  .navbar-right {
    position: fixed;
    top: max(env(safe-area-inset-top, 0px), 12px);
    right: 12px;
    gap: 8px;
    pointer-events: auto;
    z-index: 60;
  }

  /* 3. Bell trigger + avatar trigger — circular, 40 px, hairline +
     backdrop blur. They feel like overlays not chrome. */
  .notif-bell-trigger,
  .navbar-menu-trigger {
    width: 40px;
    height: 40px;
    padding: 0 !important;
    border-radius: 50% !important;
    background: rgba(255, 255, 255, 0.85) !important;
    -webkit-backdrop-filter: blur(20px) saturate(180%);
    backdrop-filter: blur(20px) saturate(180%);
    border: 1px solid rgba(15, 23, 42, 0.08);
    box-shadow: 0 4px 14px -4px rgba(15, 23, 42, 0.18);
    color: #0f172a !important;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .notif-bell-trigger svg,
  .navbar-menu-trigger svg {
    color: #0f172a;
  }
  /* Avatar's role-coloured ring stays visible and holds the menu
     trigger together visually with the dropdown header. */
  .navbar-menu-trigger .navbar-avatar {
    width: 32px;
    height: 32px;
  }

  /* 4. Dropdown panels need a touch more top inset since they no
     longer dock against the (now-absent) navbar bottom. */
  .navbar-menu-pop,
  .notif-bell-panel {
    top: calc(100% + 8px);
  }

  /* 5. Bottom-tabs → edge-to-edge bar (maintainer May 18:
     "change the mobile navbar from pill, i dont want to be
     pill anymore").  Sticks to the bottom edge, full width,
     no border-radius, no horizontal margin.  Hairline top
     border + blurred white background still hold the iOS
     translucent-chrome aesthetic — we just dropped the
     floating-pill rounding.  Safe-area inset still applies
     so the bar respects the home-bar gesture region. */
  .bottom-tabs {
    bottom: 0;
    left: 0;
    right: 0;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: rgba(255, 255, 255, 0.92);
    -webkit-backdrop-filter: blur(20px) saturate(180%);
    backdrop-filter: blur(20px) saturate(180%);
    border: 0;
    border-top: 1px solid rgba(15, 23, 42, 0.10);
    border-radius: 0;
    box-shadow: 0 -2px 8px -4px rgba(15, 23, 42, 0.10);
    overflow: hidden;
  }

  /* Active tab — full-bleed top-edge accent + soft teal tint
   * (matches the base block now that the floating pill is
   * gone). */
  .bottom-tab.is-active {
    background-color: rgba(20, 184, 166, 0.10);
  }
}

/* Mobile-only menu items (e.g. theme toggle in the avatar dropdown).
   Hidden on desktop where the dedicated navbar button is visible. */
.navbar-menu-item-mobile-only { display: none; }
@media (max-width: 767px) {
  .navbar-menu-item-mobile-only {
    display: flex;
    width: 100%;
    text-align: left;
    background: transparent;
    border: 0;
    cursor: pointer;
    font: inherit;
  }

  /* Maintainer May 16: fold the standalone notification bell into
     the avatar menu on mobile. Desktop keeps the bell + badge as
     a separate affordance. */
  details.notif-bell { display: none !important; }
}

/* Unread count pill inside menu items (Notifications entry). */
.navbar-menu-item-pill {
  margin-left: auto;
  min-width: 20px;
  padding: 1px 7px;
  border-radius: 999px;
  background: #ef4444;
  color: #fff;
  font: 700 10.5px/1.4 system-ui, sans-serif;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* Red dot on the avatar trigger when unread > 0 — mobile only,
   since the desktop bell badge already shows the count. */
.navbar-avatar { position: relative; }
/* Avatar slot — a positioning wrap around the <img> / fallback
 * <span> in base.html.  The unread-notifications red dot below
 * attaches as ::after on THIS span rather than the avatar itself,
 * because <img> elements are replaced content and CSS pseudo-
 * elements don't render on them (May 17 maintainer screenshot —
 * users with an uploaded avatar_url never saw the dot, users with
 * the initials fallback did).
 */
.navbar-avatar-slot {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}
/* Unread-notifications red dot on the navbar avatar.  Maintainer
 * ask (May 16): "supposed to get a red dot on the profile picture
 * when I get notifications" — desktop was previously suppressing
 * this on the assumption that the bell + count badge made it
 * redundant.  But the dot lives on the *profile entry point*
 * (avatar → slider → inbox), the badge lives on the *bell* — they
 * surface different paths to the same data and reading both at a
 * glance is the point.  Show on every breakpoint. */
.navbar-avatar-slot.has-notif::after {
  content: '';
  position: absolute;
  top: -2px;
  right: -2px;
  width: 10px;
  height: 10px;
  background: #ef4444;
  border-radius: 50%;
  border: 2px solid var(--color-nav, #ffffff);
  pointer-events: none;
}

/* Dark-mode tweaks for the floating chrome. The translucent
   white background + dark glyphs flip to translucent slate-700 +
   light glyphs so the buttons remain readable on a dark page.
   Maintainer May 19: "profile-picture circle issue on hall pass
   page" — the prior rgba(15,23,42,0.75) blended into the hall-
   pass dark-navy backdrop (#0A1628-ish) and the circle outline
   disappeared.  Bumped to slate-700 at 0.92 so the trigger reads
   as a visible circular pill even against a dark page; the
   border opacity is doubled so the hairline holds on the
   highest-contrast pages too. */
@media (max-width: 767px) {
  html.dark .notif-bell-trigger,
  html.dark .navbar-menu-trigger {
    background: rgba(51, 65, 85, 0.92) !important;
    border-color: rgba(255, 255, 255, 0.18);
    color: #e2e8f0 !important;
  }
  html.dark .notif-bell-trigger svg,
  html.dark .navbar-menu-trigger svg {
    color: #e2e8f0;
  }
  html.dark .bottom-tabs {
    background: rgba(15, 23, 42, 0.75);
    border-color: rgba(255, 255, 255, 0.08);
    border-top-color: rgba(255, 255, 255, 0.08);
  }
}


/* `.global-shell` / `.global-main` rules retired with the
 * roll-back of PR #925 (global persistent sidebar) ahead of
 * the demo.  Sidebar lives only on `/my-schedule` again. */


/* ── Mobile bottom tab bar ─────────────────────────────────
   Fixed-position primary navigation on small viewports. Hidden
   above the md breakpoint where the desktop nav-links take over.
   Restored verbatim from e8a6525 (yesterday's pre-session state)
   per maintainer instruction.
─────────────────────────────────────────────────────────── */
/* NOTE: a `height: 56px` that used to sit in the max-width:767px block above
   was DEAD — this base rule's later source order (equal specificity) overrode
   it, so the effective height was 4rem at every width. The dead line was
   removed. The --bottom-tabs-h token (declared in the main :root at the top of
   this file) is the single source of truth for the tab height. */
.bottom-tabs {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  /* Fold the safe-area inset INTO the height (box-sizing: border-box from
     Tailwind's reset) so the home-indicator zone doesn't squeeze the tab
     content on iPhones, and so a fixed bar pinned at this same calc() offset
     (the roll-call Submit bar) aligns flush above the tabs (Gemini #1093). */
  height: calc(var(--bottom-tabs-h, 4rem) + env(safe-area-inset-bottom, 0px));
  /* Visual surface (background, border, shadow) comes from the max-width:767px
     block above — the translucent rgba + backdrop-filter blur. Setting an
     opaque background-color here would (being later in source) override that
     media rule and render the tabs solid white, killing the blur (Gemini #1093). */
  display: flex;
  z-index: 40;
  padding-bottom: env(safe-area-inset-bottom, 0);
}
@media (min-width: 768px) {
  .bottom-tabs { display: none; }
}

.bottom-tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  text-decoration: none;
  color: #64748b;
  font-weight: 500;
  padding: 0.375rem 0;
  position: relative;
  transition: color 0.15s;
}
/* Minimalist pass (May 18): labels back on the bottom-tabs.
 * Every reference app the maintainer pointed to (Starbucks /
 * Chevy / Apple Journal / TP-Link Deco) labels its tabs.
 * Discoverability per CLAUDE.md "no-brainer UX" — the user
 * shouldn't have to guess what each glyph does. */
.bottom-tab > span:not(.bottom-tab-icon-wrap):not(.bottom-tab-badge) {
  font-size: 10.5px;
  line-height: 1.1;
  letter-spacing: 0.01em;
  margin-top: 3px;
  color: inherit;
}
/* Bottom-tab icon size — dialled back from 36 → 24 px now that
 * labels are back below the glyph.  Icon + label together still
 * carry confident visual weight; the +12 px we dropped from the
 * glyph goes into label legibility + tap-target vertical
 * breathing room. */
.bottom-tab svg { width: 1.5rem; height: 1.5rem; }

/* Nav-link unread count badge — small teal-soft pill on the
 * inline navbar Messages link. Sits flush against the label
 * so the badge reads as part of the link, not a separate
 * widget. */
.nav-link-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 6px;
  margin-left: 6px;
  border-radius: 999px;
  background: var(--brand-teal-bright, #25D6AD);
  color: var(--brand-navy, #0A1628);
  font: 700 11px/1 var(--brand-font-body);
  letter-spacing: 0;
}

/* Bottom-tab badge — sits at the top-right corner of the
 * icon (not the whole tab) so the count rides on the bell-
 * style hot-spot familiar from iOS. */
.bottom-tab-icon-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.bottom-tab-badge {
  position: absolute;
  top: -4px;
  right: -8px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--brand-teal-bright, #25D6AD);
  color: var(--brand-navy, #0A1628);
  font: 700 10px/1 var(--brand-font-body);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid var(--brand-paper, #fff);
}
.dark .bottom-tab-badge { border-color: var(--brand-navy, #0A1628); }
.bottom-tab:hover { color: #0f172a; }
.bottom-tab.is-active {
  color: var(--color-primary);
  font-weight: 600;
}
.bottom-tab.is-active::before {
  content: '';
  position: absolute;
  top: 0;
  left: 30%;
  right: 30%;
  height: 3px;
  background-color: var(--color-primary);
  border-radius: 0 0 2px 2px;
}


/* ── Public events list (.pe-*) ────────────────────────────
   Hero strip + filter chips + per-event row showing
   registration status, capacity hint, and (signed-in) the
   user's own registered count. Category accent on the date
   chip so a glance reads "type" before "title".
─────────────────────────────────────────────────────────── */
.pe-hero {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1rem;
}
.pe-hero-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }

.pe-view-toggle {
  display: inline-flex;
  border: 1px solid var(--border-hairline);
  border-radius: 0.5rem;
  overflow: hidden;
  background-color: #fff;
}
.pe-view-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.4rem 0.75rem;
  font-size: 0.8125rem;
  font-weight: 500;
  color: #475569;
  background-color: #fff;
  border-right: 1px solid #e2e8f0;
  text-decoration: none;
  transition: background-color 0.12s;
}
.pe-view-tab:last-child { border-right: none; }
.pe-view-tab:hover { background-color: #f8fafc; }
.pe-view-tab.is-active {
  background-color: var(--color-primary-light);
  color: var(--color-primary);
  font-weight: 600;
}

.pe-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.625rem;
  margin-bottom: 1rem;
}
.pe-search-form { margin: 0; }

.pe-rows { display: flex; flex-direction: column; gap: 0.625rem; }

.pe-row {
  display: flex;
  align-items: stretch;
  gap: 1rem;
  background-color: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.875rem;
  padding: 1rem 1.125rem;
  text-decoration: none;
  color: inherit;
  position: relative;
  transition: box-shadow 0.18s, transform 0.18s, border-color 0.18s;
}
.pe-row:hover {
  box-shadow: 0 8px 24px -8px rgba(15, 23, 42, 0.12);
  transform: translateY(-1px);
}

/* Category accent on the left edge of the row + the date chip. */
.pe-row::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 4px;
  border-radius: 0.875rem 0 0 0.875rem;
  opacity: 0;
  transition: opacity 0.18s;
}
.pe-row:hover::before { opacity: 1; }

.pe-date-chip {
  width: 3.25rem; height: 3.25rem;
  border-radius: 0.625rem;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  flex-shrink: 0;
  color: #fff;
  line-height: 1;
  align-self: center;
}
.pe-date-day { font-size: 1.05rem; font-weight: 800; }
.pe-date-mon { font-size: 0.625rem; font-weight: 700; opacity: 0.85; margin-top: 0.125rem; letter-spacing: 0.04em; }

/* Category accent palette — date-chip background. */
.pe-row.pe-cat-academic       .pe-date-chip,
.pe-row.pe-cat-instructional  .pe-date-chip { background: linear-gradient(140deg, #818cf8 0%, #4f46e5 100%); }
.pe-row.pe-cat-academic::before,
.pe-row.pe-cat-instructional::before { background-color: #6366f1; }

.pe-row.pe-cat-assessment .pe-date-chip { background: linear-gradient(140deg, #f87171 0%, #dc2626 100%); }
.pe-row.pe-cat-assessment::before { background-color: #ef4444; }

.pe-row.pe-cat-school_wide .pe-date-chip { background: linear-gradient(140deg, #fbbf24 0%, #d97706 100%); }
.pe-row.pe-cat-school_wide::before { background-color: #f59e0b; }

.pe-row.pe-cat-activity .pe-date-chip { background: linear-gradient(140deg, #c084fc 0%, #9333ea 100%); }
.pe-row.pe-cat-activity::before { background-color: #a855f7; }

.pe-row.pe-cat-field_trip .pe-date-chip { background: linear-gradient(140deg, #34d399 0%, #059669 100%); }
.pe-row.pe-cat-field_trip::before { background-color: #10b981; }

.pe-row.pe-cat-athletics .pe-date-chip { background: linear-gradient(140deg, #fb923c 0%, #ea580c 100%); }
.pe-row.pe-cat-athletics::before { background-color: #f97316; }

.pe-row.pe-cat-administrative .pe-date-chip,
.pe-row.pe-cat-facility       .pe-date-chip,
.pe-row.pe-cat-external       .pe-date-chip,
.pe-row.pe-cat-other          .pe-date-chip { background: linear-gradient(140deg, #94a3b8 0%, #475569 100%); }
.pe-row.pe-cat-administrative::before,
.pe-row.pe-cat-facility::before,
.pe-row.pe-cat-external::before,
.pe-row.pe-cat-other::before { background-color: #64748b; }

.pe-title-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.pe-title {
  font-size: 1.0625rem;
  font-weight: 700;
  color: #0f172a;
  line-height: 1.25;
}

.pe-badge-mine {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.125rem 0.5rem;
  border-radius: 9999px;
  font-size: 0.6875rem;
  font-weight: 600;
  background-color: #d1fae5;
  color: #065f46;
}

.pe-meta {
  font-size: 0.8125rem;
  color: #64748b;
  margin-top: 0.25rem;
  font-variant-numeric: tabular-nums;
}

.pe-capacity-bar {
  height: 0.3125rem;
  background-color: #f1f5f9;
  border-radius: 9999px;
  overflow: hidden;
  margin-top: 0.5rem;
  max-width: 18rem;
}
.pe-capacity-fill {
  height: 100%;
  border-radius: 9999px;
  transition: width 0.3s ease;
}
.pe-fill-rose    { background-color: #f43f5e; }
.pe-fill-amber   { background-color: #f59e0b; }
.pe-fill-emerald { background-color: #10b981; }

.pe-row-side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.5rem;
  flex-shrink: 0;
  text-align: right;
}

/* Status badges — color-coded by registration window state. */
.pe-status {
  display: inline-flex;
  align-items: center;
  padding: 0.25rem 0.625rem;
  border-radius: 9999px;
  font-size: 0.6875rem;
  font-weight: 600;
  white-space: nowrap;
}
.pe-status-badge-open         { background-color: #d1fae5; color: #065f46; }
.pe-status-badge-closes_soon  { background-color: #fef3c7; color: #b45309; }
.pe-status-badge-not_yet_open { background-color: #e0e7ff; color: #3730a3; }
.pe-status-badge-closed       { background-color: #f1f5f9; color: #64748b; }
.pe-status-badge-past         { background-color: #f1f5f9; color: #94a3b8; }

.pe-cta-text {
  font-size: 0.8125rem;
  font-weight: 600;
  color: #6366f1;
  white-space: nowrap;
}
.pe-cta-text.pe-cta-primary { color: var(--color-primary); }
.pe-row:hover .pe-cta-text  { transform: translateX(2px); transition: transform 0.18s; }

@media (max-width: 640px) {
  .pe-row { flex-wrap: wrap; }
  .pe-row-side {
    flex-direction: row;
    width: 100%;
    justify-content: space-between;
    align-items: center;
    border-top: 1px solid #f1f5f9;
    padding-top: 0.5rem;
    margin-top: 0.25rem;
  }
}


/* ── Evaluation form (.eval-*) ─────────────────────────────
   Hero card + per-criterion fieldset that swaps between a
   row of cards (desktop) and a vertical stack (mobile) via
   CSS columns/rows — single set of inputs, no duplicate-name
   syncing. Rubric descriptions render right inside each
   option so reviewers know what each level means.
─────────────────────────────────────────────────────────── */
.eval-hero {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  background-color: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.875rem;
  padding: 1.25rem;
  margin-bottom: 1rem;
}
.eval-hero-icon {
  width: 3rem; height: 3rem;
  border-radius: 0.75rem;
  background: linear-gradient(135deg, #c4b5fd 0%, #8b5cf6 100%);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  box-shadow: 0 6px 16px -6px rgba(139, 92, 246, 0.45);
}
.eval-hero-icon svg { width: 1.5rem; height: 1.5rem; }
.eval-hero-eyebrow {
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #6d28d9;
}
.eval-hero-title {
  font-size: 1.25rem;
  font-weight: 700;
  color: #0f172a;
  line-height: 1.25;
  margin-top: 0.125rem;
}
.eval-hero-sub { font-size: 0.875rem; color: #64748b; margin-top: 0.25rem; }
.eval-hero-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1rem;
  margin-top: 0.625rem;
  font-size: 0.75rem;
  color: #475569;
}
.eval-meta-item { display: inline-flex; align-items: center; gap: 0.3125rem; }

.eval-anon-strip {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background-color: #f1f5f9;
  border: 1px solid var(--border-hairline);
  border-radius: 0.5rem;
  padding: 0.5rem 0.75rem;
  font-size: 0.75rem;
  color: #475569;
  margin-bottom: 1rem;
}

/* Each criterion is a fieldset card. */
.eval-criterion {
  background-color: #fff;
  border: 1px solid var(--border-hairline);
  border-radius: 0.75rem;
  padding: 1rem 1.125rem 1.125rem;
}
.eval-criterion-name {
  font-size: 0.95rem;
  font-weight: 600;
  color: #0f172a;
  display: block;
  margin-bottom: 0.625rem;
  letter-spacing: -0.005em;
}

/* Options grid: side-by-side on desktop, vertical stack on mobile. */
.eval-options {
  display: grid;
  grid-template-columns: repeat(var(--eval-cols, 4), minmax(0, 1fr));
  gap: 0.5rem;
}
@media (max-width: 768px) {
  .eval-options { grid-template-columns: 1fr; }
}

.eval-option {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.625rem 0.75rem;
  border: 1px solid var(--border-hairline);
  border-radius: 0.5rem;
  background-color: #fff;
  cursor: pointer;
  transition: border-color 0.15s, background-color 0.15s, box-shadow 0.15s;
}
.eval-option:hover {
  border-color: #c7d2fe;
  background-color: #fafbff;
}
.eval-option:has(.eval-score-input:checked) {
  border-color: var(--color-primary);
  background-color: var(--color-primary-light);
  box-shadow: 0 0 0 1px var(--color-primary) inset;
}

/* Custom radio circle — fills accent on :checked, with a checkmark. */
.eval-radio {
  width: 1.125rem; height: 1.125rem;
  border-radius: 9999px;
  border: 2px solid #cbd5e1;
  background-color: #fff;
  flex-shrink: 0;
  position: relative;
  transition: border-color 0.15s, background-color 0.15s;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 0.125rem;
}
.eval-radio-tick {
  width: 0.625rem; height: 0.625rem;
  color: #fff;
  opacity: 0;
  transition: opacity 0.15s;
}
.eval-option:has(.eval-score-input:checked) .eval-radio {
  background-color: var(--color-primary);
  border-color: var(--color-primary);
}
.eval-option:has(.eval-score-input:checked) .eval-radio-tick { opacity: 1; }

.eval-option-body {
  display: flex;
  flex-direction: column;
  gap: 0.0625rem;
  min-width: 0;
}
.eval-option-num {
  font-size: 0.8125rem;
  font-weight: 700;
  color: #0f172a;
  font-variant-numeric: tabular-nums;
}
.eval-option-desc {
  font-size: 0.75rem;
  color: #64748b;
  line-height: 1.35;
}

/* Mobile rows align icon + num + desc in a single line for compact reading. */
@media (max-width: 768px) {
  .eval-option { align-items: center; }
  .eval-option-body { flex-direction: row; gap: 0.5rem; align-items: baseline; flex: 1; }
  .eval-option-num { min-width: 2rem; }
}

.eval-criterion-block .eval-options { display: block; }

.eval-choice {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border-hairline);
  border-radius: 0.5rem;
  cursor: pointer;
  transition: border-color 0.15s, background-color 0.15s;
  font-size: 0.875rem;
  color: #334155;
}
.eval-choice:hover { border-color: #c7d2fe; background-color: #fafbff; }
.eval-choice:has(input:checked) {
  border-color: var(--color-primary);
  background-color: var(--color-primary-light);
}

/* ── CMS prose ────────────────────────────────────────────────────────
   Minimal styling for markdown-rendered body_text. cms-prose is the
   default (slate-on-light); cms-prose-light flips link/heading colors
   for use inside the dark hero overlay. Anything more elaborate (full
   Tailwind typography plugin) lands in a follow-on PR.
*/
.cms-prose p             { margin-top: 0.75em; }
.cms-prose p:first-child { margin-top: 0; }
.cms-prose h1,
.cms-prose h2,
.cms-prose h3,
.cms-prose h4 {
  font-weight: 600;
  margin-top: 1.25em;
  margin-bottom: 0.4em;
  line-height: 1.25;
  color: rgb(15 23 42); /* slate-900 */
}
.cms-prose h1 { font-size: 1.5rem; }
.cms-prose h2 { font-size: 1.25rem; }
.cms-prose h3 { font-size: 1.1rem; }
.cms-prose h4 { font-size: 1rem; }
.cms-prose ul,
.cms-prose ol           { margin: 0.6em 0; padding-left: 1.5em; }
.cms-prose ul           { list-style: disc; }
.cms-prose ol           { list-style: decimal; }
.cms-prose li           { margin: 0.2em 0; }
.cms-prose a {
  color: var(--color-primary);
  text-decoration: underline;
}
.cms-prose a:hover      { opacity: 0.8; }
.cms-prose blockquote {
  border-left: 3px solid rgb(203 213 225); /* slate-300 */
  padding-left: 0.9em;
  color: rgb(71 85 105); /* slate-600 */
  margin: 0.8em 0;
}
.cms-prose code {
  font-family: ui-monospace, monospace;
  background: rgb(241 245 249); /* slate-100 */
  padding: 0.1em 0.35em;
  border-radius: 0.25em;
  font-size: 0.95em;
}
.cms-prose pre {
  background: rgb(241 245 249);
  padding: 0.75em 1em;
  border-radius: 0.5em;
  overflow-x: auto;
}

/* Light variant for dark backgrounds (hero overlay). */
.cms-prose-light h1,
.cms-prose-light h2,
.cms-prose-light h3,
.cms-prose-light h4    { color: rgb(248 250 252); /* slate-50 */ }
.cms-prose-light blockquote {
  border-color: rgba(255, 255, 255, 0.4);
  color: rgb(226 232 240); /* slate-200 */
}
.cms-prose-light a     { color: rgb(248 250 252); }
.cms-prose-light code,
.cms-prose-light pre   { background: rgba(255, 255, 255, 0.15); color: rgb(241 245 249); }

/* ── Dark mode ──────────────────────────────────────────────────
   Class-based dark theme (driven by ``html.dark`` toggled from the
   navbar moon button). The light theme is the canonical design;
   dark mode reskins major surfaces without requiring per-template
   ``dark:`` variants on every utility class. New components should
   still prefer Tailwind ``dark:`` variants where targeted.
─────────────────────────────────────────────────────────────── */
html.dark {
  background-color: var(--brand-page-deep);  /* deep navy — matches the My Day / my-schedule shell */
  color: #e2e8f0;                     /* slate-200 */
  color-scheme: dark;                 /* native scrollbars + form widgets */
}
html.dark body { background-color: var(--brand-page-deep); color: #e2e8f0; }

/* Page bg utilities used pervasively as the canvas. */
html.dark .bg-slate-50,
html.dark .bg-slate-100 { background-color: var(--brand-page-deep) !important; }

/* Cards + surfaces — promote white panels to slate-800 so contrast
   against the slate-900 page reads as a panel rather than a hole. */
html.dark .bg-white { background-color: #1e293b !important; }   /* slate-800 */
html.dark .bg-slate-50\/30,
html.dark .bg-slate-50\/40 { background-color: rgba(30, 41, 59, 0.4) !important; }

/* Borders — push them brighter so cards delineate against the
   slate-900 page. Slate-200 → slate-500 reads as a sharp 1px edge
   without looking neon. */
html.dark .border-slate-100 { border-color: #334155 !important; } /* slate-700 */
html.dark .border-slate-200 { border-color: #475569 !important; } /* slate-600 */
html.dark .border-slate-300 { border-color: #64748b !important; } /* slate-500 */
html.dark .divide-slate-100 > :not([hidden]) ~ :not([hidden]) { border-color: #334155 !important; }
html.dark .divide-slate-200 > :not([hidden]) ~ :not([hidden]) { border-color: #475569 !important; }

/* Body / heading text — flip the slate ramp. */
html.dark .text-slate-400 { color: #94a3b8 !important; }
html.dark .text-slate-500 { color: #cbd5e1 !important; }
html.dark .text-slate-600 { color: #cbd5e1 !important; }
html.dark .text-slate-700,
html.dark .text-slate-800,
html.dark .text-slate-900 { color: #f1f5f9 !important; }

/* Form fields — preserve focus colour, just flip the canvas. */
html.dark input[type="text"],
html.dark input[type="email"],
html.dark input[type="password"],
html.dark input[type="search"],
html.dark input[type="number"],
html.dark input[type="date"],
html.dark input[type="time"],
html.dark input[type="datetime-local"],
html.dark input[type="url"],
html.dark input[type="tel"],
html.dark textarea,
html.dark select {
  background-color: #1e293b;
  border-color: #475569;
  color: #f1f5f9;
}
html.dark input::placeholder,
html.dark textarea::placeholder { color: #64748b; }

/* Buttons / utility helper classes */
html.dark .btn-secondary {
  background-color: #334155;
  border-color: #475569;
  color: #f1f5f9;
}
html.dark .btn-secondary:hover { background-color: #475569; }

/* Dark-mode contrast guard: when a button/anchor in dark mode hovers
   into a *light* fill (Tailwind's white / slate-50 / slate-100 / -200),
   force the text back to a dark slate so the label stays readable.
   Without this, dark-mode's text-slate-700→#f1f5f9 flip leaves white
   text on a near-white hover fill (e.g. "Continue with Google"). */
html.dark a:hover.hover\:bg-white,
html.dark a:hover.hover\:bg-slate-50,
html.dark a:hover.hover\:bg-slate-100,
html.dark a:hover.hover\:bg-slate-200,
html.dark button:hover.hover\:bg-white,
html.dark button:hover.hover\:bg-slate-50,
html.dark button:hover.hover\:bg-slate-100,
html.dark button:hover.hover\:bg-slate-200 {
  color: #0f172a !important;          /* slate-900 */
}
html.dark a:hover.hover\:bg-white *,
html.dark a:hover.hover\:bg-slate-50 *,
html.dark a:hover.hover\:bg-slate-100 *,
html.dark a:hover.hover\:bg-slate-200 *,
html.dark button:hover.hover\:bg-white *,
html.dark button:hover.hover\:bg-slate-50 *,
html.dark button:hover.hover\:bg-slate-100 *,
html.dark button:hover.hover\:bg-slate-200 * {
  color: inherit;                     /* drag child <span>/<svg currentColor> dark */
}

/* Tinted utility backgrounds used for chips/pills — soften so they
   read against the dark page (the bright -100/-200 blends aggressively). */
html.dark .bg-indigo-50  { background-color: rgba(99, 102, 241, 0.18) !important; }
html.dark .bg-emerald-50 { background-color: rgba(16, 185, 129, 0.18) !important; }
html.dark .bg-amber-50   { background-color: rgba(245, 158, 11, 0.18) !important; }
html.dark .bg-rose-50    { background-color: rgba(244, 63, 94, 0.18) !important; }
html.dark .bg-sky-50     { background-color: rgba(56, 189, 248, 0.18) !important; }
html.dark .bg-slate-100  { background-color: #1e293b !important; }

/* Today banner / shared cards / module cards: most of these are
   .bg-white + .border-slate-200, already covered above. The few
   places that hard-code colours through inline styles are still
   driven by the CSS variables theme system (--color-primary etc.),
   which works in both themes since the school-theme accent stays
   the same. */


/* Bottom tabs (mobile bottom nav) — hard-coded white today, flip.
   Restored verbatim from e8a6525 (yesterday's pre-session state). */
html.dark .bottom-tabs       { background-color: #1e293b; border-top-color: #334155; }
html.dark .bottom-tab        { color: #94a3b8; }
html.dark .bottom-tab.is-active { color: var(--color-primary-light); }

/* Headings styled via .cms-prose etc. */
html.dark .cms-prose h1,
html.dark .cms-prose h2,
html.dark .cms-prose h3,
html.dark .cms-prose h4    { color: #f1f5f9; }
html.dark .cms-prose blockquote { color: #cbd5e1; border-color: #334155; }
html.dark .cms-prose code,
html.dark .cms-prose pre   { background: #1e293b; color: #f1f5f9; }

/* Card surfaces with hard-coded #fff backgrounds (Dashboard module
   tiles, Shared Admin cards, Schedule template cards, proposal
   cards, analytics cards, today-banner, etc.). The ``.bg-white``
   override above only catches Tailwind utility classes — these
   bespoke classes set their bg in CSS, so they need explicit
   overrides under ``html.dark`` to match the rest of the app.

   Borders bumped to slate-600 (matches the ``.border-slate-200``
   override) so the cards delineate cleanly against the slate-900
   page on either platform. */
html.dark .module-card,
html.dark .shared-card,
html.dark .tmpl-card,
html.dark .stl-card,
html.dark .prop-card,
html.dark .an-card,
html.dark .today-banner,
html.dark .today-row,
html.dark .admin-shared-card {
  background-color: #1e293b !important;
  border-color: #475569 !important;
  color: #f1f5f9;
}

/* Module-stat tiles inside .module-card use a light gradient — flip
   to a darker gradient so the inner surface still reads as a card
   within a card. */
html.dark .module-card .module-stat {
  background: linear-gradient(180deg, #1e293b 0%, #0f172a 100%) !important;
  border-color: #475569 !important;
}
html.dark .module-card.is-pending {
  background-color: #0f172a !important;
  border-color: #475569 !important;
}

/* ── Notification swipe-to-dismiss ────────────────────────────
   Each swipeable row is a relatively-positioned shell with the
   regular row content layered above an absolute-positioned red
   "delete" backdrop. Touch / mouse drag on the content slides it
   left to reveal the backdrop; releasing past the threshold POSTs
   to the dismiss endpoint and animates the row out. The form is
   still a submit-able fallback for keyboard / no-JS users — the JS
   intercepts the swipe gesture but doesn't disable the form. */
.notif-swipeable {
  position: relative;
  overflow: hidden;
  /* Round the swipe shell so the foreground card's rounded edge
   * meets a matching frame instead of a hard-square clip. iOS-
   * Notification-Center recipe — the row reads as one card. */
  border-radius: 14px;
}

/* Swipe-reveal action panel — fixed-width right tab, not a full-row
 * fill. Maintainer (May 14): "delete sliding looks not in
 * alignment, sometimes takes up the whole row." Was an inset:0
 * full-width red bg + a gradient mask trying to fake a 96 px tab.
 * The mask broke alignment when the foreground over-slid. Now
 * just a 96 px right-anchored panel — no mask, no gradient — so
 * the visible affordance is always exactly the tap target.
 */
.notif-swipe-actions {
  position: absolute;
  top: 0; bottom: 0; right: 0;
  width: 96px;
  display: flex;
  align-items: stretch;
  justify-content: stretch;
  background-color: #ef4444;            /* red-500 */
  color: #fff;
  border-radius: 0 14px 14px 0;
}
html.dark .notif-swipe-actions { background-color: #b91c1c; } /* red-700 */

.notif-swipe-delete {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  padding: 0 1rem;
  width: 100%;
  border: 0;
  background: transparent;
  color: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  cursor: pointer;
}
.notif-swipe-delete svg { width: 18px; height: 18px; }
.notif-swipe-label {
  font-size: 0.6875rem;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

/* Slide-able foreground — iOS-Notification-Center vibe: translucent
 * card with backdrop-filter blur sitting on top of whatever's
 * behind (the dark-mode aurora bg, or the light-mode paper). When
 * the user swipes, the red action panel underneath shows through
 * the gap, not THROUGH the card itself (the card stays opaque-
 * looking thanks to the saturated blur).
 *
 * Maintainer (May 14): "the notifications on mobile looks so
 * bad, can we use transparency like iphone notifications".
 */
.notif-swipe-content {
  position: relative;
  /* Opaque enough that the red dismiss panel beneath can't bleed
   * through. Was 0.72/0.55 — the maintainer's screenshot showed
   * the panel reading clearly THROUGH the card. The blur still
   * gives the iOS-vibe; we just stop the red leak. */
  background-color: rgba(255, 255, 255, 0.96);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  backdrop-filter: blur(20px) saturate(180%);
  border: 0.5px solid rgba(15, 23, 42, 0.06);
  border-radius: 14px;
  transform: translate3d(0, 0, 0);
  transition: transform 0.2s ease-out;
  will-change: transform;
  /* Keep horizontal swipes from being hijacked by browser
     overscroll on iOS Safari. */
  touch-action: pan-y;
}
.notif-swipe-content.is-dragging { transition: none; }
html.dark .notif-swipe-content {
  background-color: rgba(14, 29, 51, 0.94);
  border-color: rgba(255, 255, 255, 0.08);
}
/* Browsers without backdrop-filter (older Firefox, etc.) fall
 * through to a slightly-more-opaque fill so the card stays
 * legible. The cascade order matters: this block sits AFTER the
 * defaults so the supports-not block overrides only when needed. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .notif-swipe-content { background-color: rgba(255, 255, 255, 0.96); }
  html.dark .notif-swipe-content { background-color: #1e293b; }
}

/* The collapse animation when a row dismisses — height + opacity
   ease so neighbours slide up smoothly. */
.notif-swipeable.is-removing {
  max-height: 0;
  opacity: 0;
  margin: 0 !important;
  border: 0 !important;
  padding: 0 !important;
  transition: max-height 0.22s ease, opacity 0.18s ease,
              padding 0.22s ease, margin 0.22s ease;
  pointer-events: none;
}

/* Print: dark mode never bleeds into a printed page (paper is white). */
@media print {
  html.dark { background: #fff; color: #000; }
  html.dark body { background: #fff; color: #000; }
  html.dark .bg-white { background-color: #fff !important; }
}


/* ── Notification dropdown polish (maintainer May 14) ────────
 * Audit gap: panel reads gray-on-gray in dark mode + the
 * swipe-to-dismiss backdrop fills the entire row in a bright
 * red that overwhelms the design. Tighten:
 *
 *   1. Dark-mode panel + row + header backgrounds (the
 *      original CSS only had .dark overrides on the swipe
 *      foreground, not the panel chrome itself).
 *   2. Higher-contrast .notif-snippet / .notif-time in both
 *      modes — gray-on-gray was illegible.
 *   3. Dismiss-on-swipe becomes a 96 px end-of-row affordance
 *      (icon + label) instead of the whole-row red panel.
 *      Apple Mail / iOS Messages pattern.
 */

/* Panel chrome — dark mode */
html.dark .notif-bell-panel {
  /* Translucent dark on top of the page aurora. Stays legible
   * thanks to the saturated blur — same recipe as iOS dark
   * Notification Center. */
  background: rgba(14, 29, 51, 0.65);
  border-color: rgba(255, 255, 255, 0.10);
  box-shadow: 0 14px 32px -10px rgba(0, 0, 0, 0.55);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  html.dark .notif-bell-panel { background: #0E1D33; }
}
html.dark .notif-panel-header {
  border-bottom-color: rgba(255, 255, 255, 0.08);
}
html.dark .notif-panel-header .text-slate-900,
html.dark .notif-panel-header span,
html.dark .notif-panel-footer a {
  color: rgba(255, 255, 255, 0.92);
}
html.dark .notif-mark-all { color: #25D6AD; }
html.dark .notif-mark-all:hover { color: #4DDFB8; }

/* Secondary text — bump contrast a notch in both modes so the
 * snippet + timestamp actually read. */
.notif-snippet { color: #475569; }   /* slate-600 — was #64748b */
.notif-time    { color: #64748b; }   /* slate-500 — was #94a3b8 */
html.dark .notif-title   { color: rgba(255, 255, 255, 0.96); }
html.dark .notif-snippet { color: rgba(255, 255, 255, 0.68); }
html.dark .notif-time    { color: rgba(255, 255, 255, 0.50); }

/* Swipe-to-dismiss panel sizing + label tuning are now defined in
 * the canonical .notif-swipe-actions block above (May 14:
 * fixed-width 96 px right-anchored panel, dropped the gradient
 * mask trick). This section is intentionally empty so the prior
 * block's overrides aren't accidentally re-introduced via merge. */

/* ── Broadcast composer: multi-group picker ──────────────────
 * Maintainer May 14 audit follow-up: admins can now check N
 * groups in one submit. Server fans out one Communication row
 * per group. Multi-pick is a wrapping checkbox grid so a school
 * with many audiences (departments, programs, etc.) flows
 * without forcing a tall scroll.
 */
.comm-compose-group-multi {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 8px;
  margin-top: 8px;
}
.comm-compose-group-check {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 12px;
  background: #F8FAFC;
  border: 0.5px solid #E2E8F0;
  border-radius: 10px;
  cursor: pointer;
  transition: border-color 0.12s, background-color 0.12s;
  min-height: 44px;
}
.comm-compose-group-check:hover { border-color: #25D6AD; }
.comm-compose-group-check:has(input:checked) {
  border-color: rgba(37, 214, 173, 0.55);
  background: rgba(220, 245, 236, 0.6);
}
.comm-compose-group-check input[type="checkbox"] {
  margin-top: 3px;
  accent-color: #1AB994;
  flex: 0 0 auto;
}
.comm-compose-group-check-label {
  font: 500 13.5px/1.35 -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  color: #0F172A;
  min-width: 0;
  overflow-wrap: anywhere;
}
.comm-compose-group-hint {
  margin-top: 8px;
  font: 400 12px/1.45 -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  color: #64748B;
}
html.dark .comm-compose-group-check {
  background: rgba(255,255,255,0.04);
  border-color: rgba(255,255,255,0.10);
}
html.dark .comm-compose-group-check:has(input:checked) {
  background: rgba(37, 214, 173, 0.10);
  border-color: rgba(37, 214, 173, 0.40);
}
html.dark .comm-compose-group-check-label { color: rgba(255,255,255,0.92); }
html.dark .comm-compose-group-hint        { color: rgba(255,255,255,0.55); }

/* Account → Notifications · "on this device" push status row.
   push-init.js sets data-push-state on .prefs-device-row + swaps the
   title/sub copy; the test button shows only once state is "on". Accent
   follows the appearance palette (--brand-teal*); text uses slate +
   html.dark overrides like the rest of the account page. */
.prefs-device { margin: 0 0 1.25rem; }
.prefs-device-row {
  width: 100%; display: flex; align-items: center; gap: 0.875rem;
  padding: 0.875rem 1rem; text-align: left; cursor: pointer;
  background: #fff; border: 1px solid #e2e8f0; border-radius: 14px;
  transition: border-color .15s, background .15s;
}
html.dark .prefs-device-row { background: #0E1D33; border-color: #1e293b; }
.prefs-device-row:hover { border-color: var(--brand-teal, #2563EB); }
.prefs-device-ic {
  width: 40px; height: 40px; border-radius: 10px; flex: 0 0 auto;
  display: grid; place-items: center;
  background: var(--brand-teal-soft, #DBEAFE); color: var(--brand-teal-dark, #1D4ED8);
}
.prefs-device-ic svg { width: 20px; height: 20px; }
.prefs-device-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.prefs-device-title { font-weight: 600; font-size: 0.95rem; color: #0f172a; }
html.dark .prefs-device-title { color: #e2e8f0; }
.prefs-device-sub { font-size: 0.8rem; color: #64748b; }
html.dark .prefs-device-sub { color: #94a3b8; }
.prefs-device-chev { color: #94a3b8; flex: 0 0 auto; }
.prefs-device-chev svg { width: 18px; height: 18px; }
.prefs-device-row[data-push-state="on"] .prefs-device-ic { background: color-mix(in srgb, #10B981 14%, transparent); color: #047857; }
html.dark .prefs-device-row[data-push-state="on"] .prefs-device-ic { color: #34D399; }
.prefs-device-row[data-push-state="unavailable"] { opacity: .7; cursor: default; }
.prefs-device-debug { display: none; margin-top: 0.5rem; }
.prefs-device-row[data-push-state="on"] + .prefs-device-debug { display: block; }
.prefs-device-test {
  min-height: 36px; padding: 0 14px; border-radius: 999px; cursor: pointer;
  background: #fff; border: 1px solid #e2e8f0; font-size: 0.85rem; font-weight: 600; color: #334155;
}
html.dark .prefs-device-test { background: #0E1D33; border-color: #1e293b; color: #cbd5e1; }
.prefs-device-test:hover { border-color: var(--brand-teal, #2563EB); }
.prefs-device-test-result { font-size: 0.8rem; color: #64748b; margin-top: 6px; }
html.dark .prefs-device-test-result { color: #94a3b8; }
