/* ==========================================================================
   components.css — pieces whose markup this app emits directly, so their
   styles always load rather than riding on the modern layer's kill flag.
   Every token carries a literal fallback.
   ========================================================================== */

/* ---------- FAQ accordion (faq.php) ---------- */

.cs-faq-item {
  border-top: 1px solid var(--cs-line, rgba(15, 15, 25, 0.07));
}
.cs-faq-item:first-child,
.cs-faq-item.is-first { border-top: 0; }
.cs-faq-item[hidden] { display: none; }

.cs-faq-q {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 15px 2px;
  cursor: pointer;
  list-style: none;
  font-size: 15.5px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -0.012em;
  color: var(--cs-ink, #0b0b10);
  -webkit-tap-highlight-color: transparent;
}
/* The default disclosure triangle, in both engines */
.cs-faq-q::-webkit-details-marker { display: none; }
.cs-faq-q::marker { content: ""; }

/* A chevron that turns when the answer opens */
.cs-faq-q::after {
  content: "";
  flex: 0 0 8px;
  width: 8px;
  height: 8px;
  margin: 6px 4px 0 auto;
  border-right: 2px solid var(--cs-faint, #9a9aa5);
  border-bottom: 2px solid var(--cs-faint, #9a9aa5);
  transform: rotate(45deg);
  transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
}
.cs-faq-item[open] > .cs-faq-q::after { transform: rotate(-135deg); margin-top: 9px; }
.cs-faq-item[open] > .cs-faq-q { padding-bottom: 6px; }

.cs-faq-a {
  padding: 0 24px 16px 2px;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--cs-muted, #6b6b76);
}
.cs-faq-a a { text-decoration: underline; text-underline-offset: 2px; }

.cs-faq-empty {
  padding: 22px 2px;
  text-align: center;
  font-size: 14px;
  color: var(--cs-faint, #9a9aa5);
}

body.theme-dark .cs-faq-item { border-top-color: rgba(255, 255, 255, 0.08); }
body.theme-dark .cs-faq-q { color: #f4f5f7; }
body.theme-dark .cs-faq-a { color: #9ba0aa; }

@media (prefers-reduced-motion: reduce) {
  .cs-faq-q::after { transition: none; }
}

/* ==========================================================================
   Reactions (functions_likes.php → likeButtons)

   Every emoji used to be at full colour whether or not you had pressed it, so
   the row told you nothing about what you had done. Only yours keeps its
   colour now; the rest sit back in grey until you look at the counts.
   ========================================================================== */

.cs-reacts {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
}

.cs-react {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  position: relative;
  margin: 0;
  padding: 4px 9px 4px 7px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  font: inherit;
  line-height: 1;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color 0.18s ease, transform 0.12s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.cs-react:hover { background: rgba(15, 15, 25, 0.05); }
.cs-react:active { transform: scale(0.92); }
.cs-react:focus-visible {
  outline: none;
  box-shadow: var(--cs-ring, 0 0 0 4px rgba(47, 111, 237, 0.16));
}

.cs-react-emoji {
  display: block;
  font-size: 17px;
  line-height: 1;
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
  /* Not yours: no colour, and quiet */
  filter: grayscale(1);
  opacity: 0.45;
  transition: filter 0.2s ease, opacity 0.2s ease;
}

.cs-react-n {
  font-size: 12.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--cs-faint, #9a9aa5);
  transition: color 0.2s ease;
}
/* A reaction nobody has used shows no number at all */
.cs-react-n.is-zero { display: none; }

/* ---------- Yours ---------- */

.cs-react.is-mine { background: var(--cs-accent-soft, rgba(47, 111, 237, 0.13)); }
.cs-react.is-mine:hover { background: rgba(47, 111, 237, 0.2); }
.cs-react.is-mine .cs-react-emoji { filter: none; opacity: 1; }
.cs-react.is-mine .cs-react-n { color: var(--cs-accent, #2f6fed); }

/* ---------- The tap ---------- */

@keyframes cs-react-pop {
  0%   { transform: scale(1); }
  30%  { transform: scale(0.82); }
  62%  { transform: scale(1.32); }
  100% { transform: scale(1); }
}
.cs-react-emoji.cs-react-pop { animation: cs-react-pop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); }

/* A ring breaking out from under the emoji as it lands */
.cs-react-ring {
  position: absolute;
  left: 7px;
  top: 50%;
  width: 19px;
  height: 19px;
  margin-top: -9.5px;
  border-radius: 50%;
  border: 2px solid var(--cs-accent, #2f6fed);
  pointer-events: none;
  animation: cs-react-ring 0.55s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
@keyframes cs-react-ring {
  from { transform: scale(0.55); opacity: 0.55; }
  to   { transform: scale(2.4); opacity: 0; }
}

/* ---------- Dark theme ---------- */

body.theme-dark .cs-react:hover { background: rgba(255, 255, 255, 0.07); }
body.theme-dark .cs-react.is-mine { background: rgba(109, 155, 255, 0.18); }
body.theme-dark .cs-react.is-mine .cs-react-n { color: #6d9bff; }
body.theme-dark .cs-react-ring { border-color: #6d9bff; }
body.theme-dark .cs-react-emoji { opacity: 0.35; }
body.theme-dark .cs-react.is-mine .cs-react-emoji { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .cs-react, .cs-react-emoji, .cs-react-n { transition: none; }
  .cs-react:active { transform: none; }
  .cs-react-emoji.cs-react-pop { animation: none; }
  .cs-react-ring { display: none; }
}

/* ==========================================================================
   Achievements on a profile (profile_content.php)

   Someone who has been to ten camps had every achievement from every one of
   them listed in the sheet — a hundred and fifty rows to scroll past to reach
   nothing in particular. Each camp is a group that folds, newest open.
   ========================================================================== */

.cs-ach-groups { margin: 4px 0 14px; }

.cs-ach-groups-head {
  padding-bottom: 8px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--cs-faint, #9a9aa5);
}

.cs-ach-camp { border-top: 1px solid var(--cs-line, rgba(15, 15, 25, 0.07)); }

.cs-ach-camp-name {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 2px;
  cursor: pointer;
  list-style: none;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--cs-ink, #0b0b10);
  -webkit-tap-highlight-color: transparent;
}
.cs-ach-camp-name::-webkit-details-marker { display: none; }
.cs-ach-camp-name::marker { content: ""; }

.cs-ach-camp-title { flex: 1 1 auto; min-width: 0; }

/* How many are in there, so a shut group still tells you something */
.cs-ach-camp-count {
  flex: 0 0 auto;
  min-width: 22px;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--cs-surface-2, #f6f6f8);
  font-size: 12px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: var(--cs-muted, #6b6b76);
}

.cs-ach-camp-name::after {
  content: "";
  flex: 0 0 7px;
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--cs-faint, #9a9aa5);
  border-bottom: 2px solid var(--cs-faint, #9a9aa5);
  transform: rotate(45deg) translate(-1px, -1px);
  transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
}
.cs-ach-camp[open] > .cs-ach-camp-name::after { transform: rotate(-135deg) translate(-2px, -2px); }

.cs-ach-list {
  margin: 0 0 12px;
  padding: 0 2px 0 22px;
  list-style: none;
}
.cs-ach-list li {
  position: relative;
  padding: 5px 0;
  font-size: 14px;
  line-height: 1.45;
  color: var(--cs-ink-2, #3a3a44);
}
/* A tick, drawn rather than an icon font */
.cs-ach-list li::before {
  content: "";
  position: absolute;
  left: -18px;
  top: 11px;
  width: 9px;
  height: 5px;
  border-left: 2px solid var(--cs-green, #16a34a);
  border-bottom: 2px solid var(--cs-green, #16a34a);
  transform: rotate(-45deg);
}

body.theme-dark .cs-ach-camp { border-top-color: rgba(255, 255, 255, 0.08); }
body.theme-dark .cs-ach-camp-name { color: #f4f5f7; }
body.theme-dark .cs-ach-camp-count { background: #2b2f36; color: #9ba0aa; }
body.theme-dark .cs-ach-list li { color: #d5d7dd; }

@media (prefers-reduced-motion: reduce) {
  .cs-ach-camp-name::after { transition: none; }
}

/* A long press on an emoji is asking who reacted, not asking to select text.
   Without this, iOS starts a selection on the glyph and paints the sheet that
   opens underneath it blue, complete with a dragging handle. */
.cs-reacts,
.cs-react,
.cs-react-emoji,
.cs-react-n {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* ---------- Who reacted (#menu-modal-likelist) ---------- */

#menu-modal-likelist-content table {
  margin-bottom: 0;
  --bs-table-accent-bg: transparent;
}
#menu-modal-likelist-content table > tbody > tr > td {
  background: transparent !important;
  box-shadow: none !important;
  border: 0 !important;
  border-top: 1px solid var(--cs-line, rgba(15, 15, 25, 0.07)) !important;
  padding: 9px 2px !important;
  font-size: 14.5px;
}
#menu-modal-likelist-content table > tbody > tr:first-child > td { border-top: 0 !important; }
#menu-modal-likelist-content img.rounded-circle,
#menu-modal-likelist-content img.w3-circle {
  width: 38px !important;
  height: 38px !important;
  object-fit: cover;
}
/* The emoji beside each name is that person's reaction, so it keeps its colour */
#menu-modal-likelist-content .cs-react-emoji {
  display: inline-block;
  font-size: 17px;
  filter: none;
  opacity: 1;
}

body.theme-dark #menu-modal-likelist-content table > tbody > tr > td { border-top-color: rgba(255, 255, 255, 0.08) !important; }

/* ==========================================================================
   The "How was camp?" bubble and open sheets
   ========================================================================== */

/* The bubble is fixed at z-index 999 while the sheets sit at 101, so it floated
   over every one of them — sitting on top of the Sign me up button, the Close
   button and anything else in reach. It steps aside while a sheet is open and
   comes back when the sheet closes. */
/* The hider sits directly under <html>, not inside <body>. */
html:has(> .menu-hider.menu-active) #testimonialBubble {
  opacity: 0;
  pointer-events: none;
  transform: translateY(8px);
}

#testimonialBubble {
  transition: opacity 0.2s ease, transform 0.2s ease;
}

/* ==========================================================================
   "Print the week" — from the mobile schedule to the A4 grid
   ========================================================================== */

/* A quiet link, not a button: printing is something a few people do once, and
   it should not compete with the schedule itself. */
.cs-print-week {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 16px;
  border: 1px solid var(--cs-line-strong, rgba(15, 15, 25, 0.12));
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  color: var(--cs-muted, #6b6b76) !important;
  text-decoration: none !important;
  -webkit-tap-highlight-color: transparent;
}

.cs-print-week::before {
  content: "";
  width: 15px;
  height: 15px;
  flex: 0 0 15px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M6 9V3h12v6M6 18H4v-6h16v6h-2M8 14h8v7H8z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M6 9V3h12v6M6 18H4v-6h16v6h-2M8 14h8v7H8z'/%3E%3C/svg%3E") center / contain no-repeat;
}

.cs-print-week:active { transform: scale(0.98); }

body.theme-dark .cs-print-week {
  border-color: rgba(255, 255, 255, 0.14);
  color: #9da2ad !important;
}

@media print {
  .cs-print-week { display: none !important; }
}

/* ==========================================================================
   How far down the page actually starts
   ========================================================================== */

/* Anything that sticks to the top has to clear the app's fixed bars. The top
   bar is on every page; the ticker is only on some (index, campers, chat,
   stats, introductions), which is why a sticky weekday that cleared 50px was
   right on the schedule and hidden behind the ticker on the front page.
   Asking the page whether it has one keeps the two in step by itself. */
:root { --cs-topbar-h: 50px; --cs-ticker-h: 36px; --cs-stick-top: var(--cs-topbar-h); }
body:has(#site-ticker) { --cs-stick-top: calc(var(--cs-topbar-h) + var(--cs-ticker-h)); }

/* ==========================================================================
   Dark mode repairs

   Found by measuring contrast on every main screen rather than by eye. Each
   rule below fixes something that was legible in light mode and not in dark.
   ========================================================================== */

/* Every <label> carried the page's own background colour (#0f1117). That was
   invisible while cards were the same colour; once cards became lighter than
   the page, each label showed up as a dark box floating on the card — on the
   profile form, the photo upload, the camper cards. A label needs no fill.
   Anything that genuinely wants one sets it on a more specific selector. */
body.theme-dark label { background-color: transparent !important; }
/* The template's own exceptions keep their fill: a label inside a slide-up menu
   sits on a different surface and still needs one. */
body.theme-dark .menu label { background-color: #21252a !important; }

/* The card rule dims small text to grey, which is right for a caption and
   wrong for a badge — "Staff" came out grey on green at 1.3:1. A badge brings
   its own fill, so it keeps its own ink. */
body.theme-dark .card .badge,
body.theme-dark .card .w3-small.badge,
body.theme-dark .badge { color: #fff !important; }

/* Pale fills need dark ink, not white: amber with white text measured 1.6:1. */
/* Not scoped to dark: amber and cyan are light fills in either theme, and white
   on them measures 1.6:1 and 2:1 whichever one is on. */
body .badge.bg-warning,
body .badge.bg-warning *,
body #apple-pay-warning,
body #apple-pay-warning a,
body .card .badge.bg-warning,
body .card .w3-small.bg-warning,
body.theme-dark .card .badge.bg-warning,
body.theme-dark .card .w3-small.bg-warning,
/* the merchandise tiles put the fill on the card itself and ask for white ink */
body .bg-warning.text-white,
body .bg-warning.text-white * { color: #3c2d05 !important; }

/* The `body` prefix is doing work: the template dims every `.w3-small` inside a
   card with an !important rule of the same weight, and a badge usually carries
   that class too. One more step of specificity settles it without another
   !important arms race. */
body .badge.bg-info,
body .badge.bg-info *,
body .card .badge.bg-info,
body .card .w3-small.bg-info,
/* matching the weight of the white-ink rule above, which is deliberately deep */
body.theme-dark .card .badge.bg-info,
body.theme-dark .card .w3-small.bg-info,
body .bg-info.text-white,
body .bg-info.text-white * { color: #063b47 !important; }

/* `.bg-light` is the one that flips: its fill goes dark in dark mode (below),
   so its ink has to go light with it. Left in the group above it ended up dark
   ink on a dark fill — worse than the white-on-white it replaced. */
body.theme-dark .badge.bg-light,
body.theme-dark .card .badge.bg-light,
body.theme-dark .card .w3-small.bg-light { color: #e6e8ec !important; }

/* Pagination kept its light-mode fill: near-white on near-white, 1.09:1. */
body.theme-dark .page-link {
  background-color: #23262c;
  border-color: rgba(255, 255, 255, 0.1);
  color: #d5d7dd;
}
body.theme-dark .page-link:hover { background-color: #2b2f36; color: #f4f5f7; }
/* The active pill is handled in modern.css, which inverts it against the page. */
body.theme-dark .page-item.disabled .page-link { background-color: #1c1f24; color: #6b6b76; }

/* The plus and minus on the stats tiles were a light-mode grey chip. */
body.theme-dark .btn-plus,
body.theme-dark .btn-minus {
  background-color: #2b2f36 !important;
  color: #e6e8ec !important;
}

/* The drop-zone hint sat at 2.45:1. */
body.theme-dark .dz-hint,
body.theme-dark .dz-message { color: #9da2ad; }

/* The connection banners are white on a light green — 2.1:1 — in both themes.
   They only ever appear for a moment, but that is exactly when it has to be
   readable at a glance. */
.online-message.bg-green-dark { background-color: #157f3c !important; }
.offline-message.bg-red-dark { background-color: #a8102f !important; }

/* Bootstrap's light utility fill, wherever it is still used. */
body.theme-dark .bg-light { background-color: #23262c !important; color: #e6e8ec; }
body.theme-dark .w3-input.bg-light { color: #e6e8ec; }

/* Second pass: these lost on specificity to the card rule that dims small text
   (`.theme-dark .card .w3-small`, which carries !important). Matching its depth
   rather than adding another blanket !important keeps the intent readable. */
/* Amber and cyan fills are light whichever theme is on, so their ink is dark in
   both — white on amber measured 1.6:1 on the payment badge and the merchandise
   tiles. The descendant selector catches the figures inside those tiles, which
   inherit rather than set their own colour. */
body.theme-dark .bg-warning,
body.theme-dark .bg-warning *,
body.theme-dark .card .bg-warning,
body.theme-dark .card .bg-warning *,
body.theme-dark .btn-warning,
body.theme-dark .card .btn-warning { color: #3c2d05 !important; }


/* The quantity steppers on the inventory screen kept a light-mode chip. */
body.theme-dark .qty-step {
  background-color: #2b2f36 !important;
  border-color: rgba(255, 255, 255, 0.12) !important;
  color: #e6e8ec !important;
}

/* Third pass. The card rule that dims small text is `.theme-dark .card
   .w3-small` with !important, and a badge often carries `w3-small` too — so a
   badge on a light fill needs one class more than that rule to be heard. */
/* A form label at 60% of an already dim grey reads at 2.7:1 on a dark card.
   The same utility is fine on light. */
body.theme-dark label.opacity-60,
body.theme-dark label.opacity-50 { opacity: 0.85; }

/* The label under the nav item you are on is the brand red, and on a dark bar
   at 70% it lands at 2.5:1 — the one label in the bar you cannot read is the
   one telling you where you are. A lighter tint of the same red keeps the
   identity and clears 5:1. */
body.theme-dark #footer-bar .active-nav span { color: #ff8892 !important; opacity: 1; }

/* `.opacity-60` carries !important, so matching it takes one too. */
body.theme-dark label.opacity-60 { opacity: 0.85 !important; }
body.theme-dark label.opacity-50 { opacity: 0.8 !important; }

/* ==========================================================================
   The one card with a photo behind it
   ========================================================================== */

/* "Before the camp" is the only card in the app that sets a background image,
   and its heading takes the theme's ink — which in dark mode is near-white,
   over a photo of a sunlit map. A card whose backdrop is a photograph cannot
   borrow its contrast from the theme, so it brings its own: a scrim over the
   image, and light text on top of that, in both themes. */
.cs-photo-card {
  position: relative;
  isolation: isolate;
  background-size: cover;
  background-position: center;
}

.cs-photo-card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  background: linear-gradient(180deg, rgba(8, 8, 14, 0.66), rgba(8, 8, 14, 0.5) 55%, rgba(8, 8, 14, 0.64));
}

.cs-photo-card > .content { position: relative; z-index: 1; }

.cs-photo-card h1,
.cs-photo-card > .content > p { color: #fff !important; }
.cs-photo-card > .content > p { opacity: 0.85; }

/* --- camps.php: a camp that is over ---------------------------------------
   Set by camps.php on the card when today is past the camp's end date. It is a
   signal, not decoration: the list holds every camp the person has ever been
   to, and the ones they can still walk into should read first. Opacity alone
   went muddy on the bright modern surface, so the poster also loses most of
   its colour — which is what "past" looks like anyway.
   -------------------------------------------------------------------------- */

.camp-past img.preload-img {
  opacity: 0.62;
  filter: grayscale(0.6);
}

.camp-past .content {
  opacity: 0.55;
}

/* Touching one brings it back, so it never feels disabled */
.camp-past:hover img.preload-img,
.camp-past:active img.preload-img {
  opacity: 0.85;
  filter: grayscale(0.2);
}
.camp-past:hover .content,
.camp-past:active .content {
  opacity: 0.8;
}

.camp-past img.preload-img,
.camp-past .content {
  transition: opacity 160ms ease, filter 160ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .camp-past img.preload-img,
  .camp-past .content { transition: none; }
}

/* --- Quiz night drop box (index_quiz.inc.php) ------------------------------
   Sits under the camp status strip while a quiz night is still ahead on the
   programme. It shares the page with the story cue, so it deliberately does
   not look like it: the cue is the navy poster card that glows, this is a quiet
   paper-coloured card with one sand mark. Nothing here animates — one moving
   thing per screen, and the cue has earned it.
   -------------------------------------------------------------------------- */

.cs-quizbox {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 13px 14px;
  margin: 0;
  border-radius: var(--cs-r, 14px);
  background: var(--cs-surface-2, #f6f3ec);
  border: 1px solid var(--cs-line, rgba(26, 31, 54, 0.1));
  text-decoration: none !important;
  -webkit-tap-highlight-color: transparent;
}

.cs-quizbox:active { transform: scale(0.994); }

/* The question mark is the whole icon: it says what the card wants without a
   label, and a quiz is the one place a bare "?" is not ambiguous. */
.cs-quizbox-mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: 12px;
  background: #e0b878;
  color: #1a1f36;
  font-family: 'Archivo Black', 'Libre Franklin', system-ui, sans-serif;
  font-size: 22px;
  line-height: 1;
}

.cs-quizbox-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.cs-quizbox-kicker {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--cs-muted, #6b7085);
}

.cs-quizbox-title {
  font-size: 15px;
  font-weight: 700;
  line-height: 1.25;
  color: var(--cs-text, #1a1f36);
}

.cs-quizbox-text {
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--cs-muted, #6b7085);
}

.cs-quizbox-count {
  margin-top: 3px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--cs-faint, #9a9aa5);
}

.cs-quizbox-go {
  flex: none;
  font-size: 18px;
  color: var(--cs-faint, #9a9aa5);
}
.cs-quizbox-go::before { content: '\203A'; }

body.theme-dark .cs-quizbox {
  background: var(--cs-surface-2, #1f2438);
  border-color: var(--cs-line, rgba(248, 240, 216, 0.12));
}
body.theme-dark .cs-quizbox-mark {
  /* the sand block stays — it is the brand colour and it holds contrast on both
     grounds with navy text on it */
  background: #e0b878;
  color: #1a1f36;
}

/* The page's own rules paint anchor text, so the card's colours are restated
   here at the same specificity the rest of this file uses. */
body .cs-quizbox .cs-quizbox-title { color: var(--cs-text, #1a1f36); }
body.theme-dark .cs-quizbox .cs-quizbox-title { color: var(--cs-text, #f4f1ea); }
body .cs-quizbox .cs-quizbox-text,
body .cs-quizbox .cs-quizbox-kicker { color: var(--cs-muted, #6b7085); }

@media (prefers-reduced-motion: reduce) {
  .cs-quizbox:active { transform: none; }
}

/* --- Quiz night: the photo field (quiz_submit.php) -------------------------
   The browser's own file control is a small grey button that reads as nothing,
   and the first week of submissions arrived without a single photo. This is a
   full-width area with the camera on it, and it shows the picture back once one
   is chosen so there is no doubt whether something is attached.
   -------------------------------------------------------------------------- */

.cs-quizphoto {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 14px;
  margin-bottom: 16px;
  border: 1.5px dashed var(--cs-line-strong, rgba(26, 31, 54, 0.22));
  border-radius: var(--cs-r, 14px);
  background: var(--cs-surface-2, #f6f3ec);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.cs-quizphoto.is-filled {
  border-style: solid;
  border-color: var(--cs-line, rgba(26, 31, 54, 0.12));
}

.cs-quizphoto-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 11px;
  background: #e0b878;
  color: #1a1f36;
  font-size: 16px;
}

.cs-quizphoto-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.cs-quizphoto-title {
  font-size: 14px;
  font-weight: 700;
  color: var(--cs-text, #1a1f36);
}

.cs-quizphoto-hint {
  font-size: 12px;
  color: var(--cs-muted, #6b7085);
}

/* The chosen picture, shown back at thumbnail size */
.cs-quizphoto-preview {
  flex: none;
  width: 52px;
  height: 52px;
  object-fit: cover;
  border-radius: 10px;
}

body.theme-dark .cs-quizphoto {
  background: var(--cs-surface-2, #1f2438);
  border-color: var(--cs-line-strong, rgba(248, 240, 216, 0.22));
}
body.theme-dark .cs-quizphoto-title { color: var(--cs-text, #f4f1ea); }

/* --- Quiz night: picking who the fact is about ----------------------------
   People were sending in first names only, which is no use to a host reading
   it out to a room with three Meghans in it. The field searches the camp, and
   a picked camper brings their own photo with them.
   -------------------------------------------------------------------------- */

.cs-quizwho { position: relative; }

.cs-quizwho-list {
  margin-top: 6px;
  border: 1px solid var(--cs-line, rgba(26, 31, 54, 0.12));
  border-radius: var(--cs-r, 14px);
  background: var(--cs-surface, #fff);
  overflow: hidden;
}

.cs-quizwho-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  cursor: pointer;
  border-top: 1px solid var(--cs-line, rgba(26, 31, 54, 0.08));
}
.cs-quizwho-row:first-child { border-top: 0; }
.cs-quizwho-row:active { background: var(--cs-surface-2, #f6f3ec); }

/* The avatar is a label here, not a way into a profile — the whole row is the
   target, and showProfileImage() otherwise puts its own click on the picture. */
.cs-quizwho-avatar { flex: none; line-height: 0; pointer-events: none; }
.cs-quizwho-avatar img { border-radius: 50%; }

.cs-quizwho-name {
  font-size: 14px;
  font-weight: 600;
  color: var(--cs-text, #1a1f36);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cs-quizwho-picked {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: 7px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--cs-success, #1a9d55);
}

body.theme-dark .cs-quizwho-list {
  background: var(--cs-surface, #171b2b);
  border-color: var(--cs-line, rgba(248, 240, 216, 0.14));
}
body.theme-dark .cs-quizwho-name { color: var(--cs-text, #f4f1ea); }

/* --- Camp TV queue strip (photos.php) -------------------------------------
   Admin only, and only while photos are actually waiting for a decision. It
   borrows the drop box's shape so the page keeps one vocabulary, but takes the
   warning tone: this is a queue with something in it, not an invitation.
   -------------------------------------------------------------------------- */

.cs-tvstrip {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 13px 14px;
  border-radius: var(--cs-r, 14px);
  background: var(--cs-surface-2, #f6f3ec);
  border: 1px solid var(--cs-line, rgba(26, 31, 54, 0.1));
  text-decoration: none !important;
  -webkit-tap-highlight-color: transparent;
}

.cs-tvstrip:active { transform: scale(0.994); }

.cs-tvstrip-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: 12px;
  background: #e05038;
  color: #f8f0d8;
  font-size: 17px;
}

.cs-tvstrip-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.cs-tvstrip-title {
  font-size: 15px;
  font-weight: 700;
  line-height: 1.25;
  color: var(--cs-text, #1a1f36);
}

.cs-tvstrip-text {
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--cs-muted, #6b7085);
}

.cs-tvstrip-go {
  flex: none;
  font-size: 18px;
  color: var(--cs-faint, #9a9aa5);
}
.cs-tvstrip-go::before { content: '\203A'; }

body.theme-dark .cs-tvstrip {
  background: var(--cs-surface-2, #1f2438);
  border-color: var(--cs-line, rgba(248, 240, 216, 0.12));
}
body.theme-dark .cs-tvstrip .cs-tvstrip-title { color: var(--cs-text, #f4f1ea); }
body .cs-tvstrip .cs-tvstrip-text { color: var(--cs-muted, #6b7085); }

@media (prefers-reduced-motion: reduce) {
  .cs-tvstrip:active { transform: none; }
}

/* --- Camp on Instagram: the one-off follow card (index_instagram.inc.php) ---
   Built like the quiz box next door, with the brand's sand as the one bit of
   colour, because this is the app asking a favour rather than telling anyone
   anything. Both answers are real buttons: "I already do" has to be as easy to
   hit as the follow link, or the card keeps asking people who said no. */
/* No ground of its own: it sits inside an ordinary app card, so it carries the
   same white (and the same dark) as everything else in the stack. It was an
   inset panel like the quiz drop box at first, which made it the one block on
   the front page with a different background. */
.cs-follow {
  margin: 0;
}

.cs-follow-head {
  display: flex;
  align-items: flex-start;
  gap: 13px;
}

.cs-follow-mark {
  flex: none;
  width: 42px;
  height: 42px;
  border-radius: 12px;
}

.cs-follow-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.cs-follow-kicker {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--cs-muted, #6b7085);
}

.cs-follow-title {
  font-size: 15px;
  font-weight: 700;
  line-height: 1.25;
  color: var(--cs-text, #1a1f36);
}

.cs-follow-text {
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--cs-muted, #6b7085);
}

/* Small, quiet, and still a 34px target. */
.cs-follow-dismiss {
  flex: none;
  width: 34px;
  height: 34px;
  margin: -6px -6px 0 0;
  padding: 0;
  border: 0;
  background: none;
  font-size: 20px;
  line-height: 1;
  color: var(--cs-faint, #9a9aa5);
  -webkit-tap-highlight-color: transparent;
}

.cs-follow-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}

.cs-follow-go,
.cs-follow-already {
  flex: 1 1 auto;
  min-height: 42px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 10px 14px;
  border-radius: 11px;
  font-size: 13.5px;
  font-weight: 700;
  text-decoration: none !important;
  -webkit-tap-highlight-color: transparent;
}

.cs-follow-go {
  background: #e0b878;
  border: 1px solid #e0b878;
}

.cs-follow-already {
  background: none;
  border: 1px solid var(--cs-line, rgba(26, 31, 54, 0.18));
  color: var(--cs-muted, #6b7085);
}

.cs-follow-go:active,
.cs-follow-already:active { transform: scale(0.994); }

body.theme-dark .cs-follow-already {
  border-color: var(--cs-line, rgba(248, 240, 216, 0.22));
}

/* The page's own rules paint anchor and button text, so the card restates its
   colours at the same specificity the rest of this file uses. */
body .cs-follow .cs-follow-title { color: var(--cs-text, #1a1f36); }
body.theme-dark .cs-follow .cs-follow-title { color: var(--cs-text, #f4f1ea); }
body .cs-follow .cs-follow-text,
body .cs-follow .cs-follow-kicker { color: var(--cs-muted, #6b7085); }
/* Sand carries navy text on both grounds — it is the brand pairing and it holds
   contrast whichever theme is on. */
body .cs-follow .cs-follow-go,
body.theme-dark .cs-follow .cs-follow-go { color: #1a1f36; }
body .cs-follow .cs-follow-already { color: var(--cs-muted, #6b7085); }
body.theme-dark .cs-follow .cs-follow-already { color: var(--cs-muted, #a9adbe); }

@media (prefers-reduced-motion: reduce) {
  .cs-follow-go:active,
  .cs-follow-already:active { transform: none; }
}

/* --- Arrival day, from behind the desk (index_staffstatus.inc.php) ---------
   Three running totals in one strip, for the two days when they are the whole
   job. Narrow on purpose: it sits above everything a camper came to the front
   page for, so it earns its height by being one line tall and going away on
   the third morning. */
.cs-staffbar {
  display: flex;
  gap: 1px;
  overflow: hidden;
  border-radius: var(--cs-r, 14px);
  background: var(--cs-line, rgba(26, 31, 54, 0.1));
  border: 1px solid var(--cs-line, rgba(26, 31, 54, 0.1));
}

.cs-staffbar-cell {
  flex: 1 1 0;
  min-width: 0;
  display: block;
  padding: 9px 11px 10px;
  background: var(--cs-surface, #fff);
  text-decoration: none !important;
  -webkit-tap-highlight-color: transparent;
}
.cs-staffbar-cell:active { background: var(--cs-surface-2, #f6f6f8); }

.cs-staffbar-label {
  display: block;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--cs-muted, #6b7085);
}

.cs-staffbar-value {
  display: block;
  margin-top: 1px;
  font-size: 17px;
  font-weight: 800;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  color: var(--cs-text, #1a1f36);
}

/* The denominator is context, not the number you are reading. */
.cs-staffbar-of {
  font-size: 12px;
  font-weight: 600;
  color: var(--cs-faint, #9a9aa5);
}

.cs-staffbar-track {
  display: block;
  margin-top: 7px;
  height: 3px;
  border-radius: 999px;
  background: var(--cs-line, rgba(26, 31, 54, 0.12));
  overflow: hidden;
}

.cs-staffbar-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: #2e9e5b;
}

/* Still owed money: the one cell where a full bar is bad news. */
.cs-staffbar-warn .cs-staffbar-fill { background: #e05038; }
.cs-staffbar-warn .cs-staffbar-value { color: #c43a24; }

body.theme-dark .cs-staffbar {
  background: var(--cs-line, rgba(248, 240, 216, 0.12));
  border-color: var(--cs-line, rgba(248, 240, 216, 0.12));
}
body.theme-dark .cs-staffbar-cell { background: var(--cs-surface, #1c1f24); }
body.theme-dark .cs-staffbar-cell:active { background: var(--cs-surface-2, #23262c); }

body .cs-staffbar .cs-staffbar-value { color: var(--cs-text, #1a1f36); }
body.theme-dark .cs-staffbar .cs-staffbar-value { color: var(--cs-text, #f4f1ea); }
body .cs-staffbar .cs-staffbar-label { color: var(--cs-muted, #6b7085); }
body.theme-dark .cs-staffbar .cs-staffbar-label { color: var(--cs-muted, #a9adbe); }
body.theme-dark .cs-staffbar-warn .cs-staffbar-value { color: #f0765f; }

/* --- Upload progress on the Send button (header.php's upload handler) ------
   A clip off a phone can take a minute on camp wifi. The button that is
   already saying "Uploading…" becomes the bar, so the only thing that changes
   is that you can now see it moving. Once the bytes are up the server still
   has to re-encode a video, which is the slow half: that is "Processing…",
   and it breathes rather than sitting at 100% looking dead. */
.btn.cs-uploading {
  position: relative;
  overflow: hidden;
}

.cs-upl-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0;
  background: rgba(255, 255, 255, 0.3);
  transition: width 0.18s linear;
  pointer-events: none;
}

.cs-upl-text {
  position: relative;
  z-index: 1;
}

.btn.cs-uploading.is-processing .cs-upl-fill {
  width: 100% !important;
  animation: cs-upl-breathe 1.3s ease-in-out infinite;
}

@keyframes cs-upl-breathe {
  0%, 100% { opacity: 0.35; }
  50% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .cs-upl-fill { transition: none; }
  .btn.cs-uploading.is-processing .cs-upl-fill { animation: none; opacity: 0.6; }
}

/* --- Leaderboard on the front page (index.php) -----------------------------
   The number is the whole point of this card: who is winning at what. It was
   set with `fa-9x`, an icon-sizing class borrowed onto an `<h1>`, so the size
   depended on which stylesheet loaded last — it had quietly shrunk to roughly
   the size of the caption under it. Each cell also carried its own enormous
   margins, so two hundred pixels of nothing sat between a label and its own
   number, and a label that wrapped to two lines pushed its number out of line
   with the one beside it.

   Now: a plain grid of equal tiles, the number sized on purpose and in tabular
   figures, and a label block tall enough for two lines so every number in a
   row sits on the same line whatever its label does. */
.cs-lb {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin: 4px 0 2px;
}

.cs-lb-wide { grid-template-columns: 1fr; }

.cs-lb-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 4px;
  padding: 16px 10px 15px;
  border-radius: 16px;
  background: var(--cs-surface-2, #f6f6f8);
  text-decoration: none !important;
  -webkit-tap-highlight-color: transparent;
}
.cs-lb-cell:active { transform: scale(0.99); }

/* Two lines' worth whatever this one needs, so the numbers line up across. */
.cs-lb-label {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 2.5em;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.12em;
  line-height: 1.25;
  text-transform: uppercase;
  text-align: center;
  color: var(--cs-muted, #6b7085);
}

.cs-lb-value {
  font-size: clamp(38px, 12vw, 56px);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.cs-lb-wide .cs-lb-value { font-size: clamp(40px, 15vw, 64px); }

.cs-lb-name {
  margin-top: 2px;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.2;
  text-align: center;
  color: var(--cs-text, #1a1f36);
  opacity: 0.75;
  overflow-wrap: anywhere;
}

body.theme-dark .cs-lb-cell { background: var(--cs-surface-2, #23262c); }

/* style.css paints every span white in dark mode with `!important`
   (`.theme-dark span:not(.badge)`), and these are spans, so the label and the
   name have to say it louder or the whole tile goes white on white. */
body .cs-lb .cs-lb-name { color: var(--cs-text, #1a1f36) !important; opacity: 0.75; }
body.theme-dark .cs-lb .cs-lb-name { color: var(--cs-text, #f4f1ea) !important; }
body .cs-lb .cs-lb-label { color: var(--cs-muted, #6b7085) !important; }
body.theme-dark .cs-lb .cs-lb-label { color: var(--cs-muted, #9ba0aa) !important; }

@media (prefers-reduced-motion: reduce) {
  .cs-lb-cell:active { transform: none; }
}

/* --- Belts you have rolled with (index.php, "Your last five rolls") --------
   One thin bar, the width of each colour being that share of the people you
   have logged a roll with here. It is a picture of your own week, not a
   ranking of anybody, and it has to earn very little height: the card above it
   is five faces and a line of text. */
.cs-beltbar {
  display: flex;
  height: 9px;
  margin: 12px 0 7px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--cs-line, rgba(26, 31, 54, 0.08));
}

.cs-beltbar-seg {
  display: block;
  height: 100%;
  min-width: 3px;
}

/* A white belt on a white card reads as a gap, and a black belt on a dark card
   disappears entirely — the bar looked like it simply stopped. Each gets an
   edge on the ground it would otherwise vanish into. */
.cs-beltbar-light { box-shadow: inset 0 0 0 1px rgba(26, 31, 54, 0.18); }

.cs-beltbar-key {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 4px 12px;
  font-size: 11px;
  line-height: 1.3;
  color: var(--cs-muted, #6b7085);
}

.cs-beltbar-keyitem {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}

.cs-beltbar-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
}

body.theme-dark .cs-beltbar { background: var(--cs-line, rgba(248, 240, 216, 0.12)); }
body.theme-dark .cs-beltbar-light { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25); }
body.theme-dark .cs-beltbar-deep {
  background: #34343a !important;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}
body .cs-beltbar-key,
body .cs-beltbar-key .cs-beltbar-keyitem { color: var(--cs-muted, #6b7085) !important; }
body.theme-dark .cs-beltbar-key,
body.theme-dark .cs-beltbar-key .cs-beltbar-keyitem { color: var(--cs-muted, #9ba0aa) !important; }

/* What the bar above actually counts, so it cannot be read as this week's. */
.cs-beltbar-caption {
  margin-top: 5px;
  font-size: 10.5px;
  line-height: 1.3;
  text-align: center;
  color: var(--cs-faint, #9a9aa5) !important;
}
body.theme-dark .cs-beltbar-caption { color: var(--cs-faint, #7f8490) !important; }

/* Both scopes are rendered; the switch only changes which one is on show. */
.cs-beltbar-pane { display: none; }
.cs-beltbar-wrap[data-belt-scope="camp"] .cs-beltbar-pane[data-scope="camp"],
.cs-beltbar-wrap[data-belt-scope="all"] .cs-beltbar-pane[data-scope="all"] { display: block; }

.cs-beltbar-switch {
  display: flex;
  justify-content: center;
  gap: 4px;
  margin-top: 8px;
}

.cs-beltbar-switch button {
  padding: 4px 11px;
  border: 0;
  border-radius: 999px;
  background: none;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--cs-faint, #9a9aa5);
  -webkit-tap-highlight-color: transparent;
}

.cs-beltbar-switch button.is-on {
  background: var(--cs-surface-2, #f1f1f4);
  color: var(--cs-muted, #6b7085);
}

body.theme-dark .cs-beltbar-switch button.is-on { background: var(--cs-surface-2, #23262c); }
body .cs-beltbar-switch button { color: var(--cs-faint, #9a9aa5) !important; }
body .cs-beltbar-switch button.is-on { color: var(--cs-muted, #6b7085) !important; }
body.theme-dark .cs-beltbar-switch button.is-on { color: var(--cs-muted, #c4c8d2) !important; }
