:root {
  --color-bg: #fdfcfa;
  --color-surface: #ffffff;
  --color-text: #1b1b1b;
  --color-muted: #6b6b6b;
  --color-border: #e2e0da;
  --color-accent: #6b3fa0;
  --color-accent-contrast: #ffffff;
  /* Match the status colors already used inline in checkin.html's JS and
     the admin .error class, rather than introducing a second palette. */
  --color-danger: #b3261e;
  --color-warn: #b06000;
  --color-success: #137333;
  --radius: 10px;
  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 1.5rem;
  --nav-height: 3.25rem;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--color-bg);
  color: var(--color-text);
  line-height: 1.5;
}

.page {
  max-width: 900px;
  margin: 0 auto;
  padding: var(--space-2);
}

h1 { font-size: 1.5rem; margin: 0 0 var(--space-2); }
h2 { font-size: 1.15rem; margin: var(--space-3) 0 var(--space-1); }

a { color: var(--color-accent); }

button, input[type="submit"] {
  font: inherit;
  padding: 0.6rem 1rem;
  border-radius: var(--radius);
  border: 1px solid var(--color-accent);
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  cursor: pointer;
}
button:hover, input[type="submit"]:hover { opacity: 0.9; }
button:disabled { opacity: 0.5; cursor: default; }

input, select, textarea {
  font: inherit;
  padding: 0.5rem;
  border-radius: var(--radius);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
}
/* An unstyled <textarea> defaults to ~2 rows x 20 cols -- fine on a desktop
   form, cramped on a phone where there's no cols/rows-driven width to speak
   of and every field stacks full-width anyway. box-sizing so the 100% width
   includes the textarea's own padding/border rather than overflowing it. */
textarea {
  display: block;
  box-sizing: border-box;
  width: 100%;
  min-height: 6rem;
  resize: vertical;
}

table {
  width: 100%;
  border-collapse: collapse;
}
th, td {
  text-align: left;
  padding: 0.5rem;
  border-bottom: 1px solid var(--color-border);
  font-size: 0.95rem;
  white-space: nowrap;
}

/* The Manage Roster page's years-ridden field -- unstyled, it defaulted to
   a browser text input's native ~20-character width, truncating a veteran
   rider's full year list. .table-scroll already lets the table itself
   scroll horizontally on a narrow screen, so there's no need to cap this
   at viewport width. */
.roster-years-input { width: 18rem; }

/* Wrap a <table> in <div class="table-scroll"> so it scrolls horizontally
   on a phone instead of forcing the whole page wider than the viewport. */
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.error { color: var(--color-danger); }

/* ---- Admin nav: pure-CSS hamburger via the checkbox toggle hack.
   No JS, no build step -- consistent with how the rest of this app
   avoids dependencies (see the vendored QR scanner). One grouped
   dropdown at every width: it carries the clown-facing pages and the
   admin-only sections together, too many links for an inline bar. ---- */
.site-nav {
  /* Purple, matching .public-nav's hero-less treatment -- same strip
     look across both the clown/admin side and the public side. */
  background: var(--color-accent);
}
.site-nav-bar {
  max-width: 900px;
  margin: 0 auto;
  padding: 0 var(--space-2);
  min-height: var(--nav-height);
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: relative;
}
.site-nav-brand-group { display: flex; align-items: center; gap: 0.5rem; }
.brand {
  font-weight: 600;
  color: #fff;
  text-decoration: none;
}
/* Shown to admins in both modes -- confirming "safely in test" matters as
   much as being warned "this is live". Live stays loud (real money is
   flowing); test stays deliberately muted so it doesn't compete with it.
   A white chip behind each, rather than relying on the colored text/border
   alone, keeps them legible regardless of the nav's own background. */
.stripe-mode-badge {
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  border-radius: var(--radius);
  padding: 0.1rem 0.4rem;
  background: var(--color-surface);
}
.stripe-mode-badge--live {
  color: var(--color-danger);
  border: 1px solid var(--color-danger);
}
.stripe-mode-badge--test {
  color: var(--color-muted);
  border: 1px solid var(--color-border);
}
.nav-toggle-checkbox { display: none; }
.nav-toggle-label {
  display: block;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  padding: 0.5rem;
  user-select: none;
}
/* White hamburger to read against .site-nav's purple, same as .public-nav's
   own scoped override below. */
.site-nav .nav-toggle-label { color: #fff; }
.nav-links {
  display: none;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  position: absolute;
  top: 100%;
  right: var(--space-2);
  min-width: 15rem;
  max-width: calc(100vw - 2 * var(--space-2));
  max-height: calc(100vh - var(--nav-height));
  overflow-y: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
  padding: var(--space-1) 0;
  z-index: 20;
}
.nav-toggle-checkbox:checked ~ .nav-links { display: flex; }
.nav-links a {
  color: var(--color-text);
  text-decoration: none;
  padding: 0.6rem var(--space-2);
}
.nav-links a:hover, .nav-links a:focus {
  color: var(--color-accent);
  background: var(--color-bg);
}
.nav-group-label {
  margin: 0;
  padding: 0.85rem var(--space-2) 0.2rem;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-muted);
}
.nav-group-label:first-child { padding-top: 0.2rem; }
/* Marks a nav link (or a whole group's label) a plain clown never sees. */
.nav-admin-tag {
  display: inline-block;
  margin-left: 0.35rem;
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  background: var(--color-warn);
  color: var(--color-accent-contrast);
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  vertical-align: middle;
}

/* ---- Public nav: no bar of its own by default -- just a hamburger in the
   top-right at every width. On pages with a banner (body.has-hero) it floats
   over the top of it. Reuses the .nav-toggle-* / .nav-links mechanics.
   nav_brand (base.html) is an empty block most pages leave unset; the few
   that override it (FAQ, Past Beneficiaries) get a wordmark on the left,
   the same role .brand plays in the admin nav. ---- */
.public-nav {
  position: relative; /* anchors the dropdown */
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: var(--space-1) var(--space-2);
  /* Overridden below by body.has-hero .public-nav (higher specificity, a
     dark gradient instead) when there's a photo to float over -- this is
     the plain "no hero" case, a solid purple strip instead of blending
     into the page background. */
  background: var(--color-accent);
}
/* hamburger shown at all widths; white to read against the purple/dark-
   gradient backgrounds above -- both the hero and non-hero cases want the
   same color, so it's set once here rather than duplicated per case. */
.public-nav .nav-toggle-label { display: block; color: #fff; }
/* margin-right: auto rather than justify-content: space-between on
   .public-nav itself -- pages that never set nav_brand render nothing here,
   and space-between with only the hamburger left as a flex item would pull
   it away from the right edge, changing the (unrelated) common case. */
.public-nav-brand {
  margin-right: auto;
  color: #fff;
  font-weight: 600;
  text-decoration: none;
}
.public-nav .nav-links {
  display: none;
  position: absolute;
  top: 100%;
  right: var(--space-2);
  min-width: 10rem;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  overflow: hidden;
  padding: 0;          /* the admin .nav-links base adds vertical padding */
  box-shadow: none;
}
.public-nav .nav-links a {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--color-border);
}
.public-nav .nav-links a:last-child { border-bottom: none; }
.public-nav .nav-toggle-checkbox:checked ~ .nav-links { display: flex; }

body.has-hero .public-nav {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  background: linear-gradient(rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0));
}
body.has-hero .public-nav .nav-links {
  /* dark panel so links stay legible when it opens over a photo */
  background: rgba(0, 0, 0, 0.92);
  border-color: rgba(255, 255, 255, 0.15);
}
body.has-hero .public-nav .nav-links a {
  color: #fff;
  border-bottom-color: rgba(255, 255, 255, 0.15);
}

.site-footer {
  text-align: center;
  padding: var(--space-3) var(--space-2);
  color: var(--color-muted);
  font-size: 0.85rem;
}
.site-footer a { color: var(--color-muted); }
.site-footer a::before {
  content: "";
  display: inline-block;
  width: 0.9em;
  height: 0.9em;
  margin-right: 0.4em;
  vertical-align: -0.15em;
  background: url("/static/img/fleur.png") center / contain no-repeat;
  opacity: 0.5;
}

/* --- Krewe embellishments (transparent-PNG silhouettes in /static/img) --- */

/* Centred silhouette ornament before each section that follows the ticket
   block on the public event page, and between Past Beneficiaries entries -- a
   section break. Consecutive dividers rotate through the jester-rider and the
   two broncos so it never repeats back to back. The charity box keeps its own
   spacing so the ornament doesn't land inside its border. */
.event-layout ~ section:not(.event-charity) { margin-top: 0; }
.event-layout ~ section:not(.event-charity)::before,
.beneficiary::before,
.faq-item::before {
  content: "";
  display: block;
  width: 72px;
  height: 44px;
  margin: var(--space-3) auto var(--space-2);
  background-position: center;
  background-repeat: no-repeat;
  background-size: contain;
  opacity: 0.4;
}
.event-layout ~ section:not(.event-charity):nth-of-type(3n+1)::before,
.beneficiary:nth-of-type(3n+1)::before,
.faq-item:nth-of-type(3n+1)::before { background-image: url("/static/img/jester-rider.png"); }
.event-layout ~ section:not(.event-charity):nth-of-type(3n+2)::before,
.beneficiary:nth-of-type(3n+2)::before,
.faq-item:nth-of-type(3n+2)::before { background-image: url("/static/img/bronco.png"); }
.event-layout ~ section:not(.event-charity):nth-of-type(3n)::before,
.beneficiary:nth-of-type(3n)::before,
.faq-item:nth-of-type(3n)::before { background-image: url("/static/img/bronco-alt.png"); }

/* Mascot for sparse "nothing here yet" pages. */
.empty-state-mascot {
  display: block;
  width: min(220px, 55%);
  height: auto;
  margin: var(--space-3) auto var(--space-2);
  opacity: 0.85;
}

/* Featured artwork at the top of Clown HQ. Portrait-oriented (1254x1525) --
   capped by max-height, not width, so it reads as a banner rather than
   swallowing the whole viewport on a phone. */
.clown-hq-banner {
  display: block;
  max-width: 100%;
  max-height: 320px;
  width: auto;
  height: auto;
  margin: 0 auto var(--space-2);
}

/* ---- Event page: hero, header, two-column ticket layout ---- */

.event-banner {
  padding: var(--space-2);
  text-align: center;
  font-weight: 600;
  color: var(--color-accent-contrast);
  white-space: pre-line;
}
.event-banner--notice { background: var(--color-warn); }
.event-banner--urgent { background: var(--color-danger); }

.event-hero {
  display: block;
  width: 100%;
  height: 280px;
  object-fit: cover;
  background-color: var(--color-surface);
}
/* Photos sit at half opacity so the centred transparent-PNG logo reads
   clearly over them; the fallback gradient stays full strength. */
img.event-hero { opacity: 0.5; }
.event-hero--fallback {
  position: relative;
  overflow: hidden;
  background-image: linear-gradient(135deg, var(--color-accent), #2d1b45);
}
/* A faint bronco-rider watermark so a bannerless event still feels like ours.
   The source PNG is a black silhouette; brightness(0) invert(1) turns it white
   without disturbing the transparency. */
.event-hero--fallback::after {
  content: "";
  position: absolute;
  inset: 0;
  background: url("/static/img/bronco.png") center / auto 80% no-repeat;
  filter: brightness(0) invert(1);
  opacity: 0.13;
  pointer-events: none;
}

.event-hero-wrap { position: relative; }
.event-hero-logo {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(240px, 55%);
  height: auto;
  z-index: 2;
  pointer-events: none;
}

/* Every banner in the pool cross-fades on a timed loop, no JS. The images
   are stacked absolutely; each gets its own animation (name/duration/delay,
   set inline per-image) so exactly one is visible (at 0.5 opacity) at a
   time. The keyframe itself is generated per-event in _event_hero.html --
   its percentage offsets depend on how many images are in the pool, which a
   fixed stylesheet rule can't express for an arbitrary-sized pool. */
.event-hero-rotator {
  position: relative;
  width: 100%;
  height: 280px;
  background-color: var(--color-surface);
}
.event-hero-rotator .event-hero {
  position: absolute;
  inset: 0;
  height: 100%;
  opacity: 0;
}
@media (prefers-reduced-motion: reduce) {
  /* !important: the animation these override is set inline per-image
     (see above), which normal stylesheet specificity can't beat. */
  .event-hero-rotator .event-hero { animation: none !important; }
  .event-hero-rotator .event-hero:first-child { opacity: 0.5; }
}

.event-header { position: relative; padding-top: var(--space-2); }
.event-meta {
  color: var(--color-muted);
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.event-layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  margin-top: var(--space-2);
}
/* Rendered richtext output (event/charity/beneficiary descriptions, clown
   bios, FAQ answers) -- app/richtext.py emits its own <p>/<h3-5>/<ul>, so no
   `white-space: pre-line` is needed on these fields anymore. */
.richtext {
  overflow-wrap: anywhere;
  text-align: justify;
  /* Justified text without hyphenation tends to open ugly gaps between
     words, especially in the narrower single-column layout on mobile --
     hyphens: auto lets the browser break long words at the line edge
     instead of stretching the whole line to compensate. Needs the
     <html lang="en"> already set in base.html to know hyphenation rules. */
  hyphens: auto;
}
.richtext > :first-child { margin-top: 0; }
.richtext > :last-child { margin-bottom: 0; }
.richtext h3 { font-size: 1.1rem; margin: var(--space-2) 0 var(--space-1); }
.richtext h4 { font-size: 1.02rem; margin: var(--space-2) 0 var(--space-1); }
.richtext h5 { font-size: 0.95rem; margin: var(--space-2) 0 var(--space-1); }
.richtext ul { margin: 0.3rem 0; padding-left: 1.3rem; }

/* Formatting toolbar above a richtext-input textarea. */
.richtext-toolbar { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-bottom: 0.25rem; }
.richtext-btn { font-size: 0.8rem; font-weight: 600; border: 1px solid var(--color-border); }
.field-hint { margin: 0.25rem 0 0; font-size: 0.8rem; color: var(--color-muted); }

/* Map and Contact are now sub-divs inside one .event-visit section (see
   event.html) rather than their own top-level sections, so their spacing
   comes from .event-visit-grid's gap, not an individual margin-top. */
.event-map iframe {
  display: block;
  width: 100%;
  height: 320px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}
.event-contact p { margin: 0.25rem 0; }
/* A formatted phone number always starts with "(" -- some browsers'
   underline-skip-ink rendering leaves a small detached stroke right before
   a leading open-paren on an underlined link, which reads as a stray period
   at a glance. Not a data/content issue; just removing the underline on
   these specific tel: links (name/email links keep theirs) sidesteps it. */
.event-contact a[href^="tel:"],
.charity-contact a[href^="tel:"] { text-decoration: none; }
.event-visit-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-3); }

.event-perks { text-align: center; }
.perks-list {
  list-style: none;
  padding: 0;
  margin: 0 auto;
  max-width: 32rem;
  display: grid;
  gap: 0.5rem;
  text-align: left;
}
.perks-list li {
  padding-left: 1.5rem;
  position: relative;
}
.perks-list li::before {
  content: "\2713";
  position: absolute;
  left: 0;
  color: var(--color-accent);
  font-weight: 700;
}

.event-gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--space-1);
}
.event-gallery-grid img {
  display: block;
  width: 100%;
  height: 160px;
  object-fit: cover;
  border-radius: var(--radius);
}
.event-gallery-thumb {
  cursor: pointer;
}
.event-gallery-thumb:hover, .event-gallery-thumb:focus-visible {
  outline: 3px solid var(--color-accent);
  outline-offset: -3px;
}

/* Click-to-expand lightbox for the event photo gallery -- a native <dialog>
   (same pattern as the check-in page's confirm dialog). Full-screen and
   holds every gallery photo in one horizontally scroll-snapping track, so
   browsing the set is a swipe/scroll away instead of a close-then-reopen
   per photo. Prev/Next buttons and arrow keys drive the same scroll for
   mouse/keyboard users. */
#gallery-lightbox {
  padding: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  width: 100vw;
  height: 100vh;
  max-width: 100vw;
  max-height: 100vh;
}
#gallery-lightbox::backdrop {
  background: rgba(0, 0, 0, 0.9);
}
#gallery-lightbox-track {
  display: flex;
  width: 100%;
  height: 100%;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
}
.gallery-lightbox-slide {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  scroll-snap-align: center;
}
.gallery-lightbox-slide img {
  /* Sized to fit, not stretched to fill -- so the <img>'s own box matches
     what's actually visible. A click lands on .gallery-lightbox-slide
     itself only when it's genuinely outside the rendered photo. */
  display: block;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
#gallery-lightbox-close,
#gallery-lightbox-prev,
#gallery-lightbox-next {
  position: fixed;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}
#gallery-lightbox-close {
  top: var(--space-2);
  right: var(--space-2);
}
#gallery-lightbox-prev, #gallery-lightbox-next {
  top: 50%;
  transform: translateY(-50%);
}
#gallery-lightbox-prev { left: var(--space-2); }
#gallery-lightbox-next { right: var(--space-2); }

.event-timeline { margin-top: var(--space-3); }
.timeline-list { list-style: none; padding-left: 0; margin: 0; }
.timeline-list > li { margin-bottom: 0.6rem; }
.timeline-list .timeline-time {
  font-variant-numeric: tabular-nums;
  margin-right: 0.15rem;
}
.timeline-list ul {
  margin: 0.15rem 0 0;
  padding-left: 1.1rem;
  color: var(--color-muted);
}

/* Admin: repeatable timeline entry rows. Box/legend styling comes from the
   shared .admin-form-section class (see below) applied alongside this one. */
.timeline-row { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-bottom: 0.4rem; }
.timeline-row input[name="timeline_time"] { flex: 0 0 5rem; }
.timeline-row input[name="timeline_activity"] { flex: 1 1 10rem; }
.timeline-row input[name="timeline_details"] { flex: 2 1 14rem; }
.timeline-remove { flex: 0 0 auto; }

.event-charity {
  margin-top: var(--space-3);
  padding: var(--space-2);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  text-align: center;
}
.event-charity h2 { margin: 0 0 var(--space-1); }
/* Inline-block, not .ticket-cta's display:block -- .event-charity spans the
   full width of .event-layout (not a narrow sidebar like .ticket-card), so
   a block button here would stretch edge-to-edge instead of reading as a
   button. Same visual recipe as .ticket-cta otherwise. */
.donate-cta {
  display: inline-block;
  margin-top: var(--space-1);
  padding: 0.6rem 1.2rem;
  border-radius: var(--radius);
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  text-decoration: none;
  font-weight: 600;
}
.event-charity-logo,
.charity-detail-logo {
  display: block;
  max-width: 220px;
  max-height: 140px;
  width: auto;
  height: auto;
  margin: var(--space-1) auto;
  object-fit: contain;
}
.charity-kicker { color: var(--color-text); margin: 0; }
.charity-overview { margin-bottom: var(--space-3); }
.charity-contact { margin-top: var(--space-2); }
.charity-contact p { margin: 0.25rem 0; }

/* Public "Past Beneficiaries" list -- one section per charity, separated by a
   silhouette ornament (see the rotating .beneficiary::before rules above). */
.beneficiary { margin-top: var(--space-3); }
.beneficiary h2 { margin: 0 0 0.25rem; }

/* Public FAQ: question + answer, always visible. Entries are separated by the
   rotating silhouette ornament (see .faq-item::before above). */
.faq-item {
  padding: var(--space-1) 0;
  overflow-wrap: anywhere;
}
.faq-question {
  margin: 0 0 var(--space-1);
  font-size: 1.1rem;
}

/* Admin FAQ: one card per entry -- editable question/answer, then an action
   row with reorder icons, Save and Delete. */
.faq-admin-list { list-style: none; padding: 0; margin: 0; }
.faq-admin-card {
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
}
.faq-admin-fields label {
  display: block;
  margin-bottom: var(--space-1);
  font-size: 0.9rem;
  color: var(--color-muted);
}
.faq-admin-fields input[type="text"],
.faq-admin-fields textarea {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin-top: 0.2rem;
  color: var(--color-text);
}
.faq-admin-actions {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex-wrap: wrap;
}
.faq-admin-num { color: var(--color-muted); font-size: 0.85rem; }
.faq-admin-spacer { flex: 1 1 auto; }
.faq-admin-new { max-width: 40rem; }

/* Small borderless click target for the reorder arrows. */
.icon-btn {
  background: none;
  border: 1px solid transparent;
  color: var(--color-muted);
  padding: 0.1rem 0.4rem;
  font-size: 1rem;
  line-height: 1;
  border-radius: var(--radius);
  cursor: pointer;
}
.icon-btn:hover:not(:disabled) { color: var(--color-accent); border-color: var(--color-border); }
.icon-btn:disabled { opacity: 0.3; cursor: default; }

.btn-danger {
  background: none;
  color: var(--color-danger);
  border-color: var(--color-danger);
}
.beneficiary-year { color: var(--color-muted); font-weight: 400; }
.beneficiary-amount { font-weight: 600; margin: 0.25rem 0; }
.beneficiary-logo {
  display: block;
  max-width: 200px;
  max-height: 120px;
  width: auto;
  height: auto;
  margin-bottom: var(--space-1);
  object-fit: contain;
}

/* Clickable, swipeable, JS-free image carousel: a scroll-snap track plus
   in-page anchor links (arrows + dots) that smooth-scroll each slide into
   view. Each slide carries an optional caption below the image. */
/* Rendered in the full-width block as a top-of-page banner, like the event
   hero -- edge to edge, same height, square corners. */
.charity-carousel { margin-bottom: var(--space-2); }
.charity-carousel-track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
}
.charity-slide {
  position: relative;
  flex: 0 0 100%;
  scroll-snap-align: start;
  margin: 0;
}
.charity-slide img {
  display: block;
  width: 100%;
  height: 280px;
  object-fit: cover;
  background: var(--color-surface);
}
.charity-slide figcaption {
  padding: var(--space-1) var(--space-2);
  color: var(--color-muted);
  text-align: center;
}
.charity-carousel-arrow {
  position: absolute;
  top: 140px;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 1.5rem;
  line-height: 1;
  text-decoration: none;
}
.charity-carousel-arrow.prev { left: var(--space-1); }
.charity-carousel-arrow.next { right: var(--space-1); }
.charity-carousel-dots {
  display: flex;
  justify-content: center;
  gap: var(--space-1);
  margin-top: var(--space-1);
}
.charity-carousel-dots a {
  color: var(--color-border);
  font-size: 1.4rem;
  line-height: 1;
  text-decoration: none;
}
.charity-carousel-dots a:hover,
.charity-carousel-dots a:focus { color: var(--color-accent); }

.ticket-card,
.register-panel {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-2);
}
.register-panel { max-width: 32rem; margin: var(--space-2) auto 0; }
.ticket-price { font-size: 1.3rem; font-weight: 600; margin: 0 0 var(--space-2); }
.ticket-price span { font-size: 0.85rem; font-weight: normal; color: var(--color-muted); }
.ticket-price-increase { font-size: 0.85rem; color: var(--color-warn); margin: calc(-1 * var(--space-1)) 0 var(--space-2); }
.ticket-card form label,
.register-panel form label { display: block; margin-bottom: var(--space-1); font-size: 0.9rem; }
.ticket-card form input,
.ticket-card form textarea,
.register-panel form input,
.register-panel form textarea { display: block; width: 100%; margin-top: 0.25rem; }
.ticket-card form button,
.register-panel form button { width: 100%; margin-top: var(--space-1); }
/* Real money rides on quantity/donation_dollars: a native number-input
   spinner arrow is an easy accidental tap (mobile) or scroll-while-hovering
   (desktop) away from silently changing either one -- doubling the charge
   on quantity, or adding an unintended donation on an otherwise-untouched,
   empty field. Hiding the spinner removes that vector; the field is still a
   real <input type="number"> (keyboard entry, inputmode="numeric" on mobile
   unaffected), just without the clickable/scrollable arrows. */
.register-panel form input[type="number"]::-webkit-outer-spin-button,
.register-panel form input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.register-panel form input[type="number"] { -moz-appearance: textfield; }

.ticket-cta {
  display: block;
  text-align: center;
  padding: 0.6rem 1rem;
  border-radius: var(--radius);
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  text-decoration: none;
  font-weight: 600;
}
.register-cancel { color: var(--color-muted); }

/* Was mobile-only (hidden here, re-enabled under the max-width query below);
   now always on -- the desktop .ticket-card is only sticky within
   .event-layout, so once the page has scrolled past that (easy, now that
   there's a lot more content below it: perks, charity, timeline, gallery,
   visit-us), there was no way to buy tickets without scrolling back up. This
   bar stays reachable regardless of scroll position, same as it always did
   on mobile -- name kept as "mobile-cta-bar" to avoid an unrelated rename,
   but it's no longer mobile-only. */
.mobile-cta-bar {
  display: flex;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
  padding: var(--space-1) var(--space-2);
  align-items: center;
  justify-content: space-between;
  font-weight: 600;
  z-index: 2;
}
/* A bar with just one child (no price span to balance against, e.g. the
   charity page's donate-only floater) -- space-between would otherwise
   leave it stranded at the left edge. */
.mobile-cta-bar--center { justify-content: center; }
.mobile-cta-link {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  padding: 0.6rem 1.2rem;
  border-radius: var(--radius);
  text-decoration: none;
}
/* Scoped to the event page only (via body.has-mobile-cta), not every page --
   a body-wide selector here would pad the bottom of admin pages, the
   confirmation page, etc. for no reason. */
body.has-mobile-cta { padding-bottom: 4rem; }

@media (min-width: 800px) {
  .event-layout { grid-template-columns: 2fr 1fr; align-items: start; }
  /* No --nav-height offset here: public pages, unlike admin ones, have no
     fixed nav bar above the content to leave room for. */
  .ticket-card { position: sticky; top: var(--space-2); }
  .event-visit-grid { grid-template-columns: 3fr 2fr; align-items: start; }
}

/* ---- Admin: inline per-row image-upload form ---- */
.event-images-form {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: end;
  margin-top: var(--space-1);
}
.event-images-form label {
  font-size: 0.85rem;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}
.event-images-form label:has(input[type="checkbox"]) {
  flex-direction: row;
  align-items: center;
}
/* A textarea field takes the whole row instead of squeezing into whatever
   width it'd get competing with Name/Location/etc. -- otherwise a 100%-wide
   textarea inside an auto-sized flex item just collapses back down. */
.event-images-form label:has(textarea) { flex: 1 1 100%; }
.event-image-preview {
  /* Fixed width AND height (not max-*): a preview thumbnail must render at
     this size regardless of the uploaded image's native resolution. Relying
     on max-width/max-height alone left it sized by the label's flex-stretch
     width in practice, which could be far larger than intended. flex-shrink:0
     stops the compact inline form from squeezing it in a tight row. */
  display: block;
  width: 56px !important;
  height: 56px !important;
  max-width: 56px;
  max-height: 56px;
  flex-shrink: 0;
  object-fit: cover;
  border-radius: var(--radius);
  border: 1px solid var(--color-border);
}

/* ---- Admin: an event's whole management panel, and the titled boxes
   within it (Details' sub-groups, Logo, Banner pool, Timeline) that make
   those sections scannable at a glance instead of one long run-on form. ---- */
.admin-events-list { list-style: none; padding: 0; }
.admin-event-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-2);
  margin-bottom: var(--space-3);
  background: var(--color-surface);
}
.admin-event-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  padding-bottom: var(--space-2);
  margin-bottom: var(--space-2);
  border-bottom: 1px solid var(--color-border);
}
.admin-event-title { font-size: 1.1rem; font-weight: 700; }
.admin-event-meta { color: var(--color-muted); font-size: 0.9rem; }
/* Pushes the nav links + status buttons to the row's right edge on a wide
   screen; wraps below the title on a narrow one, same as everything else
   in this row (flex-wrap, no fixed positioning). */
.admin-event-links { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin-left: auto; }
.admin-event-links a { font-size: 0.85rem; }

/* A titled box (a <fieldset>, so <legend> gets its native straddle-the-
   border look for free) around one logical group of fields -- Basic Info,
   Pricing & Capacity, Contact, Description within Details; Logo; Banner
   pool; Timeline. Nested fieldsets (e.g. the price-increase sub-group)
   reuse the same class at the next level in -- a border is enough to tell
   the levels apart without needing a different shade per depth. */
.admin-form-section {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-2);
  margin-top: var(--space-2);
  background: var(--color-bg);
}
.admin-form-section > legend {
  font-weight: 700;
  font-size: 0.9rem;
  padding: 0 0.4rem;
  color: var(--color-text);
}
/* A form's Save button, after one or more sections -- e.g. Details' 4
   fieldsets, then one submit covering all of them. General sibling (~), not
   adjacent (+): _timeline_fields.html's own <script> tag sits between its
   fieldset and the caller's button, which + wouldn't see past. */
.admin-form-section ~ button[type="submit"] { display: block; margin-top: var(--space-2); }

/* A responsive field grid for a section with several labeled inputs --
   2 columns once there's room, 1 on a phone. A field with a textarea (or a
   nested fieldset, e.g. the price-increase sub-group) always takes the
   full row rather than squeezing into half of it. */
.admin-form-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-1) var(--space-2);
}
.admin-form-grid label {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  font-size: 0.85rem;
}
.admin-form-grid label:has(input[type="checkbox"]) {
  flex-direction: row;
  align-items: center;
}
.admin-form-grid label:has(textarea),
.admin-form-grid > fieldset,
.admin-form-grid > .field-hint {
  grid-column: 1 / -1;
}
@media (min-width: 640px) {
  .admin-form-grid { grid-template-columns: repeat(2, 1fr); }
}

/* Open/Close: state-changing, but secondary to the Save actions inside
   each section -- an outline style keeps them from reading as loud as the
   solid purple Save/Add buttons. */
.btn-secondary {
  background: none;
  color: var(--color-text);
  border-color: var(--color-border);
}
.btn-secondary:hover { background: var(--color-bg); opacity: 1; }

.banner-pool-grid {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-1);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}
.banner-pool-grid li { display: flex; flex-direction: column; align-items: center; gap: 0.2rem; }
.banner-pool-grid button { font-size: 0.75rem; padding: 0.15rem 0.5rem; }

.clown-grid { list-style: none; padding: 0; display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--space-2); }
/* A card is an <a> for an active rider (links to their directory row) or a
   plain <div> for an inactive one (no row there to link to) -- same class,
   same look either way, so the link/no-link split is invisible. */
.clown-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  text-decoration: none;
  color: inherit;
}
.clown-card img { width: 120px; height: 120px; object-fit: cover; border-radius: var(--radius);
  background: var(--color-surface); }
.clown-card-placeholder { object-fit: contain; opacity: 0.4; padding: 1rem; }
.clown-card-name { font-weight: 600; margin-top: 0.4rem; }
.clown-card-tenure { color: var(--color-muted); font-size: 0.85rem; }
.clown-card-milestone { font-size: 0.85rem; }

/* Directory: marks a row as a lieutenant -- rows are already sorted so
   lieutenants come first, this is what says so. */
.lieutenant-badge {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.05rem 0.5rem;
  border-radius: 999px;
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  font-size: 0.72rem;
  font-weight: 600;
  white-space: nowrap;
}
/* On a roster card the badge sits on its own centered line below the name,
   not inline after text -- the directory's left-margin doesn't apply here. */
.clown-card .lieutenant-badge { margin: 0.15rem 0 0; }

/* Landing on a directory row via a roster card's link (#clown-<id>) --
   briefly distinguishes which row it is, since a mid-table anchor jump has
   nothing else to say "you are here". */
.directory-row:target { background: rgba(107, 63, 160, 0.12); }

.krewe-links { padding-left: 1.1rem; }
.krewe-links > li { margin-bottom: var(--space-2); }
.krewe-link-desc { margin: 0.1rem 0 0; color: var(--color-muted); }
.krewe-link-label { font-weight: 600; }  /* a resource with no url -- info, not a link */
/* Admin resources: label + reorder on one row, edit form tucked behind a
   disclosure so a page of links isn't a wall of input fields. */
.krewe-link-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-1);
}
.krewe-link-reorder {
  align-items: center;
  gap: var(--space-1);
  flex: 0 0 auto;
}
.krewe-link-edit { margin-top: 0.35rem; }
.krewe-link-edit > summary {
  display: inline-block;
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--color-accent);
}
.krewe-link-edit[open] > summary { margin-bottom: var(--space-1); }
.krewe-link-edit .faq-admin-fields { max-width: 40rem; }

/* All resource management (reorder, edit, delete, add) stays off until an
   admin opts in via the checkbox -- pure CSS, same toggle mechanic as the
   nav hamburger. Members never see the checkbox or label at all. */
.resources-manage-checkbox { display: none; }
.resources-manage-label {
  display: inline-block;
  cursor: pointer;
  color: var(--color-accent);
  font-size: 0.9rem;
  margin: var(--space-2) 0 var(--space-1);
}
.resources-manage-label-on { display: none; }
.resources-admin-inline, .resources-admin-block { display: none; }
#resources-manage-toggle:checked ~ .resources-body .resources-manage-label-off { display: none; }
#resources-manage-toggle:checked ~ .resources-body .resources-manage-label-on { display: inline-block; }
#resources-manage-toggle:checked ~ .resources-body .resources-admin-inline { display: inline-flex; }
#resources-manage-toggle:checked ~ .resources-body .resources-admin-block { display: block; }
