/* ==========================================================================
   Central Furry Calendar | design system
   Dark, playful, high-contrast. Built to sit on top of a per-month artwork
   background without the artwork ever eating legibility.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */
:root {
  /* Night palette | the calendar always renders over artwork, so the surfaces
     are translucent panes rather than flat fills. */
  --ink-950: #0A0813;
  --ink-900: #0E0B18;
  --ink-800: #14101F;
  --ink-700: #1D1730;
  --ink-600: #2A2142;
  --ink-500: #3A2F58;

  --paper:   #F6F3FF;
  --paper-dim: #C6BEE4;
  --paper-faint: #8E85B0;

  /* Brand accents */
  --violet:  #7C5CFF;
  --violet-lo: #5B3FE0;
  --pink:    #FF4D9D;
  --mint:    #2FD3A5;
  --amber:   #FFB020;
  --sky:     #38BDF8;

  /* Tag colors | these four are spec-locked, do not re-map */
  --tag-18:   #FF3B4E;   /* red    */
  --tag-21:   #A855F7;   /* purple */
  --tag-fee:  #22C55E;   /* green  */
  --tag-rsvp: #EC4899;   /* pink   */

  /* Approval key | spec-locked 3-color system */
  --ok:    #22C55E;
  --warn:  #FACC15;
  --bad:   #FF3B4E;
  --idle:  #94A3B8;

  /* Glass surfaces */
  --glass:      rgba(20, 16, 31, 0.72);
  --glass-hi:   rgba(42, 33, 66, 0.78);
  --glass-line: rgba(246, 243, 255, 0.10);
  --glass-line-hi: rgba(246, 243, 255, 0.20);

  --radius-sm: 8px;
  --radius:    14px;
  --radius-lg: 22px;
  --radius-pill: 999px;

  --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.30);
  --shadow:    0 10px 30px rgba(0, 0, 0, 0.40);
  --shadow-lg: 0 24px 60px rgba(0, 0, 0, 0.50);

  --font: "Nunito", ui-rounded, "Segoe UI Variable", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-display: "Baloo 2", var(--font);

  /* One knob for the whole app's motion feel */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --fast: 120ms;
  --med:  220ms;
}

/* Respect the OS setting | every transition below collapses to nothing. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* --------------------------------------------------------------------------
   2. Reset & base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.55;
  color: var(--paper);
  background: var(--ink-950);
  /* Subtle aurora so pages without month artwork still feel alive */
  background-image:
    radial-gradient(1200px 600px at 15% -10%, rgba(124, 92, 255, 0.20), transparent 60%),
    radial-gradient(900px 500px at 95% 0%, rgba(255, 77, 157, 0.14), transparent 55%);
  background-attachment: fixed;
  min-height: 100vh;
  overflow-x: hidden;
}

img { max-width: 100%; display: block; }

a { color: var(--violet); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 800;
  line-height: 1.15;
  margin: 0 0 0.5em;
  letter-spacing: -0.01em;
}

button, input, select, textarea { font: inherit; color: inherit; }

/* Visible focus for keyboard users, invisible for mouse users. */
:focus-visible {
  outline: 3px solid var(--sky);
  outline-offset: 2px;
  border-radius: 4px;
}

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

/* Skip link | first tab stop on every page */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  background: var(--violet); color: #fff;
  padding: 0.75rem 1.25rem; border-radius: 0 0 var(--radius) 0;
  font-weight: 700;
}
.skip-link:focus { left: 0; }

/* --------------------------------------------------------------------------
   3. Layout
   -------------------------------------------------------------------------- */
.wrap {
  width: min(1320px, 100% - 2rem);
  margin-inline: auto;
}

.stack > * + * { margin-top: var(--gap, 1rem); }

/* --------------------------------------------------------------------------
   4. Glass panel | the workhorse surface
   -------------------------------------------------------------------------- */
.panel {
  background: var(--glass);
  border: 1px solid var(--glass-line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  backdrop-filter: blur(18px) saturate(1.4);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
}

.panel-pad { padding: clamp(1rem, 3vw, 1.75rem); }

/* Panel that sits on Admin-chosen month artwork. The ::before scrim is what
   guarantees text contrast no matter how busy or bright the artwork is. */
.art-panel {
  position: relative;
  isolation: isolate;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background-size: cover;
  background-position: center;
  box-shadow: var(--shadow-lg);
}
.art-panel::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(
    180deg,
    rgba(10, 8, 19, 0.28) 0%,
    rgba(10, 8, 19, 0.50) 45%,
    rgba(10, 8, 19, 0.62) 100%
  );
}

/* --------------------------------------------------------------------------
   5. Buttons & pills
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.6rem 1.1rem;
  border: 1px solid var(--glass-line-hi);
  border-radius: var(--radius-pill);
  background: var(--glass-hi);
  color: var(--paper);
  font-weight: 700;
  cursor: pointer;
  transition: transform var(--fast) var(--ease),
              background var(--fast) var(--ease),
              border-color var(--fast) var(--ease);
  text-decoration: none;
  /* Comfortable thumb target on touch devices */
  min-height: 44px;
}
.btn:hover { background: var(--ink-500); text-decoration: none; transform: translateY(-1px); }
.btn:active { transform: translateY(0) scale(0.98); }
.btn[disabled], .btn[aria-disabled="true"] {
  opacity: 0.4; cursor: not-allowed; pointer-events: none;
}

.btn-primary {
  background: linear-gradient(135deg, var(--violet), var(--pink));
  border-color: transparent;
  color: #fff;
  box-shadow: 0 6px 20px rgba(124, 92, 255, 0.35);
}
.btn-primary:hover { background: linear-gradient(135deg, var(--violet-lo), var(--pink)); }

.btn-ghost { background: transparent; }
.btn-sm { padding: 0.35rem 0.75rem; min-height: 34px; font-size: 0.85rem; }

.btn-icon {
  width: 44px; height: 44px; padding: 0;
  justify-content: center; border-radius: 50%;
}

/* Generic pill */
.pill {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.15rem 0.6rem;
  border-radius: var(--radius-pill);
  font-size: 0.75rem; font-weight: 800;
  letter-spacing: 0.02em;
  white-space: nowrap;
  line-height: 1.6;
}

/* Tag pills | full labels, used in Event Details */
.pill-18   { background: color-mix(in srgb, var(--tag-18) 22%, transparent);
             color: #FFB3BA; border: 1px solid var(--tag-18); }
.pill-21   { background: color-mix(in srgb, var(--tag-21) 22%, transparent);
             color: #E4C7FF; border: 1px solid var(--tag-21); }
.pill-fee  { background: color-mix(in srgb, var(--tag-fee) 22%, transparent);
             color: #B6F3CE; border: 1px solid var(--tag-fee); }
.pill-rsvp { background: color-mix(in srgb, var(--tag-rsvp) 22%, transparent);
             color: #FFC2E0; border: 1px solid var(--tag-rsvp); }

/* Log action pills */
.pill-green  { background: rgba(34,197,94,.18);  color: #B6F3CE; border: 1px solid var(--ok); }
.pill-blue   { background: rgba(56,189,248,.18); color: #BFE7FF; border: 1px solid var(--sky); }
.pill-red    { background: rgba(255,59,78,.18);  color: #FFB3BA; border: 1px solid var(--bad); }
.pill-yellow { background: rgba(250,204,21,.18); color: #FCEFA8; border: 1px solid var(--warn); }
.pill-grey   { background: rgba(148,163,184,.18);color: #D6DDE8; border: 1px solid var(--idle); }

/* --------------------------------------------------------------------------
   6. Tag dots | the compact calendar-grid markers
   A 14px circle carrying a single glyph. Four of these fit where one pill
   would not, which is the whole point on a dense day cell.
   -------------------------------------------------------------------------- */
.dots { display: inline-flex; gap: 3px; flex-shrink: 0; }

.dot {
  width: 14px; height: 14px;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 8px; font-weight: 900;
  color: #fff;
  line-height: 1;
  box-shadow: 0 0 0 1.5px rgba(0, 0, 0, 0.45);
  flex-shrink: 0;
  /* The markup uses <i> as the badge, so the UA stylesheet italicises the
     glyph. A slanted "18" leans out of a circle no matter how well the box is
     centered | the layout box measures dead center, the ink does not. */
  font-style: normal;
  /* Digits share a width, so "18" and "21" sit identically rather than one
     looking nudged against the other. */
  font-variant-numeric: tabular-nums;
  /* Ends the run cleanly: without this the trailing side bearing of the last
     glyph is counted in the centring and pushes short labels left. */
  letter-spacing: 0;
  text-indent: 0;
}
.dot-18   { background: var(--tag-18); }
.dot-21   { background: var(--tag-21); }
.dot-fee  { background: var(--tag-fee); }
.dot-rsvp { background: var(--tag-rsvp); }

/* Slightly larger, easier-to-read variant for the agenda list */
.dot-lg { width: 18px; height: 18px; font-size: 10px; }

/* --------------------------------------------------------------------------
   7. Status key | the 3-color approval indicator
   Color is never the only signal: each carries a glyph and a text label.
   -------------------------------------------------------------------------- */
.status {
  display: inline-flex; align-items: center; gap: 0.45rem;
  padding: 0.3rem 0.8rem;
  border-radius: var(--radius-pill);
  font-weight: 800; font-size: 0.8rem;
}
.status::before {
  content: ""; width: 10px; height: 10px; border-radius: 50%;
  box-shadow: 0 0 0 3px currentColor; opacity: 0.35;
}
.status-approved  { background: rgba(34,197,94,.16);  color: var(--ok); }
.status-exception { background: rgba(250,204,21,.16); color: var(--warn); }
.status-denied    { background: rgba(255,59,78,.16);  color: var(--bad); }
.status-pending   { background: rgba(148,163,184,.16);color: var(--idle); }

/* Full-width explanation banner shown under an event's status */
.status-note {
  border-left: 4px solid currentColor;
  padding: 0.6rem 0.9rem;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: 0.9rem;
  background: rgba(0, 0, 0, 0.25);
}

/* --------------------------------------------------------------------------
   8. Calendar grid | desktop / tablet
   -------------------------------------------------------------------------- */
.cal {
  --cell-min: 124px;
  padding: clamp(0.75rem, 2vw, 1.5rem);
}

/* Month title zone: artwork PNG overlay sits here */
.cal-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  margin-bottom: 1rem;
}

.cal-title { display: flex; align-items: center; gap: 0.85rem; min-width: 0; }

/* Transparent month-name PNG chosen by an Admin */
.cal-month-png {
  max-height: clamp(56px, 9vw, 96px);
  width: auto;
  filter: drop-shadow(0 4px 14px rgba(0, 0, 0, 0.6));
}

/* Text fallback when no month PNG has been uploaded yet */
.cal-month-text {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 5vw, 2.9rem);
  font-weight: 800;
  margin: 0;
  background: linear-gradient(135deg, #fff, var(--pink));
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
  text-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
}

.cal-nav { display: flex; align-items: center; gap: 0.5rem; }

/* --------------------------------------------------------------------------
   A month that exists but has not been published.

   The calendar is still drawn, because the shape of a month is information in
   itself and an empty panel is not. What is drawn is deliberately quiet: the
   dates recede, nothing looks clickable, and the notice above carries the one
   thing worth reading.
   -------------------------------------------------------------------------- */
.cal-locked {
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  padding: 1rem 1.15rem;
  margin-bottom: 1.1rem;
  border: 1px solid var(--glass-line);
  border-radius: var(--radius);
  background: var(--glass-hi);
}

.cal-locked-mark { font-size: 1.6rem; line-height: 1; flex-shrink: 0; }

/* Takes the free space so the button is pushed to the far edge on a wide
   panel, and drops onto its own line below the text on a narrow one. */
.cal-locked-text { flex: 1 1 16rem; min-width: 0; }
.cal-locked-text strong { display: block; font-size: 1rem; color: var(--paper); }
.cal-locked-text p {
  margin: 0.2rem 0 0;
  font-size: 0.85rem; line-height: 1.5; color: var(--paper-dim);
}

.cal-locked-cta { flex-shrink: 0; }

/* The dates stay readable | this is still a calendar | but sit back far enough
   that nobody reads an empty cell as a day with nothing on it yet. */
.cal.is-locked .cal-grid,
.cal.is-locked .cal-dotmap { opacity: 0.55; }
.cal.is-locked .cal-cell,
.cal.is-locked .dm-cell { cursor: default; }

.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 6px;
}

.cal-dow {
  text-align: center;
  font-size: 0.72rem; font-weight: 900;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--paper-faint);
  padding: 0.35rem 0;
}

.cal-cell {
  position: relative;
  min-height: var(--cell-min);
  padding: 0.4rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.42);
  border: 1px solid rgba(246, 243, 255, 0.17);
  display: flex; flex-direction: column; gap: 3px;
  transition: border-color var(--fast) var(--ease),
              background var(--fast) var(--ease);
  overflow: hidden;
}
.cal-cell:hover { border-color: var(--glass-line-hi); background: rgba(42, 33, 66, 0.6); }

/* Leading/trailing days | darkened per spec.
   Halved again from the original 0.5 so the month being viewed carries the
   eye. These days are orientation only: they say where the week starts and
   ends, and are never the thing being read. The plate underneath is deepened
   at the same time, so the cells read as recessed rather than merely faded. */
.cal-cell.is-out {
  background: rgba(3, 2, 7, 0.88);
  border-color: rgba(246, 243, 255, 0.05);
  opacity: 0.25;
}
.cal-cell.is-out .cal-daynum { color: var(--paper-faint); }

/* Hovering restores enough of the cell to read the date | the dimming is a
   visual hierarchy, not a statement that the day is unavailable. */
.cal-cell.is-out:hover {
  opacity: 0.55;
  background: rgba(3, 2, 7, 0.88);
  border-color: rgba(246, 243, 255, 0.14);
}

/* Today */
.cal-cell.is-today {
  border-color: var(--violet);
  box-shadow: inset 0 0 0 1px var(--violet), 0 0 22px rgba(124, 92, 255, 0.3);
}
.cal-cell.is-today .cal-daynum {
  background: var(--violet); color: #fff;
  border-radius: var(--radius-pill);
  padding: 0 0.45rem;
}

.cal-daynum {
  font-size: 0.8rem; font-weight: 800;
  color: var(--paper-dim);
  align-self: flex-start;
}

/* Event chip inside a day cell.
   Carries the furgroup icon plus its tag badges, mirroring the Photoshop
   calendars where every event shows its group art with +18 / $ / RSVP
   markers pinned to the corner. */
.chip {
  display: flex; align-items: center; gap: 5px;
  padding: 3px 4px;
  border-radius: 7px;
  background: rgba(124, 92, 255, 0.18);
  border-left: 3px solid var(--chip-color, var(--violet));
  font-size: 0.7rem;
  line-height: 1.25;
  cursor: pointer;
  text-align: left;
  width: 100%;
  border-top: 0; border-right: 0; border-bottom: 0;
  color: var(--paper);
  transition: background var(--fast) var(--ease), transform var(--fast) var(--ease);
  /* The cell is a flex column, so a chip would otherwise be squeezed if the
     row height were ever constrained. A day with four events must show four
     readable chips, not four compressed ones. */
  flex-shrink: 0;
}
.chip:hover { background: rgba(124, 92, 255, 0.38); transform: translateX(2px); }

/* Icon + its overlaid tag badges */
.chip-thumb-wrap { position: relative; flex-shrink: 0; line-height: 0; }

.chip-thumb {
  width: 30px; height: 30px;
  border-radius: 7px;
  object-fit: cover;
  border: 1.5px solid rgba(255, 255, 255, 0.8);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.55);
  background: var(--ink-700);
}

/* Badges hang off the icon's bottom-right, stacking leftwards */
.chip-tags {
  position: absolute; right: -4px; bottom: -4px;
  display: flex; gap: 1px;
  flex-direction: row-reverse;
}
.chip-tags .dot {
  width: 13px; height: 13px; font-size: 7.5px;
  box-shadow: 0 0 0 1.5px var(--ink-900);
}

.chip-text { min-width: 0; flex: 1; display: flex; flex-direction: column; }
.chip-name {
  font-weight: 700;
  /* Furmeet names run longer than venue names, so allow a second line rather
     than clipping the group's identity. Anything beyond two lines ellipses. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
  line-height: 1.2;
}
.chip-time {
  font-weight: 800; color: var(--paper-dim);
  font-variant-numeric: tabular-nums; font-size: 0.64rem;
}
.chip.is-cancelled .chip-name { text-decoration: line-through; opacity: 0.65; }

/* The same on the CFC Event Details cards, the title only. The red Cancelled
   pill beside it stays as it is: it is a sibling of the name, not its parent,
   so the line cannot reach it. */
.ev-card.is-cancelled .ev-open,
.ev-card.is-cancelled .ev-name-text { text-decoration: line-through; }

/* And on a phone. Below 768px the grid above is hidden and replaced by the
   agenda list, so the chip rule never shows there. The rows already carried
   is-cancelled; nothing was styling it, which is why a cancelled meet looked
   struck on desktop and perfectly normal on mobile. Title only, the same as
   the details cards: the time and venue line beneath it is left alone. */
.agenda-item.is-cancelled .agenda-title { text-decoration: line-through; }

/* Agenda rows carry the same icon, a little larger for touch */
.agenda-thumb-wrap { position: relative; flex-shrink: 0; line-height: 0; }
.agenda-thumb {
  width: 46px; height: 46px;
  border-radius: 11px;
  object-fit: cover;
  border: 2px solid rgba(255, 255, 255, 0.85);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.55);
}
.agenda-tags {
  position: absolute; right: -3px; bottom: -3px;
  display: flex; gap: 1px; flex-direction: row-reverse;
}
.agenda-tags .dot {
  width: 16px; height: 16px; font-size: 9px;
  box-shadow: 0 0 0 2px var(--ink-900);
}

.chip-more {
  font-size: 0.65rem; font-weight: 800;
  color: var(--pink);
  background: none; border: 0; padding: 2px 5px;
  cursor: pointer; text-align: left;
}
.chip-more:hover { text-decoration: underline; }

/* --------------------------------------------------------------------------
   9. Responsive strategy
   The hard problem: a 7-column grid with several events per day is unusable
   below ~700px. Rather than shrink it, we swap representation entirely.

   >= 1024px : full grid, up to 3 chips per cell
   768-1023  : grid, chips collapse to a single dot row per day
   < 768px   : DOT MAP + AGENDA. The month survives as a tiny tappable
               heatmap for orientation; real event data moves to a vertical
               list. Tapping a day scrolls the agenda to it.
   -------------------------------------------------------------------------- */

.cal-agenda { display: none; }
.cal-dotmap { display: none; }

@media (max-width: 1023px) {
  .cal { --cell-min: 96px; }
  .chip-time { display: none; }
  .chip-name { font-size: 0.65rem; }
}

@media (max-width: 767px) {
  /* Hide the full grid, reveal the dot map + agenda */
  .cal-grid { display: none; }
  .cal-dotmap { display: grid; }
  .cal-agenda { display: block; }

  .cal { padding: 0.75rem; }
  .cal-head { margin-bottom: 0.75rem; }
}

/* --- Mobile dot map ------------------------------------------------------ */
.cal-dotmap {
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 4px;
  margin-bottom: 1rem;
}

.dm-dow {
  text-align: center; font-size: 0.6rem; font-weight: 900;
  letter-spacing: 0.08em; color: var(--paper-faint);
  padding-bottom: 2px;
}

.dm-cell {
  aspect-ratio: 1;
  border-radius: 10px;
  background: rgba(20, 16, 31, 0.45);
  border: 1px solid rgba(246, 243, 255, 0.17);
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 2px;
  font-size: 0.72rem; font-weight: 800;
  color: var(--paper-dim);
  cursor: pointer;
  padding: 2px;
  transition: transform var(--fast) var(--ease), border-color var(--fast) var(--ease);
  /* Never below the 44px tap target on real phones */
  min-height: 40px;
}
.dm-cell:active { transform: scale(0.92); }
/* Matched to .cal-cell.is-out, held a little higher: these cells are ~40px
   across, so the same 0.25 would erase the date rather than recede it. */
.dm-cell.is-out { opacity: 0.2; background: rgba(3, 2, 7, 0.85); }
.dm-cell.is-today { border-color: var(--violet); color: #fff; }
.dm-cell.has-events { background: rgba(124, 92, 255, 0.22); border-color: var(--violet-lo); }
.dm-cell.is-selected {
  border-color: var(--pink);
  box-shadow: 0 0 0 2px var(--pink);
}

/* Miniature event indicators inside a dot-map cell */
.dm-pips { display: flex; gap: 2px; height: 5px; align-items: center; }
.dm-pip  { width: 5px; height: 5px; border-radius: 50%; background: var(--violet); }
.dm-pip.overflow { width: auto; height: auto; font-size: 7px; font-weight: 900;
                   color: var(--pink); background: none; line-height: 1; }

/* --- Mobile agenda ------------------------------------------------------- */
.agenda-day {
  scroll-margin-top: 80px;
  padding: 0.75rem 0;
  border-top: 1px solid var(--glass-line);
}
.agenda-day:first-child { border-top: 0; }
.agenda-day.is-empty { display: none; }

.agenda-date {
  display: flex; align-items: baseline; gap: 0.5rem;
  font-family: var(--font-display); font-weight: 800;
  font-size: 0.95rem; margin-bottom: 0.5rem;
  color: var(--paper);
  position: sticky; top: 0; z-index: 2;
  /* Glass rather than a solid bar, so the month artwork stays visible behind
     the sticky day headings. */
  background: linear-gradient(180deg, rgba(14, 11, 24, 0.86) 55%, rgba(14, 11, 24, 0));
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  padding: 0.4rem 0.6rem;
  border-radius: var(--radius-sm);
}
.agenda-date .dow { color: var(--pink); font-size: 0.75rem; text-transform: uppercase;
                    letter-spacing: 0.1em; }
.agenda-date.is-today .num { color: var(--violet); }

.agenda-item {
  display: flex; gap: 0.6rem;
  padding: 0.6rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.6);
  border-left: 4px solid var(--chip-color, var(--violet));
  margin-bottom: 0.5rem;
  cursor: pointer;
  width: 100%; text-align: left; border-top: 0; border-right: 0; border-bottom: 0;
  color: inherit;
}
.agenda-item:active { background: rgba(42, 33, 66, 0.75); }
.agenda-time {
  font-size: 0.72rem; font-weight: 800; color: var(--paper-dim);
  font-variant-numeric: tabular-nums; white-space: nowrap; padding-top: 2px;
}
.agenda-body { min-width: 0; flex: 1; }
.agenda-title { font-weight: 800; font-size: 0.9rem; }
.agenda-meta  { font-size: 0.75rem; color: var(--paper-faint);
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --------------------------------------------------------------------------
   10. Legend
   -------------------------------------------------------------------------- */
.legend {
  display: flex; flex-wrap: wrap; gap: 0.5rem 1rem;
  align-items: center;
  font-size: 0.78rem; color: var(--paper-dim);
  padding-top: 0.9rem; margin-top: 0.9rem;
  border-top: 1px solid var(--glass-line);
}
.legend-item { display: inline-flex; align-items: center; gap: 0.35rem; }

/* --------------------------------------------------------------------------
   11. Empty / gate states
   -------------------------------------------------------------------------- */
.empty {
  text-align: center;
  padding: clamp(2rem, 8vw, 4rem) 1rem;
  color: var(--paper-dim);
}
.empty-emoji { font-size: 3rem; margin-bottom: 0.5rem; filter: saturate(1.2); }
.empty h3 { color: var(--paper); }

/* --------------------------------------------------------------------------
   12. Credits
   The character is a sticker with a white die-cut outline, so it needs no
   card behind it | it sits directly on the page with a soft drop shadow.
   -------------------------------------------------------------------------- */
.credits {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(0.75rem, 3vw, 1.75rem);
  padding: clamp(1.25rem, 4vw, 2.5rem) 1rem;
  text-align: left;
}

.credits-sona {
  width: clamp(96px, 18vw, 168px);
  height: auto;
  filter: drop-shadow(0 8px 22px rgba(0, 0, 0, 0.55));
  transform-origin: bottom center;
}



.credits-text { min-width: 0; }

.credits-by {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1rem, 2.6vw, 1.3rem);
  margin: 0 0 0.15rem;
  background: linear-gradient(100deg, #DCDCE6 0%, #DCDCE6 22%, var(--sky) 92%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
}

.credits-art {
  margin: 0;
  font-size: 0.82rem;
  color: var(--paper-faint);
}
.credits-art strong { color: var(--paper-dim); font-weight: 800; }

@media (max-width: 480px) {
  .credits { flex-direction: column; text-align: center; }
}

/* --------------------------------------------------------------------------
   13. Fursona picker (admin)
   -------------------------------------------------------------------------- */
.sona-picker {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 0.75rem;
}

.sona-option {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 0.35rem;
  padding: 0.6rem 0.4rem 0.5rem;
  border-radius: var(--radius);
  border: 2px solid var(--glass-line);
  background: rgba(20, 16, 31, 0.55);
  cursor: pointer;
  transition: border-color var(--fast) var(--ease),
              background var(--fast) var(--ease),
              transform var(--fast) var(--ease);
}
.sona-option:hover { border-color: var(--glass-line-hi); transform: translateY(-2px); }
.sona-option img { width: 76px; height: 76px; object-fit: contain; }
.sona-option span { font-size: 0.7rem; font-weight: 700; color: var(--paper-dim); }

/* The radio itself is hidden; the label is the control. */
.sona-option input { position: absolute; opacity: 0; pointer-events: none; }
.sona-option:has(input:checked) {
  border-color: var(--pink);
  background: rgba(255, 77, 157, 0.14);
  box-shadow: 0 0 0 3px rgba(255, 77, 157, 0.18);
}
.sona-option:has(input:checked) span { color: #fff; }
/* Keyboard focus must still be visible even though the input is hidden. */
.sona-option:has(input:focus-visible) { outline: 3px solid var(--sky); outline-offset: 2px; }

.sona-option.is-random img { opacity: 0.85; }
.sona-option.is-random::after {
  content: "🎲";
  position: absolute; top: 4px; right: 6px; font-size: 0.9rem;
}

/* --------------------------------------------------------------------------
   14. Site header
   -------------------------------------------------------------------------- */
.site-header {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  padding: 1.25rem 0;
}
.brand { display: flex; align-items: center; gap: 0.75rem; }
.brand-logo { width: clamp(52px, 8vw, 76px); height: auto; }
.brand-text h1 {
  font-size: clamp(1.15rem, 3vw, 1.7rem); margin: 0; line-height: 1.05;
}
/* The title sets its own line-height at 1.05 to keep a two-line name tight,
   which also pulls the tagline right up under it. The gap is put back here
   rather than by loosening the title's leading, so a title that wraps still
   sits as one block. */
.brand-text p {
  margin: 0.65rem 0 0;
  font-size: 0.8rem;
  color: var(--paper-faint);
  line-height: 1.3;
}

.site-nav { display: flex; gap: 0.4rem; flex-wrap: wrap; }

/* --------------------------------------------------------------------------
   15. CFC Event Details + Event Updates
   Two columns on desktop; Updates drops below Details on narrow screens.
   -------------------------------------------------------------------------- */
.details-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.85fr) minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}
@media (max-width: 940px) {
  .details-layout { grid-template-columns: 1fr; }
}

.section-title {
  display: flex; align-items: center; gap: 0.6rem;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(1.05rem, 2.6vw, 1.4rem);
  margin: 0 0 0.9rem;
}
.section-title .count {
  font-size: 0.72rem; font-weight: 800;
  background: var(--glass-hi); border: 1px solid var(--glass-line);
  padding: 0.1rem 0.55rem; border-radius: var(--radius-pill);
  color: var(--paper-dim);
}

/* --- furgroup card --- */
.ev-list { display: grid; gap: 0.9rem; }
@media (min-width: 720px) {
  /* Mirrors the two-up arrangement of the printed event list */
  .ev-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.ev-card {
  display: flex; gap: 0.8rem;
  padding: 0.85rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.55);
  border: 1px solid var(--glass-line);
  border-left: 4px solid var(--accent, var(--violet));
  transition: transform var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.ev-card:hover { transform: translateY(-2px); border-color: var(--glass-line-hi); }

.ev-thumb {
  width: 66px; height: 66px; flex-shrink: 0;
  border-radius: 14px;
  object-fit: cover;
  border: 2px solid rgba(255, 255, 255, 0.85);
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.5);
}

.ev-body { min-width: 0; flex: 1; }

.ev-name {
  font-family: var(--font-display); font-weight: 800;
  font-size: 1.02rem; line-height: 1.2;
  color: var(--accent, var(--violet));
  display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap;
  margin-bottom: 0.15rem;
  /* The furgroup colors come from artwork and can be dark; this keeps them
     legible against the dark panel without changing the hue. */
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}

.ev-venue { font-weight: 700; font-size: 0.9rem; color: var(--paper); }
.ev-host  { font-size: 0.78rem; color: var(--paper-faint); margin-bottom: 0.35rem; }

.ev-row {
  display: flex; align-items: flex-start; gap: 0.4rem;
  font-size: 0.82rem; color: var(--paper-dim);
  margin-top: 0.15rem;
}
.ev-row .ico { flex-shrink: 0; opacity: 0.9; }
.ev-row a { color: var(--sky); }

.ev-tags { display: flex; gap: 0.3rem; flex-wrap: wrap; margin-top: 0.4rem; }

/* --- Event Updates: read-only chat window --- */
.updates {
  display: flex; flex-direction: column;
  max-height: 620px;
}
.updates-scroll {
  overflow-y: auto;
  display: flex; flex-direction: column; gap: 0.7rem;
  padding-right: 0.35rem;
  scrollbar-width: thin;
  scrollbar-color: var(--ink-500) transparent;
}
.updates-scroll::-webkit-scrollbar { width: 8px; }
.updates-scroll::-webkit-scrollbar-thumb {
  background: var(--ink-500); border-radius: 4px;
}

.upd {
  background: rgba(42, 33, 66, 0.6);
  border: 1px solid var(--glass-line);
  border-left: 3px solid var(--accent, var(--violet));
  border-radius: 4px var(--radius) var(--radius) var(--radius);
  padding: 0.6rem 0.75rem;
}
.upd-head {
  display: flex; align-items: baseline; gap: 0.4rem; flex-wrap: wrap;
  font-size: 0.72rem; margin-bottom: 0.25rem;
}
.upd-who  { font-weight: 800; color: var(--accent, var(--violet)); }
.upd-when { color: var(--paper-faint); margin-left: auto; }
.upd-body { font-size: 0.85rem; color: var(--paper); }
.upd-body p { margin: 0 0 0.4rem; }
.upd-body p:last-child { margin-bottom: 0; }

/* --------------------------------------------------------------------------
   16. Month navigation state
   -------------------------------------------------------------------------- */
.cal-nav .btn[aria-disabled="true"] { opacity: 0.28; }

.month-gate {
  display: flex; align-items: center; gap: 0.5rem;
  font-size: 0.78rem; color: var(--paper-faint);
}

/* Artist credit | red-to-orange, echoing the Now & Later brand palette */
.credit-artist {
  font-weight: 800;
  background: linear-gradient(95deg, #E8352B 0%, #F2542D 45%, #FF8A3D 100%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
  /* Fallback for engines that ignore background-clip on text */
  -webkit-text-fill-color: transparent;
}
@supports not (background-clip: text) {
  .credit-artist { color: #F2542D; -webkit-text-fill-color: #F2542D; }
}

/* --------------------------------------------------------------------------
   17. About page
   -------------------------------------------------------------------------- */
.about-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(1rem, 4vw, 2.5rem);
  align-items: center;
}
@media (max-width: 720px) {
  .about-hero { grid-template-columns: 1fr; }
  .about-hero-art { justify-self: center; }
}

.about-title {
  font-size: clamp(1.5rem, 5vw, 2.4rem);
  margin-bottom: 0.6rem;
  background: linear-gradient(100deg, #DCDCE6 0%, #DCDCE6 20%, var(--sky) 90%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
}

.about-hero-art {
  width: clamp(120px, 22vw, 190px);
  /* The same halo in black, removed for the same reason. */
}

/* Styling for Admin-authored rich text. Kept deliberately narrow | these are
   the only tags the sanitiser lets through. */
.prose { color: var(--paper-dim); max-width: 68ch; }
.prose p { margin: 0 0 0.9rem; }
.prose p:last-child { margin-bottom: 0; }
.prose strong { color: var(--paper); font-weight: 800; }
.prose em { color: var(--paper); }
.prose a { color: var(--sky); text-decoration: underline; }
.prose ul, .prose ol { margin: 0 0 0.9rem; padding-left: 1.3rem; }
.prose li { margin-bottom: 0.35rem; }
.prose h3 {
  font-size: 1.05rem; color: var(--paper);
  margin: 1.2rem 0 0.4rem;
}
.prose blockquote {
  margin: 0 0 0.9rem; padding: 0.6rem 0.9rem;
  border-left: 3px solid var(--violet);
  background: rgba(0, 0, 0, 0.25);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

/* --- how it works cards --- */
.about-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.9rem;
}

.about-card {
  padding: 1rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.5);
  border: 1px solid var(--glass-line);
  transition: transform var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.about-card:hover { transform: translateY(-3px); border-color: var(--violet); }
.about-card-ico { font-size: 1.6rem; display: block; margin-bottom: 0.4rem; }
.about-card h3 { font-size: 1rem; margin: 0 0 0.3rem; }
.about-card p { margin: 0; font-size: 0.85rem; color: var(--paper-faint); }

/* --- tag explainer --- */
.about-tags { display: grid; gap: 0.75rem; }
@media (min-width: 640px) {
  .about-tags { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.about-tag {
  display: flex; align-items: flex-start; gap: 0.7rem;
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.45);
  border: 1px solid var(--glass-line);
}
.about-tag p { margin: 0; font-size: 0.85rem; color: var(--paper-dim); }

/* --- CTA --- */
.about-cta {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
}

/* Inline icons inside buttons. Using SVG rather than emoji keeps the glyph
   monochrome | emoji carry their own palette, which bled color into the
   gradient buttons. */
.btn-ico { flex-shrink: 0; display: block; }

/* Explicit left-to-right ramp so the violet end always anchors the left edge */
.btn-primary {
  background: linear-gradient(90deg, var(--violet) 0%, var(--pink) 100%);
}
.btn-primary:hover {
  background: linear-gradient(90deg, var(--violet-lo) 0%, var(--pink) 100%);
}

/* --------------------------------------------------------------------------
   18. People | creators and Furmeet Manager intros (About page)
   -------------------------------------------------------------------------- */

/* --- Creators: large, two-up --- */
.creators {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
  gap: 1rem;
}

.creator {
  padding: 1.1rem;
  border-radius: var(--radius-lg);
  background: rgba(20, 16, 31, 0.5);
  border: 1px solid var(--glass-line);
  transition: transform var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.creator:hover { transform: translateY(-3px); border-color: var(--violet); }

/* Name and title sit above the pair below, which lets the portrait start on
   the same line as the bio instead of beside the heading. */
.creator-head { margin-bottom: 0.6rem; }

.creator-body {
  display: flex; gap: 1rem;
  align-items: flex-start;          /* portrait top = first line of the bio */
}

@media (max-width: 520px) {
  .creator { text-align: center; }
  .creator-name { justify-content: center; }
  .creator-body { flex-direction: column; align-items: center; }
}

.creator-avatar {
  width: 148px; height: 148px; flex-shrink: 0;
  border-radius: 24px;
  object-fit: cover;
  border: 2px solid rgba(255, 255, 255, 0.85);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.55);
  background: var(--ink-700);
}

.creator-name {
  font-family: var(--font-display); font-weight: 800;
  font-size: 1.15rem; margin: 0;
  display: flex; align-items: baseline; gap: 0.45rem; flex-wrap: wrap;
}
.creator-pronouns {
  font-size: 0.72rem; font-weight: 700;
  color: var(--paper-faint);
  background: rgba(255, 255, 255, 0.07);
  padding: 0.05rem 0.45rem; border-radius: var(--radius-pill);
}
.creator-role {
  display: inline-block;
  font-size: 0.72rem; font-weight: 800;
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--pink);
  margin-top: 0.15rem;
}
.creator-bio { font-size: 0.87rem; color: var(--paper-dim); }
.creator-bio p { margin: 0 0 0.6rem; }
.creator-bio p:last-child { margin-bottom: 0; }

/* --- Managers: compact strip --- */
.manager-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(216px, 1fr));
  gap: 0.75rem;
}

.manager-card {
  display: flex; gap: 0.7rem;
  padding: 0.75rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.45);
  border: 1px solid var(--glass-line);
}
.manager-card:hover { border-color: var(--glass-line-hi); }

.manager-avatar {
  width: 52px; height: 52px; flex-shrink: 0;
  border-radius: 12px;
  object-fit: cover;
  border: 2px solid rgba(255, 255, 255, 0.75);
  background: var(--ink-700);
}

.manager-body { min-width: 0; }
.manager-name { font-weight: 800; font-size: 0.92rem; line-height: 1.2; }
.manager-pronouns { font-size: 0.68rem; color: var(--paper-faint); }
.manager-bio {
  font-size: 0.78rem; color: var(--paper-faint);
  margin: 0.25rem 0 0.35rem;
  /* Short intros only | long ones clamp rather than break the grid rhythm */
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Which furmeets this manager runs */
.manager-meets { display: flex; flex-wrap: wrap; gap: 0.25rem; }
.meet-chip {
  font-size: 0.66rem; font-weight: 800;
  padding: 0.08rem 0.45rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--meet-color, var(--violet));
  color: var(--meet-color, var(--violet));
  background: color-mix(in srgb, var(--meet-color, var(--violet)) 16%, transparent);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}

/* Avatar fallback when no image has been uploaded */
.avatar-fallback {
  display: grid; place-items: center;
  font-family: var(--font-display); font-weight: 800;
  color: #fff;
  background: var(--fallback, var(--violet));
}
.creator-avatar.avatar-fallback { font-size: 2.4rem; }
.manager-avatar.avatar-fallback { font-size: 1.3rem; }

/* Shown when no one has opted in yet */
.people-empty {
  font-size: 0.85rem; color: var(--paper-faint);
  padding: 0.9rem; border-radius: var(--radius);
  border: 1px dashed var(--glass-line-hi);
}

/* Credit lead-in above the designer name */
.credits-label {
  margin: 0 0 0.15rem;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--paper-faint);
}

/* About hero: give the copy the full column. The 68ch prose cap was leaving
   the intro sitting in half the panel with dead space beside it. */
.about-hero { grid-template-columns: minmax(0, 1fr) auto; }
.about-hero .prose { max-width: none; }
.about-hero-art { width: clamp(104px, 15vw, 156px); }

/* --------------------------------------------------------------------------
   19. Animated CFC logo
   The mark is a flat raster, so the motion is applied to the element rather
   than to internal shapes. Two layers:
     - the image itself, which idles and perks on hover
     - a shine band masked to the logo's own alpha, so the highlight travels
       across the fox and never spills into a rectangle around it
   -------------------------------------------------------------------------- */
.cfc-logo {
  position: relative;
  display: inline-block;
  line-height: 0;
  flex-shrink: 0;
  /* No drop-shadow here.
     There was a warm orange one, and a drop-shadow is drawn from the image's
     alpha channel | so on a logo that fills its own square it spilled past the
     edges of whatever the mark was sitting in. The idle float, the hover perk
     and the shine band are all still here; only the halo has gone. */
  animation: cfc-logo-idle 6s cubic-bezier(.45,0,.55,1) infinite;
  transform-origin: 50% 55%;
}

.cfc-logo img {
  display: block;
  width: 100%;
  height: auto;
}

@keyframes cfc-logo-idle {
  0%, 100% { transform: translateY(0)      rotate(0deg); }
  50%      { transform: translateY(-3.5px) rotate(-1.6deg); }
}

/* Shine band. mask-image clips it to the fox silhouette. */
.cfc-logo-shine {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    112deg,
    transparent 42%,
    rgba(255, 255, 255, 0.55) 50%,
    transparent 58%
  );
  background-size: 260% 100%;
  background-repeat: no-repeat;
  /* Masked to the silhouette of whatever logo is in force. --logo-mask is set
     once in head.php from the same helper the <img> uses. There is no literal
     fallback: the site ships no logo of its own, and a hard-coded path here
     would be a 404 on every page and a wrongly-shaped highlight on any logo
     that is not the one it named. Without the variable the shine simply does
     not paint, which is right | there is no mark to shine on. */
  -webkit-mask-image: var(--logo-mask, none);
          mask-image: var(--logo-mask, none);
  -webkit-mask-size: contain;  mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;  mask-position: center;
  animation: cfc-logo-shine 6s cubic-bezier(.5,0,.5,1) infinite;
  mix-blend-mode: screen;
}

@keyframes cfc-logo-shine {
  0%, 55%  { background-position: 170% 0; }
  90%, 100%{ background-position: -60% 0; }
}

/* Hover: a quick wag, as though the fox noticed you */
.cfc-logo:hover {
  animation: cfc-logo-perk 700ms cubic-bezier(.34,1.56,.64,1);
}
@keyframes cfc-logo-perk {
  0%   { transform: scale(1)    rotate(0deg); }
  30%  { transform: scale(1.07) rotate(4.5deg); }
  60%  { transform: scale(0.99) rotate(-2.5deg); }
  100% { transform: scale(1)    rotate(0deg); }
}

@media (prefers-reduced-motion: reduce) {
  .cfc-logo, .cfc-logo:hover { animation: none; }
  .cfc-logo-shine { animation: none; opacity: 0; }
}

/* Size variants */
.cfc-logo-header { width: clamp(54px, 8vw, 82px); }
.cfc-logo-hero   { width: clamp(120px, 18vw, 190px); }

/* --------------------------------------------------------------------------
   20. Apply button | warm orange, picking up the logo's palette
   Dark ink rather than white: white on mid-orange lands around 2.3:1 contrast,
   which fails WCAG AA. Near-black on the same orange clears 8:1.
   -------------------------------------------------------------------------- */
.btn-apply {
  background: linear-gradient(95deg, #FFB93F 0%, #F79429 48%, #EE6A24 100%);
  border-color: transparent;
  color: #2B1405;
  font-weight: 800;
  box-shadow: 0 6px 18px rgba(238, 118, 36, 0.36);
}
.btn-apply:hover {
  background: linear-gradient(95deg, #FFC55A 0%, #FBA036 48%, #F5772C 100%);
  color: #2B1405;
  box-shadow: 0 8px 24px rgba(238, 118, 36, 0.48);
}
.btn-apply:active { box-shadow: 0 3px 10px rgba(238, 118, 36, 0.4); }

/* Current page marker in the nav */
.site-nav .btn:not(.btn-apply)[aria-current="page"] {
  border-color: var(--violet);
  background: rgba(124, 92, 255, 0.18);
  color: #fff;
}

/* Apply is recognized by its orange, so on /apply.php it keeps the gradient
   rather than being repainted violet like the outlined nav items. The
   current-page state is carried by a ring and a slightly damped glow instead,
   which reads as "you are here" without losing the button's identity. */
.site-nav .btn-apply[aria-current="page"] {
  background: linear-gradient(95deg, #FFB93F 0%, #F79429 48%, #EE6A24 100%);
  color: #2B1405;
  box-shadow: 0 0 0 2px rgba(255, 201, 111, 0.85),
              0 3px 10px -3px rgba(238, 118, 36, 0.55);
}
.site-nav .btn-apply[aria-current="page"]:hover {
  background: linear-gradient(95deg, #FFC55A 0%, #FBA036 48%, #F5772C 100%);
  box-shadow: 0 0 0 2px rgba(255, 201, 111, 0.95),
              0 5px 14px -4px rgba(238, 118, 36, 0.65);
}

/* The brand block is an identity mark, not navigation | nothing in it should
   read as clickable. Navigation to the calendar lives in the nav bar. */
.brand,
.brand * {
  text-decoration: none !important;
  cursor: default;
}
.brand h1,
.brand p { user-select: none; }

/* --------------------------------------------------------------------------
   21. Gradient button edge fixes
   Two causes of color bleeding past the button edge:
     1. .btn carries a 1px border; the gradient buttons set border-color to
        transparent, and background-clip defaults to border-box | so the
        gradient painted UNDER that transparent border and showed as a fringe
        of the ramp's end color (pale violet on the left of Sync, pale orange
        on the right of Apply).
     2. The wide, low-opacity box-shadow glow haloed sideways rather than
        sitting beneath the button.
   -------------------------------------------------------------------------- */
.btn-primary,
.btn-apply {
  /* Clip the gradient to the padding box so nothing paints under the border */
  background-clip: padding-box;
  border: 0;
  /* Reinstate the 1px the removed border was contributing, so the gradient
     buttons stay the same height as their outlined neighbours */
  padding: calc(0.6rem + 1px) calc(1.1rem + 1px);
}
.btn-primary.btn-sm,
.btn-apply.btn-sm {
  padding: calc(0.35rem + 1px) calc(0.75rem + 1px);
}

/* Negative spread keeps the glow beneath the button instead of haloing out
   to the sides. */
.btn-primary {
  box-shadow: 0 5px 14px -4px rgba(124, 92, 255, 0.55);
}
.btn-primary:hover {
  box-shadow: 0 7px 18px -5px rgba(124, 92, 255, 0.65);
}
.btn-apply {
  box-shadow: 0 5px 14px -4px rgba(238, 118, 36, 0.60);
}
.btn-apply:hover {
  box-shadow: 0 7px 18px -5px rgba(238, 118, 36, 0.70);
}
.btn-apply:active,
.btn-primary:active {
  box-shadow: 0 2px 6px -2px rgba(0, 0, 0, 0.5);
}

/* --------------------------------------------------------------------------
   22. Footer placement
   Deliberately NOT a sticky footer. Pushing it down with margin-top:auto put
   the credits on the viewport's bottom edge but opened a void between them and
   the content | 532px of nothing on a tall window. The credits belong attached
   to the content that precedes them; leftover viewport space simply shows the
   page background below, which is what a reader expects.
   -------------------------------------------------------------------------- */
body {
  /* Kept so the background gradient always covers the viewport */
  min-height: 100vh;
  min-height: 100dvh;
}

/* No bottom padding | the fursona is meant to sit flush on the page's bottom
   edge, so nothing should sit beneath it. Spacing above still separates the
   credits from the content. */
.wrap { padding-bottom: 0; }

.credits {
  padding-top: clamp(1rem, 3vw, 1.8rem);
  padding-bottom: 0;
  align-items: flex-end;
}

/* The sticker canvases are 512x512 but the character only fills ~453px of
   that, leaving transparent padding at the foot. A small negative margin
   absorbs it so the art itself lands on the page edge rather than its box. */
.credits-sona {
  margin-bottom: -2%;
  transform-origin: bottom center;
}

/* The text sits on the baseline of the art rather than centered against it */
.credits-text { padding-bottom: clamp(0.75rem, 2.5vw, 1.5rem); }

/* --------------------------------------------------------------------------
   23. Flash messages and banners
   -------------------------------------------------------------------------- */
.flash {
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0.75rem 1rem;
  border-radius: var(--radius);
  margin-bottom: 0.75rem;
  font-size: 0.9rem; font-weight: 600;
  border: 1px solid var(--glass-line-hi);
  background: var(--glass-hi);
}
.flash-success { border-color: var(--ok);   background: rgba(34,197,94,.14);  color: #C6F6D9; }
.flash-error   { border-color: var(--bad);  background: rgba(255,59,78,.14);  color: #FFC7CD; }
.flash-warn    { border-color: var(--warn); background: rgba(250,204,21,.14); color: #FBEDA6; }
.flash-info    { border-color: var(--sky);  background: rgba(56,189,248,.14); color: #C8E9FF; }

.banner {
  display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap;
  padding: 0.8rem 1.1rem;
  border-radius: var(--radius);
  margin-bottom: 1rem;
  font-size: 0.9rem;
  border: 1px solid var(--glass-line-hi);
  background: var(--glass-hi);
}
.banner-warn {
  border-color: var(--warn);
  background: rgba(250, 204, 21, 0.12);
  color: #FBEDA6;
}
.banner strong { color: #fff; }

/* Copy-to-clipboard confirmation */
.is-copied { border-color: var(--ok) !important; color: #C6F6D9 !important; }

/* Agenda day gets focus programmatically after a dot-map tap; suppress the
   outline since the scroll itself is the visible feedback. */
.agenda-day:focus { outline: none; }

/* --------------------------------------------------------------------------
   24. Forms and the auth screen
   -------------------------------------------------------------------------- */
.auth-shell {
  display: grid; place-items: center;
  min-height: 100vh; min-height: 100dvh;
  padding: 2rem 1rem;
}
.auth-card { width: min(420px, 100%); }
.auth-brand {
  display: flex; align-items: center; gap: 0.75rem;
  margin-bottom: 1.25rem;
}
.auth-note {
  font-size: 0.8rem; color: var(--paper-faint);
  margin: 0.75rem 0 0; text-align: center;
}

.form { display: grid; gap: 0.9rem; }

.field { display: block; }
.field-label {
  display: block;
  font-size: 0.8rem; font-weight: 800;
  letter-spacing: 0.03em; text-transform: uppercase;
  color: var(--paper-faint);
  margin-bottom: 0.3rem;
}
.field-hint { font-size: 0.78rem; color: var(--paper-faint); margin-top: 0.25rem; }
.field-error { font-size: 0.78rem; color: #FFB3BA; margin-top: 0.25rem; }

.input, .textarea, .select {
  width: 100%;
  padding: 0.65rem 0.85rem;
  border-radius: var(--radius);
  border: 1px solid var(--glass-line-hi);
  background: rgba(10, 8, 19, 0.55);
  color: var(--paper);
  /* 16px minimum stops iOS Safari zooming the viewport on focus */
  font-size: 16px;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.input:focus, .textarea:focus, .select:focus {
  border-color: var(--violet);
  background: rgba(10, 8, 19, 0.75);
}
.input[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: var(--bad); }
.textarea { min-height: 120px; resize: vertical; line-height: 1.5; }

/* --- Select: replace the platform arrow ---------------------------------
   The native control draws its own arrow hard against the right edge, ignoring
   the field's padding, so it sat almost off the end and in the OS's own color
   rather than the site's. appearance:none drops it; the chevron below is drawn
   in as a background and given real room to sit in.

   The SVG is a data: URI so there is no extra request and nothing for the CSP
   to fetch. currentColor is not available inside a background image, so the
   stroke is the paper tone written out. */
.select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23B9B2D6' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.85rem center;
  background-size: 1.05rem;
  /* Room for the chevron plus the padding it should keep from the text. */
  padding-right: 2.6rem;
  cursor: pointer;
}
/* The shorthand above would otherwise wipe out the panel color it inherits. */
.select { background-color: rgba(10, 8, 19, 0.55); }
.select:focus { background-color: rgba(10, 8, 19, 0.75); }

/* IE/Edge legacy arrow, and Firefox's dotted focus ring on the text. */
.select::-ms-expand { display: none; }
.select:-moz-focusring { color: transparent; text-shadow: 0 0 0 var(--paper); }

/* The dropdown list itself is drawn by the OS, which does not inherit the
   page's palette | dark text on a dark popup is the usual result. */
.select option {
  background: var(--ink-800);
  color: var(--paper);
}

.btn-block { width: 100%; justify-content: center; }

/* Two-up field rows that stack on narrow screens */
.field-row {
  display: grid; gap: 0.9rem;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}

/* Checkbox / radio groups */
.choice {
  display: flex; align-items: flex-start; gap: 0.6rem;
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius);
  border: 1px solid var(--glass-line);
  background: rgba(20, 16, 31, 0.45);
  cursor: pointer;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.choice:hover { border-color: var(--glass-line-hi); }
.choice input { margin-top: 0.2rem; accent-color: var(--violet); width: 18px; height: 18px; }
.choice:has(input:checked) {
  border-color: var(--violet);
  background: rgba(124, 92, 255, 0.14);
}
.choice-text { font-size: 0.9rem; }

/* --------------------------------------------------------------------------
   25. Console shell (Admin + Manager)
   Sidebar collapses to a horizontal scroller on narrow screens rather than
   hiding behind a hamburger | organizers work from phones and a visible nav
   beats a hidden one.
   -------------------------------------------------------------------------- */
.console {
  display: grid;
  grid-template-columns: 248px minmax(0, 1fr);
  min-height: 100vh; min-height: 100dvh;
  gap: 0;
}

.console-side {
  background: rgba(10, 8, 19, 0.7);
  border-right: 1px solid var(--glass-line);
  padding: 1rem 0.75rem;
  position: sticky; top: 0;
  height: 100vh; height: 100dvh;
  overflow-y: auto;
}

.console-brand {
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0 0.5rem 1rem;
  border-bottom: 1px solid var(--glass-line);
  margin-bottom: 0.75rem;
}
.console-brand strong { display: block; font-size: 0.92rem; line-height: 1.2; }
.console-brand span   { display: block; font-size: 0.75rem; color: var(--paper-faint); }

.console-nav { display: flex; flex-direction: column; gap: 2px; }

.console-group {
  font-size: 0.68rem; font-weight: 900;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--paper-faint);
  padding: 0.85rem 0.6rem 0.25rem;
}

.console-link {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.5rem 0.65rem;
  border-radius: var(--radius-sm);
  font-size: 0.88rem; font-weight: 600;
  color: var(--paper-dim);
  text-decoration: none;
  border: 0; background: none; width: 100%;
  text-align: left; cursor: pointer;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.console-link:hover { background: var(--glass-hi); color: var(--paper); text-decoration: none; }
.console-link.is-active {
  background: rgba(124, 92, 255, 0.18);
  color: #fff;
  box-shadow: inset 3px 0 0 var(--violet);
}
.console-signout { color: var(--paper-faint); }
.console-signout:hover { color: #FFC7CD; }

.console-badge {
  margin-left: auto;
  background: var(--warn); color: #2A1F00;
  font-size: 0.7rem; font-weight: 900;
  padding: 0.05rem 0.4rem; border-radius: var(--radius-pill);
  min-width: 20px; text-align: center;
}

.console-main { padding: clamp(1rem, 3vw, 2rem); min-width: 0; }

.console-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  margin-bottom: 1.25rem;
}
.console-head h1 { margin: 0; font-size: clamp(1.3rem, 3.5vw, 1.9rem); }
.console-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }

@media (max-width: 860px) {
  .console { grid-template-columns: 1fr; }
  .console-side {
    position: static; height: auto;
    border-right: 0; border-bottom: 1px solid var(--glass-line);
  }
  .console-nav {
    flex-direction: row; flex-wrap: nowrap;
    overflow-x: auto; gap: 4px;
    scrollbar-width: thin;
  }
  .console-group { display: none; }
  .console-link { white-space: nowrap; width: auto; }
  .console-link.is-active { box-shadow: inset 0 -3px 0 var(--violet); }
}

/* --------------------------------------------------------------------------
   26. Console widgets | stats, queues, month status
   -------------------------------------------------------------------------- */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0.75rem;
  margin-bottom: 1rem;
}

.stat {
  display: block;
  padding: 1rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.55);
  border: 1px solid var(--glass-line);
  text-decoration: none; color: inherit;
  transition: transform var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.stat:hover { transform: translateY(-2px); border-color: var(--glass-line-hi); text-decoration: none; }
.stat-num {
  display: block;
  font-family: var(--font-display); font-weight: 800;
  font-size: 2rem; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.stat-label { display: block; font-size: 0.8rem; color: var(--paper-faint); margin-top: 0.3rem; }
/* Zero is the good state, so only tint when there is actually something to do */
.stat.is-ok   { border-color: var(--ok); }
.stat.is-ok .stat-num   { color: var(--ok); }
.stat.is-warn { border-color: var(--warn); }
.stat.is-warn .stat-num { color: var(--warn); }
.stat.is-bad  { border-color: var(--bad); }
.stat.is-bad .stat-num  { color: var(--bad); }

/* --- review queue --- */
.queue { display: grid; gap: 0.6rem; }
.queue-item {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.75rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.5);
  border: 1px solid var(--glass-line);
  border-left: 4px solid var(--accent, var(--violet));
  text-decoration: none; color: inherit;
  transition: border-color var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.queue-item:hover { transform: translateX(2px); border-color: var(--glass-line-hi); text-decoration: none; }
.queue-body { min-width: 0; flex: 1; }
.queue-body strong { display: block; font-size: 0.94rem; }
.queue-meta { display: block; font-size: 0.78rem; color: var(--paper-faint); margin-top: 0.15rem; }
.queue-tags { display: flex; gap: 0.25rem; flex-wrap: wrap; margin-top: 0.35rem; }

/* --- month publishing --- */
.month-status { display: grid; gap: 0.7rem; }
.month-status-item {
  display: flex; align-items: center; gap: 0.85rem; flex-wrap: wrap;
  padding: 0.8rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.5);
  border: 1px solid var(--glass-line);
}
.month-status-item > div { flex: 1; min-width: 180px; }
.month-status-item strong { display: block; font-size: 0.95rem; }
.month-status-item p { margin: 0.1rem 0 0; font-size: 0.8rem; color: var(--paper-faint); }

/* --------------------------------------------------------------------------
   27. Event rows, review grid, decision buttons, log diffs
   -------------------------------------------------------------------------- */
.ev-rows { display: grid; gap: 0.5rem; }
.ev-rows.is-past { opacity: 0.82; }

.ev-row-link {
  display: flex; align-items: center; gap: 0.85rem;
  padding: 0.7rem 0.8rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.5);
  border: 1px solid var(--glass-line);
  border-left: 4px solid var(--accent, var(--violet));
  text-decoration: none; color: inherit;
  transition: transform var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.ev-row-link:hover { transform: translateX(2px); border-color: var(--glass-line-hi); text-decoration: none; }

.ev-row-date {
  text-align: center; flex-shrink: 0; min-width: 46px;
  font-variant-numeric: tabular-nums;
}
.ev-row-date strong { display: block; font-size: 0.9rem; }
.ev-row-date span   { display: block; font-size: 0.68rem; color: var(--paper-faint);
                      text-transform: uppercase; letter-spacing: 0.08em; }

.ev-row-main { flex: 1; min-width: 0; }
.ev-row-title { display: block; font-weight: 700; font-size: 0.92rem; }
.ev-row-meta  { display: block; font-size: 0.78rem; color: var(--paper-faint);
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 560px) {
  .ev-row-link { flex-wrap: wrap; }
  .ev-row-link .status { order: 3; }
}

/* --- status block above a form --- */
.status-block {
  padding: 0.9rem 1.1rem;
  border-radius: var(--radius);
  border: 1px solid currentColor;
  margin-bottom: 1rem;
  background: rgba(0, 0, 0, 0.25);
}
.status-block p { margin: 0.4rem 0 0; font-size: 0.85rem; color: var(--paper-dim); }
.status-block .status-note { margin-top: 0.6rem; color: var(--paper); }
.status-block.status-approved  { color: var(--ok); }
.status-block.status-exception { color: var(--warn); }
.status-block.status-denied    { color: var(--bad); }
.status-block.status-pending   { color: var(--idle); }

/* --- review detail grid --- */
.review-grid {
  display: grid;
  grid-template-columns: minmax(110px, auto) 1fr;
  gap: 0.5rem 1rem;
  margin: 0;
}
.review-grid dt {
  font-size: 0.74rem; font-weight: 800;
  letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--paper-faint);
  padding-top: 0.15rem;
}
.review-grid dd { margin: 0; font-size: 0.9rem; }
@media (max-width: 520px) {
  .review-grid { grid-template-columns: 1fr; gap: 0.15rem; }
  .review-grid dd { margin-bottom: 0.6rem; }
}

/* --- decision buttons: the 3-color key as actions --- */
.decision-row { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.decision-approve   { border-color: var(--ok);   color: #C6F6D9; background: rgba(34,197,94,.16); }
.decision-approve:hover   { background: rgba(34,197,94,.3); }
.decision-exception { border-color: var(--warn); color: #FBEDA6; background: rgba(250,204,21,.16); }
.decision-exception:hover { background: rgba(250,204,21,.3); }
.decision-deny      { border-color: var(--bad);  color: #FFC7CD; background: rgba(255,59,78,.16); }
.decision-deny:hover      { background: rgba(255,59,78,.3); }

/* --- log trail --- */
.log-list { display: grid; gap: 0.6rem; }
.log-item {
  padding: 0.7rem 0.8rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.5);
  border: 1px solid var(--glass-line);
}
.log-head {
  display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap;
  font-size: 0.82rem;
}
.log-role {
  font-size: 0.68rem; color: var(--paper-faint);
  border: 1px solid var(--glass-line); border-radius: var(--radius-pill);
  padding: 0 0.4rem;
}
.log-when { margin-left: auto; font-size: 0.74rem; color: var(--paper-faint); }
.log-summary { margin: 0.35rem 0 0; font-size: 0.85rem; color: var(--paper-dim); }

.log-diff {
  width: 100%; margin-top: 0.5rem;
  border-collapse: collapse; font-size: 0.8rem;
}
.log-diff th {
  text-align: left; font-weight: 700; color: var(--paper-faint);
  padding: 0.2rem 0.5rem 0.2rem 0; white-space: nowrap; vertical-align: top;
}
.log-diff td { padding: 0.2rem 0.4rem; vertical-align: top; }
.log-diff .was   { color: #FFB3BA; text-decoration: line-through; opacity: .85; }
.log-diff .arrow { color: var(--paper-faint); }
.log-diff .now   { color: #B6F3CE; font-weight: 600; }

/* --------------------------------------------------------------------------
   28. Vertical rhythm between top-level page sections

   Scoped to DIRECT children of the public <main> only. An earlier attempt used
   bare sibling selectors like `.art-panel + .panel`, which also matched the
   Event Updates aside sitting next to the Event Details panel INSIDE
   .details-layout | pushing Updates a rem down inside its own grid row and
   breaking their top alignment. The child combinator prevents that.
   -------------------------------------------------------------------------- */
main:not(.console-main) > * + * { margin-top: 1.5rem; }

/* Breathing room before the credits. The footer is a sibling of <main>, not of
   the panels inside it, so the selector sits at that level. The credits carry
   their own top padding, so this only tops it up. */
main + footer { margin-top: 0.75rem; }

/* Both columns of the details row start at the same y. */
.details-layout > * { margin-top: 0 !important; align-self: start; }

/* --------------------------------------------------------------------------
   29. Legend | enlarged for readability
   -------------------------------------------------------------------------- */
.legend {
  font-size: 0.92rem;
  gap: 0.6rem 1.4rem;
  padding-top: 1.1rem;
  margin-top: 1.1rem;
}
.legend strong { font-size: 0.92rem; }
.legend-item { gap: 0.45rem; }
/* Legend dots are reference markers, not space-constrained like grid chips */
.legend .dot {
  width: 20px; height: 20px;
  font-size: 11px;
}

/* --------------------------------------------------------------------------
   30. Event Details | image and tags share the left column
   All four tags stack beneath the thumbnail rather than sitting beside the
   group name, so every card has one predictable place to look for them.
   -------------------------------------------------------------------------- */
.ev-media {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  flex-shrink: 0;
  width: 72px;
}

.ev-media .ev-thumb { width: 72px; height: 72px; }

.ev-media .ev-tags {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 3px;
  width: 100%;
  margin-top: 0;
}
.ev-media .ev-tags .pill {
  justify-content: center;
  padding-inline: 0.35rem;
  font-size: 0.72rem;
}

@media (max-width: 400px) {
  /* Very narrow: let the tags run along under the image instead of stacking,
     so a four-tag card does not tower over its own text. */
  .ev-media { width: auto; }
  .ev-media .ev-tags { flex-direction: row; flex-wrap: wrap; justify-content: center; }
}

/* Google Maps mark used in place of the generic pin emoji */
.ico-maps { flex-shrink: 0; display: block; margin-top: 1px; }

/* --------------------------------------------------------------------------
   31. Event summary modal
   Native <dialog>, so focus trapping, Escape-to-close and the backdrop come
   from the browser rather than hand-rolled JS.
   -------------------------------------------------------------------------- */
.ev-modal {
  border: 0;
  padding: 0;
  background: transparent;
  max-width: min(620px, calc(100vw - 2rem));
  width: 100%;
  color: var(--paper);
  overflow: visible;
}
.ev-modal::backdrop {
  background: rgba(6, 4, 12, 0.72);
  backdrop-filter: blur(4px);
}

.ev-modal-inner {
  position: relative;
  background: var(--ink-800);
  border: 1px solid var(--glass-line-hi);
  border-top: 4px solid var(--accent, var(--violet));
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: clamp(1.1rem, 4vw, 1.75rem);
}

.ev-modal[open] .ev-modal-inner {
  animation: ev-modal-in 220ms var(--ease);
}
@keyframes ev-modal-in {
  from { opacity: 0; transform: translateY(10px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

.ev-modal-close {
  position: absolute; top: 0.5rem; right: 0.6rem;
  width: 38px; height: 38px;
  border: 0; border-radius: 50%;
  background: transparent;
  color: var(--paper-faint);
  font-size: 1.6rem; line-height: 1;
  cursor: pointer;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.ev-modal-close:hover { background: var(--glass-hi); color: var(--paper); }

.ev-modal-head {
  display: flex; gap: 0.9rem; align-items: flex-start;
  padding-right: 2.5rem;
  margin-bottom: 0.9rem;
}
.ev-modal-thumb {
  width: 78px; height: 78px; flex-shrink: 0;
  border-radius: 16px; object-fit: cover;
  border: 2px solid rgba(255, 255, 255, 0.85);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
}
.ev-modal-heading { min-width: 0; }
.ev-modal-title {
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(1.1rem, 3.5vw, 1.45rem);
  margin: 0 0 0.15rem;
  color: var(--accent, var(--violet));
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}
.ev-modal-venue { margin: 0 0 0.5rem; font-weight: 700; font-size: 0.95rem; }
.ev-modal-tags  { display: flex; gap: 0.3rem; flex-wrap: wrap; }

.ev-modal-cancelled {
  padding: 0.6rem 0.8rem;
  border-radius: var(--radius-sm);
  border-left: 4px solid var(--bad);
  background: rgba(255, 59, 78, 0.14);
  font-size: 0.85rem;
  margin: 0 0 0.9rem;
}
.ev-modal-cancelled strong { color: #FFC7CD; }

.ev-modal-facts { margin: 0 0 1.1rem; display: grid; gap: 0.55rem; }
.ev-modal-facts .fact {
  display: grid;
  grid-template-columns: 72px 1fr;
  gap: 0.75rem;
  align-items: start;
}
.ev-modal-facts dt {
  font-size: 0.72rem; font-weight: 800;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--paper-faint);
  padding-top: 2px;
}
.ev-modal-facts dd { margin: 0; font-size: 0.92rem; }
.ev-modal-facts a  { color: var(--sky); }

.ev-modal-actions {
  display: flex; gap: 0.5rem; flex-wrap: wrap;
  padding-top: 0.9rem;
  border-top: 1px solid var(--glass-line);
}

@media (max-width: 460px) {
  .ev-modal-head { flex-direction: column; align-items: center; text-align: center; }
  .ev-modal-tags { justify-content: center; }
  .ev-modal-facts .fact { grid-template-columns: 1fr; gap: 0.1rem; }
}

/* The UA stylesheet's [hidden] { display: none } loses to an explicit
   display rule, so a hidden .fact still laid out as a grid and showed its
   empty label. Restore the intent explicitly. */
[hidden] { display: none !important; }

/* Sync button lives at the right end of the legend row. margin-left:auto
   pushes it there without needing a wrapper, and it drops onto its own line
   when the legend items wrap on narrow screens. */
.legend-sync { margin-left: auto; }
@media (max-width: 700px) {
  .legend-sync { margin-left: 0; width: 100%; justify-content: center; }
}

/* --------------------------------------------------------------------------
   32. "Open in Google Maps" | same treatment as .btn-apply, Maps palette
   Gradient pill with a colored glow, matching the Add-to-calendar button's
   shape and weight, but in Google's blue so the button looks like where it
   actually goes.

   Both gradient stops are dark enough to clear 5:1 against white text. Google
   Blue #4285F4 alone only reaches ~3.4:1, which would fail AA at this weight,
   so the ramp starts at the deeper #1A73E8 instead.
   -------------------------------------------------------------------------- */
.btn-maps {
  background: linear-gradient(95deg, #1A73E8 0%, #1765D8 50%, #1350C4 100%);
  background-clip: padding-box;
  border: 0;
  padding: calc(0.6rem + 1px) calc(1.1rem + 1px);
  color: #FFFFFF;
  font-weight: 800;
  box-shadow: 0 5px 14px -4px rgba(26, 115, 232, 0.65);
}
.btn-maps.btn-sm { padding: calc(0.35rem + 1px) calc(0.75rem + 1px); }
.btn-maps:hover {
  background: linear-gradient(95deg, #2B82F0 0%, #1A73E8 50%, #1858D4 100%);
  color: #FFFFFF;
  box-shadow: 0 7px 18px -5px rgba(26, 115, 232, 0.75);
}
.btn-maps:active { box-shadow: 0 2px 6px -2px rgba(0, 0, 0, 0.5); }

/* The four-color Maps mark reads as an accent against the blue field */
.btn-maps .ico-maps { margin-top: 0; }

/* --------------------------------------------------------------------------
   33. Modal footer actions
   "Back" sits on the left; the two outbound actions stack on the right.
   -------------------------------------------------------------------------- */
.ev-modal-actions {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.ev-modal-actions-right {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.4rem;
  margin-left: auto;
}
/* Stacked buttons match each other's width rather than each sizing to its own
   label, which would leave a ragged right edge. */
.ev-modal-actions-right .btn { justify-content: center; }

@media (max-width: 460px) {
  .ev-modal-actions { flex-direction: column; align-items: stretch; }
  .ev-modal-actions-right { margin-left: 0; width: 100%; }
  .ev-modal-actions > .btn { justify-content: center; }
}

/* ==========================================================================
   34. Touch ergonomics
   Organizers and attendees mostly use this on a phone, so the interactive
   surfaces are sized and placed for thumbs rather than cursors.
   ========================================================================== */

/* Removes the ~300ms tap delay older mobile engines add while they wait to see
   if a tap is a double-tap-to-zoom. Applied to controls only, so pinch-zoom on
   the page itself still works. */
.btn, .chip, .agenda-item, .dm-cell, .ev-card, .ev-modal-close,
.console-link, .choice, .sona-option, summary, [data-modal-close] {
  touch-action: manipulation;
  -webkit-tap-highlight-color: rgba(124, 92, 255, 0.25);
}

/* 44px is the smallest reliably tappable target. The close control was 37px. */
.ev-modal-close {
  width: 44px; height: 44px;
  display: grid; place-items: center;
}

/* Dot-map cells were 40px tall at their smallest. */
.dm-cell { min-height: 44px; }

@media (max-width: 600px) {
  /* --- Modal becomes a bottom sheet ---------------------------------------
     A centered dialog puts its close button in the top-right, the hardest
     corner to reach one-handed. Anchoring to the bottom edge brings the
     actions into the natural thumb arc instead. */
  .ev-modal {
    /* margin must be 0, not `0 auto auto` | an auto bottom margin absorbs the
       free space and floats the sheet away from the edge it is anchored to. */
    margin: 0;
    position: fixed;
    inset: auto 0 0 0;
    max-width: 100%;
    width: 100%;
    max-height: 88dvh;
  }
  .ev-modal-inner {
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    border-bottom: 0;
    /* Clears the home indicator on gesture-nav phones */
    padding-bottom: calc(1.1rem + env(safe-area-inset-bottom, 0px));
    max-height: 88dvh;
    overflow-y: auto;
  }
  .ev-modal[open] .ev-modal-inner { animation: ev-sheet-in 260ms var(--ease); }
  @keyframes ev-sheet-in {
    from { transform: translateY(28px); opacity: 0; }
    to   { transform: none; opacity: 1; }
  }

  /* Grab handle: signals the sheet is dismissable */
  .ev-modal-inner::before {
    content: "";
    display: block;
    width: 42px; height: 4px;
    margin: 0 auto 0.85rem;
    border-radius: 999px;
    background: var(--glass-line-hi);
  }

  /* Full-width actions, and the primary ones first in the thumb zone */
  .ev-modal-actions { flex-direction: column-reverse; align-items: stretch; }
  .ev-modal-actions-right { width: 100%; margin-left: 0; }
  .ev-modal-actions .btn { min-height: 48px; justify-content: center; }

  /* Agenda rows are the main way people open an event on a phone */
  .agenda-item { min-height: 64px; align-items: center; padding: 0.7rem; }

  /* .btn-sm is 34px, which is fine for a mouse but under the 44px tap
     minimum. On a phone these ARE the primary navigation, so they grow. */
  .btn-sm { min-height: 44px; padding-inline: 0.9rem; }

  /* Header nav wraps into a scrollable row rather than crushing five
     full-height buttons onto one line. */
  .site-nav { gap: 0.4rem; }
  .site-header { gap: 0.75rem; }
}

/* --------------------------------------------------------------------------
   35. Section count pills | pushed right and enlarged
   These are a running total the reader glances at, so they sit at the far end
   of the heading rather than crowding the title.
   -------------------------------------------------------------------------- */
.section-title .count {
  margin-left: auto;
  font-size: 0.86rem;
  padding: 0.28rem 0.85rem;
  border-radius: var(--radius-pill);
  background: var(--glass-hi);
  border: 1px solid var(--glass-line-hi);
  color: var(--paper);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* The heading must be able to grow for the pill to have anywhere to go. */
.section-title { width: 100%; }

/* --------------------------------------------------------------------------
   36. Manager update list
   -------------------------------------------------------------------------- */
.upd-list { display: grid; gap: 0.7rem; }

.upd-context {
  margin: 0.35rem 0;
  font-size: 0.78rem;
  color: var(--paper-faint);
}
.upd-context a { color: var(--sky); font-weight: 700; }

.upd-foot {
  display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap;
  margin-top: 0.6rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--glass-line);
  font-size: 0.74rem;
  color: var(--paper-faint);
}

/* --------------------------------------------------------------------------
   37. Drag-and-drop image field
   The real <input type="file"> stays in the DOM, visually hidden but
   focusable and label-linked, so the field works by keyboard, by screen
   reader and with JS off. Drag-and-drop is layered on top of it.
   -------------------------------------------------------------------------- */
.dropzone {
  position: relative;
  border: 2px dashed var(--glass-line-hi);
  border-radius: var(--radius);
  background: rgba(10, 8, 19, 0.4);
  transition: border-color var(--fast) var(--ease),
              background var(--fast) var(--ease);
}
.dropzone:hover { border-color: var(--violet); background: rgba(124, 92, 255, 0.07); }

/* Dragging a file over the zone */
.dropzone.is-dragover {
  border-color: var(--violet);
  border-style: solid;
  background: rgba(124, 92, 255, 0.16);
}
/* Keyboard focus lands on the hidden input; surface it on the zone */
.dropzone:focus-within { border-color: var(--sky); }

/* Hidden but reachable | display:none would drop it out of the tab order */
.dropzone-input {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0;
  pointer-events: none;
}

.dropzone-label {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.9rem 1rem;
  cursor: pointer;
  min-height: 96px;
}

.dropzone-preview {
  width: 74px; height: 74px;
  flex-shrink: 0;
  border-radius: 14px;
  object-fit: cover;
  border: 2px solid rgba(255, 255, 255, 0.85);
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.5);
  background: var(--ink-700);
}

.dropzone-empty {
  width: 74px; height: 74px;
  flex-shrink: 0;
  display: grid; place-items: center;
  border-radius: 14px;
  background: rgba(124, 92, 255, 0.12);
  color: var(--violet);
}

.dropzone-text { min-width: 0; }
.dropzone-text strong {
  display: block;
  font-size: 0.92rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dropzone-hint {
  display: block;
  font-size: 0.78rem;
  color: var(--paper-faint);
  margin-top: 0.15rem;
}

.dropzone-clear {
  position: absolute;
  top: 0.5rem; right: 0.5rem;
}

/* A whole-zone overlay while dragging, so the drop target is unmistakable */
.dropzone.is-dragover::after {
  content: "Drop to use this image";
  position: absolute; inset: 0;
  display: grid; place-items: center;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.82);
  font-weight: 800; font-size: 0.9rem;
  color: #fff;
  pointer-events: none;
}

@media (max-width: 460px) {
  .dropzone-label { flex-direction: column; text-align: center; padding-top: 1.1rem; }
  .dropzone-clear { position: static; margin: 0 0.75rem 0.75rem; }
}

/* --------------------------------------------------------------------------
   38. Console signed-in-as block
   Sits between the console identity and the navigation. Makes it immediately
   clear whose furmeets the sidebar is listing | which matters when access is
   scoped per person.
   -------------------------------------------------------------------------- */
.console-user {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.85rem 0.7rem;
  margin-bottom: 0.6rem;
  border-radius: var(--radius);
  background: rgba(124, 92, 255, 0.10);
  border: 1px solid var(--glass-line);
  text-decoration: none;
  color: inherit;
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.console-user:hover {
  background: rgba(124, 92, 255, 0.18);
  border-color: var(--violet);
  text-decoration: none;
}

.console-avatar {
  width: 44px; height: 44px;
  flex-shrink: 0;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid rgba(255, 255, 255, 0.85);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
  background: var(--ink-700);
}
/* The initial-badge fallback shares the circle, so both look deliberate */
.console-avatar.avatar-fallback { font-size: 1.15rem; }

.console-user-text { min-width: 0; }
.console-user-text strong {
  display: block;
  font-size: 0.95rem;
  line-height: 1.2;
  color: var(--paper);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.console-user-text span {
  display: block;
  font-size: 0.72rem;
  line-height: 1.2;
  /* Breathing room under the name | the two lines were sitting on top of
     each other at the tight shared line-height. */
  margin-top: 0.3rem;
  color: var(--paper-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

@media (max-width: 860px) {
  /* The sidebar is a horizontal strip here; keep the identity compact and
     let the nav scroll beside it rather than under it. */
  .console-user { margin-bottom: 0.6rem; }
}

/* --------------------------------------------------------------------------
   39. Sidebar furmeet access chips
   Directly under the signed-in identity, so "who you are" and "what you can
   touch" read as one statement.
   -------------------------------------------------------------------------- */
.console-meets {
  padding: 0 0.6rem 0.85rem;
  margin-bottom: 0.25rem;
  border-bottom: 1px solid var(--glass-line);
}

.console-meets-label {
  /* Matches the "Furmeets you Manage" heading on the My Profile page rather
     than the tiny uppercase nav-group labels around it. */
  font-family: var(--font-display);
  font-size: 0.88rem;
  font-weight: 800;
  letter-spacing: 0;
  text-transform: none;
  color: var(--paper);
  margin: 0 0 0.5rem;
}

/* Stacked, one furmeet per line | the list is short and each name stays
   fully readable rather than being squeezed onto a shared row. */
.console-meets-chips {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.3rem;
}

/* Sidebar chips wrap onto their own lines rather than truncating | the whole
   point is knowing exactly which groups you hold. */
.console-meets .meet-chip {
  font-size: 0.75rem;
  padding: 0.3rem 0.6rem;
  white-space: normal;
  text-align: left;
  border-radius: var(--radius-sm);
}

.console-meets-none {
  font-size: 0.76rem;
  color: var(--paper-faint);
  font-style: italic;
}

@media (max-width: 860px) {
  /* Horizontal sidebar: keep the chips on one scrollable line */
  .console-meets { border-bottom: 0; padding-bottom: 0.5rem; }
  /* Horizontal sidebar on small screens: a stacked column would push the nav
     far down the page, so the chips share one scrollable row instead. */
  .console-meets-chips { flex-direction: row; flex-wrap: nowrap; overflow-x: auto; }
  .console-meets .meet-chip { white-space: nowrap; }
}

/* Logout sits at the foot of the sidebar on desktop */
.console-signout-form { margin-top: 0.5rem; }

@media (max-width: 860px) {
  /* The sidebar is a horizontal strip here, so the sign-out control is lifted
     out of the scrolling nav row and pinned to the top right | level with the
     "Manager Dashboard" title. Buried at the end of a horizontally scrolling
     list it was effectively unreachable. */
  .console-side { position: relative; }

  .console-signout-form {
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    margin-top: 0;
    z-index: 2;
  }

  .console-signout {
    width: auto;
    min-height: 44px;
    padding: 0.45rem 0.9rem;
    border: 1px solid var(--glass-line-hi);
    border-radius: var(--radius-pill);
    background: var(--glass-hi);
    color: var(--paper-dim);
  }
  .console-signout:hover { color: #FFC7CD; border-color: var(--bad); }

  /* Keep the brand text clear of the pinned button */
  .console-brand { padding-right: 6.5rem; }
}

/* ==========================================================================
   40. Approval queues (events and updates)
   Each card is one decision. The structure is deliberately repetitive so an
   Admin working a queue always finds the same information in the same place:
     header  | what it is, whose it is, what state it is in
     facts   | labeled detail, never a run-on sentence
     reason  | any existing admin note
     decide  | the actions, visually separated from the reading material
   ========================================================================== */

/* ---------- status tabs ---------- */
.tabs {
  display: flex; flex-wrap: wrap; gap: 0.4rem;
  margin-bottom: 1.25rem;
  border-bottom: 1px solid var(--glass-line);
  padding-bottom: 0.75rem;
}
.tab {
  display: inline-flex; align-items: center; gap: 0.45rem;
  padding: 0.5rem 0.9rem;
  min-height: 44px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--glass-line);
  background: rgba(20, 16, 31, 0.5);
  color: var(--paper-dim);
  font-size: 0.86rem; font-weight: 700;
  text-decoration: none;
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.tab:hover { background: var(--glass-hi); color: var(--paper); text-decoration: none; }
.tab.is-active {
  background: rgba(124, 92, 255, 0.18);
  border-color: var(--violet);
  color: #fff;
}
.tab-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; }
.tab-count {
  font-size: 0.74rem; font-weight: 900;
  background: rgba(0, 0, 0, 0.35);
  padding: 0.05rem 0.45rem;
  border-radius: var(--radius-pill);
  font-variant-numeric: tabular-nums;
}

/* ---------- the card ---------- */
.approval-list { display: grid; gap: 1rem; }

.acard {
  border-radius: var(--radius-lg);
  background: var(--glass);
  border: 1px solid var(--glass-line);
  /* The furmeet's color runs down the edge, so scanning a mixed queue you can
     tell whose event it is before reading a word. */
  border-left: 5px solid var(--accent, var(--violet));
  overflow: hidden;
}

/* ---------- header ---------- */
.acard-head {
  display: flex; align-items: flex-start; gap: 1rem;
  padding: 1rem 1.1rem 0.85rem;
}

/* Big legible date block, like a calendar tear-off */
.acard-date {
  flex-shrink: 0;
  width: 62px;
  text-align: center;
  padding: 0.45rem 0.2rem 0.5rem;
  border-radius: var(--radius);
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid var(--glass-line);
  line-height: 1.05;
}
.acard-date-mon {
  display: block; font-size: 0.68rem; font-weight: 900;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--pink);
}
.acard-date-day {
  display: block; font-family: var(--font-display); font-weight: 800;
  font-size: 1.55rem; color: var(--paper);
  font-variant-numeric: tabular-nums;
}
.acard-date-dow {
  display: block; font-size: 0.66rem; color: var(--paper-faint);
  text-transform: uppercase; letter-spacing: 0.08em;
}

.acard-ident { flex: 1; min-width: 0; }
.acard-title {
  font-family: var(--font-display); font-weight: 800;
  font-size: 1.06rem; line-height: 1.25;
  margin: 0 0 0.4rem;
}
.acard-title a { color: var(--paper); text-decoration: none; }
.acard-title a:hover { color: var(--sky); text-decoration: underline; }

.acard-badges { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }

.acard-thumb {
  width: 64px; height: 64px; flex-shrink: 0;
  border-radius: 14px; object-fit: cover;
  border: 2px solid rgba(255, 255, 255, 0.8);
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.5);
}

/* ---------- labeled facts ---------- */
.acard-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 0.65rem 1.1rem;
  margin: 0;
  padding: 0.85rem 1.1rem;
  background: rgba(10, 8, 19, 0.35);
  border-top: 1px solid var(--glass-line);
  border-bottom: 1px solid var(--glass-line);
}
/* Address and tags need the full width rather than a narrow column */
.acard-fact-wide { grid-column: 1 / -1; }

.acard-fact dt {
  font-size: 0.66rem; font-weight: 900;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--paper-faint);
  margin-bottom: 0.15rem;
}
.acard-fact dd {
  margin: 0;
  font-size: 0.88rem;
  color: var(--paper);
  display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center;
}
.acard-fact dd a { color: var(--sky); }
.acard-none { color: var(--paper-faint); font-style: italic; }

/* ---------- existing admin note ---------- */
.acard-reason {
  margin: 0;
  padding: 0.7rem 1.1rem;
  font-size: 0.86rem;
  border-left: 4px solid currentColor;
  background: rgba(0, 0, 0, 0.28);
}
.acard-reason.status-exception { color: var(--warn); }
.acard-reason.status-denied    { color: var(--bad); }
.acard-reason.status-approved  { color: var(--ok); }
.acard-reason strong { color: inherit; }

/* ---------- decision bar ---------- */
.acard-decide {
  display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap;
  padding: 0.85rem 1.1rem;
  background: rgba(124, 92, 255, 0.06);
}
.acard-reason-field { flex: 1 1 260px; min-width: 0; }
.acard-reason-field .input { font-size: 0.86rem; }

.acard-buttons { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.acard-full { margin-left: auto; }

/* ---------- update-specific ---------- */
.ucard-head { padding-bottom: 0.6rem; }
.ucard-title { font-size: 0.94rem; font-weight: 700; margin: 0.45rem 0 0; }
.ucard-eventdate { color: var(--paper-faint); font-weight: 600; font-size: 0.84rem; }

.ucard-body { padding: 0 1.1rem 0.9rem; }
.ucard-label {
  display: block;
  font-size: 0.66rem; font-weight: 900;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--paper-faint);
  margin-bottom: 0.35rem;
}
/* The update text is the thing being judged, so it gets real prominence
   rather than sitting as another line of metadata. */
.ucard-quote {
  margin: 0;
  padding: 0.85rem 1rem;
  border-radius: var(--radius);
  background: rgba(0, 0, 0, 0.3);
  border-left: 3px solid var(--accent, var(--violet));
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--paper);
}

@media (max-width: 620px) {
  .acard-head { flex-wrap: wrap; gap: 0.75rem; }
  .acard-date { width: 54px; }
  .acard-thumb { order: -1; }
  .acard-facts { grid-template-columns: 1fr; }
  .acard-decide { flex-direction: column; align-items: stretch; }
  .acard-buttons .btn { flex: 1 1 auto; justify-content: center; }
  .acard-full { margin-left: 0; }
}

/* ==========================================================================
   41. Approval queues | dense layout on wide screens
   Below 1100px the card reads top-to-bottom. Above it, the decision controls
   move into a fixed side column so they sit BESIDE the information rather
   than under it. That removes the tallest part of each card and lets an Admin
   see several decisions at once without scrolling.
   ========================================================================== */
@media (min-width: 1100px) {
  .acard {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 268px;
    grid-template-areas:
      "head   decide"
      "facts  decide"
      "reason decide";
    align-items: start;
  }

  .acard-head   { grid-area: head;   padding-bottom: 0.6rem; }
  .acard-facts  { grid-area: facts;  border-bottom: 0; }
  .acard-reason { grid-area: reason; }

  .acard-decide {
    grid-area: decide;
    flex-direction: column;
    align-items: stretch;
    align-self: stretch;
    gap: 0.5rem;
    padding: 1rem;
    border-left: 1px solid var(--glass-line);
    background: rgba(124, 92, 255, 0.07);
  }
  .acard-reason-field { flex: 0 0 auto; }
  .acard-buttons { flex-direction: column; gap: 0.35rem; }
  .acard-buttons .btn { justify-content: center; width: 100%; }
  /* "Full review" is the secondary path, so it drops to the bottom */
  .acard-full { margin-left: 0; margin-top: auto; }

  /* Tighter facts: more columns, and the wide fields span two rather than
     hogging an entire row. */
  .acard-facts {
    grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
    gap: 0.55rem 1rem;
    padding: 0.75rem 1.1rem 0.9rem;
  }
  .acard-fact-wide { grid-column: span 2; }

  .acard-head  { padding-top: 0.9rem; }
  .acard-date  { width: 56px; padding: 0.35rem 0.2rem 0.4rem; }
  .acard-date-day { font-size: 1.4rem; }
  .acard-title { font-size: 1rem; margin-bottom: 0.3rem; }
  .acard-thumb { width: 56px; height: 56px; }

  .approval-list { gap: 0.85rem; }
}

/* Very wide: the address rarely needs two columns of its own */
@media (min-width: 1500px) {
  .acard { grid-template-columns: minmax(0, 1fr) 288px; }
  .acard-facts { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
}

/* ---------- updates: quote beside the decision ---------- */
@media (min-width: 1100px) {
  .ucard {
    grid-template-areas:
      "head   decide"
      "body   decide"
      "facts  decide"
      "reason decide";
  }
  .ucard-head  { grid-area: head; }
  .ucard-body  { grid-area: body; padding: 0 1.1rem 0.75rem; }
  .ucard-quote { font-size: 0.92rem; padding: 0.7rem 0.9rem; }
  /* Only one fact row on an update, so it does not need a boxed band */
  .ucard .acard-facts {
    background: transparent;
    border-top: 1px solid var(--glass-line);
    padding-top: 0.6rem; padding-bottom: 0.75rem;
  }
}

/* --------------------------------------------------------------------------
   42. Approval cards with no decision form
   On the Published and Denied tabs there are no action controls, so the
   reserved side column had nothing in it and the content only used ~75% of
   the card width. These collapse to a single column instead.
   -------------------------------------------------------------------------- */
@media (min-width: 1100px) {
  .acard-readonly {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "head"
      "body"
      "facts"
      "reason";
  }

  /* Belt and braces: catches any card that ends up without a decision form
     even if the server-side class is missed. */
  .acard:not(:has(.acard-decide)) {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "head"
      "body"
      "facts"
      "reason";
  }

  .acard-readonly .acard-facts { border-bottom: 0; }
}

/* A cleared image field, pending save | makes it obvious the removal has not
   happened yet and still needs the form submitted. */
.dropzone.is-cleared {
  border-color: var(--bad);
  border-style: solid;
  background: rgba(255, 59, 78, 0.08);
}
.dropzone.is-cleared .dropzone-text strong { color: #FFC7CD; }
.dropzone.is-cleared .dropzone-empty { background: rgba(255, 59, 78, 0.14); color: #FFB3BA; }

/* Application references */
.ref-list { margin: 0; padding-left: 1.1rem; }
.ref-list li { margin-bottom: 0.25rem; font-size: 0.86rem; }
.ref-list li:last-child { margin-bottom: 0; }
.ref-contact { color: var(--paper-faint); }
.ref-contact a { color: var(--sky); }

/* --------------------------------------------------------------------------
   43. Months & artwork
   -------------------------------------------------------------------------- */
.month-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: 0.5rem;
}

/* A strip whose length varies. auto-fit collapses the tracks it does not use,
   so a month on its own stretched to the full width of the panel and stopped
   looking like a tile at all; auto-fill keeps the empty tracks, and one month
   stays the same size as one of nine. */
.month-strip.is-fill {
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
}

.month-tile {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 0.15rem;
  padding: 0.7rem 0.4rem 0.6rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.5);
  border: 1px solid var(--glass-line);
  text-decoration: none; color: inherit;
  min-height: 44px;
  transition: transform var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.month-tile:hover { transform: translateY(-2px); border-color: var(--glass-line-hi); text-decoration: none; }

.month-tile-name {
  font-family: var(--font-display); font-weight: 800;
  font-size: 1.05rem; color: var(--paper);
}
.month-tile-year  { font-size: 0.7rem; color: var(--paper-faint); }
.month-tile-count { font-size: 0.7rem; color: var(--paper-dim); font-variant-numeric: tabular-nums; }
.month-tile-state .status { font-size: 0.65rem; padding: 0.12rem 0.45rem; }

/* The month being edited */
.month-tile.is-editing {
  border-color: var(--violet);
  background: rgba(124, 92, 255, 0.16);
  box-shadow: inset 0 0 0 1px var(--violet);
}
/* Today's month gets a marker regardless of which one is open */
.month-tile.is-current { border-color: var(--pink); }
.month-tile-now {
  position: absolute; top: -7px; right: 6px;
  font-size: 0.6rem; font-weight: 900;
  letter-spacing: 0.08em; text-transform: uppercase;
  background: var(--pink); color: #2A0714;
  padding: 0.05rem 0.35rem; border-radius: var(--radius-pill);
}
/* Future months are dimmed | they cannot be seen by the public yet */
.month-tile.is-future:not(.is-editing) { opacity: 0.72; }

/* ---------- artwork slots ---------- */
.art-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0.9rem;
}
.art-slot { margin: 0; }
/* Backgrounds are wide images, so the preview is a strip rather than a square */
.art-slot .dropzone-preview {
  width: 96px; height: 58px;
  border-radius: 8px;
}
.art-slot .dropzone-empty { width: 96px; height: 58px; border-radius: 8px; }
.art-slot .dropzone-label { min-height: 82px; padding: 0.7rem 0.8rem; }

.art-advanced {
  border: 1px solid var(--glass-line);
  border-radius: var(--radius);
  padding: 0.75rem 0.9rem;
  background: rgba(10, 8, 19, 0.3);
}
.art-advanced summary {
  cursor: pointer; font-weight: 700; font-size: 0.9rem;
  min-height: 32px; display: flex; align-items: center;
}
.art-advanced[open] summary { margin-bottom: 0.6rem; }

.input-color {
  width: 100%; height: 44px; padding: 0.2rem;
  cursor: pointer;
}

/* Artwork slots sit in a grid. Stretching the row and making each slot a
   flex column means all three boxes end up identical in height whatever their
   label or filename length | rather than each sizing to its own content. */
.art-grid { align-items: stretch; }

.art-slot {
  display: flex;
  flex-direction: column;
}
.art-slot > .field-label {
  min-height: 1.2em;
  display: flex;
  align-items: flex-end;
}
.art-slot .dropzone {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 168px;
}
/* The label area takes the slack so the Clear button stays pinned at the foot
   of every box, level across all three. */
.art-slot .dropzone-label { flex: 1; align-items: center; }

/* Clear moves out of the corner and becomes a pill along the bottom. */
.art-slot .dropzone-clear {
  position: static;
  margin: 0 0.7rem 0.7rem;
  border-radius: var(--radius-pill);
  justify-content: center;
  width: auto;
}

/* --------------------------------------------------------------------------
   44. Filter bar + data table (All Events)
   -------------------------------------------------------------------------- */
.filters {
  display: grid;
  grid-template-columns: minmax(200px, 2fr) repeat(3, minmax(130px, 1fr)) auto;
  gap: 0.6rem;
  align-items: end;
  padding: 0.9rem 1rem;
  border-radius: var(--radius);
  background: var(--glass);
  border: 1px solid var(--glass-line);
  margin-bottom: 0.9rem;
}
.filter-field { display: block; min-width: 0; }
.filter-field .field-label { margin-bottom: 0.25rem; }
.filter-search .input { min-height: 44px; }
.filter-actions { display: flex; gap: 0.4rem; }

@media (max-width: 900px) {
  .filters { grid-template-columns: 1fr 1fr; }
  .filter-search { grid-column: 1 / -1; }
  .filter-actions { grid-column: 1 / -1; }
  .filter-actions .btn { flex: 1; justify-content: center; }
}

.result-count {
  font-size: 0.85rem;
  color: var(--paper-faint);
  margin: 0 0 0.75rem;
}
.result-count strong { color: var(--paper); font-variant-numeric: tabular-nums; }

/* ---------- table ---------- */
.evtable-wrap {
  /* A wide table scrolls inside its own container rather than pushing the
     whole page sideways. */
  overflow-x: auto;
  border-radius: var(--radius-lg);
}
.evtable {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
  min-width: 760px;
}
.evtable thead th {
  text-align: left;
  font-size: 0.68rem; font-weight: 900;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--paper-faint);
  padding: 0.7rem 0.85rem;
  border-bottom: 1px solid var(--glass-line-hi);
  white-space: nowrap;
  position: sticky; top: 0;
  background: var(--ink-800);
  z-index: 1;
}
.evtable tbody tr {
  border-bottom: 1px solid var(--glass-line);
  transition: background var(--fast) var(--ease);
}
.evtable tbody tr:last-child { border-bottom: 0; }
.evtable tbody tr:hover { background: rgba(124, 92, 255, 0.08); }
.evtable td { padding: 0.6rem 0.85rem; vertical-align: middle; }

.evtable-date { white-space: nowrap; font-variant-numeric: tabular-nums; }
.evtable-date strong { display: block; font-size: 0.9rem; }
.evtable-date span   { display: block; font-size: 0.7rem; color: var(--paper-faint); }

.evtable-title {
  display: block; font-weight: 700; color: var(--paper); text-decoration: none;
}
.evtable-title:hover { color: var(--sky); text-decoration: underline; }
.evtable-sub {
  display: block; font-size: 0.76rem; color: var(--paper-faint);
  overflow: hidden; text-overflow: ellipsis;
  max-width: 42ch; white-space: nowrap;
}

.evtable-actions form { display: flex; gap: 0.3rem; justify-content: flex-end; }

/* Deleted rows stay findable but visibly inactive */
.evtable tr.is-deleted { opacity: 0.6; }
.evtable tr.is-deleted .evtable-title { text-decoration: line-through; }

/* ---------- pager ---------- */
.pager {
  display: flex; align-items: center; justify-content: center; gap: 0.75rem;
  margin-top: 1rem;
}
.pager-info { font-size: 0.85rem; color: var(--paper-faint); }

/* --------------------------------------------------------------------------
   45. Furmeets list
   -------------------------------------------------------------------------- */
.fm-list { display: grid; gap: 0.75rem; }

.fm-card {
  display: flex; gap: 0.9rem; align-items: flex-start;
  padding: 0.9rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.5);
  border: 1px solid var(--glass-line);
  border-left: 5px solid var(--accent, var(--violet));
}
/* Deactivated groups stay listed but read as switched off */
.fm-card.is-inactive { opacity: 0.62; }

.fm-logo {
  width: 60px; height: 60px; flex-shrink: 0;
  border-radius: 14px; object-fit: cover;
  border: 2px solid rgba(255, 255, 255, 0.8);
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.5);
  background: var(--ink-700);
}
.fm-logo.avatar-fallback { font-size: 1.5rem; }

.fm-body { flex: 1; min-width: 0; }
.fm-name {
  font-family: var(--font-display); font-weight: 800;
  font-size: 1.05rem; margin: 0 0 0.35rem;
  color: var(--accent, var(--violet));
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}
.fm-badges { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-bottom: 0.4rem; }
.fm-desc {
  margin: 0 0 0.5rem; font-size: 0.84rem; color: var(--paper-faint);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

.fm-managers { display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; }
.fm-managers-label {
  font-size: 0.66rem; font-weight: 900;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--paper-faint);
  margin-right: 0.15rem;
}

.fm-actions {
  display: flex; flex-direction: column; gap: 0.35rem;
  flex-shrink: 0;
}
.fm-actions .btn { justify-content: center; }

/* Edit is a bare <a> and stretches to the column, but Reset password sits in a
   form: the form stretched and the button inside it stayed its natural width,
   so the two ended up different sizes. The child combinator keeps this off the
   2FA/Deactivate pair below, which shares one line and sizes itself. */
.fm-actions > form { display: flex; }
.fm-actions > form > .btn { width: 100%; }

/* Reset 2FA and Deactivate share the last line of the actions column, 2FA on
   the left. Two rarely-used controls, and giving each its own row would make
   every card in the list taller for the sake of buttons most rows never need.
   Deactivate takes the slack so it stays the width it was. */
.fm-actions-pair { display: flex; gap: 0.35rem; align-items: stretch; }
.fm-actions-pair > form { display: flex; }
.fm-actions-pair > form:last-child { flex: 1; }
.fm-actions-pair > form:last-child .btn { width: 100%; }

@media (max-width: 700px) {
  .fm-card { flex-wrap: wrap; }
  .fm-actions { flex-direction: row; width: 100%; }
  .fm-actions > * { flex: 1; }
  .fm-actions form { display: flex; }
  .fm-actions form .btn { width: 100%; }
}

/* --------------------------------------------------------------------------
   46. New-credentials panel
   Shown once after creating an account or resetting a password. Styled to look
   deliberately temporary | this is the only time the password is readable.
   -------------------------------------------------------------------------- */
.creds-panel {
  border-color: var(--warn);
  background: rgba(250, 204, 21, 0.08);
  margin-bottom: 1rem;
}
.creds-grid {
  display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-end;
}
.creds-value {
  display: block;
  font-family: ui-monospace, Consolas, monospace;
  font-size: 1.05rem;
  letter-spacing: 0.04em;
  padding: 0.5rem 0.8rem;
  border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, 0.45);
  border: 1px solid var(--glass-line-hi);
  color: #FBEDA6;
  /* Selectable in one gesture | these get copied by hand */
  user-select: all;
}

/* Furmeet access checkboxes */
.access-grid { display: grid; gap: 0.35rem; }

/* ==========================================================================
   47. Image editor
   Opens over any drag-and-drop field once a file is chosen. Native <dialog>,
   so focus trapping and Escape come from the browser.
   ========================================================================== */
.imed {
  border: 0; padding: 0; background: transparent;
  max-width: min(760px, calc(100vw - 1.5rem));
  width: 100%;
  color: var(--paper);
}
.imed::backdrop { background: rgba(4, 3, 8, 0.82); backdrop-filter: blur(5px); }

.imed-inner {
  background: var(--ink-800);
  border: 1px solid var(--glass-line-hi);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: clamp(0.9rem, 3vw, 1.35rem);
}
.imed[open] .imed-inner { animation: ev-modal-in 200ms var(--ease); }

.imed-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; margin-bottom: 0.85rem;
}
.imed-title {
  font-family: var(--font-display); font-weight: 800;
  font-size: 1.15rem; margin: 0;
}
.imed-close {
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border: 0; border-radius: 50%;
  background: transparent; color: var(--paper-faint);
  font-size: 1.6rem; line-height: 1; cursor: pointer;
}
.imed-close:hover { background: var(--glass-hi); color: var(--paper); }

/* ---------- stage ---------- */
.imed-stage {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  background:
    repeating-conic-gradient(#1b1626 0% 25%, #141020 0% 50%) 50% / 20px 20px;
  display: grid; place-items: center;
}
.imed-canvas {
  display: block;
  max-width: 100%;
  cursor: grab;
  touch-action: none;   /* we handle panning ourselves */
}
.imed-canvas.is-grabbing { cursor: grabbing; }

.imed-hint {
  position: absolute; left: 50%; bottom: 8px;
  transform: translateX(-50%);
  font-size: 0.72rem; color: var(--paper-dim);
  background: rgba(6, 4, 12, 0.75);
  padding: 0.2rem 0.6rem; border-radius: var(--radius-pill);
  pointer-events: none;
  white-space: nowrap;
}

/* ---------- aspect presets ---------- */
.imed-ratios { display: flex; gap: 0.35rem; flex-wrap: wrap; margin: 0.75rem 0; }
.imed-ratio {
  min-height: 36px;
  padding: 0.3rem 0.8rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--glass-line);
  background: rgba(20, 16, 31, 0.55);
  color: var(--paper-dim);
  font-size: 0.82rem; font-weight: 700;
  cursor: pointer;
}
.imed-ratio:hover { border-color: var(--glass-line-hi); color: var(--paper); }
.imed-ratio.is-active {
  border-color: var(--violet);
  background: rgba(124, 92, 255, 0.2);
  color: #fff;
}

/* ---------- sliders ---------- */
.imed-controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
.imed-slider { display: flex; align-items: center; gap: 0.6rem; }
.imed-slider span {
  font-size: 0.7rem; font-weight: 900;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--paper-faint);
  min-width: 74px;
}
.imed-slider input[type="range"] {
  flex: 1; accent-color: var(--violet);
  height: 32px;   /* comfortable thumb target */
}

/* ---------- tools + footer ---------- */
.imed-tools {
  display: flex; gap: 0.35rem; flex-wrap: wrap;
  padding-bottom: 0.85rem;
  border-bottom: 1px solid var(--glass-line);
  margin-bottom: 0.85rem;
}
.imed-foot {
  display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
}
.imed-size {
  margin-left: auto;
  font-size: 0.78rem; color: var(--paper-faint);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 560px) {
  .imed-tools .btn { flex: 1 1 calc(50% - 0.35rem); justify-content: center; }
  .imed-foot { flex-direction: column-reverse; align-items: stretch; }
  .imed-foot .btn { justify-content: center; min-height: 48px; }
  .imed-size { margin: 0 0 0.25rem; text-align: center; }
  .imed-slider span { min-width: 62px; font-size: 0.65rem; }
}

/* --------------------------------------------------------------------------
   48. Role badges (Users & Access)
   The two roles carry the whole permission model, so they get their own
   gradients rather than borrowing the generic status pills.

   Both use dark ink: white on a mid-cyan or lime field lands around 2:1
   contrast and would fail AA, whereas near-black clears 9:1 on each.
   -------------------------------------------------------------------------- */
/* Both carry background-origin: border-box, and it is load-bearing.
   A gradient is sized to the PADDING box by default but painted across the
   BORDER box, and background-repeat defaults to repeat | so the 1px strip
   under the transparent border showed the start of the next tile, drawing a
   pale line down the right edge of every role pill. */
.pill-role-admin {
  background: linear-gradient(95deg, #67E8F9 0%, #22D3EE 45%, #0E9BB8 100%);
  background-origin: border-box;
  border: 1px solid transparent;
  color: #052A33;
  font-weight: 800;
  box-shadow: 0 2px 8px -2px rgba(34, 211, 238, 0.5);
}

.pill-role-manager {
  /* Weighted toward green rather than yellow */
  background: linear-gradient(95deg, #BEF264 0%, #84CC16 45%, #4D7C0F 100%);
  background-origin: border-box;
  border: 1px solid transparent;
  color: #14250A;
  font-weight: 800;
  box-shadow: 0 2px 8px -2px rgba(132, 204, 22, 0.5);
}

/* --------------------------------------------------------------------------
   49. Rich-text editor (Site Content)
   -------------------------------------------------------------------------- */
.rte {
  border: 1px solid var(--glass-line-hi);
  border-radius: var(--radius);
  background: rgba(10, 8, 19, 0.5);
  overflow: hidden;
}

.rte-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px;
  padding: 0.4rem;
  background: rgba(20, 16, 31, 0.7);
  border-bottom: 1px solid var(--glass-line);
}
.rte-btn {
  min-width: 36px; height: 36px;
  display: grid; place-items: center;
  border: 0; border-radius: var(--radius-sm);
  background: transparent;
  color: var(--paper-dim);
  font-size: 0.9rem;
  cursor: pointer;
  padding: 0 0.4rem;
}
.rte-btn:hover { background: var(--glass-hi); color: var(--paper); }
.rte-btn.is-active { background: rgba(124, 92, 255, 0.28); color: #fff; }
.rte-sep {
  width: 1px; height: 22px;
  background: var(--glass-line);
  margin: 0 0.25rem;
}
.rte-source { font-family: ui-monospace, Consolas, monospace; font-size: 0.78rem; }

.rte-surface {
  min-height: 190px;
  max-height: 460px;
  overflow-y: auto;
  padding: 0.9rem 1rem;
  outline: none;
  max-width: none;
}
.rte-surface:focus { background: rgba(10, 8, 19, 0.7); }
/* Headings inside the editor should look like the published page */
.rte-surface h3 { font-size: 1.05rem; margin: 1rem 0 0.4rem; color: var(--paper); }
.rte-surface blockquote {
  margin: 0 0 0.9rem; padding: 0.5rem 0.85rem;
  border-left: 3px solid var(--violet);
  background: rgba(0, 0, 0, 0.25);
}

.rte-html {
  border: 0; border-radius: 0;
  min-height: 190px;
  font-family: ui-monospace, Consolas, monospace;
  font-size: 0.82rem;
  background: rgba(6, 4, 12, 0.7);
}

/* --------------------------------------------------------------------------
   50. Revision list
   -------------------------------------------------------------------------- */
.rev-list { display: grid; gap: 0.6rem; }
.rev {
  padding: 0.7rem 0.8rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.5);
  border: 1px solid var(--glass-line);
}
.rev-head {
  display: flex; align-items: baseline; gap: 0.5rem;
  font-size: 0.82rem; margin-bottom: 0.3rem;
}
.rev-head span { margin-left: auto; font-size: 0.74rem; color: var(--paper-faint); }
.rev-preview {
  margin: 0 0 0.5rem;
  font-size: 0.8rem; color: var(--paper-faint);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Clearing the revision history is destructive and cannot be undone, so the
   button reads as a warning rather than a neutral control | but it sits quiet
   at the foot of the list instead of competing with Restore. */
.rev-clear {
  margin-top: 0.85rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--glass-line);
  display: flex;
  justify-content: center;
}
.rev-clear-btn {
  border-radius: var(--radius-pill);
  color: #FF8A97;
  border-color: rgba(255, 128, 149, 0.35);
  background: rgba(255, 128, 149, 0.08);
}
.rev-clear-btn:hover {
  background: rgba(255, 128, 149, 0.18);
  border-color: rgba(255, 128, 149, 0.6);
  color: #ffd0d8;
}

/* --------------------------------------------------------------------------
   51. Onboarding application (/apply.php)
   -------------------------------------------------------------------------- */
.apply, .apply-done { max-width: 820px; margin-inline: auto; }

.apply-head { margin-bottom: 1.25rem; }
.apply-title {
  font-size: clamp(1.5rem, 4vw, 2rem);
  margin: 0 0 0.5rem;
  letter-spacing: -0.01em;
}
.apply-lede { color: var(--paper-dim); margin: 0; line-height: 1.6; }

.apply-form { display: grid; gap: 1rem; }

.apply-step { position: relative; }
.apply-step-title {
  display: flex; align-items: center; gap: 0.6rem;
  font-size: 1.05rem; margin: 0 0 0.85rem;
}
/* Lettered markers stand in for the sections of the original form, so an
   applicant reading alongside it can tell where they are. */
.apply-step-no {
  flex: 0 0 auto;
  width: 28px; height: 28px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--violet), var(--sky));
  color: #fff;
  font-size: 0.85rem; font-weight: 700;
}

.apply-rules { margin: 0 0 1rem; padding-left: 1.15rem; display: grid; gap: 0.5rem; }
.apply-rules li { color: var(--paper-dim); line-height: 1.55; }
.apply-rules strong { color: var(--tag-18); }

.apply-aside {
  color: var(--paper-faint);
  font-size: 0.9rem;
  border-left: 2px solid var(--glass-line-hi);
  padding-left: 0.8rem;
  margin: 0 0 1rem;
}

/* The question is the point of each section, so it gets a raised surface
   rather than sitting flush with the rules it follows. */
.apply-q {
  border: 1px solid var(--glass-line-hi);
  border-radius: var(--radius);
  background: rgba(10, 8, 19, 0.45);
  padding: 0.85rem 1rem;
  margin: 0 0 0.85rem;
}
.apply-q:last-child { margin-bottom: 0; }
.apply-q legend {
  padding: 0 0.35rem;
  font-weight: 600;
  color: var(--paper);
  line-height: 1.45;
}
.apply-choices { display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem; margin-top: 0.6rem; }
.apply-choices-stack { flex-direction: column; gap: 0.55rem; }

.apply-notice {
  border: 1px solid rgba(250, 204, 21, 0.4);
  background: rgba(250, 204, 21, 0.09);
  border-radius: var(--radius);
  padding: 0.9rem 1.1rem;
}
.apply-notice strong { display: block; color: var(--warn); margin-bottom: 0.3rem; }
.apply-notice p { margin: 0; color: var(--paper-dim); }

.apply-ref {
  border: 1px solid var(--glass-line);
  border-radius: var(--radius);
  padding: 0.75rem 0.9rem 0.9rem;
  margin-bottom: 0.75rem;
}
.apply-ref:last-of-type { margin-bottom: 0; }
.apply-ref legend {
  padding: 0 0.4rem;
  font-size: 0.9rem; font-weight: 600;
  display: flex; align-items: center; gap: 0.45rem;
}
.apply-req, .apply-opt {
  font-size: 0.68rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.04em;
  padding: 0.1rem 0.45rem;
  border-radius: var(--radius-pill);
}
.apply-req { background: rgba(255, 77, 157, 0.18); color: var(--pink); }
.apply-opt { background: var(--glass-hi); color: var(--paper-faint); }

.apply-ref-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 0.6rem;
}

.apply-ack { margin-bottom: 1rem; align-items: flex-start; }
.apply-ack .choice-text { line-height: 1.5; color: var(--paper-dim); }

.apply-errors { margin: 0.4rem 0 0; padding-left: 1.15rem; display: grid; gap: 0.25rem; }
.apply-fails {
  margin: 0 0 1rem; padding-left: 1.15rem;
  display: grid; gap: 0.4rem;
  color: var(--paper-dim);
}
.apply-fails li::marker { color: var(--tag-18); }

@media (max-width: 640px) {
  .apply-q { padding: 0.75rem 0.8rem; }
  .apply-rules { padding-left: 1rem; }
}

/* Furmeet Details | the questions double as labels here, so they vary in
   length and wrap unevenly. Bottom-aligning each pair keeps the inputs on a
   common baseline no matter how many lines the label above them takes. */
.apply-fields { display: grid; gap: 1.35rem; }

.apply-pair {
  display: grid;
  gap: 1rem 1.1rem;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  align-items: end;
}
.apply-pair-lead { grid-template-columns: minmax(230px, 2.2fr) minmax(150px, 1fr); }

.apply-fields .field-label { min-height: 1.5em; }
.apply-fields .field-hint { margin-top: 0.35rem; }

@media (max-width: 620px) {
  /* Once these stack, a fixed ratio would leave the count field oddly narrow. */
  .apply-pair, .apply-pair-lead { grid-template-columns: 1fr; }
  .apply-fields { gap: 1.1rem; }
}

/* Stating this once up front is kinder than an asterisk on every one of the
   dozen-plus fields below. */
.apply-required-note {
  margin: 0.7rem 0 0;
  font-size: 0.85rem;
  color: var(--paper-faint);
  padding-left: 0.7rem;
  border-left: 2px solid var(--violet);
}
.apply-opt { white-space: nowrap; }

/* --------------------------------------------------------------------------
   52. Telegram nav item + leave-site interstitial

   The Telegram button borrows the brand's blue rather than the site violet, so
   an off-site destination is visibly not one of the calendar's own pages. It is
   an outline button, not a filled one | Apply is the only filled item in the
   nav and it should stay the single call to action there.
   -------------------------------------------------------------------------- */
.btn-telegram {
  border-color: rgba(42, 171, 238, 0.55);
  color: #9BDBFF;
  gap: 0.4rem;
}
.btn-telegram:hover {
  border-color: #2AABEE;
  background: rgba(42, 171, 238, 0.16);
  color: #D6F0FF;
}
/* The mark carries its own color, so it stays full strength on hover while
   the label brightens. */
.ico-telegram { border-radius: 50%; }

/* Apply is the only call to action up here, and it should not read as the
   fourth item in a run of four. A gap wider than the nav's own separates it
   from the links, without needing a divider. */
.site-nav .btn-apply { margin-left: 0.55rem; }

@media (max-width: 560px) {
  /* Once the nav wraps, the extra margin only pushes Apply off its own line. */
  .site-nav .btn-apply { margin-left: 0; }
}

/* The dialog reuses .ev-modal wholesale | sizing, backdrop, the mobile sheet
   treatment | and only retints the top rule and narrows the box. */
.leave-modal {
  --accent: #2AABEE;
  max-width: min(460px, calc(100vw - 2rem));
}

.leave-title { margin: 0 0 0.6rem; padding-right: 2.2rem; }

.leave-lead {
  margin: 0 0 0.85rem;
  font-size: 0.92rem;
  color: var(--paper-dim);
}
.leave-lead strong { color: var(--paper); }

/* The URL is shown verbatim, so it wraps anywhere rather than pushing the
   dialog wider than the viewport. break-all is deliberate: a long path with no
   separators has nowhere else to break. */
.leave-url {
  margin: 0 0 1.1rem;
  padding: 0.5rem 0.65rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.6);
  border: 1px solid var(--glass-line);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.78rem;
  color: var(--sky);
  overflow-wrap: anywhere;
  word-break: break-all;
}

/* --------------------------------------------------------------------------
   53. Google Maps mark

   Self-hosted app icon, used by the modal's "Open Google Maps" button and by
   the address row in Event Details. It replaced a hand-drawn SVG pin, so the
   rules that positioned that SVG live here now.
   -------------------------------------------------------------------------- */
.ico-maps {
  flex-shrink: 0;
  display: inline-block;
  /* The source is a rounded-square app icon on white. A little radius keeps
     its corners from reading as a hard crop against the dark panel. */
  border-radius: 4px;
}

/* .ev-row aligns to flex-start so the icon sits level with the top of the
   address rather than floating above it. A hair of offset lines its optical
   center up with the first line of text. */
.ev-row .ico-maps { margin-top: 0.08rem; }

/* --------------------------------------------------------------------------
   54. Month navigation

   Was a pair of bare chevron circles, which left the reader to infer what
   they did. Now each says so, with the chevron kept as a direction cue.
   -------------------------------------------------------------------------- */
.btn-month {
  gap: 0.4rem;
  font-weight: 800;
  /* Near-opaque dark fill rather than a translucent violet wash. These sit on
     top of the month artwork, which varies month to month | a dark plate keeps
     the label legible whatever is behind it, instead of letting the background
     bleed through and set the contrast. */
  border-color: rgba(124, 92, 255, 0.38);
  background: linear-gradient(180deg, rgba(17, 12, 30, 0.90), rgba(10, 7, 19, 0.94));
}
.btn-month:hover {
  border-color: var(--violet);
  background: linear-gradient(180deg, rgba(30, 21, 54, 0.95), rgba(19, 13, 35, 0.97));
}

/* The chevron leans toward the direction of travel on hover | a small cue
   that the button moves you rather than opening something. */
.btn-month .btn-ico {
  transition: transform var(--fast) var(--ease);
  opacity: 0.85;
}
.btn-month:hover .btn-ico { opacity: 1; }
.btn-month:hover .btn-ico:first-child { transform: translateX(-2px); }
.btn-month:hover .btn-ico:last-child  { transform: translateX(2px); }

/* Nothing to navigate to. Not focusable and not clickable, so it must not
   invite either. */
.cal-nav .btn-month[aria-disabled="true"] {
  /* Dimmed by color rather than by opacity: fading the whole button would let
     the artwork show through the plate the dark fill is there to provide. */
  background: linear-gradient(180deg, rgba(14, 10, 24, 0.82), rgba(9, 6, 17, 0.88));
  border-color: var(--glass-line);
  color: var(--paper-faint);
  cursor: not-allowed;
}
.cal-nav .btn-month[aria-disabled="true"] .btn-ico { opacity: 0.5; }
.cal-nav .btn-month[aria-disabled="true"]:hover .btn-ico { transform: none; }

/* Narrow screens: the words are the first thing to go, since the chevron and
   the aria-label both survive and the header would otherwise wrap awkwardly. */
@media (max-width: 460px) {
  .btn-month-label { display: none; }
  /* Icon-only, but still a full 44px thumb target rather than the width the
     lone chevron would collapse to. */
  .btn-month {
    padding-left: 0.7rem; padding-right: 0.7rem;
    min-width: 44px; justify-content: center;
  }
}

/* --------------------------------------------------------------------------
   55. Day view (/day.php)

   Where a "+N more" link lands. The grid cell can only show so many meets, so
   this page is the one place a busy day is guaranteed to be fully readable.
   It borrows the event modal's fact list rather than inventing a second way to
   present the same four facts.
   -------------------------------------------------------------------------- */
.day-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  padding-bottom: 1rem; margin-bottom: 1rem;
  border-bottom: 1px solid var(--glass-line);
}

.day-dow {
  display: block;
  font-size: 0.75rem; font-weight: 900;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--pink);
}
.day-date {
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(1.3rem, 3.4vw, 1.9rem);
  margin: 0.1rem 0 0;
  display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
}
.day-count { margin: 0.35rem 0 0; font-size: 0.85rem; color: var(--paper-dim); }

.day-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }

.day-list { display: grid; gap: 0.9rem; }

.day-item {
  padding: 1rem;
  border-radius: var(--radius-lg);
  background: rgba(20, 16, 31, 0.5);
  border: 1px solid var(--glass-line);
  border-left: 4px solid var(--accent, var(--violet));
}

/* The heading is the link; the facts below are not, so an address can be
   selected and copied without navigating away. */
.day-item-head {
  display: flex; align-items: center; gap: 0.8rem;
  text-decoration: none; color: inherit;
  margin-bottom: 0.85rem;
}
.day-item-head:hover .day-item-name { color: var(--violet-hi, var(--violet)); }

.day-item-heading { min-width: 0; }
.day-item-name {
  display: block;
  font-family: var(--font-display); font-weight: 800;
  font-size: 1.05rem;
}
.day-item-venue {
  display: block;
  font-size: 0.85rem; color: var(--paper-dim);
  margin-top: 0.1rem;
}
.day-item.is-cancelled .day-item-name { text-decoration: line-through; opacity: 0.7; }

/* Tighter than the modal's: several of these stack on one page. */
.day-facts { margin: 0 0 0.9rem; gap: 0.4rem; }

.day-item-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }

.day-nav {
  display: flex; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap;
  padding-top: 1.1rem; margin-top: 1.1rem;
  border-top: 1px solid var(--glass-line);
}
/* These labels are dates, not the word "Month" | they must survive the
   narrow-screen rule that collapses .btn-month to its chevron. */
@media (max-width: 460px) {
  .day-nav .btn-month-label { display: inline; }
  .day-nav .btn-month { min-width: 0; }
}

/* --------------------------------------------------------------------------
   56. Subscribe page (/subscribe.php)
   -------------------------------------------------------------------------- */
/* Full width of the panel. The 62ch cap that was here is the right default for
   a column of prose, but this is three lines above a copyable address, and
   holding it to two-thirds of the panel left an obvious empty gutter beside it
   rather than a comfortable measure. */
.sub-lead {
  margin: 0 0 1.1rem;
  font-size: 0.95rem; color: var(--paper-dim);
}

.sub-actions {
  display: flex; gap: 0.6rem; flex-wrap: wrap;
  margin-bottom: 1.4rem;
}

.sub-url { margin-bottom: 1.6rem; }

/* The field takes the space; the button keeps its natural width so it stays
   the same size whether the address is long or short. */
.sub-url-row { display: flex; gap: 0.5rem; align-items: stretch; }
.sub-url-row .input {
  flex: 1; min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.8rem;
}
.sub-url-row .btn { flex-shrink: 0; }
.sub-url-row .btn.is-copied {
  border-color: var(--ok);
  color: #B6F3CE;
  background: rgba(34, 197, 94, 0.16);
}

.sub-how { margin-top: 0; }

.sub-steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0.75rem;
  margin-bottom: 1.4rem;
}
.sub-step {
  padding: 0.9rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.5);
  border: 1px solid var(--glass-line);
}
.sub-step h3 {
  margin: 0 0 0.4rem;
  font-family: var(--font-display); font-weight: 800; font-size: 0.95rem;
}
.sub-step p { margin: 0; font-size: 0.83rem; color: var(--paper-dim); }
.sub-step strong { color: var(--paper); }

.sub-note {
  margin: 0 0 1.2rem;
  padding: 0.8rem 0.9rem;
  border-radius: var(--radius);
  border: 1px dashed var(--glass-line-hi);
  font-size: 0.83rem; color: var(--paper-dim);
  max-width: 68ch;
}

.sub-back { margin: 0; }

/* --------------------------------------------------------------------------
   57. "How it works" card editor + footer preview (admin)
   -------------------------------------------------------------------------- */
.how-lead { margin: -0.35rem 0 1rem; }

.how-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 0.85rem;
  margin-bottom: 1rem;
}

.how-card {
  margin: 0;
  padding: 0.9rem;
  border: 1px solid var(--glass-line);
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.45);
  display: grid; gap: 0.6rem;
  /* Grid children stretch by default, which would make a one-line heading
     field as tall as the textarea beside it. */
  align-content: start;
}

.how-card-num {
  padding: 0 0.45rem;
  font-size: 0.68rem; font-weight: 900;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--paper-faint);
}

/* The icon needs room for one emoji and no more; the heading takes the rest. */
.how-card-top {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: 0.6rem;
  align-items: start;
}
.how-icon {
  text-align: center;
  /* Emoji render from the system font | the display face has no glyphs for
     them and would fall back inconsistently between fields. */
  font-family: "Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji", sans-serif;
  font-size: 1.25rem;
  line-height: 1.4;
}

/* Tall enough for the card copy at the narrowest column, so the common case
   never scrolls even before the auto-grow script runs. The manual resize grip
   is gone: the box already sizes itself, and dragging it only produced a
   height that the next keystroke undid. */
.how-body { resize: none; min-height: 11rem; overflow-y: hidden; }

@media (max-width: 380px) {
  .how-card-top { grid-template-columns: 1fr; }
  .how-icon { text-align: left; }
}

/* Live footer preview on /admin/footer.php. The credit block is built to sit
   on the page background, so it gets a darker plate to sit on here. */
.footer-preview {
  border-radius: var(--radius);
  background: rgba(10, 7, 19, 0.6);
  border: 1px solid var(--glass-line);
  overflow: hidden;
}

/* Revision cards now span several sections, so each names its own. */
.rev-where {
  margin: 0 0 0.15rem;
  font-size: 0.68rem; font-weight: 900;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--violet);
  display: flex; align-items: center; gap: 0.35rem;
}
.rev-icon {
  font-family: "Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji", sans-serif;
  font-size: 0.85rem;
}
.rev-title {
  margin: 0 0 0.2rem;
  font-size: 0.82rem; font-weight: 800;
  color: var(--paper);
}

/* --------------------------------------------------------------------------
   58. Per-month day-of-week artwork

   A month may replace the SUN..SAT headings with its own lettering. The
   heading cell keeps its height either way, so swapping a day between text and
   art never shifts the grid below it.
   -------------------------------------------------------------------------- */
.cal-dow.has-art {
  /* The text padding is baked into the art, and letter-spacing on a container
     whose only child is an image just adds a stray gap on the right. */
  padding: 0.15rem 0;
  letter-spacing: 0;
  display: flex; align-items: center; justify-content: center;
}

.cal-dow-art {
  display: block;
  max-width: 100%;
  /* Bounded by height rather than width: the seven columns are equal, but the
     art inside them will not be, and a tall PNG would otherwise push its own
     row taller than its neighbours. */
  max-height: 34px;
  width: auto;
  object-fit: contain;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.55));
}

@media (max-width: 1023px) {
  .cal-dow-art { max-height: 26px; }
}

/* Seven slots side by side rather than the three-up artwork grid | these are
   small and belong in a row that reads as a week. */
.art-grid-dow {
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}

/* --------------------------------------------------------------------------
   59. Generated announcement previews (/admin/months.php)
   -------------------------------------------------------------------------- */
.render-previews {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 0.9rem;
  margin: 0 0 1.1rem;
}

.render-preview { margin: 0; }

.render-preview figcaption {
  display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
  margin-bottom: 0.4rem;
  font-size: 0.85rem;
}

.render-preview img {
  display: block;
  width: 100%; height: auto;
  border-radius: var(--radius);
  border: 1px solid var(--glass-line-hi);
  /* The images are drawn over dark artwork; a matching plate stops a
     transparent or letterboxed edge reading as a rendering fault. */
  background: var(--ink-950);
}
.render-preview a:hover img { border-color: var(--violet); }

.render-preview .field-hint { margin-top: 0.35rem; }

/* Regenerate control above the generated previews */
.render-actions {
  /* Regenerate images and Force Telegram Messages sit side by side here, and
     two buttons of similar weight running together read as one control. The
     gap is what keeps them separate enough to aim at. */
  display: flex; align-items: center; gap: 0.85rem; flex-wrap: wrap;
  margin: 0 0 0.85rem;
}
.render-actions-hint { margin: 0; }

/* The force-send form carries only the token and the month; the button that
   submits it lives in the confirmation dialog and reaches it by id. Hidden
   rather than laid out, so it neither shows nor takes a slot in the flex row
   between the two buttons. A hidden form still submits. */
.render-force-form { display: none; }

/* What the three messages are, inside the confirmation. */
.force-tg-list {
  margin: 0.9rem 0; padding-left: 1.35rem;
  display: grid; gap: 0.45rem;
  font-size: 0.94rem; line-height: 1.5; color: var(--paper-dim);
}
.force-tg-list strong { color: var(--paper); }
.force-tg-modal .banner { margin-top: 1rem; }

.render-actions .btn.is-busy { opacity: 0.7; pointer-events: none; }
.render-actions .btn.is-busy .btn-ico { animation: regen-spin 900ms linear infinite; }

@keyframes regen-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .render-actions .btn.is-busy .btn-ico { animation: none; }
}

.render-preview img { transition: opacity var(--fast) var(--ease); }

/* --------------------------------------------------------------------------
   Export Calendar Images (/manage/export.php)
   Reuses the month strip and the render previews from the admin months screen;
   only the two controls that screen does not have need styling of their own.
   -------------------------------------------------------------------------- */
/* The page is a stack of three panels and .panel carries no margin of its own,
   so without this they sat edge to edge and read as one long box with rules
   drawn across it. Bottom margin rather than a gap on the parent: the sections
   are siblings of whatever else the console shell puts in main, not children of
   a wrapper this page controls. */
.export-panel { margin-bottom: 1.1rem; }
.export-panel:last-child { margin-bottom: 0; }

.export-pick {
  display: flex; align-items: flex-end; gap: 0.7rem; flex-wrap: wrap;
}
/* The select takes the room it needs and no more | a furmeet name is short,
   and a full-width control beside a small button reads as a form that is
   missing its other fields. */
.export-pick .field { flex: 0 1 22rem; min-width: 12rem; margin: 0; }

.export-dl { margin: 0.75rem 0 0; }

/* Stepping through the details images when a month needs more than one. */
.render-pager {
  display: flex; align-items: center; justify-content: center;
  gap: 0.6rem; flex-wrap: wrap;
  margin: 0.7rem 0 0;
}
.render-pager-count { font-size: 0.8rem; color: var(--paper-dim); }
.render-pager-count strong { color: var(--paper); }

/* At either end the control stays in place rather than disappearing, so the
   row does not reflow as you page through it. */
.render-pager [aria-disabled="true"] {
  opacity: 0.4; pointer-events: none;
}

/* --------------------------------------------------------------------------
   60. Telegram images panel (right column of /admin/months.php)
   -------------------------------------------------------------------------- */
/* Column-aware, not panel-aware: whichever panel is in the narrow column
   stacks its grids, and the wide one keeps them side by side. Tagging the
   panels this way means swapping which column they live in is a markup change
   with no stylesheet edit chasing it. */
.is-narrow .art-grid,
.is-narrow .art-grid-dow,
.is-narrow .art-grid-tg,
.is-narrow .render-previews {
  grid-template-columns: 1fr;
}

/* Two dropzones fit comfortably across the wide column. */
.is-wide .art-grid-tg { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }

/* Swatch, label and the Default box on one line. The middle column is the
   only flexible one, so a long hint shortens itself rather than wrapping the
   checkbox onto a row of its own. */
.tg-ink {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.6rem;
  padding: 0.35rem 0;
}
.tg-ink + .tg-ink { border-top: 1px solid var(--glass-line); }

.tg-ink-body { min-width: 0; display: flex; flex-direction: column; }
.tg-ink-body strong { font-size: 0.82rem; }
.tg-ink-body .field-hint { margin: 0; font-size: 0.7rem; }

.tg-inks { border: 0; padding: 0; margin: 0 0 1rem; }

.tg-ink-swatch {
  width: 64px; height: 40px;
  padding: 2px;
  border-radius: var(--radius);
  border: 1px solid var(--glass-line-hi);
  background: rgba(10, 8, 19, 0.55);
  cursor: pointer;
}
/* Chrome and Firefox each wrap the swatch differently; both need flattening
   or the color sits inside a thick grey chrome border. */
.tg-ink-swatch::-webkit-color-swatch-wrapper { padding: 0; }
.tg-ink-swatch::-webkit-color-swatch { border: 0; border-radius: 5px; }
.tg-ink-swatch::-moz-color-swatch { border: 0; border-radius: 5px; }

.tg-ink-default {
  padding: 0.25rem 0.5rem;
  white-space: nowrap;      /* "Default" must never wrap to two lines */
  flex-shrink: 0;
}
.tg-ink-default .choice-text { font-size: 0.75rem; }

/* --------------------------------------------------------------------------
   61. Compact artwork slots for the narrow column

   The dropzones were built for a wide three-across grid: 168px tall with a
   74px preview. Stacked one per row in a third of the page, ten of them ran
   past 1700px of column. In the narrow column they turn into rows | small
   thumbnail, label, Clear | which is the same information at a third of the
   height.
   -------------------------------------------------------------------------- */
.is-narrow .art-grid { gap: 0.55rem; }

/*
 * Rows, not cards. The originals were 168px tall with the thumbnail stacked
 * above its label, built for three across a wide page; one per row in a 385px
 * column that was mostly empty space. Turning each into a row keeps the
 * preview generous | these are the three images that set the month's whole
 * look and need to be judged at a glance | while cutting the height roughly
 * in half.
 */
.is-narrow .art-slot .dropzone {
  min-height: 0;
  border-width: 1px;
}
.is-narrow .art-slot > .field-label {
  min-height: 0;
  font-size: 0.76rem;
  margin-bottom: 0.25rem;
}
.is-narrow .art-slot .dropzone-label {
  min-height: 0;
  padding: 0.6rem 0.65rem;
  gap: 0.7rem;
  align-items: center;
}
.is-narrow .art-slot .dropzone-preview,
.is-narrow .art-slot .dropzone-empty {
  width: 118px; height: 72px;
  border-radius: 8px;
}
.is-narrow .art-slot .dropzone-empty svg { width: 24px; height: 24px; }
.is-narrow .art-slot .dropzone-text strong { font-size: 0.82rem; }
.is-narrow .art-slot .dropzone-hint { font-size: 0.72rem; }

/* Clear returns to a small corner control instead of a full-width foot pill. */
.is-narrow .art-slot .dropzone-clear {
  position: absolute; top: 5px; right: 5px;
  margin: 0;
  padding: 0.12rem 0.45rem;
  font-size: 0.68rem;
  min-height: 0;
}

/* --- Day of week: seven of the smallest possible slots --------------------
   These are short words on transparent art, so the preview only has to prove
   something is there. Three across even in the narrow column. */
.art-grid-dow { gap: 0.4rem; }
.is-narrow .art-grid-dow { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.is-narrow .art-grid-dow .art-slot > .field-label,
.art-grid-dow .art-slot > .field-label {
  font-size: 0.68rem; letter-spacing: 0.04em; text-transform: uppercase;
}
.art-grid-dow .dropzone-label {
  flex-direction: column; gap: 0.25rem;
  padding: 0.4rem 0.3rem;
  text-align: center;
}
.is-narrow .art-grid-dow .dropzone-label { padding: 0.4rem 0.3rem; gap: 0.25rem; }
.is-narrow .art-grid-dow .dropzone-empty svg { width: 16px; height: 16px; }
/*
 * Taller than they started out, because the bigger preview is easier to judge
 * at a glance | but the width has to come from the column, not from the
 * .is-narrow row sizing above. That rule's fixed 118px is wider than the 94px
 * column these sit in, so the previews spilled past the panel edge.
 */
.is-narrow .art-grid-dow .dropzone-preview,
.is-narrow .art-grid-dow .dropzone-empty,
.art-grid-dow .dropzone-preview,
.art-grid-dow .dropzone-empty {
  width: 100%; height: 72px;
  /* contain, not cover: this artwork is lettering, and cropping it to fill a
     box would hide the very thing being previewed. */
  object-fit: contain;
  background: rgba(10, 8, 19, 0.5);
}
/* The filename would wrap to three lines in a 90px column and says nothing the
   thumbnail does not. */
.art-grid-dow .dropzone-text { display: none; }
.art-grid-dow .dropzone-clear { top: 2px; right: 2px; padding: 0 0.3rem; }

/* Group heading for sections that are no longer <details> */
.art-group {
  border: 1px solid var(--glass-line);
  border-radius: var(--radius);
  padding: 0.7rem 0.8rem;
  background: rgba(10, 8, 19, 0.3);
}
.art-group-title {
  display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
  margin: 0 0 0.15rem;
  font-family: var(--font-display); font-weight: 800; font-size: 0.9rem;
}

/* --- "This month" condensed ---------------------------------------------
   Three stacked stat cards with 2rem numerals took most of the column for
   three integers. One row, numbers still legible, most of the height back. */
.is-narrow .stat-row,
.month-stats {
  grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
  gap: 0.4rem;
}
.month-stats .stat { padding: 0.5rem 0.4rem; text-align: center; }
.month-stats .stat-num { font-size: 1.3rem; }
.month-stats .stat-label { font-size: 0.62rem; line-height: 1.25; }

/* A tone left on Default is greyed so it is obvious at a glance which rows are
   actually doing something. Not disabled | the swatch stays usable, and using
   it is what clears the tick. */
.tg-ink.is-default .tg-ink-swatch { opacity: 0.45; }
.tg-ink.is-default .tg-ink-body strong { color: var(--paper-faint); }

/* --------------------------------------------------------------------------
   62. Credit character manager (/admin/footer.php)
   -------------------------------------------------------------------------- */
.char-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
  gap: 0.6rem;
  margin-bottom: 1.2rem;
}

.char-card {
  margin: 0;
  padding: 0.5rem 0.4rem 0.55rem;
  border-radius: var(--radius);
  border: 1px solid var(--glass-line);
  background: rgba(20, 16, 31, 0.5);
  display: flex; flex-direction: column; align-items: center; gap: 0.35rem;
  text-align: center;
}
.char-card img {
  width: 100%; height: 76px;
  /* contain, not cover: these are characters, and cropping one to fill a
     square is how you end up with a picker full of torsos. */
  object-fit: contain;
}
.char-card figcaption { font-size: 0.74rem; font-weight: 700; }

.char-tag {
  font-size: 0.62rem; font-weight: 800;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--paper-faint);
}

.char-remove {
  border-color: rgba(255, 59, 78, 0.5);
  color: #FFB3BA;
  padding: 0.1rem 0.5rem;
  font-size: 0.7rem; min-height: 0;
}
.char-remove:hover { border-color: var(--bad); background: rgba(255, 59, 78, 0.14); }

/* Adding is a rarer, heavier action than choosing, so it sits below a rule. */
.char-add {
  padding-top: 1rem;
  border-top: 1px solid var(--glass-line);
}

/* --------------------------------------------------------------------------
   63. Audit Log

   The screen is a read-only record, so it leans on shape rather than controls:
   an activity sparkline, filter chips, and a vertical timeline where each entry
   is colored by the area of the console it came from.
   -------------------------------------------------------------------------- */
.audit { display: grid; gap: 1rem; }

/* --- overview ------------------------------------------------------------ */
.audit-hero-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  margin-bottom: 1.1rem;
}

.audit-figures { display: flex; gap: 1.6rem; }
.audit-figure { text-align: right; }
.audit-figure-num {
  display: block;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(1.4rem, 3vw, 2rem); line-height: 1;
  background: linear-gradient(180deg, var(--paper), var(--violet));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.audit-figure-label {
  display: block; margin-top: 0.2rem;
  font-size: 0.66rem; font-weight: 800;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--paper-faint);
}

/* --- filters ------------------------------------------------------------- */
.audit-filters { display: grid; gap: 0.75rem; }

.audit-search { position: relative; display: flex; gap: 0.5rem; align-items: center; }
.audit-search-icon {
  position: absolute; left: 0.75rem; top: 50%; transform: translateY(-50%);
  color: var(--paper-faint); pointer-events: none; line-height: 0;
}
.audit-search-input { padding-left: 2.4rem; }

.audit-chips { display: flex; align-items: center; gap: 0.35rem; flex-wrap: wrap; }
.audit-chips-label {
  font-size: 0.66rem; font-weight: 800;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--paper-faint);
  width: 3.2rem; flex-shrink: 0;
}

.audit-chip {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.22rem 0.65rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--glass-line-hi);
  background: rgba(20, 16, 31, 0.5);
  color: var(--paper-dim);
  font-size: 0.76rem; font-weight: 700;
  text-decoration: none;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease),
              color var(--fast) var(--ease);
}
.audit-chip:hover { background: rgba(42, 33, 66, 0.7); color: var(--paper); }
.audit-chip.is-on {
  border-color: var(--chip, var(--violet));
  background: color-mix(in srgb, var(--chip, var(--violet)) 20%, transparent);
  color: #fff;
}
.audit-chip-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--chip, var(--violet));
}
.audit-chip-n {
  font-size: 0.66rem; font-weight: 800;
  color: var(--paper-faint);
}

/* --- timeline ------------------------------------------------------------ */
.timeline {
  list-style: none; margin: 0; padding: 0;
  position: relative;
}
/* The rail. Drawn on the list rather than per row so it runs continuously
   through the day headings instead of restarting at each one. */
.timeline::before {
  content: "";
  position: absolute; left: 15px; top: 4px; bottom: 4px;
  width: 2px;
  background: linear-gradient(180deg, transparent, var(--glass-line-hi) 6%,
                                      var(--glass-line-hi) 94%, transparent);
}

.timeline-day { position: relative; margin: 1.1rem 0 0.5rem; padding-left: 46px; }
.timeline-day:first-child { margin-top: 0; }
.timeline-day-label {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border-radius: var(--radius-pill);
  background: var(--ink-800);
  border: 1px solid var(--glass-line);
  font-size: 0.7rem; font-weight: 800;
  letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--paper-faint);
}
.timeline-day-label.is-today { border-color: var(--violet); color: var(--paper); }

.timeline-row { position: relative; padding-left: 46px; margin-bottom: 0.5rem; }

/* The node sits on the rail, tinted by the console area it came from. */
.timeline-mark {
  position: absolute; left: 0; top: 6px;
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border-radius: 50%;
  font-size: 0.85rem;
  background: var(--ink-800);
  border: 2px solid var(--fam, var(--violet));
  box-shadow: 0 0 0 4px rgba(10, 8, 19, 0.9), 0 0 14px -2px var(--fam, var(--violet));
}

.timeline-card {
  padding: 0.6rem 0.8rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.5);
  border: 1px solid var(--glass-line);
  border-left: 3px solid var(--fam, var(--violet));
  transition: border-color var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.timeline-row:hover .timeline-card {
  transform: translateX(2px);
  border-color: var(--glass-line-hi);
  border-left-color: var(--fam, var(--violet));
}

.timeline-head { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }
.timeline-actor { font-size: 0.88rem; }
.timeline-action {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.7rem;
  padding: 0.05rem 0.4rem;
  border-radius: 5px;
  background: rgba(10, 8, 19, 0.6);
  color: var(--fam, var(--violet));
}
.timeline-time { margin-left: auto; font-size: 0.72rem; color: var(--paper-faint); }

.timeline-summary { margin: 0.35rem 0 0; font-size: 0.85rem; color: var(--paper-dim); }

.timeline-foot {
  display: flex; gap: 0.5rem; flex-wrap: wrap;
  margin-top: 0.4rem;
  font-size: 0.66rem; color: var(--paper-faint);
}
.timeline-fam {
  font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--fam, var(--violet));
}
.timeline-target, .timeline-ip {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* --- before/after -------------------------------------------------------- */
.timeline-changes { margin-top: 0.45rem; }
.timeline-changes summary {
  cursor: pointer;
  font-size: 0.72rem; font-weight: 700;
  color: var(--sky);
  width: fit-content;
}
.diff { margin: 0.45rem 0 0; display: grid; gap: 0.3rem; }
.diff-row {
  display: grid; grid-template-columns: minmax(0, 8rem) minmax(0, 1fr);
  gap: 0.6rem; align-items: baseline;
}
.diff dt {
  font-size: 0.7rem; font-weight: 800;
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--paper-faint);
}
.diff dd {
  margin: 0; font-size: 0.78rem;
  display: flex; gap: 0.4rem; flex-wrap: wrap; align-items: baseline;
}
.diff-before { color: #FFB3BA; text-decoration: line-through; opacity: 0.75; overflow-wrap: anywhere; }
.diff-arrow  { color: var(--paper-faint); }
.diff-after  { color: #B6F3CE; overflow-wrap: anywhere; }

/* --- pager --------------------------------------------------------------- */
.audit-pager {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.75rem; flex-wrap: wrap;
  margin-top: 1rem; padding-top: 1rem;
  border-top: 1px solid var(--glass-line);
}
.audit-pager-at { font-size: 0.78rem; color: var(--paper-faint); }
/* An end-of-range arrow stays visible but inert, so the control does not jump
   position as you page. */
.audit-pager .btn.is-off { opacity: 0.3; pointer-events: none; }

@media (max-width: 620px) {
  .audit-figures { width: 100%; justify-content: space-between; gap: 0.75rem; }
  .audit-figure { text-align: left; }
  .audit-chips-label { width: 100%; }
  .timeline-time { margin-left: 0; width: 100%; }
}

/* --------------------------------------------------------------------------
   64. Settings & Telegram
   -------------------------------------------------------------------------- */
.banner-good {
  border-color: var(--ok);
  background: rgba(34, 197, 94, 0.12);
  color: #B6F3CE;
}

.settings-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; }
.settings-actions-sep {
  margin-top: 1rem; padding-top: 1rem;
  border-top: 1px solid var(--glass-line);
}
.settings-danger { border-color: rgba(255, 59, 78, 0.5); color: #FFB3BA; }
.settings-danger:hover { border-color: var(--bad); background: rgba(255, 59, 78, 0.14); }

.outbox-list { list-style: none; margin: 0.85rem 0 0; padding: 0; display: grid; gap: 0.4rem; }
.outbox-row {
  display: flex; gap: 0.55rem; align-items: flex-start;
  padding: 0.45rem 0.55rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.45);
  border: 1px solid var(--glass-line);
  font-size: 0.78rem;
}
.outbox-dot {
  width: 8px; height: 8px; border-radius: 50%;
  margin-top: 0.35rem; flex-shrink: 0;
  background: var(--idle);
}
.outbox-row.is-queued  .outbox-dot { background: var(--warn); }
.outbox-row.is-sent    .outbox-dot { background: var(--ok); }
.outbox-row.is-failed  .outbox-dot { background: var(--bad); }
.outbox-row.is-sending .outbox-dot { background: var(--sky); }

.outbox-body { display: flex; flex-direction: column; min-width: 0; }
.outbox-meta { font-size: 0.7rem; color: var(--paper-faint); }
.outbox-error { font-size: 0.7rem; color: #FFB3BA; overflow-wrap: anywhere; }

/* --------------------------------------------------------------------------
   65. Activity chart (Audit Log hero)

   An SVG curve for the shape of the activity, a row of invisible columns over
   it for the pointer, and a readout that follows the cursor. Everything moves
   once on load and then holds still | a panel that keeps animating while you
   are trying to read the log underneath it is a distraction, not a feature.
   -------------------------------------------------------------------------- */
.audit-hero {
  position: relative;
  overflow: hidden;
}

/* A slow aurora behind the panel. Sits under the content and is entirely
   decorative, so it is pointer-transparent and hidden from assistive tech. */
.audit-hero::before {
  content: "";
  position: absolute; inset: -40% -10% auto -10%;
  height: 320px;
  pointer-events: none;
  background:
    radial-gradient(40% 60% at 20% 40%, rgba(124, 92, 255, 0.30), transparent 70%),
    radial-gradient(35% 55% at 70% 30%, rgba(56, 189, 248, 0.22), transparent 70%),
    radial-gradient(30% 50% at 90% 60%, rgba(255, 77, 157, 0.20), transparent 70%);
  filter: blur(28px);
  animation: aurora 22s ease-in-out infinite alternate;
  z-index: 0;
}
.audit-hero > * { position: relative; z-index: 1; }

@keyframes aurora {
  0%   { transform: translate3d(-4%, 0, 0) scale(1); }
  50%  { transform: translate3d(3%, 2%, 0) scale(1.08); }
  100% { transform: translate3d(6%, -2%, 0) scale(1.02); }
}

/* --- the figures --------------------------------------------------------- */
.audit-figure-num {
  font-variant-numeric: tabular-nums;   /* counting up must not reflow the row */
}

/* --- chart --------------------------------------------------------------- */
.spark {
  position: relative;
  margin: 0;
  padding: 0.5rem 0.6rem 0;
  border-radius: var(--radius);
  background: linear-gradient(180deg, rgba(10, 8, 19, 0.55), rgba(10, 8, 19, 0.25));
  border: 1px solid var(--glass-line);
}

.spark-svg {
  display: block;
  width: 100%; height: 150px;
  /* preserveAspectRatio="none" stretches the curve to the panel width; stroke
     width is corrected below so it does not stretch with it. */
  overflow: visible;
}

.spark-grid line {
  stroke: rgba(246, 243, 255, 0.07);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.spark-line {
  vector-effect: non-scaling-stroke;
  /* Drawn on: the dash pattern is one long dash offset out of view, then
     animated back to zero. The length is set generously so any path fits. */
  stroke-dasharray: 4000;
  stroke-dashoffset: 4000;
  animation: spark-draw 1.6s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
@keyframes spark-draw { to { stroke-dashoffset: 0; } }

.spark-area {
  opacity: 0;
  transform-origin: bottom;
  animation: spark-fill 1.1s ease-out 0.55s forwards;
}
@keyframes spark-fill {
  from { opacity: 0; transform: scaleY(0.72); }
  to   { opacity: 1; transform: scaleY(1); }
}

/* The busiest day, marked and quietly pulsing so the eye finds it. */
.spark-peak { opacity: 0; animation: spark-pop 0.5s ease-out 1.5s forwards; }
.spark-peak-dot  { fill: #fff; }
.spark-peak-halo {
  fill: none;
  stroke: var(--pink);
  stroke-width: 2;
  transform-origin: center;
  animation: spark-pulse 2.6s ease-out infinite;
}
@keyframes spark-pop   { from { opacity: 0; } to { opacity: 1; } }
@keyframes spark-pulse {
  0%   { transform: scale(0.55); opacity: 0.9; }
  70%  { transform: scale(1.5);  opacity: 0; }
  100% { transform: scale(1.5);  opacity: 0; }
}

/* --- hover columns ------------------------------------------------------- */
.spark-hit {
  position: absolute; inset: 0.5rem 0.6rem 2rem;
  display: flex; gap: 1px;
}

.spark-col {
  flex: 1;
  position: relative;
  display: flex; align-items: flex-end;
  border: 0; padding: 0;
  background: transparent;
  cursor: pointer;
  border-radius: 4px;
  transition: background var(--fast) var(--ease);
}
.spark-col:hover,
.spark-col:focus-visible { background: rgba(124, 92, 255, 0.16); outline: none; }
.spark-col:focus-visible { box-shadow: inset 0 0 0 1px var(--sky); }

/* A faint bar under the curve, so the value of a single day is readable even
   where the smoothed line passes between two points. */
.spark-col-bar {
  width: 100%;
  height: var(--v, 2%);
  border-radius: 3px 3px 0 0;
  background: linear-gradient(180deg, rgba(246, 243, 255, 0.22), rgba(246, 243, 255, 0.03));
  transform-origin: bottom;
  transform: scaleY(0);
  animation: spark-rise 0.5s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  /* Staggered left to right, capped so a 90-day window does not take 9s. */
  animation-delay: calc(var(--i, 0) * 18ms);
}
@keyframes spark-rise { to { transform: scaleY(1); } }

.spark-col:hover .spark-col-bar { background: linear-gradient(180deg, rgba(124, 92, 255, 0.6), rgba(124, 92, 255, 0.1)); }
.spark-col.is-peak .spark-col-bar { background: linear-gradient(180deg, rgba(255, 77, 157, 0.5), rgba(255, 77, 157, 0.05)); }

/* --- readout ------------------------------------------------------------- */
.spark-readout {
  position: absolute; top: 0.35rem; left: 50%;
  transform: translate(-50%, -6px);
  padding: 0.2rem 0.6rem;
  border-radius: var(--radius-pill);
  background: var(--ink-950);
  border: 1px solid var(--violet);
  font-size: 0.72rem; font-weight: 700;
  white-space: nowrap;
  opacity: 0; pointer-events: none;
  transition: opacity var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.spark-readout.is-on { opacity: 1; transform: translate(-50%, 0); }
.spark-readout b { color: var(--pink); }

.spark-axis {
  display: flex; justify-content: space-between; align-items: center;
  padding: 0.3rem 0.1rem 0.45rem;
  font-size: 0.66rem; font-weight: 700;
  letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--paper-faint);
}
.spark-axis-peak { color: var(--pink); }

/*
 * Reduced motion: the chart still draws itself, it just arrives rather than
 * animating in. The aurora and the pulse stop completely | those are ambience,
 * and ambience is exactly what this setting is asking to be spared.
 */
@media (prefers-reduced-motion: reduce) {
  .audit-hero::before { animation: none; }
  .spark-line { animation: none; stroke-dashoffset: 0; }
  .spark-area { animation: none; opacity: 1; transform: none; }
  .spark-peak { animation: none; opacity: 1; }
  .spark-peak-halo { animation: none; opacity: 0.35; }
  .spark-col-bar { animation: none; transform: scaleY(1); }
}

/* --------------------------------------------------------------------------
   66. Activity chart | larger canvas and the second layer of detail
   -------------------------------------------------------------------------- */

/* Roughly half again as tall as it started. The curve now has room to show a
   shape rather than a bump, and the hover columns become comfortable targets. */
.spark-svg { height: 230px; }
.spark { padding: 0.75rem 0.85rem 0; }

@media (max-width: 720px) {
  .spark-svg { height: 170px; }
}

/* --- the previous window ------------------------------------------------- */
.spark-prev {
  vector-effect: non-scaling-stroke;
  opacity: 0;
  animation: spark-ghost 0.9s ease-out 1.1s forwards;
}
@keyframes spark-ghost { to { opacity: 1; } }

/* --- travelling light ---------------------------------------------------- */
.spark-comet {
  fill: #fff;
  filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.9))
          drop-shadow(0 0 14px var(--violet));
  offset-rotate: 0deg;
  opacity: 0;
  animation: spark-comet-run 7s linear 1.7s infinite,
             spark-comet-in  0.4s ease-out 1.7s forwards;
}
@keyframes spark-comet-run {
  from { offset-distance: 0%; }
  to   { offset-distance: 100%; }
}
@keyframes spark-comet-in { to { opacity: 1; } }

/* A browser without offset-path leaves the dot at the origin, which would read
   as a stray mark in the corner. @supports keeps it hidden unless it can move. */
@supports not (offset-path: path("M0 0")) {
  .spark-comet { display: none; }
}

/* --- sheen --------------------------------------------------------------- */
.spark-sheen {
  opacity: 0;
  animation: spark-sheen-in 1s ease-out 1.4s forwards;
  pointer-events: none;
}
@keyframes spark-sheen-in { to { opacity: 1; } }

/* --- crosshair ----------------------------------------------------------- */
.spark-cross {
  position: absolute;
  top: 0.75rem; bottom: 2.4rem;
  width: 1px;
  background: linear-gradient(180deg, transparent, rgba(124, 92, 255, 0.85) 25%,
                                      rgba(124, 92, 255, 0.85) 75%, transparent);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--fast) var(--ease), left 90ms var(--ease);
}
.spark-cross.is-on { opacity: 1; }
/* A soft bloom around the line, so it reads as a highlight rather than a hair. */
.spark-cross::after {
  content: "";
  position: absolute; inset: 0 -6px;
  background: radial-gradient(closest-side, rgba(124, 92, 255, 0.28), transparent);
}

/* --- trend badge --------------------------------------------------------- */
.audit-trend {
  display: inline-flex; align-items: center; gap: 0.3rem;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(1.1rem, 2.4vw, 1.5rem); line-height: 1;
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius-pill);
  border: 1px solid currentColor;
}
.audit-trend-arrow { font-size: 0.6em; }
.audit-trend.is-up   { color: #6EE7A8; background: rgba(34, 197, 94, 0.12); }
.audit-trend.is-down { color: #FFB3BA; background: rgba(255, 59, 78, 0.12); }
.audit-trend.is-flat { color: var(--paper-faint); background: rgba(148, 163, 184, 0.12); }

/* --- taller columns, livelier hover -------------------------------------- */
.spark-hit { inset: 0.75rem 0.85rem 2.4rem; }

.spark-col-bar {
  transition: background var(--fast) var(--ease), filter var(--fast) var(--ease);
}
.spark-col:hover .spark-col-bar,
.spark-col:focus-visible .spark-col-bar {
  filter: drop-shadow(0 0 8px rgba(124, 92, 255, 0.8));
}

/* The readout sits above the curve and tracks the column, so it needs to clear
   the taller chart rather than the old one. */
.spark-readout { top: 0.5rem; }

/* --- reduced motion ------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .spark-prev  { animation: none; opacity: 1; }
  .spark-sheen { animation: none; opacity: 0; }   /* a moving band with no motion is just a smear */
  .spark-comet { animation: none; display: none; }
  .spark-cross { transition: opacity 0.01ms; }
}

/* --------------------------------------------------------------------------
   67. Site logo panel (/admin/content.php, right column)
   -------------------------------------------------------------------------- */
.logo-now {
  display: flex; align-items: center; gap: 0.85rem;
  padding: 0.7rem 0.8rem;
  margin-bottom: 0.9rem;
  border-radius: var(--radius);
  background: rgba(10, 8, 19, 0.5);
  border: 1px solid var(--glass-line);
}

.logo-now-art { width: 64px; flex-shrink: 0; }
.logo-now-art img { width: 100%; height: auto; display: block; }

.logo-now-text { display: flex; flex-direction: column; min-width: 0; gap: 0.1rem; }
.logo-now-text strong { font-size: 0.9rem; }
.logo-now-text .field-hint { margin: 0; }

/* The column is narrow, so the dropzone is a row like the artwork slots. */
.logo-form .dropzone-label {
  min-height: 0;
  padding: 0.55rem 0.6rem;
  gap: 0.65rem;
  align-items: center;
}
.logo-form .dropzone-preview,
.logo-form .dropzone-empty {
  width: 54px; height: 54px;
  border-radius: 10px;
}
.logo-form .dropzone-text strong { font-size: 0.8rem; }
.logo-form .dropzone-hint { font-size: 0.7rem; }
.logo-form .dropzone-clear {
  position: absolute; top: 5px; right: 5px;
  margin: 0; padding: 0.12rem 0.45rem;
  font-size: 0.68rem; min-height: 0;
}

.logo-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }

/* --------------------------------------------------------------------------
   68. Site Health

   The vitals panel is the whole point: a score ring, a heartbeat whose rate
   follows that score, and a tally. Everything below it is detail for when the
   trace says something is wrong.

   Band colours are set on the wrapper, so the ring, the trace and the glow all
   move together from one attribute rather than from six rules that could drift
   apart.
   -------------------------------------------------------------------------- */
.health { display: grid; gap: 1rem; }

.health[data-health="good"]     { --band-a: #22C55E; --band-b: #6EE7A8; }
.health[data-health="fair"]     { --band-a: #7C5CFF; --band-b: #38BDF8; }
.health[data-health="poor"]     { --band-a: #FACC15; --band-b: #FFB020; }
.health[data-health="critical"] { --band-a: #FF3B4E; --band-b: #FF8A97; }

/* --- vitals panel -------------------------------------------------------- */
.vitals {
  position: relative;
  overflow: hidden;
  border-color: color-mix(in srgb, var(--band-a) 35%, var(--glass-line));
}
/* A slow wash of the band colour behind everything, so the panel itself reads
   as healthy or not before a single word is read. */
.vitals::before {
  content: "";
  position: absolute; inset: -50% -10% auto -10%; height: 340px;
  pointer-events: none; z-index: 0;
  background:
    radial-gradient(45% 60% at 18% 40%, color-mix(in srgb, var(--band-a) 34%, transparent), transparent 70%),
    radial-gradient(40% 55% at 78% 30%, color-mix(in srgb, var(--band-b) 28%, transparent), transparent 70%);
  filter: blur(30px);
  animation: vitals-drift 18s ease-in-out infinite alternate;
}
.vitals > * { position: relative; z-index: 1; }

@keyframes vitals-drift {
  from { transform: translate3d(-3%, 0, 0) scale(1); }
  to   { transform: translate3d(4%, 2%, 0) scale(1.07); }
}

.vitals-grid {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 1.4rem;
  align-items: center;
}

/* --- score ring ---------------------------------------------------------- */
.vitals-score { position: relative; width: 130px; height: 130px; flex-shrink: 0; }

.ring { width: 130px; height: 130px; transform: rotate(-90deg); }
.ring-track {
  fill: none;
  stroke: rgba(246, 243, 255, 0.08);
  stroke-width: 10;
}
.ring-fill {
  fill: none;
  stroke-width: 10;
  stroke-linecap: round;
  /* Drawn on from zero: the dash is the whole circumference, the gap closes to
     the score. */
  stroke-dasharray: var(--fill) var(--len);
  stroke-dashoffset: 0;
  animation: ring-draw 1.5s cubic-bezier(0.22, 1, 0.36, 1) both;
  filter: drop-shadow(0 0 8px color-mix(in srgb, var(--band-a) 70%, transparent));
}
@keyframes ring-draw { from { stroke-dasharray: 0 var(--len); } }

.ring-face {
  position: absolute; inset: 0;
  display: grid; place-content: center; text-align: center;
}
.ring-num {
  display: block;
  font-family: var(--font-display); font-weight: 800;
  font-size: 2.6rem; line-height: 1;
  font-variant-numeric: tabular-nums;
  background: linear-gradient(180deg, var(--band-b), var(--band-a));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.ring-unit {
  display: block; margin-top: 0.15rem;
  font-size: 0.6rem; font-weight: 800;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--paper-faint);
}

/* --- trace --------------------------------------------------------------- */
.vitals-trace { min-width: 0; }

.trace-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 0.75rem; flex-wrap: wrap;
  margin-bottom: 0.5rem;
}
.trace-bpm { font-size: 0.78rem; color: var(--paper-faint); }
.trace-bpm strong {
  font-family: var(--font-display); font-size: 1.1rem;
  color: var(--band-b);
  /* One pulse per beat, at the rate the score set. */
  animation: bpm-pulse var(--beat, 1s) ease-in-out infinite;
}
@keyframes bpm-pulse {
  0%, 72%, 100% { opacity: 1; transform: scale(1); }
  78%           { opacity: 1; transform: scale(1.16); }
}

.ekg {
  position: relative;
  height: 118px;
  border-radius: var(--radius);
  background: rgba(6, 4, 12, 0.62);
  border: 1px solid color-mix(in srgb, var(--band-a) 22%, var(--glass-line));
  overflow: hidden;
}

/* Monitor graticule, drawn with gradients rather than an image. */
.ekg-grid {
  position: absolute; inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(to right, rgba(246, 243, 255, 0.05) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(246, 243, 255, 0.05) 1px, transparent 1px);
  background-size: 24px 24px;
}

.ekg-svg { position: relative; display: block; width: 100%; height: 100%; }

/* The whole run of beats slides left by exactly one beat width and repeats, so
   the loop is seamless. Duration is the beat interval times the beats crossed. */
.ekg-scroll {
  animation: ekg-scroll calc(var(--beat, 1s) * 4) linear infinite;
}
@keyframes ekg-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-600px); }
}

.ekg-line {
  vector-effect: non-scaling-stroke;
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--band-b) 80%, transparent));
}

/* The bright head that travels with the trace. */
.ekg-head {
  fill: url(#ekg-fade);
  opacity: 0.12;
  animation: ekg-head calc(var(--beat, 1s) * 4) linear infinite;
}
@keyframes ekg-head {
  from { transform: translateX(-90px); }
  to   { transform: translateX(600px); }
}

.trace-tally {
  display: flex; gap: 0.9rem; flex-wrap: wrap;
  margin-top: 0.6rem;
  font-size: 0.72rem; color: var(--paper-faint);
}
.tally b { font-family: var(--font-display); font-size: 0.95rem; margin-right: 0.15rem; }
.tally-ok b   { color: var(--ok); }
.tally-warn b { color: var(--warn); }
.tally-bad b  { color: var(--bad); }
.tally-idle b { color: var(--idle); }

.vitals-foot {
  margin: 1rem 0 0; padding-top: 0.85rem;
  border-top: 1px solid var(--glass-line);
  font-size: 0.72rem; color: var(--paper-faint);
}

/* --- system cards -------------------------------------------------------- */
/*
 * Four across, two rows.
 *
 * align-items defaults to stretch here on purpose. The previous `start` let a
 * four-check card sit at its natural height beside a six-check one, leaving a
 * band of dead space under it | which is what the ragged gaps were. Stretching
 * makes every card in a row the same height, and sorting the groups by length
 * means the two rows are close to uniform to begin with.
 */
.health-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1rem;
}

/* The card has to fill the row it was stretched to, or the border and
   background stop short of the bottom and the gap comes back visually. */
.health-grid .sys { display: flex; flex-direction: column; height: 100%; }
.health-grid .checks { flex: 1; align-content: start; }

@media (max-width: 1500px) { .health-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 1100px) { .health-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 700px)  { .health-grid { grid-template-columns: 1fr; } }

.sys { border-left: 3px solid var(--idle); }
.sys-ok   { border-left-color: var(--ok); }
.sys-warn { border-left-color: var(--warn); }
.sys-bad  { border-left-color: var(--bad); }

.sys-head {
  display: flex; align-items: center; gap: 0.6rem;
  margin: 0 0 0.75rem;
  font-family: var(--font-display); font-weight: 800; font-size: 1rem;
}
.sys-icon {
  display: grid; place-items: center;
  width: 30px; height: 30px;
  border-radius: 9px;
  background: rgba(10, 8, 19, 0.6);
  border: 1px solid var(--glass-line);
  font-size: 0.9rem;
}
.sys-name { flex: 1; min-width: 0; }
.sys-state {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--idle);
  flex-shrink: 0;
}
.sys-ok   .sys-state { background: var(--ok);   box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18); }
.sys-warn .sys-state { background: var(--warn); box-shadow: 0 0 0 3px rgba(250, 204, 21, 0.18); }
/* Only a real failure blinks. If everything blinked, nothing would. */
.sys-bad  .sys-state {
  background: var(--bad); box-shadow: 0 0 0 3px rgba(255, 59, 78, 0.2);
  animation: sys-alarm 1.1s ease-in-out infinite;
}
@keyframes sys-alarm { 50% { box-shadow: 0 0 0 7px rgba(255, 59, 78, 0); } }

/* --- individual checks --------------------------------------------------- */
.checks { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.3rem; }

.check {
  display: flex; gap: 0.55rem; align-items: flex-start;
  padding: 0.4rem 0.5rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.4);
  border: 1px solid transparent;
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.check:hover { background: rgba(42, 33, 66, 0.55); border-color: var(--glass-line); }

.check-dot {
  width: 7px; height: 7px; border-radius: 50%;
  margin-top: 0.42rem; flex-shrink: 0;
  background: var(--idle);
}
.check.is-ok   .check-dot { background: var(--ok); }
.check.is-warn .check-dot { background: var(--warn); }
.check.is-bad  .check-dot { background: var(--bad); }

.check-body { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 0.1rem; }
.check-top {
  display: flex; align-items: baseline; gap: 0.5rem; justify-content: space-between;
}
.check-label { font-size: 0.82rem; font-weight: 700; }
.check-value {
  font-size: 0.76rem; font-weight: 800;
  color: var(--paper-dim);
  font-variant-numeric: tabular-nums;
  text-align: right;
  overflow-wrap: anywhere;
}
.check.is-warn .check-value { color: #FCEFA8; }
.check.is-bad  .check-value { color: #FFB3BA; }

.check-detail { font-size: 0.7rem; color: var(--paper-faint); overflow-wrap: anywhere; }

/* The suggested fix only appears when there is something to fix, so it can be
   loud without adding noise to a healthy card. */
.check-fix {
  font-size: 0.7rem;
  color: var(--sky);
  border-left: 2px solid var(--sky);
  padding-left: 0.45rem;
  margin-top: 0.15rem;
}

.sys-action { margin-top: 0.75rem; }

@media (max-width: 640px) {
  .vitals-grid { grid-template-columns: 1fr; justify-items: center; }
  .vitals-trace { width: 100%; }
  .trace-head { justify-content: center; text-align: center; }
}

/*
 * Reduced motion: the trace holds still and the ring arrives at its value.
 * The heartbeat is the one thing worth keeping legible either way, so the line
 * and the graticule stay | it is only the movement that stops.
 */
@media (prefers-reduced-motion: reduce) {
  .vitals::before,
  .ekg-scroll,
  .ekg-head,
  .trace-bpm strong,
  .sys-bad .sys-state { animation: none; }
  .ring-fill { animation: none; }
  .ekg-head { opacity: 0; }
}

/* A choice whose label carries an explanation underneath it. The default
   .choice-text runs inline, which butts the hint straight up against the
   label. */
.choice-stacked {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.choice-stacked .field-hint { margin: 0; }

/* --------------------------------------------------------------------------
   69. Backup & Restore
   -------------------------------------------------------------------------- */
.bk { display: grid; gap: 1rem; }

/* --- statistics ---------------------------------------------------------- */
.bk-stats { position: relative; overflow: hidden; }
/* Tinted by how stale the newest backup is, so the panel answers "am I covered"
   before anything is read. */
.bk-stats.is-ok   { border-color: color-mix(in srgb, var(--ok) 35%, var(--glass-line)); }
.bk-stats.is-warn { border-color: color-mix(in srgb, var(--warn) 40%, var(--glass-line)); }
.bk-stats.is-bad  { border-color: color-mix(in srgb, var(--bad) 40%, var(--glass-line)); }

.bk-stats-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  margin-bottom: 1.1rem;
}

.bk-create { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.choice-inline { padding: 0.3rem 0.6rem; }
.bk-est {
  font-size: 0.7rem; font-weight: 700;
  opacity: 0.75;
  padding-left: 0.4rem;
  border-left: 1px solid rgba(255, 255, 255, 0.3);
  margin-left: 0.15rem;
}

.bk-figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 0.6rem;
  margin-bottom: 1rem;
}
.bk-fig {
  padding: 0.6rem 0.7rem;
  border-radius: var(--radius);
  background: rgba(10, 8, 19, 0.45);
  border: 1px solid var(--glass-line);
}
.bk-fig-num {
  display: block;
  font-family: var(--font-display); font-weight: 800;
  font-size: 1.25rem; line-height: 1.1;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.bk-fig-label {
  display: block; margin-top: 0.15rem;
  font-size: 0.62rem; font-weight: 800;
  letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--paper-faint);
}
.bk-age-ok   { color: var(--ok); }
.bk-age-warn { color: var(--warn); }
.bk-age-bad  { color: var(--bad); }

/* Archive sizes over time. */
.bk-spark {
  display: flex; align-items: flex-end; gap: 3px;
  height: 54px;
  padding: 0.4rem 0.5rem;
  margin-bottom: 0.85rem;
  border-radius: var(--radius);
  background: rgba(10, 8, 19, 0.45);
  border: 1px solid var(--glass-line);
}
.bk-bar {
  flex: 1; min-width: 3px;
  height: var(--h, 4%);
  border-radius: 3px 3px 1px 1px;
  background: linear-gradient(180deg, var(--violet), var(--violet-lo));
  transform-origin: bottom;
  transform: scaleY(0);
  animation: bk-rise 0.5s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  animation-delay: calc(var(--i, 0) * 30ms);
}
@keyframes bk-rise { to { transform: scaleY(1); } }
/* Safety snapshots are a different kind of thing from a backup you chose to
   take, so they read differently in the series. */
.bk-bar.is-safety { background: linear-gradient(180deg, var(--warn), #B98A08); }
.bk-bar:hover { filter: brightness(1.4); }

.bk-note {
  margin: 0; padding-top: 0.85rem;
  border-top: 1px solid var(--glass-line);
  font-size: 0.74rem; color: var(--paper-faint);
}

/* --- archive list -------------------------------------------------------- */
.bk-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.6rem; }

.bk-item {
  padding: 0.75rem 0.85rem;
  border-radius: var(--radius);
  background: rgba(20, 16, 31, 0.5);
  border: 1px solid var(--glass-line);
  border-left: 3px solid var(--violet);
}

.bk-item-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 0.75rem; flex-wrap: wrap;
}
.bk-item-id { display: flex; flex-direction: column; min-width: 0; }
.bk-item-id strong { font-size: 0.92rem; }
.bk-name {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.68rem; color: var(--paper-faint);
  overflow-wrap: anywhere;
}

.bk-tags { display: flex; align-items: center; gap: 0.35rem; flex-wrap: wrap; }
.bk-size {
  font-family: var(--font-display); font-weight: 800;
  font-size: 0.88rem;
  font-variant-numeric: tabular-nums;
}

.bk-meta { margin: 0.4rem 0 0; font-size: 0.72rem; color: var(--paper-faint); }

.bk-actions {
  display: flex; gap: 0.4rem; flex-wrap: wrap; align-items: flex-start;
  margin-top: 0.6rem;
}

.bk-danger { border-color: rgba(255, 59, 78, 0.5); color: #FFB3BA; }
.bk-danger:hover { border-color: var(--bad); background: rgba(255, 59, 78, 0.14); }

/* The restore form is folded away behind a summary, because it is the one
   control on the page that cannot be undone by clicking again. */
.bk-restore { display: inline-block; }
.bk-restore > summary { list-style: none; cursor: pointer; }
.bk-restore > summary::-webkit-details-marker { display: none; }
.bk-restore[open] > summary { border-color: var(--warn); color: #FCEFA8; }

.bk-restore-form {
  margin-top: 0.6rem;
  padding: 0.75rem;
  border-radius: var(--radius);
  background: rgba(10, 8, 19, 0.6);
  border: 1px solid rgba(250, 204, 21, 0.35);
  display: grid; gap: 0.6rem;
  /* The list item is a flex row; without this the panel would try to sit in a
     column the width of the button that opened it. */
  width: min(520px, 100%);
}
.bk-warn { margin: 0; font-size: 0.78rem; color: #FCEFA8; }
.bk-confirm {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.78rem;
}

.bk-prune { margin-top: 0.85rem; }

/* --- side panels --------------------------------------------------------- */
.bk-contents { list-style: none; margin: 0 0 0.85rem; padding: 0; display: grid; gap: 0.5rem; }
.bk-contents li {
  display: flex; flex-direction: column;
  padding: 0.45rem 0.55rem;
  border-radius: var(--radius);
  background: rgba(10, 8, 19, 0.45);
  border: 1px solid var(--glass-line);
}
.bk-contents strong {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.76rem; color: var(--sky);
}
.bk-contents span { font-size: 0.72rem; color: var(--paper-faint); }

@media (prefers-reduced-motion: reduce) {
  .bk-bar { animation: none; transform: scaleY(1); }
}

/* --------------------------------------------------------------------------
   70. Offsite backup panel
   -------------------------------------------------------------------------- */
.offsite-pane {
  display: grid; gap: 0.75rem;
  padding: 0.8rem;
  margin-bottom: 0.25rem;
  border-radius: var(--radius);
  background: rgba(10, 8, 19, 0.45);
  border: 1px solid var(--glass-line);
}
.offsite-pane[hidden] { display: none; }

.offsite-pane .textarea {
  min-height: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.72rem;
}

/* The redirect URI has to be copied character-for-character into Google Cloud,
   so it gets its own block rather than sitting inside a sentence. */
.offsite-redirect code {
  display: block;
  margin-top: 0.3rem;
  padding: 0.4rem 0.5rem;
  border-radius: var(--radius-sm);
  background: rgba(6, 4, 12, 0.7);
  border: 1px solid var(--glass-line);
  font-size: 0.7rem;
  color: var(--sky);
  overflow-wrap: anywhere;
}

.bk-last {
  margin: 0 0 0.85rem;
  padding: 0.5rem 0.6rem;
  border-radius: var(--radius);
  font-size: 0.75rem;
  background: rgba(10, 8, 19, 0.5);
  border-left: 3px solid var(--idle);
  color: var(--paper-dim);
}
.bk-last.is-ok  { border-left-color: var(--ok); }
.bk-last.is-bad { border-left-color: var(--bad); }
.bk-last-err { display: block; margin-top: 0.15rem; color: #FFB3BA; overflow-wrap: anywhere; }

/* --------------------------------------------------------------------------
   71. Site Mode + maintenance holding page
   -------------------------------------------------------------------------- */
.mode-panel.is-normal      { border-color: color-mix(in srgb, var(--ok) 32%, var(--glass-line)); }
.mode-panel.is-maintenance { border-color: color-mix(in srgb, var(--bad) 45%, var(--glass-line)); }

.mode-pill {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.15rem 0.6rem;
  border-radius: var(--radius-pill);
  font-size: 0.72rem; font-weight: 800;
  border: 1px solid currentColor;
}
.mode-pill.is-normal      { color: #6EE7A8; background: rgba(34, 197, 94, 0.12); }
.mode-pill.is-maintenance { color: #FFB3BA; background: rgba(255, 59, 78, 0.14); }

.mode-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}
/* Only the closed state pulses. A steady green says "fine"; a pulsing red is
   asking to be dealt with. */
.mode-pill.is-maintenance .mode-dot { animation: mode-alarm 1.4s ease-in-out infinite; }
@keyframes mode-alarm {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 59, 78, 0.55); }
  60%      { box-shadow: 0 0 0 6px rgba(255, 59, 78, 0); }
}

.mode-choices {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0.6rem;
}

.mode-choice {
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0.65rem 0.75rem;
  border-radius: var(--radius);
  border: 1px solid var(--glass-line);
  background: rgba(20, 16, 31, 0.45);
  cursor: pointer;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.mode-choice input { position: absolute; opacity: 0; pointer-events: none; }
.mode-choice.is-normal      .mode-dot { color: var(--ok); }
.mode-choice.is-maintenance .mode-dot { color: var(--bad); }

/* :has keeps the selected state honest when the radio is changed by keyboard
   or by clicking the other option, which the server-rendered class cannot. */
.mode-choice:has(input:checked) { background: rgba(42, 33, 66, 0.7); }
.mode-choice.is-normal:has(input:checked)      { border-color: var(--ok); }
.mode-choice.is-maintenance:has(input:checked) { border-color: var(--bad); }
.mode-choice:has(input:focus-visible) { box-shadow: 0 0 0 2px var(--sky); }

.mode-choice-text { display: flex; flex-direction: column; min-width: 0; }
.mode-choice-text strong { font-size: 0.86rem; }
.mode-choice-text .field-hint { margin: 0; }

.mode-msg { min-height: 0; }
.mode-confirm { font-weight: 700; letter-spacing: 0.06em; }

.mode-danger { border-color: rgba(255, 59, 78, 0.5); color: #FFB3BA; }
.mode-danger:hover { border-color: var(--bad); background: rgba(255, 59, 78, 0.14); }

/* --- console-wide reminder ---------------------------------------------- */
.maint-strip {
  display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap;
  padding: 0.7rem 1rem;
  margin-bottom: 1rem;
  border-radius: var(--radius);
  border: 1px solid var(--bad);
  background: rgba(255, 59, 78, 0.12);
  color: #FFD5D9;
  font-size: 0.86rem;
}
.maint-strip strong { color: #fff; }
.maint-strip .btn { margin-left: auto; }
.maint-strip-dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--bad);
  animation: mode-alarm 1.4s ease-in-out infinite;
  flex-shrink: 0;
}

/* --- the public holding page -------------------------------------------- */
.maint-page {
  display: grid; place-items: center;
  min-height: 100vh;
  padding: 2rem 1rem;
}
.maint {
  max-width: 32rem;
  text-align: center;
  padding: clamp(1.5rem, 5vw, 2.75rem);
  border-radius: var(--radius-lg);
  background: var(--glass);
  border: 1px solid var(--glass-line-hi);
  box-shadow: var(--shadow-lg);
}
.maint-logo { width: clamp(96px, 20vw, 148px); display: inline-block; }
.maint h1 {
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(1.3rem, 4vw, 1.9rem);
  margin: 0.75rem 0 0.5rem;
}
.maint-msg { margin: 0 0 1.25rem; color: var(--paper-dim); font-size: 0.95rem; }
.maint-foot { margin: 0; font-size: 0.8rem; color: var(--paper-faint); }
.maint-foot a { color: var(--sky); }

@media (prefers-reduced-motion: reduce) {
  .mode-pill.is-maintenance .mode-dot,
  .maint-strip-dot { animation: none; }
}

/* --- Telegram nav link -----------------------------------------------------
   A three-field panel does not need three rows. Name, address and Save share
   one line, and the visibility control moves up into the heading, where the
   state pill used to be | it says the same thing and sets it. */
.tg-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.5fr) auto;
  gap: 0.6rem;
  align-items: end;
}
/* Save is the third control on the row, so it is built from the same padding
   and line box as the .input beside it. A btn-sm here stands 34px against a
   48px field and reads as though it has slipped below the line. */
.tg-row .btn {
  align-self: end;
  min-height: 0;
  /* The extra 1px stands in for the border .input carries and .btn does not,
     which is the whole of the remaining height difference. */
  padding: calc(0.65rem + 1px) 1.1rem;
  font-size: 16px;
  line-height: 1.6;
}
@media (max-width: 900px) {
  .tg-row { grid-template-columns: minmax(0, 1fr); }
  .tg-row .btn { justify-self: start; }
}

.tg-switch {
  display: inline-flex; align-items: center; gap: 0.45rem;
  margin-left: auto;
  font-family: var(--font-body); font-weight: 700; font-size: 0.72rem;
  letter-spacing: 0.02em;
  color: var(--paper-dim);
  cursor: pointer;
  user-select: none;
}
/* The real checkbox stays in the layout for the keyboard and for assistive
   tech; it is the track and knob that are drawn. */
.tg-switch input {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%);
}
.tg-track {
  position: relative;
  width: 32px; height: 18px;
  border-radius: var(--radius-pill);
  background: rgba(10, 8, 19, 0.7);
  border: 1px solid var(--glass-line);
  transition: background 0.18s ease, border-color 0.18s ease;
}
.tg-knob {
  position: absolute; top: 2px; left: 2px;
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--paper-dim);
  transition: transform 0.18s ease, background 0.18s ease;
}
.tg-switch:has(input:checked) { color: var(--paper); }
.tg-switch:has(input:checked) .tg-track {
  background: rgba(52, 211, 153, 0.28);
  border-color: rgba(52, 211, 153, 0.6);
}
.tg-switch:has(input:checked) .tg-knob { transform: translateX(14px); background: #34d399; }
/* Focus lands on the hidden input, so the ring has to be drawn on the track. */
.tg-switch:has(input:focus-visible) .tg-track {
  outline: 2px solid var(--sky);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .tg-track, .tg-knob { transition: none; }
}

/* The offsite panel spans the page, so four credential fields fit across it in
   one row. Four fixed tracks rather than auto-fit: auto-fit sizes tracks from
   the container, not from the content, so a wide panel would hand out five or
   six tracks and leave the row of four hanging short of the right edge. */
#offsite .offsite-pane {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  align-items: start;
}
@media (max-width: 1150px) {
  #offsite .offsite-pane { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  #offsite .offsite-pane { grid-template-columns: minmax(0, 1fr); }
}
/* The key textarea, the redirect URI and the connection line want the full
   width regardless. */
#offsite .offsite-pane > .field:has(textarea),
#offsite .offsite-pane > .bk-last,
#offsite .offsite-redirect { grid-column: 1 / -1; }
/* A remote path runs longer than a hostname, and it is alone on its row, so it
   takes two of the four tracks instead of sitting in a lonely quarter. */
#offsite .offsite-pane > .offsite-wide { grid-column: span 2; }

#offsite .form { display: grid; gap: 0.85rem; }

/* Title and destination picker share the header row: the picker decides what
   the rest of the panel shows, so it reads as part of the heading rather than
   as the first of a dozen fields. */
.offsite-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--glass-line);
}
.offsite-head-text { min-width: 0; flex: 1 1 22rem; }
.offsite-where { width: min(18rem, 100%); flex-shrink: 0; }
/* The two toggles share a row and split it evenly. */
.offsite-toggles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 0.6rem;
}
/* The checkbox is the label's first flex child, so it must not be stretched to
   the height of a two-line text block beside it. */
.offsite-toggles .choice { align-items: center; }
.offsite-toggles .choice input[type="checkbox"] { flex-shrink: 0; }

/* --------------------------------------------------------------------------
   73. Telegram invite links | preview
   -------------------------------------------------------------------------- */
.render-invite-cta { margin: 0.55rem 0 0; }

/* Wider than the event modal: the whole content is lines of URL, and wrapping
   an invite address in the middle makes it unreadable. */
.invite-modal .ev-modal-inner { max-width: min(46rem, 94vw); }

.invite-lead {
  margin: 0 0 0.9rem;
  font-size: 0.82rem;
  color: var(--paper-dim);
}

/* Drawn as the chat bubble it becomes, so the preview answers what people will
   see rather than what is in the database. */
.invite-bubble {
  padding: 0.85rem 1rem;
  border-radius: var(--radius);
  background: rgba(10, 8, 19, 0.55);
  border: 1px solid var(--glass-line);
  border-left: 3px solid var(--sky);
}
.invite-bubble-lead {
  margin: 0 0 0.7rem;
  font-weight: 700;
  font-size: 0.88rem;
}

.invite-list {
  margin: 0; padding: 0;
  list-style: none;
  display: grid; gap: 0.3rem;
  font-size: 0.82rem;
  line-height: 1.45;
}
/* A long invite URL breaks anywhere rather than pushing the dialog sideways. */
.invite-list li { overflow-wrap: anywhere; }

.invite-count {
  margin: 0.7rem 0 0;
  font-size: 0.78rem;
  color: var(--paper-dim);
}
.invite-missing { margin: 0.9rem 0 0; }

/* --------------------------------------------------------------------------
   74. Two-step verification
   -------------------------------------------------------------------------- */

/* --- the second stage of sign-in --- */
.auth-lead {
  margin: 0 0 1rem;
  font-size: 0.88rem;
  line-height: 1.55;
  color: var(--paper-dim);
}
.auth-lead strong { color: var(--paper); }

/* A six-digit code is read off a screen and typed back. Wide tracking and a
   monospace face make a transposed pair obvious before it is submitted. */
.auth-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 1.35rem;
  letter-spacing: 0.28em;
  text-align: center;
}
.auth-code::placeholder { letter-spacing: 0.28em; opacity: 0.35; }

.auth-restart { margin: 0.35rem 0 0; text-align: center; }

/* A button that has to read as a link: "start over" is a POST, because it
   clears session state, but it is not an action worth a button's weight. */
.btn-link {
  border: 0; background: none; padding: 0;
  font: inherit; font-size: 0.82rem;
  color: var(--sky);
  cursor: pointer;
}
.btn-link:hover { text-decoration: underline; }

/* --- the panel on the profile screen --- */
.tfa-state {
  display: flex; align-items: center; gap: 0.6rem;
  flex-wrap: wrap;
  margin: 0 0 0.9rem;
}

/* Enrolment: the QR, then what to do with it.
   Stacked rather than side by side. This panel lives in the narrow right-hand
   column, and a 232px code beside the instructions squeezes them into a strip
   too narrow to read. */
.tfa-enrol {
  display: grid;
  gap: 0.9rem;
  justify-items: start;
  margin: 0 0 1rem;
}

/* Scanners want dark-on-light. The site is dark, so the code gets its own
   white plate rather than being inverted and hoping the phone copes. */
.tfa-qr {
  padding: 0.6rem;
  border-radius: var(--radius);
  background: #fff;
  line-height: 0;
}
.tfa-qr svg { display: block; width: 232px; height: 232px; }

.tfa-steps { margin: 0; padding-left: 1.1rem; font-size: 0.82rem; line-height: 1.6; }
.tfa-steps li + li { margin-top: 0.35rem; }

/* The typed-in fallback for a phone that will not scan. */
.tfa-key {
  display: block;
  margin-top: 0.45rem;
  padding: 0.45rem 0.6rem;
  border-radius: var(--radius-sm);
  background: rgba(6, 4, 12, 0.7);
  border: 1px solid var(--glass-line);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.82rem;
  letter-spacing: 0.06em;
  overflow-wrap: anywhere;
}

/* Recovery codes, shown exactly once. */
.tfa-codes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.4rem;
  margin: 0.8rem 0;
  padding: 0.8rem;
  border-radius: var(--radius);
  background: rgba(6, 4, 12, 0.7);
  border: 1px solid var(--glass-line);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.88rem;
  letter-spacing: 0.08em;
  list-style: none;
}
.tfa-codes li { text-align: center; }

.tfa-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; }
.tfa-sep { margin: 1.1rem 0 0.9rem; border: 0; border-top: 1px solid var(--glass-line); }

/* The 2FA reset in a user row: the rarest of that row's actions, so it takes
   the least width. Icon plus a three-character label, tightened up, which is
   what keeps a row with four buttons on one line. */
.btn-2fa {
  padding-left: 0.55rem;
  padding-right: 0.6rem;
  gap: 0.3rem;
  letter-spacing: 0.02em;
}

/* Event Notes in the modal: free-form prose among a list of short values, so
   it keeps the line breaks it was written with instead of running together. */
.ev-modal-facts .fact-notes dd,
.fact-notes dd {
  white-space: pre-line;
  line-height: 1.55;
  overflow-wrap: anywhere;
}

/* Event Notes on the review screen: prose in a list of one-line values, so it
   keeps its own line breaks rather than collapsing into a paragraph. */
.ev-public-notes {
  white-space: pre-line;
  line-height: 1.55;
  overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------------
   75. Footer | staff entrance
   -------------------------------------------------------------------------- */

/* The credits keep their own centred layout; the button sits beside them on
   the right. Wrapping rather than shrinking, so on a narrow screen it drops
   under the credits instead of squeezing the artwork. */
/* Bottom-aligned, because the credits text block is: the fursona art is taller
   than the text beside it and hangs past the footer's edge, which leaves the
   text flush with the bottom. Aligning to that edge is what gives the button a
   fixed relationship to the credit lines. */
.site-footer {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: clamp(1rem, 4vw, 2.5rem);
  flex-wrap: wrap;
}
.site-footer .credits { flex: 0 1 auto; }

/* Quiet on purpose. It is a door for a handful of organizers, not a call to
   action competing with Apply up in the navigation.

   The margin lifts it to sit level with the designer's name | the large second
   credit line, centre to centre. Measured from the bottom rather than the top:
   the text block is bottom-aligned to the footer, so this offset is the height
   of the line below it plus the button's own. Add or remove a credit line and
   this wants adjusting; it is the one number here tied to that block. */
.footer-admin {
  flex-shrink: 0;
  margin-bottom: 2.875rem;
  color: var(--paper-faint);
  border-color: var(--glass-line);
}
.footer-admin:hover { color: var(--paper); border-color: var(--glass-line-hi); }

/* Stacked under the credits on a narrow screen, where lining up with a
   particular line no longer means anything. */
@media (max-width: 480px) {
  .footer-admin { margin: 0 0 1.5rem; }
}

/* An inherited furmeet icon in the event form. Shown at reduced strength so it
   reads as the default in force rather than as a picture this event owns |
   the label beside it says whose icon it is. */
.dropzone.is-inherited .dropzone-preview { opacity: 0.72; }
.dropzone.is-inherited { border-style: dashed; }

/* --------------------------------------------------------------------------
   76. Event Updates | editing
   -------------------------------------------------------------------------- */
.upd-edited {
  margin: 0.4rem 0 0;
  font-size: 0.72rem;
  font-style: italic;
  color: var(--paper-faint);
}

.upd-edit { margin: 0.6rem 0 0; }
.upd-edit .textarea { min-height: 5.5rem; }
.upd-edit-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }

/* Delete on an Event Update card.

   The card is a two-column grid | content on the left, the approve/deny panel
   on the right | so the header's right edge is nowhere near the card's. Sitting
   at the end of the header, the button landed in the middle of the card looking
   adrift. Taking it out of flow puts it in the actual corner.

   Inset from both edges rather than flush: hard against the corner it reads as
   though it has slipped out of the layout rather than been placed. */
.ucard { position: relative; }

.ucard-delete {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  z-index: 2;
}

/* Out of flow means nothing else knows it is there, so both things that pass
   underneath it are given room deliberately. */
.ucard .acard-head { padding-right: 5.5rem; }

@media (min-width: 1100px) {
  /* Two-column layout: the corner belongs to the decision panel, whose first
     field would otherwise run under the button. */
  .ucard .acard-decide { padding-top: 2.9rem; }
  .ucard .acard-head   { padding-right: 1.1rem; }
}

/* --------------------------------------------------------------------------
   77. Site Health | per-check actions and their modals
   -------------------------------------------------------------------------- */

/* A check that can do something about itself, rather than only describe the
   problem. Sits under the detail line at the same size as the fix text, so it
   reads as part of the check rather than as a control bolted beside it. */
.check-actions {
  display: block;
  margin-top: 0.25rem;
  font-size: 0.74rem;
}
.check-actions-sep { color: var(--paper-faint); margin: 0 0.35rem; }
.btn-link-danger { color: #FFB3BA; }
.btn-link-danger:hover { color: #fff; }

.health-modal .ev-modal-inner { max-width: min(44rem, 94vw); }
.health-modal-lead {
  margin: 0 0 0.9rem;
  font-size: 0.84rem;
  line-height: 1.55;
  color: var(--paper-dim);
}

/* A long orphan list scrolls inside the dialog rather than growing it past the
   viewport, which would put the Close button off screen. */
.health-modal-scroll {
  max-height: 46vh;
  overflow: auto;
  border: 1px solid var(--glass-line);
  border-radius: var(--radius);
}

.health-table { width: 100%; border-collapse: collapse; font-size: 0.78rem; }
.health-table th {
  position: sticky; top: 0;
  background: var(--panel, #17122b);
  text-align: left;
  font-size: 0.68rem; font-weight: 900;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--paper-faint);
  padding: 0.5rem 0.6rem;
  border-bottom: 1px solid var(--glass-line);
}
.health-table td {
  padding: 0.42rem 0.6rem;
  border-bottom: 1px solid var(--glass-line);
  vertical-align: top;
}
.health-table tr:last-child td { border-bottom: 0; }
/* Generated filenames are long and have no spaces; let them wrap rather than
   push the table sideways. */
.health-table-path {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.74rem;
  overflow-wrap: anywhere;
}

.health-modal-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.45rem; }
.health-modal-list li { display: flex; align-items: center; gap: 0.6rem; }

/* --------------------------------------------------------------------------
   78. Pager | pages of a long list
   -------------------------------------------------------------------------- */

/* The gap above is the point: butted against the last card the row reads as
   part of that person's record rather than as navigation for the list. */
.pager {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--glass-line);
}

/* Which range you are looking at, not just which page | "9-16 of 20" answers
   the question a page number only half answers. */
.pager-status {
  margin: 0 0.5rem;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--paper-dim);
  white-space: nowrap;
}

/* At the ends the control stays in place and stops working, rather than
   vanishing and shifting everything beside it. */
.pager .is-disabled {
  opacity: 0.35;
  pointer-events: none;
}

@media (max-width: 560px) {
  /* Four buttons and a range do not fit a phone; the ends are the ones to
     lose, since Previous and Next still reach them. */
  .pager .btn:first-child,
  .pager .btn:last-child { display: none; }
}

/* --------------------------------------------------------------------------
   79. Cards that open a modal
   -------------------------------------------------------------------------- */

/* The "stretched trigger": one control inside the card grows an invisible layer
   over the whole card, so the card is clickable without being wrapped in an
   anchor | which would swallow the map link, the website link and the tag pills
   inside it, and is invalid markup besides. */
.ev-card.is-openable,
.manager-card { position: relative; }

/* z-index 1, not 0, and that distinction matters.
   An un-layered pseudo-element only paints above content that comes BEFORE it
   in the document, so at z-index 0 the trigger covered the avatar above it but
   not the furmeet chips below | leaving the bottom third of the card dead to
   the click. Lifting it out of document order covers the whole card. */
.ev-open::after,
.manager-open::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* Anything genuinely interactive sits above that layer and keeps working.
   Without this the stretched trigger eats the map and website links. */
.ev-card.is-openable a:not(.ev-open),
.ev-card.is-openable button:not(.ev-open),
.manager-card a,
.manager-card button:not(.manager-open) { position: relative; z-index: 2; }

/* The trigger inherits the heading it replaces, so nothing moves. */
.ev-open {
  color: inherit;
  text-decoration: none;
}
.ev-card.is-openable:hover .ev-open { text-decoration: underline; }

/* appearance:none as well as background:none. A <button> carries a platform
   look that background alone does not fully remove, and when this rule was
   missing entirely | a stale cached stylesheet | the names rendered as white
   boxes with white text inside them, which is how this was first noticed. */
.manager-open {
  appearance: none; -webkit-appearance: none;
  border: 0; background: none; padding: 0;
  font: inherit; color: inherit;
  text-align: left;
  cursor: pointer;
}
.manager-card:hover .manager-open { text-decoration: underline; }

/* A pointer over the whole card, so it looks like what it is. */
.ev-card.is-openable,
.manager-card { cursor: pointer; }

/* Focus has to show on the card, not on a zero-height stretched pseudo-element,
   or keyboard users get no visible target at all. */
.ev-card.is-openable:focus-within,
.manager-card:focus-within { outline: 2px solid var(--sky); outline-offset: 2px; }
.ev-open:focus-visible,
.manager-open:focus-visible { outline: none; }

/* --------------------------------------------------------------------------
   80. Organizer profile modal
   -------------------------------------------------------------------------- */
/* Wider than the event modal, and shorter for it. Stacked, a profile ran the
   height of the screen; the two columns keep it about as tall as the picture. */
.organizer-modal .ev-modal-inner { max-width: min(44rem, 94vw); }

.organizer-layout {
  display: grid;
  grid-template-columns: 128px minmax(0, 1fr);
  gap: 1.2rem;
  align-items: start;
}
.organizer-media { min-width: 0; }
.organizer-info  { min-width: 0; }

.organizer-avatar {
  width: 128px; height: 128px;
  border-radius: 16px;
  object-fit: cover;
  border: 2px solid rgba(255, 255, 255, 0.75);
}

/* Below this the two columns are narrower than the picture is useful at, so it
   goes back to stacking | with the picture smaller, since it is no longer
   holding up a column. */
@media (max-width: 560px) {
  .organizer-layout { grid-template-columns: minmax(0, 1fr); gap: 0.9rem; }
  .organizer-avatar { width: 84px; height: 84px; }
}

/* The label the name is given, small and quiet above it. */
.organizer-label {
  margin: 0;
  font-size: 0.66rem; font-weight: 900;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--paper-faint);
}
.organizer-modal .ev-modal-title { margin: 0.1rem 0 0; }
.organizer-pronouns {
  margin: 0.15rem 0 0;
  font-size: 0.82rem;
  color: var(--paper-dim);
}

/* The whole bio, keeping its own line breaks | the card outside clamps to
   three lines, which is the reason this modal exists. */
.organizer-bio {
  margin: 0.6rem 0 0.9rem;
  font-size: 0.88rem;
  line-height: 1.6;
  color: var(--paper-dim);
  overflow-wrap: anywhere;
}

/* The actions row carries a top border, and with the chips ending exactly at
   its edge that line landed hard against the bottom of the last pill. The gap
   is on the modal's actions rather than on .manager-meets, which is shared with
   the cards outside and does not want the extra space. */
.organizer-modal .ev-modal-actions { margin-top: 1.35rem; }

.organizer-meets-label {
  margin: 0 0 0.4rem;
  font-size: 0.66rem; font-weight: 900;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--paper-faint);
}

/* --------------------------------------------------------------------------
   81. Terms of Service
   -------------------------------------------------------------------------- */

/* Terms on the left, an index of its sections on the right.
   Legal text is read rather than skimmed, so the prose keeps a readable measure
   instead of running the full width of the panel | but capping it alone left
   well over half the page empty. The index fills that with the thing a reader
   of terms actually wants: a way to jump to the clause they came for. */
/* Terms on the left, an index of its sections on the right, the pair centred.

   Two things had to be true at once. Legal text has to keep a readable line
   length, so the prose cannot simply stretch to fill a 1200px panel | at that
   width a line runs to about 120 characters and the eye loses its place
   returning to the left margin. But a capped column inside a full-width grid
   does not remove the empty space, it relocates it: text stopped at 72ch, the
   index sat hard right, and a few hundred pixels of nothing opened between
   them.

   Fixed columns plus justify-content: center splits whatever is left over
   evenly on both sides, so it reads as page margin rather than a hole. The
   panel itself keeps the site's width, so this page still lines up with the
   header and with every other page. */
.terms-layout {
  display: grid;
  grid-template-columns: minmax(0, 82ch) 16rem;
  justify-content: center;
  gap: clamp(1.5rem, 3vw, 2.5rem);
  align-items: start;
}
.terms-layout.is-single {
  grid-template-columns: minmax(0, 82ch);
}

/* The column is already the measure; a second cap inside it is what produced
   the gap in the first place. */
.terms-body { max-width: none; }

/* Follows the reader down a long document rather than scrolling away at the
   top of it. */
.terms-index {
  position: sticky;
  top: 1.25rem;
  padding: 0.9rem 1rem;
  border-radius: var(--radius);
  background: rgba(10, 8, 19, 0.45);
  border: 1px solid var(--glass-line);
}
.terms-index-label {
  margin: 0 0 0.5rem;
  font-size: 0.66rem; font-weight: 900;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--paper-faint);
}
.terms-index-list {
  margin: 0; padding: 0;
  list-style: none;
  counter-reset: terms-section;
  display: grid; gap: 0.3rem;
  font-size: 0.78rem;
  line-height: 1.4;
}
.terms-index-list a {
  color: var(--paper-dim);
  text-decoration: none;
  display: block;
}
.terms-index-list a:hover { color: var(--paper); text-decoration: underline; }

/* An anchored heading must not land under the sticky index or the top of the
   window. */
.terms-body h3 { scroll-margin-top: 1.5rem; }

@media (max-width: 900px) {
  /* Below this the two columns are both too narrow to be worth having. The
     index goes above the text, where it still works as a table of contents. */
  .terms-layout { grid-template-columns: minmax(0, 1fr); }
  .terms-index   { position: static; order: -1; }
  .terms-body    { max-width: none; }
}

.terms-updated {
  margin: -0.4rem 0 1.4rem;
  font-size: 0.78rem;
  color: var(--paper-faint);
}

/* Sections need to be findable at a glance in a document this long. */
.terms-body h3 {
  margin: 1.6rem 0 0.5rem;
  font-family: var(--font-display);
  font-size: 1.02rem;
  font-weight: 800;
  color: var(--paper);
}
.terms-body h3:first-child { margin-top: 0; }
.terms-body p  { margin: 0 0 0.8rem; line-height: 1.65; }
.terms-body ul { margin: 0 0 0.9rem; padding-left: 1.2rem; line-height: 1.6; }
.terms-body li { margin-bottom: 0.3rem; }
.terms-body strong { color: var(--paper); }

.terms-back { margin: 1.75rem 0 0; }

/* The editing surface gets the same measure, so what an Admin types looks like
   what a reader will see rather than reflowing on save. */
.terms-surface { min-height: 26rem; }

/* Beside the staff entrance, styled to match it | neither is the reason anyone
   came to the page. */
.footer-terms {
  flex-shrink: 0;
  margin-bottom: 2.875rem;
  color: var(--paper-faint);
  border-color: var(--glass-line);
}
.footer-terms:hover { color: var(--paper); border-color: var(--glass-line-hi); }

@media (max-width: 480px) {
  .footer-terms { margin: 0 0 1.5rem; }
}

/* The two footer buttons as one group. Half the footer's own gap sits between
   them | that larger gap is meant to separate them from the credits, and using
   it between the pair made them look unrelated to each other. */
.footer-links {
  display: flex;
  align-items: center;
  /* Half the footer's own gap, which resolves to 2.5rem at desktop widths. */
  gap: 1.25rem;
  flex-shrink: 0;
  margin-bottom: 2.875rem;
}
/* The offset now belongs to the group, not to each button. */
.footer-admin,
.footer-terms { margin-bottom: 0; }

@media (max-width: 480px) {
  .footer-links { margin: 0 0 1.5rem; }
}

/* The invite preview: the name is the link, as it will be in the channel. */
.invite-list li { display: grid; gap: 0.1rem; }
.invite-link {
  color: var(--sky);
  font-weight: 700;
  text-decoration: none;
  justify-self: start;
}
.invite-link:hover { text-decoration: underline; }

/* Where that name points. Not part of the message | it is here so the address
   can be checked before the message goes out, which is the one thing the
   rendered form no longer shows. */
.invite-target {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.68rem;
  color: var(--paper-faint);
  overflow-wrap: anywhere;
}
