/* ==========================================================================
   MatchUply — Prototype design system
   ========================================================================== */

:root {
  /* Dark-blue-pastel theme — refer to CLAUDE.md history for the old
     pink/purple values this replaced. --pink/--purple keep their old
     variable NAMES (renaming 46+ call sites across this file for a color
     swap would be pure churn with no functional benefit) but now hold
     blue tones instead. Both are still used standalone as icon/link/
     active-state TEXT color dozens of times below, so they're deepened
     versions of the requested Accent (#8BC3E6) / Primary (#A7C6ED) hues
     rather than those exact light values — the literal pastels have too
     little contrast against white to read as text (that's also exactly
     why they're a good fit for --surface-alt below, used as a background
     FILL behind dark text, where low contrast against white is the point).
     The Complementary powder blue (#BFE5F0) is used at exactly its given
     value there. */
  --pink: #4a90d9;
  --purple: #0D3B66;
  --gold: #f5b301;
  --coral: #ff7a59;
  --red: #ef4444;
  --gradient-brand: linear-gradient(135deg, var(--pink), var(--purple));
  --gradient-gold: linear-gradient(135deg, #ffd76a, var(--gold));

  --bg: #f4fafd;
  --surface: rgba(255, 255, 255, 0.9);
  --surface-alt: #e1f1fd;
  --border: #cfe8f5;
  /* .post-comment-body's own background — defaults to --surface-alt
     (identical to today's look) but gets repointed to --bg specifically
     when a Color Palette theme is active (see includes/head.php's inline
     style="" override) — a fixed pale-blue bubble clashed against a
     colored --bg/--surface combo like light coral or light violet, so it
     needs to follow the chosen palette instead of staying hardcoded. Kept
     as its own variable rather than repointing --surface-alt itself, since
     that's used dozens of other places (badges, hovers, section
     backgrounds) that were never asked to change. */
  --comment-bg: var(--surface-alt);

  --text: #1c2e3d;
  --text-muted: #5a7a91;
  --text-soft: #9db8c9;

  --success: #1fa971;
  --danger: var(--red);

  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 24px;
  --radius-pill: 999px;

  --shadow-sm: 0 2px 8px rgba(30, 70, 100, 0.04);
  --shadow-md: 0 8px 24px rgba(30, 70, 100, 0.07);
  --shadow-lg: 0 16px 48px rgba(30, 70, 100, 0.11);

  --header-h: 68px;
  --container: 1180px;
  --font: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

html { color-scheme: light; }

body {
  margin: 0;
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
h1, h2, h3, h4 { margin: 0; line-height: 1.2; }
p { margin: 0; }
ul { margin: 0; padding: 0; }

/* A blanket floor for every input/textarea/select site-wide — iOS Safari
   (and some Android browsers) auto-zoom the viewport on focusing any field
   with a computed font-size under 16px. This used to live only on the
   scoped .form-field input/.detail-card textarea rule further down, which
   missed fields added in a new context later (e.g. the comment add/edit
   textareas on profile.php's "Posts" cards, which live inside .explore-post,
   not .detail-card, so they never got 16px and triggered zoom-on-focus on
   mobile). A global rule here means no future field can miss it again;
   more specific selectors can still override this if a field genuinely
   needs a different size, but nothing should ever need to go below 16px. */
input, textarea, select { font-size: 16px; }

#main, #site-footer{ position: relative; z-index: 40; }
.container {
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 20px;
}

:focus-visible {
  outline: 3px solid var(--purple);
  outline-offset: 2px;
  border-radius: 4px;
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.skip-link {
  position: absolute;
  left: -999px;
  top: 8px;
  background: var(--surface);
  color: var(--text);
  padding: 10px 16px;
  border-radius: var(--radius-sm);
  z-index: 200;
  box-shadow: var(--shadow-md);
}
.skip-link:focus { left: 8px; }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: inherit;
  font-weight: 700;
  font-size: 0.95rem;
  border: none;
  border-radius: var(--radius-pill);
  padding: 12px 24px;
  transition: transform 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease;
  white-space: nowrap;
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }

.btn-primary {
  background: var(--gradient-brand);
  color: #fff;
  box-shadow: var(--shadow-sm);
}
.btn-primary:hover { box-shadow: var(--shadow-md); }

.btn-secondary {
  background: var(--surface);
  color: var(--purple);
  border: 2px solid var(--border);
}
.btn-secondary:hover { border-color: var(--purple); }

.btn-gold {
  background: var(--gradient-gold);
  color: #3a2a00;
}

.btn-ghost {
  background: transparent;
  color: var(--text-muted);
}
.btn-ghost:hover { color: var(--text); }

.btn-sm { padding: 8px 16px; font-size: 0.85rem; }
.btn-lg { padding: 16px 32px; font-size: 1.05rem; }
.btn-block { width: 100%; }
.btn-icon {
  width: 40px; height: 40px; padding: 0;
  border-radius: 50%;
  background: var(--surface-alt);
  color: var(--text-muted);
  display: flex;
  align-items: center;
  justify-content: center;
}
.btn-icon:hover { color: var(--pink); }
.btn-icon.is-active { color: var(--pink); }

/* ---------- Header ---------- */
#site-header{
  position: sticky;
  top: 0;
  z-index: 100;
}
.site-header {
  height: var(--header-h);
  background: rgba(255, 255, 255, 0.85);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}
.site-header .container {
  height: 100%;
  display: flex;
  align-items: center;
  gap: 24px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 800;
  font-size: 1.25rem;
  margin-right: auto;
}
.brand-mark {
  position: relative;
  width: 34px; height: 34px;
  border-radius: 50%;
  background: var(--gradient-brand);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  flex-shrink: 0;
}
.brand-mark .fa-star {
  position: absolute;
  top: -4px; right: -4px;
  color: var(--gold);
  font-size: 0.7rem;
  filter: drop-shadow(0 0 1px rgba(0,0,0,0.3));
}
.brand-text { background: var(--gradient-brand); -webkit-background-clip: text; background-clip: text; color: transparent; }

.main-nav { display: flex; gap: 4px; }
.main-nav a {
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--text-muted);
  transition: background 0.15s, color 0.15s;
}
.main-nav a:hover, .main-nav a[aria-current="page"] {
  background: var(--surface-alt);
  color: var(--purple);
}
.main-nav a .nav-popular-badge{ bottom: 2px; }

.header-actions { display: flex; align-items: center; gap: 10px; }

.header-search { position: relative; }
.header-search-toggle {
  width: 40px; height: 40px;
  border-radius: 50%;
  border: none;
  background: var(--surface-alt);
  color: var(--text);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
}
.header-search-toggle:hover { color: var(--purple); }
.header-search-panel {
  display: none;
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  width: 320px;
  max-width: calc(100vw - 32px);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  padding: 10px;
  z-index: 30;
}
.header-search-panel.is-open { display: block; }
.header-search-panel input {
  width: 100%;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  font-family: inherit;
  font-size: 16px; /* iOS Safari zoom-on-focus prevention, same as .form-field inputs */
}
.header-search-results {
  display: none;
  max-height: 320px;
  overflow-y: auto;
  margin-top: 8px;
}
.header-search-results.is-open { display: block; }
.header-search-group-label {
  font-size: 0.72rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-soft);
  padding: 8px 6px 4px;
}
.header-search-result {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 6px;
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: inherit;
}
.header-search-result:hover { background: var(--surface-alt); }
.header-search-result img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.header-search-result-pair { display: flex; flex-shrink: 0; }
.header-search-result-pair img:last-of-type { margin-left: -10px; }
.header-search-result strong { display: block; font-size: 0.85rem; }
.header-search-result small { display: block; font-size: 0.76rem; color: var(--text-muted); }
.header-search-empty { padding: 10px 6px; font-size: 0.82rem; color: var(--text-muted); }
@media (max-width: 768px) {
  .header-search-panel{
    right: 50%;
    transform: translateX(50%);
    position: fixed;
  }
}

/* Language switcher (multilingual support, see js/main.js's langSwitcherHTML/
   wireLangSwitcher()) — same toggle+popover shape as .header-search above,
   just a short list of plain links instead of a live-search panel. */
.header-lang { position: relative; }
.header-lang-toggle {
  width: 40px; height: 40px;
  border-radius: 50%;
  border: none;
  background: var(--surface-alt);
  color: var(--text);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  font-size: 1.1rem;
}
.header-lang-toggle:hover { background: var(--surface); }
.header-lang-panel {
  display: none;
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 160px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  padding: 6px;
  z-index: 30;
}
.header-lang-panel.is-open { display: block; }
.header-lang-option {
  display: block;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: inherit;
  font-size: 0.86rem;
  font-weight: 600;
}
.header-lang-option:hover { background: var(--surface-alt); }
.header-lang-option.is-active { color: var(--purple); }

.notif-bell { position: relative; }
.notif-bell i { font-size: 1.15rem; }
.notif-bell .badge {
  position: absolute;
  top: -4px; right: -4px;
  min-width: 18px; height: 18px;
  padding: 0 4px;
  border-radius: 50%;
  background: var(--pink);
  color: #fff;
  font-size: 0.68rem;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Lit up when there's something new — a colored fill instead of the
   neutral .btn-icon gray, so the bell itself is obviously "on", not just
   the small count badge in the corner. */
.notif-bell.has-notif {
  background: rgba(74, 144, 217, 0.15);
  color: var(--pink);
}

.profile-icon {
  width: 40px; height: 40px;
  border-radius: 50%;
  background: var(--surface-alt);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-muted);
  border: none;
}

.hamburger {
  display: none;
  width: 40px; height: 40px;
  border-radius: 50%;
  border: none;
  background: var(--surface-alt);
  align-items: center; justify-content: center;
  color: var(--text);
}

.mobile-nav {
  display: none;
  flex-direction: column;
  gap: 4px;
  padding: 12px 20px 20px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}
.mobile-nav.is-open { display: flex; }
.mobile-nav a {
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  font-weight: 600;
  color: var(--text);
}
.mobile-nav a:hover { background: var(--surface-alt); }

/* The header search box, relocated here for mobile (see .header-actions
   .header-search above) — inline and always visible rather than a
   toggle+popover, since the mobile-nav dropdown itself is already what the
   hamburger opens/closes; no second toggle needed inside it. Reuses
   .header-search-results/.header-search-result etc. (css above) for the
   result-row styling — those classes aren't scoped to the popover, so they
   work unchanged here too. */
.mobile-nav-search {
  padding: 4px 0 12px;
  margin-bottom: 8px;
  border-bottom: 1px solid var(--border);
}
.mobile-nav-search input {
  width: 100%;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  font-family: inherit;
  font-size: 16px; /* iOS Safari zoom-on-focus prevention, same as .form-field inputs */
}
.mobile-nav-search .header-search-results { max-height: 260px; }

/* Language switcher's mobile-nav row (see .mobile-nav-search above for the
   identical inline-not-popover reasoning) — a simple row of language links
   rather than a dropdown, since the mobile-nav dropdown is already the
   "dropdown" here. */
.mobile-nav-lang {
  display: flex;
  gap: 8px;
  padding: 4px 0 12px;
  margin-bottom: 8px;
  border-bottom: 1px solid var(--border);
}
.mobile-nav-lang a {
  flex: 1;
  text-align: center;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
  text-decoration: none;
  color: inherit;
  font-size: 0.86rem;
  font-weight: 600;
}
.mobile-nav-lang a.is-active { color: var(--purple); background: var(--surface); border: 1px solid var(--border); }

/* Logged-out-only mobile CTA (currently "Drop Challenges", was "Play &
   Win"), visible directly in the persistent mobile header bar (not tucked
   inside the hamburger dropdown) — this is the ad-conversion entry point,
   so it needs to be seen without a tap. Hidden entirely on desktop, where
   the equivalent page is already reachable from .main-nav (see NAV_LINKS
   in js/main.js). */
.header-mobile-cta { display: none; }

/* Same gold "shine" as every other star icon in the app (#bottom-nav-star,
   package cards, etc.) — this is that same balance button, just relocated
   to the mobile header instead of the bottom nav (see renderHeader() /
   renderBottomNav() in js/main.js). */
.header-mobile-star-balance i { color: var(--gold); text-shadow: 0 0 8px rgba(245, 179, 1, 0.65); }

/* profile-icon-link (js/main.js) is a plain link to the dashboard at every
   width — it used to divert into a mobile-only "Play & Win" submenu here,
   but that was pure duplication (Play & Win is already reachable from
   #mobile-nav's own NAV_LINKS list) and just made the icon behave
   unexpectedly on mobile. Removed. */

@media (max-width: 860px) {
  .main-nav { display: none; }
  .hamburger { display: flex; }
  .header-actions .desktop-only { display: none; }
  .header-mobile-cta { display: inline-flex; align-items: center; gap: 6px; }
  /* Moved into the mobile-nav dropdown instead (see .mobile-nav-search
     below) — the persistent header bar got too crowded once it also
     carries the star-balance button (see renderHeader() in js/main.js). */
  .header-actions .header-search { display: none; }
  /* Same reasoning, moved into .mobile-nav-lang instead. */
  .header-actions .header-lang { display: none; }
}

/* ---------- Disclaimer banner ---------- */
.disclaimer-banner {
  background: var(--surface-alt);
  border-bottom: 1px solid var(--border);
  color: var(--text-muted);
  font-size: 0.82rem;
  text-align: center;
  padding: 8px 16px;
}

/* ---------- Hero ---------- */
.hero {
  padding: 56px 0 40px;
  background: radial-gradient(circle at 20% 0%, rgba(74, 144, 217, 0.12), transparent 55%),
    radial-gradient(circle at 85% 10%, rgba(47, 102, 144, 0.14), transparent 50%);
}
.hero-inner { text-align: center; }
.hero-kicker {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  font-weight: 700;
  font-size: 0.78rem;
  color: var(--purple);
  margin-bottom: 18px;
}
.hero h1 {
  font-size: clamp(2rem, 5vw, 3.2rem);
  font-weight: 800;
  max-width: 780px;
  margin: 0 auto 14px;
}
.hero-sub {
  color: var(--text-muted);
  max-width: 560px;
  margin: 0 auto 28px;
  font-size: 1.05rem;
}
.hero-ctas { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; margin-bottom: 40px; }
.hero-incentive {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  max-width: 100%;
  background: var(--gradient-gold);
  color: #3a2a00;
  font-weight: 700;
  font-size: 0.88rem;
  padding: 9px 18px;
  border-radius: var(--radius-pill);
  text-decoration: none;
  margin: -6px 0 20px;
  box-shadow: var(--shadow-sm);
  transition: box-shadow 0.2s, transform 0.2s;
}
.hero-incentive i { flex-shrink: 0; }
.hero-incentive:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }

/* Once the text wraps to 2+ lines, a fully-rounded pill (border-radius:
   var(--radius-pill)) looks like an odd blob rather than a pill — soften it
   to a normal rounded-rectangle card and left-align so the icon reads as a
   leading bullet next to a paragraph, not centered oddly against wrapped text. */
@media (max-width: 480px) {
  .hero-incentive {
    align-items: flex-start;
    text-align: left;
    border-radius: var(--radius-md);
    padding: 10px 16px;
    line-height: 1.4;
  }
  .hero-incentive i { margin-top: 3px; }
}

.top-pair-card {
  max-width: 760px;
  margin: 0 auto;
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 32px;
  position: relative;
  overflow: hidden;
}
.top-pair-card::before {
  content: "";
  position: absolute; inset: 0;
  background: var(--gradient-brand);
  opacity: 0.05;
}
.top-pair-photos {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  margin-bottom: 18px;
}
.top-pair-photos img {
  width: 132px; height: 132px;
  border-radius: 50%;
  object-fit: cover;
  border: 4px solid var(--surface);
  box-shadow: var(--shadow-md);
}
.top-pair-photos img:first-child { margin-right: -20px; z-index: 1; }
.top-pair-connector {
  width: 44px; height: 44px;
  border-radius: 50%;
  background: var(--gradient-brand);
  color: #fff;
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  z-index: 2;
  box-shadow: var(--shadow-md);
  font-size: 1rem;
}
.top-pair-photos img:last-child { margin-left: -20px; }

.top-pair-meta { position: relative; z-index: 1; }
.top-pair-title { font-size: 1.5rem; font-weight: 800; margin-bottom: 6px; }
.top-pair-tags { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-bottom: 16px; }

.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  font-size: 0.76rem;
  font-weight: 700;
  background: var(--surface-alt);
  color: var(--text-muted);
}
.badge-rank1 { background: var(--gradient-gold); color: #3a2a00; }
.badge-region { background: var(--surface-alt); }
.badge-trend-up { color: var(--success); background: rgba(31,169,113,0.12); }
.badge-trend-down { color: var(--red); background: rgba(239,68,68,0.12); }
.badge-trend-new { color: var(--purple); background: rgba(47, 102, 144,0.12); }

/* "Popular" tag next to a nav link's label (currently the Flip game link) —
   reuses the existing .badge/.badge-rank1 gold pill (same one rankings uses
   for the #1 spot) rather than a one-off style, this class only positions
   it inline next to the link text. */
.nav-popular-badge {
  margin-left: 6px;
  vertical-align: middle;
  font-size: .5rem;
  padding: 2px 8px;
  position: relative;
  bottom: 1px;
}

.top-pair-stats {
  display: flex;
  justify-content: center;
  gap: 32px;
  margin-bottom: 20px;
}
.stat-block { text-align: center; }
.stat-value { font-size: 1.4rem; font-weight: 800; }
.stat-label { font-size: 0.78rem; color: var(--text-muted); }

.top-pair-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-bottom: 14px; }

.needed-stars {
  font-size: 0.85rem;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.needed-stars strong { color: var(--pink); }

/* ---------- Section shells ---------- */
.section { padding: 56px 0; }
.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 28px;
  flex-wrap: wrap;
}
.section-head h2 { font-size: 1.7rem; font-weight: 800; }
.section-head p { color: var(--text-muted); margin-top: 4px; }
.section-alt { background: var(--surface-alt); }

/* ---------- Filters ---------- */
.filter-row {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  padding-bottom: 8px;
  margin-bottom: 20px;
  scrollbar-width: thin;
}
.filter-row::-webkit-scrollbar { height: 6px; }
.filter-chip {
  flex-shrink: 0;
  padding: 9px 18px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--border);
  background: var(--surface);
  font-weight: 700;
  font-size: 0.86rem;
  color: var(--text-muted);
}
.filter-chip:hover { border-color: var(--purple); }
.filter-chip.is-active {
  background: var(--gradient-brand);
  color: #fff;
}

/* ---------- Match cards grid ---------- */
.match-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 20px;
}
.match-carousel {
  display: flex;
  gap: 20px;
  overflow-x: auto;
  padding-bottom: 12px;
  scroll-snap-type: x mandatory;
}
.match-carousel .match-card { min-width: 270px; scroll-snap-align: start; }

.match-card {
  background: var(--surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: 18px;
  transition: box-shadow 0.2s, transform 0.2s;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 12px;
  cursor: pointer;
}
.match-card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
.match-card:focus-visible { outline: 3px solid var(--purple); outline-offset: 2px; }

.match-card-photos { display: flex; align-items: center; justify-content: center; }
.match-card-photos img {
  width: 100px; height: 100px;
  border-radius: 50%;
  object-fit: cover;
  border: 3px solid var(--surface);
  box-shadow: var(--shadow-sm);
}
.match-card-photos img:first-child { margin-right: -18px; }
.match-card-photos img:last-child { margin-left: -18px; }
.match-card-connector {
  width: 30px; height: 30px;
  border-radius: 50%;
  background: var(--gradient-brand);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.8rem;
  z-index: 1;
  box-shadow: var(--shadow-sm);
}

.match-card-title { text-align: center; font-weight: 800; font-size: 1.05rem; }
/* Date badge, upper right of the card — used only where a bare .match-card
   tile isn't already inside an .explore-post with its own header row (see
   .explore-post-date above for that case). Currently just profile.php's
   Matches grid; .match-card itself already has position:relative for this.
   A deliberately separate, narrowly-scoped class rather than a change to
   .match-card's own CSS, since that component is reused all over the site
   (rankings, homepage, create-match, dashboard, ...) and none of those
   other places asked for a date shown on it. */
.match-card-date { position: absolute; top: 12px; right: 14px; color: var(--text-soft); font-size: 0.68rem; cursor: pointer; }
.match-card-tags { display: flex; gap: 6px; justify-content: center; flex-wrap: wrap; }

.match-card-stats {
  display: flex;
  justify-content: space-between;
  font-size: 0.82rem;
  color: var(--text-muted);
}
.match-card-stats strong { color: var(--text); display: block; font-size: 0.95rem; }

.progress-track {
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-alt);
  overflow: hidden;
}
.progress-fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--gradient-brand);
  transition: width 0.6s ease;
}

.match-card-actions { display: flex; gap: 8px; margin-top: auto; }
.match-card-actions .btn { flex: 1; }

.fav-btn.is-active i { color: var(--pink); }
.fav-btn.is-active i::before { content: "\f004"; font-weight: 900; }

/* ---------- How it works ---------- */
.steps-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}
.step-card {
  background: var(--surface);
  border-radius: var(--radius-md);
  padding: 28px 22px;
  text-align: center;
  box-shadow: var(--shadow-sm);
}
.step-number {
  width: 40px; height: 40px;
  margin: 0 auto 14px;
  border-radius: 50%;
  background: var(--gradient-brand);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-weight: 800;
}
.step-icon { font-size: 1.6rem; color: var(--purple); margin-bottom: 10px; }
.step-card h3 { margin-bottom: 8px; font-size: 1.05rem; }
.step-card p { color: var(--text-muted); font-size: 0.9rem; }

/* ---------- Star packages ---------- */
.package-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 18px;
}
.package-card {
  position: relative;
  background: var(--surface);
  border-radius: var(--radius-md);
  padding: 24px 18px;
  text-align: center;
  box-shadow: var(--shadow-sm);
  border: 2px solid transparent;
}
.package-card.is-popular { border-color: var(--gold); }
.package-popular-tag {
  position: absolute;
  top: -12px; left: 50%; transform: translateX(-50%);
  background: var(--gradient-gold);
  color: #3a2a00;
  font-size: 0.7rem;
  font-weight: 800;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
}
.package-stars { font-size: 1.7rem; font-weight: 800; margin: 10px 0 2px; }
.package-bonus { color: var(--success); font-size: 0.8rem; font-weight: 700; margin-bottom: 10px; min-height: 1.1em; }
.package-price { color: var(--text-muted); margin-bottom: 14px; }
.stars-note { text-align: center; color: var(--text-soft); font-size: 0.82rem; margin-top: 18px; }

/* ---------- Recent list / activity ---------- */
.recent-list { display: flex; flex-direction: column; gap: 10px; }
.recent-item {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--surface);
  border-radius: var(--radius-md);
  padding: 12px 16px;
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}
.recent-item:hover { box-shadow: var(--shadow-md); }
.recent-item img { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; }
.recent-item img:nth-child(2) { margin-left: -14px; }
.recent-item-info { flex: 1; min-width: 0; }
.recent-item-info strong { display: block; font-size: 0.92rem; }
.recent-item-info span { color: var(--text-muted); font-size: 0.78rem; }

@media (max-width: 600px) {
  /* Badge + button competing with the title/date text for room in one flex
     row squeezed the info block down to almost nothing, wrapping the title
     into an unreadable single-word-per-line column. Wrap onto a second row
     instead: [avatars][title/date] on top, [stars] ... [Quick Support] below. */
  .recent-item { flex-wrap: wrap; }
  .recent-item-info { flex-basis: 140px; }
  .recent-item .badge { order: 1; }
  .recent-item [data-support-btn] { order: 2; margin-left: auto; }
}

.activity-feed { display: flex; flex-direction: column; gap: 8px; }
.activity-item {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 16px;
  background: var(--surface);
  border-radius: var(--radius-sm);
  font-size: 0.88rem;
  box-shadow: var(--shadow-sm);
  animation: fadeSlideIn 0.4s ease both;
}
.activity-item i { color: var(--pink); }
.activity-item time { margin-left: auto; color: var(--text-soft); font-size: 0.76rem; }
@keyframes fadeSlideIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* ---------- Final CTA ---------- */
.final-cta {
  text-align: center;
  padding: 64px 0;
  background: var(--gradient-brand);
  color: #fff;
  border-radius: var(--radius-lg);
  margin: 0 20px;
}
.final-cta h2 { font-size: 1.9rem; margin-bottom: 20px; max-width: 600px; margin-inline: auto; }
.final-cta .btn-secondary { background: rgba(255,255,255,0.15); color: #fff; border-color: rgba(255,255,255,0.4); }
.final-cta .btn-gold { color: #3a2a00; }
@media screen and (max-width: 768px) {
  .final-cta{ margin: 0; }
}

/* ---------- Footer ---------- */
.site-footer { padding: 48px 0 24px; border-top: 1px solid var(--border); margin-top: 40px; }
.footer-grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1fr;
  gap: 24px;
  margin-bottom: 24px;
}
.footer-col h4 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-soft); margin-bottom: 12px; }
.footer-col a, .footer-col p { display: block; color: var(--text-muted); font-size: 0.9rem; margin-bottom: 8px; }
.footer-col a:hover { color: var(--purple); }
/* .social-icon is now a <button> (see wireFooterShare() in js/main.js —
   these used to be dead "MatchUply on X/..." links, now real share-this-
   page buttons), so this is scoped by class alone rather than "a.social-icon". */
.footer-col .social-icon{ color: #fff; background: var(--gradient-brand); font-size: 1rem; position: relative; border: none; cursor: pointer; }
.footer-col .social-icon > i{ position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);}
.footer-social { display: flex; gap: 10px; margin-top: 8px; }
.footer-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
  color: var(--text-soft);
  font-size: 0.8rem;
}
.footer-disclaimer {
  background: var(--surface-alt);
  border-radius: var(--radius-md);
  padding: 14px 18px;
  font-size: 0.82rem;
  color: var(--text-muted);
  margin-bottom: 24px;
  display: flex;
  gap: 10px;
  align-items: flex-start;
}
.footer-disclaimer i { color: var(--gold); margin-top: 2px; }

/* ---------- Page header (non-landing pages) ---------- */
.page-header { padding: 40px 0 24px; }
.page-header h1 { font-size: 2rem; font-weight: 800; margin-bottom: 8px; }
.page-header p { color: var(--text-muted); }
@media screen and (max-width: 768px) {
  .page-header{ padding-left: 20px; padding-right: 20px;}
}

/* ---------- Rankings page ---------- */
.rankings-tabs {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  border-bottom: 1px solid var(--border);
  margin-bottom: 24px;
}
.rankings-tab {
  padding: 12px 6px;
  border: none;
  background: none;
  font-weight: 700;
  color: var(--text-muted);
  border-bottom: 3px solid transparent;
  white-space: nowrap;
  margin-right: 18px;
}
.rankings-tab.is-active { color: var(--purple); border-color: var(--purple); }

.ranking-row {
  display: grid;
  grid-template-columns: 48px 1fr 120px 120px 110px 120px;
  align-items: center;
  gap: 12px;
  background: var(--surface);
  border-radius: var(--radius-md);
  padding: 14px 18px;
  margin-bottom: 10px;
  box-shadow: var(--shadow-sm);
}
.ranking-row.rank-gold { border: 2px solid var(--gold); }
.ranking-row.rank-silver { border: 2px solid #c9c9d6; }
.ranking-row.rank-bronze { border: 2px solid #cd8a5d; }

.rank-cell { display: flex; align-items: center; justify-content: center; gap: 4px; font-weight: 800; font-size: 1.05rem; }
.rank-medal { font-size: 1.3rem; }
.rank-prev { font-size: 0.7rem; color: var(--text-soft); display: block; font-weight: 500; }

.pairing-cell { display: flex; align-items: center; gap: 12px; min-width: 0; }
.pairing-cell img { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; }
.pairing-cell img:nth-child(2) { margin-left: -12px; }
.pairing-cell-info { min-width: 0; }
.pairing-cell-info strong { display: block; font-size: 0.95rem; }
.pairing-cell-info span { font-size: 0.76rem; color: var(--text-muted); }

.ranking-row .stat-cell { text-align: center; font-size: 0.88rem; }
.ranking-row .stat-cell strong { display: block; font-size: 0.98rem; }
.ranking-row .stat-cell span { color: var(--text-muted); font-size: 0.76rem; }

.trend-cell { text-align: center; }

.load-more-wrap { text-align: center; margin-top: 24px; }

.pagination-bar { display: flex; justify-content: center; align-items: center; gap: 12px; margin-top: 16px; }
.pagination-bar span { color: var(--text-muted); font-size: 0.85rem; }

/* On small screens, convert rows into a 2-row compact card instead of
   cramming rank/pairing/Stars/Support into one cramped line: rank spans
   both rows on the left, pairing info (avatars + title) gets its own full
   top row to breathe, and Stars + Support share the bottom row. Supporters/
   trend are dropped entirely to make room. Each cell's grid-area actually
   gets assigned this time — an earlier version of this rule set
   grid-template-areas without ever assigning grid-area on the children, so
   it silently did nothing; the fix after that crammed everything into one
   3-then-4-column row instead, which was correct-but-cramped. */
@media (max-width: 760px) {
  .ranking-row {
    grid-template-columns: 40px 1fr auto;
    grid-template-areas:
      "rank pair pair"
      "rank stat action";
    row-gap: 6px;
    column-gap: 10px;
  }
  .ranking-row .rank-cell { grid-area: rank; }
  .ranking-row .pairing-cell { grid-area: pair; }
  .ranking-row .stat-cell-primary { grid-area: stat; text-align: left; }
  .ranking-row .stat-cell:not(.stat-cell-primary),
  .ranking-row .trend-cell { display: none; }
  .ranking-row .flex-center { grid-area: action; justify-content: flex-end; }
}

/* ---------- Pairing detail page ---------- */
.match-hero {
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  padding: 32px;
  text-align: center;
  margin-bottom: 32px;
}
.match-hero-photos { display: flex; align-items: center; justify-content: center; margin-bottom: 18px; }
.match-hero-photos a { display: block; width: 150px; height: 150px; flex-shrink: 0; }
.match-hero-photos img {
  width: 150px; height: 150px;
  border-radius: 50%;
  object-fit: cover;
  border: 5px solid var(--surface);
  box-shadow: var(--shadow-md);
}
.match-hero-photos img:first-child, .match-hero-photos a:first-child { margin-right: -34px; }
.match-hero-photos img:last-child, .match-hero-photos a:last-child { margin-left: -12px; }
.match-hero h1 { font-size: 1.8rem; margin-bottom: 10px; }
.match-hero-tags { display: flex; justify-content: center; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; }
.match-hero-stats { display: flex; justify-content: center; gap: 36px; flex-wrap: wrap; margin-bottom: 20px; }
.match-hero-actions { display: flex; justify-content: center; gap: 12px; flex-wrap: wrap; }
.match-meta-line { color: var(--text-muted); font-size: 0.85rem; margin-top: 14px; }

/* profile.php's own single-person hero — .match-hero-topline groups the
   avatar with the username/name/occupation so the mobile override below can
   turn them into a side-by-side row without touching the markup again.
   Desktop keeps today's stacked/centered look (block layout, avatar
   centered via its own margin). */
.match-hero-topline { display: block; }
.match-hero-avatar { width: 120px; height: 120px; border-radius: 50%; object-fit: cover; margin: 0 auto 16px; cursor: pointer; display: block; }
.match-hero-username { color: var(--text-muted); font-size: .9rem; font-weight: 700; }
.match-hero-occupation { color: var(--text-muted); font-weight: 700; margin-bottom: 10px; }
.match-hero-bio { color: var(--text-muted); max-width: 480px; margin: 0 auto 14px; white-space: pre-wrap; }

@media (max-width: 768px) {
  .match-hero-stats{ gap: 8px; margin-bottom: 14px; display: grid; grid-template-columns: 1fr 1fr;}
  .match-hero-tags{ margin-bottom: 14px; }
  .match-hero{ margin-bottom: 24px; }
  .match-hero-actions{ justify-content: space-evenly; }

  /* Profile hero only (.profile-hero — the pairing detail page's two-person
     hero doesn't carry this class, so this can't affect that layout):
     avatar moves beside the name instead of stacked above it, and
     everything left-aligns — this is what actually shortens the section,
     since the avatar no longer costs its own full-width row. */
  .profile-hero { text-align: left; padding: 20px; }
  .profile-hero .match-hero-topline { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
  .profile-hero .match-hero-avatar { width: 100px; height: 100px; margin: 0; flex-shrink: 0; }
  .profile-hero .match-hero-identity { text-align: left; min-width: 0; }
  .profile-hero .match-hero-bio { text-align: left; margin: 0 0 14px; max-width: none; }
  .profile-hero h1 { font-size: 1.25rem; }
  .profile-hero .match-hero-tags,
  .profile-hero .match-hero-actions { justify-content: flex-start; }

  /* Stats collapse from stacked "big number / label" blocks to a single
     inline "value label" line each, wrapping in a row — this is the other
     big height contributor on mobile (used to be up to 3 grid rows). */
  .profile-hero .match-hero-stats {
    display: flex;
    justify-content: flex-start;
    gap: 8px;
  }
  .profile-hero .match-hero-stats .stat-block {
    display: flex!important;
    align-items: baseline;
    gap: 4px;
    text-align: left;
    width: calc(50% - 20px);
  }
  .profile-hero .match-hero-stats .stat-block:last-child{ width: 100%; }
  .profile-hero .match-hero-stats .stat-value { font-size: 0.95rem; }
  .profile-hero .match-hero-stats .stat-label { font-size: 0.78rem; }
}

.match-detail-grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: 28px; }
.detail-card {
  background: var(--surface);
  border-radius: var(--radius-md);
  padding: 24px;
  box-shadow: var(--shadow-sm);
  margin-bottom: 20px;
}
.detail-card h3 { font-size: 1.1rem; margin-bottom: 14px; }
.detail-card p { color: var(--text-muted); font-size: 0.92rem; }

/* Profile Playlist teaser (profile.php) — collapsed by default; clicking it
   both expands #spotify-embed-wrap and starts playback (js/profile-page.js's
   wireSpotifyTeaser()). Styled as the card's whole header/button rather than
   a separate <h3>, so the entire row is one obvious click target. */
.spotify-teaser {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  background: none;
  border: none;
  padding: 0;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
}
.spotify-teaser-icon {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(29, 185, 84, 0.12);
  color: #1DB954;
  font-size: 1.3rem;
}
.spotify-teaser-text { flex: 1; min-width: 0; }
.spotify-teaser-text strong { display: block; font-size: 1.1rem; }
.spotify-teaser-hint {
  display: block;
  color: var(--text-muted);
  font-size: 0.85rem;
  margin-top: 2px;
}
.spotify-teaser-chevron {
  flex-shrink: 0;
  color: var(--text-soft);
  transition: transform 0.25s ease;
}
.spotify-teaser[aria-expanded="true"] .spotify-teaser-chevron { transform: rotate(180deg); }
.spotify-teaser[aria-expanded="true"] .spotify-teaser-hint::before { content: "▸ Now playing — "; color: #1DB954; font-weight: 700; }

.spotify-embed-wrap {
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: max-height 0.35s ease, opacity 0.25s ease, margin-top 0.35s ease;
}
.spotify-embed-wrap.is-expanded {
  max-height: 420px;
  opacity: 1;
  margin-top: 16px;
}

.profile-nudge {
  display: flex;
  align-items: center;
  gap: 16px;
  border-left: 4px solid var(--purple);
}
.profile-nudge > .fa-circle-user { font-size: 1.8rem; color: var(--purple); flex-shrink: 0; }
.profile-nudge-text { flex: 1; min-width: 200px; }
.profile-nudge-text p { margin-top: 2px; }
@media (max-width: 600px) {
  .profile-nudge { flex-wrap: wrap; }
}

.chart-wrap { display: flex; align-items: flex-end; gap: 6px; }
.share-row { display: flex; gap: 10px; flex-wrap: wrap; }

.supporter-list { display: flex; flex-direction: column; gap: 10px; }
.supporter-row { display: flex; align-items: center; gap: 10px; font-size: 0.88rem; }
.supporter-row img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; }
.supporter-row strong { flex: 1; }
.supporter-row span { color: var(--gold); font-weight: 700; }

/* Support panel */
.support-panel { position: sticky; top: calc(var(--header-h) + 20px); }
.support-amounts {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin: 16px 0;
}
.amount-chip {
  padding: 12px;
  border-radius: var(--radius-sm);
  border: 2px solid var(--border);
  background: var(--surface);
  font-weight: 700;
  text-align: center;
}
.amount-chip.is-active { border-color: var(--pink); background: rgba(74, 144, 217,0.08); color: var(--pink); }
.custom-amount-input {
  width: 100%;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  border: 2px solid var(--border);
  font-family: inherit;
  font-size: 0.95rem;
  margin-bottom: 14px;
  background: var(--surface);
  color: var(--text);
}
.support-summary { font-size: 0.85rem; color: var(--text-muted); margin: 14px 0; display: flex; flex-direction: column; gap: 6px; }
.support-summary strong { color: var(--text); }
.estimate-line {
  background: var(--surface-alt);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  font-size: 0.85rem;
  margin-bottom: 16px;
}

.sticky-support-bar {
  display: none;
  position: fixed;
  bottom: 0; left: 0; right: 0;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding: 12px 16px;
  box-shadow: 0 -4px 16px rgba(0,0,0,0.08);
  z-index: 90;
}
@media (max-width: 900px) {
  .match-detail-grid { grid-template-columns: 1fr; }
  .support-panel { position: static; }
  body.has-sticky-bar { padding-bottom: 76px; }
  .sticky-support-bar.is-visible { display: flex; gap: 10px; }
}

/* ---------- Bottom sticky nav (logged-in users only, every breakpoint) ----------
   Explore/Feed/Friends/Messages at every width, plus .bottom-nav-desktop-only
   (Stars — mobile gets it in the header instead, see #header-mobile-star-
   balance in renderHeader(); Profile — reachable via Dashboard on mobile
   instead) and .bottom-nav-mobile-only (Alerts — the notif bell otherwise
   lives in the header itself, desktop-only) — see renderBottomNav() in
   js/main.js. Always present at all widths once logged in, unlike
   .sticky-support-bar above, which only ever shows on mobile. */
.bottom-nav {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  z-index: 95;
  display: flex;
  justify-content: space-around;
  height: 60px;
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--border);
}
.bottom-nav-item {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  border: none;
  background: none;
  font-family: inherit;
  color: var(--text-muted);
  font-size: 0.68rem;
  font-weight: 700;
  cursor: pointer;
}
.bottom-nav-item:hover, .bottom-nav-item:hover .bottom-nav-icon i { color: var(--purple); }
.bottom-nav-icon { position: relative; font-size: 1.15rem; line-height: 1; }
.bottom-nav-icon i { color: var(--text-muted); }
/* Stars button gets the same gold "shine" as every other star icon in the
   app (.star-balance, package cards, etc.), plus a soft glow so it stands
   out from the other, neutral-colored bottom-nav icons. */
#bottom-nav-star .bottom-nav-icon i { color: var(--gold); text-shadow: 0 0 8px rgba(245, 179, 1, 0.65); }
.bottom-nav-icon .badge {
  position: absolute;
  top: -6px; right: -10px;
  min-width: 16px; height: 16px;
  padding: 0 4px;
  border-radius: 50%;
  background: var(--pink);
  color: #fff;
  font-size: 0.62rem;
  display: flex;
  align-items: center;
  justify-content: center;
}
body.has-bottom-nav { padding-bottom: 60px; }

/* ---------- Profile background music (profile.php + dashboard.php's upload card) ----------
   A floating toggle (play/pause) + a thin progress line pinned to the very
   bottom of the screen, both in the brand gradient — see
   wireProfileMusicPlayer() in js/profile-page.js. Positioned above
   .bottom-nav (z-index 95) rather than under it: body.has-bottom-nav only
   applies for logged-in visitors (see renderBottomNav() in js/main.js), so
   both elements shift up by the nav's 60px height in that case, reusing
   that existing marker class rather than duplicating the "am I logged in"
   check here. */
.profile-music-toggle {
  position: fixed;
  right: 20px;
  bottom: 24px;
  z-index: 96;
  width: 52px; height: 52px;
  border: none;
  border-radius: 50%;
  background: var(--gradient-brand);
  color: #fff;
  font-size: 1.05rem;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: var(--shadow-md);
  transition: transform 0.2s;
}
.profile-music-toggle:hover { transform: scale(1.06); }
.profile-music-toggle.is-playing { animation: avatar-ring-pulse 2.4s ease-in-out infinite; }
body.has-bottom-nav .profile-music-toggle { bottom: 84px; }

.profile-music-progress {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 96;
  height: 4px;
  background: rgba(0, 0, 0, 0.08);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s;
}
.profile-music-progress.is-active { opacity: 1; visibility: visible; }
.profile-music-progress-fill {
  height: 100%;
  width: 0%;
  background: var(--gradient-brand);
  transition: width 0.15s linear;
}
body.has-bottom-nav .profile-music-progress { bottom: 60px; }
@media (prefers-reduced-motion: reduce) {
  .profile-music-toggle.is-playing { animation: none; }
}

.bottom-nav-mobile-only { display: none; }

@media (max-width: 860px) {
  .bottom-nav-desktop-only { display: none; }
  .bottom-nav-mobile-only { display: flex; }
}

/* Both the match.php sticky support bar and this bottom nav can be visible
   on mobile at once — stack the support bar above the bottom nav instead
   of letting the two overlap. */
@media (max-width: 900px) {
  body.has-bottom-nav.has-sticky-bar { padding-bottom: 136px; }
  body.has-bottom-nav .sticky-support-bar.is-visible { bottom: 60px; }
}

/* ---------- Wizard (Create a Match) ---------- */
.wizard-steps {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-bottom: 32px;
  flex-wrap: wrap;
}
.wizard-step-indicator {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-radius: var(--radius-pill);
  background: var(--surface-alt);
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--text-soft);
}
.wizard-step-indicator .num {
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--border);
  color: var(--text-muted);
  display: flex; align-items: center; justify-content: center;
  font-size: 0.72rem;
}
.wizard-step-indicator.is-active { background: rgba(74, 144, 217,0.1); color: var(--pink); }
.wizard-step-indicator.is-active .num { background: var(--gradient-brand); color: #fff; }
.wizard-step-indicator.is-done .num { background: var(--success); color: #fff; }

.wizard-panel { display: none; }
.wizard-panel.is-active { display: block; }

.search-filter-bar { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; }
.search-input-wrap { flex: 1; min-width: 200px; position: relative; }
.search-input-wrap i { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--text-soft); }
.search-input {
  width: 100%;
  padding: 12px 14px 12px 38px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--border);
  font-family: inherit;
  background: var(--surface);
  color: var(--text);
}
.select-filter {
  padding: 12px 14px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
}

.person-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 14px;
  margin-bottom: 20px;
}
.person-tile {
  background: var(--surface);
  border-radius: var(--radius-md);
  padding: 14px;
  text-align: center;
  border: 2px solid transparent;
  box-shadow: var(--shadow-sm);
}
.person-tile img { width: 72px; height: 72px; border-radius: 50%; object-fit: cover; margin: 0 auto 8px; }
.person-tile strong { display: block; font-size: 0.85rem; }
.person-tile span { font-size: 0.72rem; color: var(--text-muted); }
.person-tile.is-selected { border-color: var(--pink); box-shadow: var(--shadow-md); }
.person-tile:disabled, .person-tile.is-disabled { opacity: 0.35; cursor: not-allowed; }

/* ---------- Matchmaker side pickers (create-match step 1) ---------- */
.matchmaker-side {
  background: var(--surface-alt);
  border-radius: var(--radius-lg);
  padding: 20px;
  margin-bottom: 20px;
}
.side-label {
  font-weight: 800;
  font-size: 0.95rem;
  color: var(--purple);
  margin-bottom: 14px;
  display: flex;
  align-items: center;
  gap: 8px;
}
#side-a-self-card { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
#side-a-self-card .selected-preview { margin-bottom: 0; flex: 1; }

/* Between roughly 390-430px (most current iPhones), the preview card
   (flex:1, can shrink) and the nowrap "Not you?" button sit right at
   flex-wrap's threshold — a few px either way flips between squeezing the
   preview card down (button just barely fits alongside it) or dropping the
   button to its own line (button doesn't fit at all). Stack them
   deliberately on mobile instead of leaving that to chance. */
@media (max-width: 760px) {
  #side-a-self-card { flex-direction: column; align-items: stretch; }
  #side-a-self-card .selected-preview { flex: none; }
  #side-a-toggle-search { width: 100%; white-space: normal; text-align: center; }
}

.selected-preview {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--surface-alt);
  border-radius: var(--radius-md);
  padding: 14px;
  margin-bottom: 16px;
}
.selected-preview img { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; }

.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; }
.form-field { display: flex; flex-direction: column; gap: 6px; }
.form-field.full { grid-column: 1 / -1; }
.form-field label { font-size: 0.85rem; font-weight: 700; }
.form-field input, .form-field select, .form-field textarea, .detail-card textarea {
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  border: 2px solid var(--border);
  font-family: inherit;
  /* font-size intentionally not set here — the global input/textarea/
     select rule near the top of this file already floors it at 16px
     (iOS Safari zoom-on-focus prevention); this rule just adds the
     padding/border/background chrome on top of that. */
  background: var(--surface);
  color: var(--text);
}
.layout-options { display: flex; gap: 10px; }
.layout-option {
  flex: 1;
  padding: 10px;
  border-radius: var(--radius-sm);
  border: 2px solid var(--border);
  background: var(--surface);
  text-align: center;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--text-muted);
}
.layout-option.is-selected { border-color: var(--purple); color: var(--purple); }

.preview-pair-card {
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  padding: 26px;
  text-align: center;
  max-width: 420px;
  margin: 0 auto 24px;
}

.launch-options { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 24px; }
.launch-chip {
  padding: 16px;
  border-radius: var(--radius-md);
  border: 2px solid var(--border);
  background: var(--surface);
  text-align: center;
  font-weight: 700;
}
.launch-chip.is-active { border-color: var(--pink); background: rgba(74, 144, 217,0.08); }

.wizard-nav { display: flex; justify-content: space-between; margin-top: 8px; gap: 12px; }

/* Longer button labels (e.g. "Create Free Account to Publish" for the
   anonymous create-match flow) push a nowrap .btn wider than the button
   itself can shrink to inside a space-between flex row, overflowing the
   viewport on narrow screens. Stack + go full-width there instead, and
   allow the label to wrap onto a second line rather than staying on one
   long unbroken line. */
@media (max-width: 760px) {
  .wizard-nav { flex-direction: column; }
  .wizard-nav .btn { width: 100%; white-space: normal; text-align: center; }
}

/* ---------- Dashboard ---------- */
.dashboard-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 16px;
  margin-bottom: 32px;
}
.summary-card {
  background: var(--surface);
  border-radius: var(--radius-md);
  padding: 20px;
  box-shadow: var(--shadow-sm);
  text-align: center;
  transition: box-shadow 0.2s, transform 0.2s;
}
.summary-card i { font-size: 1.3rem; color: var(--purple); margin-bottom: 8px; }
.summary-card .value { font-size: 1.5rem; font-weight: 800; }
.summary-card .label { font-size: 0.78rem; color: var(--text-muted); }
.summary-card.is-clickable { cursor: pointer; }
.summary-card.is-clickable:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }

.dashboard-tabs { display: flex; gap: 8px; overflow-x: auto; border-bottom: 1px solid var(--border); margin-bottom: 20px; }
.dash-panel { display: none; }
.dash-panel.is-active { display: block; }

/* Admin dashboard (mudrethope.php) sidebar nav — a growing tab list (Users/
   Moderators/Matches/.../Audit Log, plus Settings/Log Out) scales far better
   as a vertical sidebar than the horizontal .dashboard-tabs scroll strip
   above, which is why this is a separate layout rather than reusing it. */
.dashboard-layout { display: grid; grid-template-columns: 220px 1fr; gap: 32px; align-items: start; }
.dashboard-sidebar { display: flex; flex-direction: column; gap: 2px; position: sticky; top: 80px; }
.dash-sidebar-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  border: none;
  background: none;
  width: 100%;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--text-muted);
}
.dash-sidebar-link i { width: 18px; text-align: center; color: var(--text-soft); }
.dash-sidebar-link:hover { background: var(--surface-alt); color: var(--text); }
.dash-sidebar-link.is-active { background: var(--surface-alt); color: var(--purple); }
.dash-sidebar-link.is-active i { color: var(--purple); }
.dash-sidebar-link.is-logout { color: var(--red); }
.dash-sidebar-link.is-logout i { color: var(--red); }
.dash-sidebar-divider { height: 1px; background: var(--border); margin: 10px 4px; }
.dashboard-content { min-width: 0; }
@media (max-width: 760px) {
  .dashboard-layout { display: block; }
  .dashboard-sidebar {
    flex-direction: row;
    overflow-x: auto;
    gap: 8px;
    position: static;
    border-bottom: 1px solid var(--border);
    padding-bottom: 10px;
    margin-bottom: 20px;
  }
  .dash-sidebar-link { white-space: nowrap; }
  .dash-sidebar-divider { display: none; }
}

.notification-list { display: flex; flex-direction: column; gap: 10px; margin-top: 8px; }
.notification-item {
  display: flex; align-items: center; gap: 10px;
  background: var(--surface);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  font-size: 0.88rem;
  box-shadow: var(--shadow-sm);
}
.notification-item i { color: var(--pink); }

.transaction-row { display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid var(--border); font-size: 0.9rem; }
.transaction-row:last-child { border-bottom: none; }
.transaction-row .amount.positive { color: var(--success); font-weight: 700; }
.transaction-row .amount.negative { color: var(--red); font-weight: 700; }

/* dashboard.php's "Who's Viewed You" tab (js/dashboard-page.js's
   visitorRowHTML()) — same row-list shape as .transaction-row above, with
   an avatar+name instead of a plain label. */
.visitor-row { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--border); text-decoration: none; color: inherit; }
.visitor-row:last-child { border-bottom: none; }
.visitor-row img { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.visitor-row-name { display: block; }
.visitor-row-time { color: var(--text-soft); font-size: 0.82rem; }

.empty-state { text-align: center; padding: 40px 20px; color: var(--text-muted); }
.empty-state i { font-size: 2rem; color: var(--text-soft); margin-bottom: 10px; }

/* ---------- Modals ---------- */
.modal-overlay {
  position: fixed; inset: 0;
  background: rgba(40, 30, 50, 0.35);
  display: none;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 200;
}
.modal-overlay.is-open { display: flex; }
.modal-box {
  background: var(--surface);
  border-radius: var(--radius-sm);
  padding: 28px;
  max-width: 420px;
  width: 100%;
  box-shadow: var(--shadow-lg);
  max-height: 90vh;
  overflow-y: auto;
  position: relative;
}
.modal-box.modal-lg { max-width: 640px; }
.modal-close {
  position: absolute;
  top: 16px; right: 16px;
  background: var(--surface-alt);
  border: none;
  width: 34px; height: 34px;
  border-radius: 50%;
  color: var(--text-muted);
}
.modal-box h2 { font-size: 1.25rem; margin-bottom: 16px; padding-right: 30px; }
.modal-summary-row { display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid var(--border); font-size: 0.9rem; }
.modal-summary-row:last-child { border-bottom: none; }
.modal-actions { display: flex; gap: 10px; margin-top: 20px; }
.modal-actions .btn { flex: 1; }
.modal-pair-thumb { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
.modal-pair-thumb img { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; }
.modal-pair-thumb img:nth-child(2) { margin-left: -12px; }

.success-check { width: 56px; height: 56px; border-radius: 50%; background: var(--success); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 1.6rem; margin: 0 auto 16px; }

/* ---------- Toasts ---------- */
.toast-stack {
  position: fixed;
  bottom: 20px; right: 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  z-index: 300;
  transition: bottom 0.15s ease;
}
/* Keep toasts from rendering on top of the bottom nav (see renderBottomNav()
   in js/main.js). */
body.has-bottom-nav .toast-stack { bottom: 80px; }
.toast {
  background: var(--text);
  color: var(--bg);
  padding: 12px 18px;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  font-size: 0.88rem;
  display: flex;
  align-items: center;
  gap: 8px;
  animation: toastIn 0.25s ease both;
}
@keyframes toastIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* ---------- Utility ---------- */
.text-center { text-align: center; }
.mt-24 { margin-top: 24px; }
.mb-24 { margin-bottom: 24px; }
.flex-center { display: flex; align-items: center; justify-content: center; }

@media (max-width: 900px) {
  .steps-grid { grid-template-columns: 1fr; }
  .footer-grid { grid-template-columns: 1fr 1fr; }
  .form-grid { grid-template-columns: 1fr; }
}
@media (max-width: 600px) {
  .footer-grid { grid-template-columns: 1fr; }
  .top-pair-stats { gap: 18px; }
  .hero-ctas{ padding: 0 10px; }
  .hero-ctas .btn { width: 100%; }
  .modal-box{ max-height: 90dvh; }
}

/* ---------- Admin page ---------- */
.admin-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
  background: var(--surface);
  border-radius: var(--radius-md);
  padding: 14px 18px;
  margin-bottom: 10px;
  box-shadow: var(--shadow-sm);
}
.admin-cell { min-width: 0; }
.admin-cell-main { flex: 2 1 220px; }
.admin-cell-main.is-clickable { cursor: pointer; border-radius: var(--radius-sm); padding: 4px 8px; margin: -4px -8px; }
.admin-cell-main.is-clickable:hover { background: var(--surface-alt); }
.admin-cell strong { display: block; font-size: 0.95rem; }
.admin-cell span { font-size: 0.78rem; color: var(--text-muted); }
.admin-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-left: auto; }

.status-badge { text-transform: capitalize; }
.status-pending { color: #9a7300; background: rgba(245, 179, 1, 0.15); }
.status-approved, .status-active, .status-live { color: var(--success); background: rgba(31, 169, 113, 0.12); }
.status-rejected, .status-removed, .status-suspended { color: var(--red); background: rgba(239, 68, 68, 0.12); }
.status-unclaimed { color: var(--gold); background: rgba(245, 179, 1, 0.14); }

@media (max-width: 760px) {
  .admin-row { flex-direction: column; align-items: stretch; }
  .admin-actions { margin-left: 0; width: 100%; }
}

.admin-search-bar { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
.admin-search-bar input, .admin-search-bar select {
  flex: 1;
  min-width: 200px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: inherit;
  background: var(--surface);
  color: var(--text);
  /* font-size intentionally not set — the global input/textarea/select
     rule near the top of this file already floors it at 16px (iOS Safari
     zoom-on-focus prevention). */
}

.admin-reference-preview { margin-top: 8px; }
.admin-reference-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-radius: var(--radius-pill);
  background: var(--surface-alt);
  font-size: 0.78rem;
  color: var(--text);
  text-decoration: none;
}
.admin-reference-chip:hover { box-shadow: var(--shadow-sm); }
.admin-reference-chip img { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; }
.admin-reference-chip img:last-of-type { margin-left: -8px; }
.admin-reference-chip.is-missing { color: var(--text-muted); background: none; }

/* ---------- Takedown page: reference search widget ---------- */
.reference-search { position: relative; }
.reference-search-results {
  display: none;
  position: absolute;
  top: 100%; left: 0; right: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  margin-top: 4px;
  max-height: 280px;
  overflow-y: auto;
  z-index: 20;
  box-shadow: var(--shadow-md);
}
.reference-search-results.is-open { display: block; }
.reference-result {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 12px;
  text-align: left;
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
}
.reference-result:hover { background: var(--surface-alt); }
.reference-result img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; }
.reference-result img:last-of-type { margin-left: -10px; }
.reference-result strong { display: block; font-size: 0.85rem; }
.reference-search-empty { padding: 10px 12px; font-size: 0.82rem; color: var(--text-muted); }
.reference-selected-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
  padding: 8px 12px;
  background: var(--surface-alt);
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
}
.reference-selected-chip button {
  margin-left: auto;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-muted);
  font-size: 0.9rem;
}

/* ---------- Animated brand-gradient avatar ring ----------
   A rotating conic-gradient ring in the same --pink/--purple brand colors
   as --gradient-brand, applied directly to every circular avatar image
   site-wide (not just a user's own main profile photo) — no wrapper
   element needed: border-color is set to transparent and a conic-gradient
   background paints through that transparent border (background-clip:
   border-box), so the ring is really just the existing element's own
   border area. This works on a plain <img> because it's a real border/
   background paint, not generated content — ::before/::after don't render
   on replaced elements like <img>, which is why this deliberately avoids
   that route. @property registers --avatar-ring-angle as an animatable
   <angle>, which is what makes animating the conic-gradient's rotation
   possible via a plain @keyframes (browsers without @property support just
   render the ring static at 0deg — still shows the brand colors, just
   without the spin, a harmless degradation).

   Three variants:
   - .avatar-ring: full treatment (rotating border + a slow glow pulse) for
     every STANDALONE avatar (one photo per row/card) — search results,
     supporter/friends/notification rows, explore/feed post headers, game
     leaderboard, admin/takedown reference chips, create-match person
     pickers, and the two "this is you" instances (profile.php's hero,
     dashboard's photo-upload preview). Sets its own border width (these
     selectors had no border of their own before).
   - .avatar-ring-loud: same full treatment (rotating border + glow pulse),
     but for the two showcase PAIR instances — the homepage's #1 top-pair
     card and match.php's own hero — where there's only ever one such pair
     on screen at a time, so the extra glow reads as a highlight rather
     than clutter. Only overrides border-color (not the whole border
     shorthand), preserving each context's own existing border width
     (4px/5px) instead of flattening both to .avatar-ring's 2px.
   - .avatar-ring-subtle: rotating border only, no glow — for every OTHER
     overlapping person-A/person-B pair (.match-card-photos — often many
     per page, e.g. a whole grid of match cards — plus mudrethope.php's
     admin pairing cells and the share/support confirm modals' pair
     thumbnail), where a pulsing glow on every pair at once would be
     genuine clutter rather than a highlight. Same border-color-only
     override as .avatar-ring-loud. */
@property --avatar-ring-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}
.header-search-result img,
.recent-item img,
.supporter-row img,
.person-tile img,
.selected-preview img,
.admin-reference-chip img,
.reference-result img,
.game-leaderboard-avatar,
.friends-row-avatar,
.drop-submission-meta img,
.explore-post-header img,
.avatar-ring {
  --avatar-ring-angle: 0deg;
  border: 2px solid transparent;
  background: conic-gradient(from var(--avatar-ring-angle), var(--pink), var(--purple), var(--pink)) border-box;
  background-origin: border-box;
  background-clip: border-box;
  animation: avatar-ring-spin 4s linear infinite, avatar-ring-pulse 2.4s ease-in-out infinite;
}
.top-pair-photos img,
.match-hero-photos img,
.avatar-ring-loud {
  --avatar-ring-angle: 0deg;
  border-color: transparent;
  background: conic-gradient(from var(--avatar-ring-angle), var(--pink), var(--purple), var(--pink)) border-box;
  background-origin: border-box;
  background-clip: border-box;
  animation: avatar-ring-spin 4s linear infinite, avatar-ring-pulse 2.4s ease-in-out infinite;
}
.match-card-photos img,
.pairing-cell img,
.modal-pair-thumb img,
.avatar-ring-subtle {
  --avatar-ring-angle: 0deg;
  border-color: transparent;
  background: conic-gradient(from var(--avatar-ring-angle), var(--pink), var(--purple), var(--pink)) border-box;
  background-origin: border-box;
  background-clip: border-box;
  animation: avatar-ring-spin 4s linear infinite;
}
@keyframes avatar-ring-spin {
  to { --avatar-ring-angle: 360deg; }
}
@keyframes avatar-ring-pulse {
  0%, 100% { box-shadow: 0 0 20px rgba(74, 144, 217, 0.35); }
  50% { box-shadow: 0 0 32px rgba(47, 102, 144, 0.55); }
}
@media (prefers-reduced-motion: reduce) {
  .header-search-result img, .recent-item img, .pairing-cell img, .supporter-row img,
  .person-tile img, .selected-preview img, .modal-pair-thumb img, .admin-reference-chip img,
  .reference-result img, .game-leaderboard-avatar, .friends-row-avatar, .drop-submission-meta img,
  .explore-post-header img, .avatar-ring, .avatar-ring-loud,
  .top-pair-photos img, .match-card-photos img, .match-hero-photos img, .avatar-ring-subtle {
    animation: none;
  }
}

/* ---------- Dashboard: profile photo upload ---------- */
.profile-photo-row { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.profile-avatar-preview {
  width: 88px; height: 88px;
  border-radius: 50%;
  object-fit: cover;
  cursor: pointer;
  flex-shrink: 0;
}
.profile-photo-actions { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.profile-photo-actions > div { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

/* Below 760px, center this the same way profile.php's own .match-hero
   already centers itself — a side-by-side row reads fine on desktop but
   feels lopsided once it wraps to a narrow single column. */
@media (max-width: 760px) {
  .profile-photo-row { flex-direction: column; text-align: center; }
  .profile-photo-actions { align-items: center; }
  .profile-photo-actions > div { justify-content: center; }
  .profile-photo-note { text-align: center; }
}

/* ---------- Gallery — photos + videos combined (GALLERY_FREE_LIMIT+, dashboard.php + profile.php) ---------- */
.gallery-name-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
  flex-wrap: wrap;
}
.gallery-name-display { font-weight: 700; color: var(--text); }
.gallery-name-edit { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.gallery-name-edit input {
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  font-size: 16px; /* iOS Safari zoom-on-focus prevention, same as .form-field inputs */
  max-width: 220px;
}
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
  gap: 10px;
}
.gallery-item { position: relative; aspect-ratio: 1; border-radius: var(--radius-sm); overflow: hidden; }
.gallery-item img, .gallery-item video { width: 100%; height: 100%; object-fit: cover; cursor: pointer; display: block; }
.gallery-video-badge {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 30px; height: 30px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  pointer-events: none;
}
.gallery-remove-btn {
  position: absolute;
  top: 4px; right: 4px;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  cursor: pointer;
}
.gallery-remove-btn:hover { background: rgba(0, 0, 0, 0.8); }
.gallery-add-tile {
  aspect-ratio: 1;
  border: 2px dashed var(--border);
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-soft);
  cursor: pointer;
  font-size: 1.4rem;
}
.gallery-add-tile:hover { border-color: var(--purple); color: var(--purple); }
.gallery-add-tile.is-uploading { opacity: 0.5; pointer-events: none; }

/* profile.php caps the visible gallery grid at 5 photos — items beyond
   that get this class (still in the DOM so the lightbox's "View All" can
   still show every photo, just not rendered as a 6th+ visible tile). */
.gallery-item.is-hidden { display: none; }
.gallery-view-all-tile {
  aspect-ratio: 1;
  border: none;
  border-radius: var(--radius-sm);
  background: var(--gradient-brand);
  color: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  cursor: pointer;
  font-size: 0.8rem;
  font-weight: 700;
  text-align: center;
  line-height: 1.3;
  text-decoration: none;
}
.gallery-view-all-tile i { font-size: 1.2rem; }
.gallery-view-all-tile:hover { opacity: 0.9; }

/* Multi-select delete (dashboard.php/gallery.php, owner only) — "Select"
   toggles .is-select-mode on the grid, which swaps the always-there
   per-item (x) remove button for a checkbox and disables the lightbox
   click on each item in favor of toggling selection instead. */
.gallery-select-checkbox {
  position: absolute;
  top: 4px; left: 4px;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.4);
  border: 2px solid rgba(255, 255, 255, 0.8);
  color: transparent;
  display: none;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  cursor: pointer;
  padding: 0;
}
.gallery-grid.is-select-mode .gallery-select-checkbox { display: flex; }
.gallery-grid.is-select-mode .gallery-remove-btn { display: none; }
.gallery-item.is-selected .gallery-select-checkbox { background: var(--purple); border-color: var(--purple); color: #fff; }
.gallery-item.is-selected { outline: 3px solid var(--purple); outline-offset: -3px; }

.gallery-toolbar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-top: 12px; }
.gallery-bulk-actions-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  background: var(--bg-soft, rgba(0, 0, 0, 0.03));
  font-size: 0.88rem;
  font-weight: 700;
}

/* Multiple-galleries view (dashboard.php, owner only, once
   api/create_gallery.php has been used at least once) — a box per
   gallery instead of a flat photo grid. */
.gallery-boxes-grid { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); }
.gallery-box-tile {
  position: relative;
  aspect-ratio: 1;
  border-radius: var(--radius-sm);
  overflow: hidden;
  display: block;
  color: inherit;
  text-decoration: none;
}
.gallery-box-tile img, .gallery-box-tile video { width: 100%; height: 100%; object-fit: cover; display: block; }
.gallery-box-empty {
  width: 100%; height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-alt, rgba(0, 0, 0, 0.05));
  color: var(--text-soft);
  font-size: 1.8rem;
}
.gallery-box-overlay {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 6px 8px;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.75), transparent);
  color: #fff;
}
.gallery-box-overlay strong { display: block; font-size: 0.82rem; line-height: 1.2; }
.gallery-box-overlay span { font-size: 0.72rem; opacity: 0.85; }

/* Public/Friends Only visibility toggle — per-item (bottom-right of
   .gallery-item, the only free corner: top-right is .gallery-remove-btn,
   top-left is .gallery-select-checkbox, dead-center is
   .gallery-video-badge) and per-gallery-box (top-left of .gallery-box-tile,
   free since only .gallery-box-overlay/its own delete button — top-right —
   are already placed there). Purple fill (var(--purple), same as the
   .btn-icon.is-active accent used for the gallery-name-row's own toggle)
   flags "Friends Only" at a glance; the default dark-translucent fill
   (matching .gallery-remove-btn) reads as "Public". */
.gallery-visibility-btn {
  position: absolute;
  bottom: 4px; right: 4px;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.65rem;
  cursor: pointer;
  padding-top: 4px;
}
.gallery-visibility-btn:hover { background: rgba(0, 0, 0, 0.8); }
.gallery-visibility-btn[data-visibility="friends"] { background: var(--purple); }
.gallery-grid.is-select-mode .gallery-visibility-btn { display: none; }
@media (max-width: 760px) {
  .gallery-grid{ max-width: 480px; }
}

.gallery-box-visibility-btn {
  position: absolute;
  top: 4px; left: 4px;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.65rem;
  cursor: pointer;
}
.gallery-box-visibility-btn:hover { background: rgba(0, 0, 0, 0.8); }
.gallery-box-visibility-btn[data-visibility="friends"] { background: var(--purple); }

.gallery-create-form { display: flex; gap: 8px; align-items: flex-start; flex-wrap: wrap; margin-top: 14px; }
.gallery-create-form input {
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  font-size: 16px; /* iOS Safari zoom-on-focus prevention */
  min-width: 220px;
}

/* Multi-file upload progress (dashboard.php/gallery.php) — a plain
   file-count bar ("Uploading 2 of 5…"), not byte-level per-file progress:
   files already upload one at a time in sequence (see wireGallery()), so
   a completed-count / total is both accurate and far simpler than wiring
   XHR upload-progress events for something this size. */
.gallery-upload-progress { margin-top: 10px; }
.gallery-upload-progress-track {
  height: 6px;
  border-radius: 3px;
  background: var(--border);
  overflow: hidden;
}
.gallery-upload-progress-bar {
  height: 100%;
  width: 0%;
  background: var(--gradient-brand);
  transition: width 0.25s ease;
}
.gallery-upload-progress span {
  display: block;
  margin-top: 4px;
  font-size: 0.78rem;
  color: var(--text-soft);
}

/* Text posts (dashboard.php's composer / profile.php's "Posts" card). */
.text-post-item { padding-bottom: 24px; border-bottom: 1px solid var(--border); }
.text-post-item:last-child { border-bottom: none; padding-bottom: 0; }

/* Editing your own post (dashboard.php's "Create a Post" card). */
.text-post-view .btn { margin-top: 8px; }
.text-post-edit-form textarea { width: 100%; resize: vertical; min-height: 70px; }

/* Comments on a post (profile.php's "Posts" card). */
.post-comments { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.post-comment-list { display: flex; flex-direction: column; gap: 10px; }
.post-comment-list:not(:empty) { margin-bottom: 10px; }
.post-comment { display: flex; gap: 8px; align-items: flex-start; }
.post-comment-avatar { flex-shrink: 0; }
.post-comment-avatar img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; display: block; }
.post-comment-body { flex: 1; background: var(--comment-bg); border-radius: var(--radius-sm); padding: 6px 12px; line-height: 1.4;}
.post-comment-name { font-weight: 700; font-size: 0.82rem; color: inherit; text-decoration: none; display: inline-block; }
.post-comment-name:hover { color: var(--purple); }
.post-comment-body p { font-size: 0.88rem; color: var(--text-muted); margin: 0; white-space: pre-wrap; }
.post-comment-toggle-btn {
  background: none;
  border: none;
  color: var(--text-soft);
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
  padding: 4px 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.post-comment-toggle-btn:hover { color: var(--purple); }

/* Heart button on a match comment (match.php) — same visual weight as
   .post-comment-toggle-btn above, just its own color states. */
.comment-heart-btn {
  background: none;
  border: none;
  color: var(--text-soft);
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
  padding: 4px 0 0;
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.comment-heart-btn:hover { color: var(--red); }
.comment-heart-btn.is-hearted { color: var(--red); }
.comment-heart-btn:disabled { opacity: 0.6; cursor: default; }

/* "View more comments" batching (js/main.js's wireCommentBatching()) —
   inserted right after .post-comment-list, before the Comment toggle. */
.post-comment-view-more-btn {
  background: none;
  border: none;
  color: var(--purple);
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
  padding: 4px 0;
  display: block;
}
.post-comment-view-more-btn:hover { text-decoration: underline; }

.post-comment-form { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.post-comment-input { width: 100%; min-height: 64px; resize: vertical; }

/* Editing your own comment (profile.php's "Posts" card). */
.post-comment-edit-btn {
  background: none;
  border: none;
  color: var(--text-soft);
  font-size: 0.76rem;
  font-weight: 700;
  cursor: pointer;
  padding: 4px 0 0;
}
.post-comment-edit-btn:hover { color: var(--purple); }
.post-comment-edit-form { margin-top: 6px; }
.post-comment-edit-input { width: 100%; min-height: 50px; resize: vertical; }

/* Emoji picker (see wireEmojiPicker()/wireAllEmojiPickers() in js/main.js)
   — shared by every comment/message/text-post composer in the app. */
.emoji-picker-trigger {
  background: none;
  border: none;
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
  color: var(--text-muted);
  padding: 4px 6px;
  border-radius: var(--radius-sm);
}
.emoji-picker-trigger:hover { color: var(--purple); background: var(--surface-alt); }
.emoji-picker-panel {
  position: fixed;
  z-index: 500;
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  gap: 2px;
  padding: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  max-width: 240px;
}
.emoji-picker-option {
  background: none;
  border: none;
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
  padding: 4px;
  border-radius: var(--radius-sm);
}
.emoji-picker-option:hover { background: var(--surface-alt); }

/* Briefly flashed on the comment a "commented on your post" notification
   link jumps to (see scrollToFocusedComment() in js/profile-page.js) — a
   box-shadow/background pair rather than padding/border, so it can't
   nudge layout while animating. */
.post-comment.is-highlighted {
  border-radius: var(--radius-sm);
  animation: post-comment-highlight 2.5s ease;
}
@keyframes post-comment-highlight {
  0% { background: rgba(74, 144, 217, 0.12); box-shadow: 0 0 0 6px rgba(74, 144, 217, 0.12); }
  100% { background: transparent; box-shadow: 0 0 0 0 rgba(74, 144, 217, 0); }
}

/* ---------- Photo lightbox navigation (arrows + swipe target) ---------- */
.lightbox-viewer { position: relative; touch-action: pan-y; }
.lightbox-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px; height: 40px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.2);
  color: #fff;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 5;
}
.lightbox-nav:hover { background: rgba(0, 0, 0, 0.75); }
.lightbox-prev { left: 8px; }
.lightbox-next { right: 8px; }
.lightbox-nav.is-hidden { display: none; }

/* "View Full Photo/Video" CTA (js/main.js's updateLightboxImage()) — links
   to photo.php for the current lightbox item, when it corresponds to a
   real gallery_photos row (hidden via .is-hidden otherwise, e.g. a hero
   avatar/dashboard preview/text-post image). .modal-box has padding:0 for
   this specific modal (gallery.php etc.), so this needs its own spacing. */
.lightbox-detail-link {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 10px 16px;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--purple);
  text-decoration: none;
}
.lightbox-detail-link:hover { text-decoration: underline; }
.lightbox-detail-link.is-hidden { display: none; }

/* ---------- Memory Match mini-game (game.php) ---------- */
.game-wrap { max-width: 640px; margin: 0 auto 48px; text-align: center; position: relative; }
.game-hud { display: flex; justify-content: center; align-items: center; gap: 24px; margin-bottom: 20px; flex-wrap: wrap; }
.game-hud-stat { display: flex; align-items: center; gap: 8px; font-weight: 700; color: var(--text); }
.game-hud-stat i { color: var(--pink); }
#game-lives { display: flex; gap: 4px; font-size: 1.1rem; }
#game-lives i { color: var(--pink); }
#game-lives i.game-life-lost { color: var(--border); }

.game-grid { display: grid; gap: 10px; margin: 0 auto; }

.game-card { position: relative; aspect-ratio: 1; border: none; background: none; padding: 0; cursor: pointer; perspective: 600px; }
.game-card-face {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  backface-visibility: hidden;
  transition: transform 0.4s;
  font-size: 1.6rem;
}
.game-card-back { background: var(--gradient-brand); color: #fff; transform: rotateY(0deg); }
.game-card-front { background: var(--surface); border: 2px solid var(--border); box-shadow: var(--shadow-sm); transform: rotateY(-180deg); }
.game-card.is-flipped .game-card-back, .game-card.is-matched .game-card-back { transform: rotateY(180deg); }
.game-card.is-flipped .game-card-front, .game-card.is-matched .game-card-front { transform: rotateY(0deg); }
.game-card.is-matched .game-card-front { border-color: var(--success); opacity: 0.6; }

/* An overlay on top of #game-hud/#game-grid (both stay visible underneath,
   never display:none) rather than a panel that replaces them — the blur +
   translucent background is a deliberate "sneak peek" of the real board
   through it, for both the pre-game start screen and the final game-over
   screen (which naturally has real, revealed/matched cards behind it from
   the run that just ended). */
.game-over-panel {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 32px 20px;
  border-radius: 8px;
  background: rgba(251, 249, 252, 0.82);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.game-over-panel.is-hidden { display: none; }
.game-over-panel h2 { font-size: 1.6rem; font-weight: 800; margin-bottom: 8px; }
.game-over-panel p { color: var(--text-muted); margin-bottom: 16px; max-width: 320px; }

.game-leaderboard-section { margin: 48px auto 60px; max-width: 720px; }
.game-leaderboard { border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; background: var(--surface); }
.game-leaderboard-row { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--border); }
.game-leaderboard-row:last-child { border-bottom: none; }
.game-leaderboard-rank { font-weight: 800; color: var(--text-soft); width: 32px; flex-shrink: 0; }
.game-leaderboard-avatar { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.game-leaderboard-name { font-weight: 700; flex: 1; text-align: left; }
.game-leaderboard-level { color: var(--text-muted); font-size: 0.85rem; }
.game-leaderboard-stars { font-weight: 800; color: var(--text); display: flex; align-items: center; gap: 6px; margin-left: 12px; }

@media (max-width: 760px) {
  .game-grid { gap: 6px; }
  .game-card-face { font-size: 1.1rem; border-radius: 8px; }
  .game-hud { gap: 14px; font-size: 0.9rem; }
  .game-leaderboard-row { gap: 8px; padding: 10px 12px; }
  .game-leaderboard-level { display: none; }
}

/* ---------- Flip mini-game (flip.php) ----------
   Purely for entertainment, no Stars involved — see game.php above for the
   Stars-earning Memory Match. The game itself is a separate, self-contained
   document (games/flip.html) loaded here via iframe, so this is just the
   card/frame chrome around it, not the game's own styling. */
.flip-game-wrap { max-width: 420px; margin: 0 auto 56px; text-align: center; }
.flip-game-frame {
  aspect-ratio: 9 / 16;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-lg);
  border: 1px solid var(--border);
  background: var(--surface-alt);
  margin-bottom: 16px;
}
.flip-game-frame iframe { width: 100%; height: 100%; border: 0; display: block; }
.flip-fullscreen-btn { display: inline-flex; align-items: center; gap: 8px; }

@media (max-width: 480px) {
  .flip-game-wrap { max-width: 100%; }
  .flip-game-frame { aspect-ratio: auto; height: 72vh; border-radius: var(--radius-md); }
}

/* ---------- Friends (friends.php) ---------- */
.friends-section { margin-bottom: 32px; }
.friends-section h2 { font-size: 1.1rem; margin-bottom: 14px; }
.friends-row { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
.friends-row-avatar { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.friends-row-info { flex: 1; min-width: 0; color: inherit; text-decoration: none; }
.friends-row-info span { display: block; color: var(--text-muted); font-size: 0.82rem; }
.friends-row-actions { display: flex; gap: 8px; flex-shrink: 0; }

@media (max-width: 760px) {
  .friends-row { flex-wrap: wrap; gap: 4px; padding: 14px 24px; }
  .friends-row-actions { width: 100%; justify-content: flex-end; }
}

/* ---------- Member discovery (members.php, friends.php's preview) ---------- */
.member-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 16px; }
.member-card {
  display: block;
  background: var(--surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: 20px 14px;
  text-align: center;
  text-decoration: none;
  color: inherit;
}
.member-card img { width: 64px; height: 64px; border-radius: 50%; object-fit: cover; margin: 0 auto 10px; }
.member-card strong { display: block; font-size: 0.95rem; }
.member-card span { display: block; color: var(--text-muted); font-size: 0.78rem; margin-top: 2px; }
.member-card-tags { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; margin-top: 10px; }

/* ---------- Drop Challenges (create-drop.php / drop.php) ---------- */
.progress-bar-track {
  width: 100%;
  max-width: 280px;
  height: 8px;
  margin: 0 auto;
  border-radius: var(--radius-pill);
  background: var(--surface-alt);
  overflow: hidden;
}
.progress-bar-fill {
  height: 100%;
  width: 0%;
  background: var(--gradient-brand);
  border-radius: var(--radius-pill);
  transition: width 0.2s ease;
}

/* Shared inline button spinner (dashboard.php's "Create a Post" widget —
   see wireTextPostWidget()/wireTextPostEditForms() in js/dashboard-page.js)
   — a conic-gradient ring in the same pink→purple brand colors as
   --gradient-brand, masked down to a ring via a radial-gradient so only
   the outline animates rather than a filled disc. Any button showing this
   should also set aria-busy="true" and keep its label text next to it
   (not replace it), so screen readers and sighted users alike still know
   what's in progress. Pair with .btn.is-loading below — .btn-primary's own
   background is that same brand gradient, so the ring would nearly vanish
   against it without swapping to a neutral background first. */
@keyframes btn-spinner-rotate { to { transform: rotate(360deg); } }
.btn-spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  margin-right: 8px;
  vertical-align: -2px;
  border-radius: 50%;
  background: conic-gradient(from 0deg, var(--pink), var(--purple), var(--pink));
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #fff calc(100% - 2.5px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #fff calc(100% - 2.5px));
  animation: btn-spinner-rotate 0.7s linear infinite;
}
.btn.is-loading {
  background: var(--surface-alt) !important;
  color: var(--text-muted) !important;
  box-shadow: none !important;
  cursor: wait;
}
/* For a <label>-as-button file picker (e.g. "Add/Replace Photo/Video") —
   a <label> has no native disabled state, so this is the visual half of
   disabling it; the file input it's `for=` is also set .disabled in JS,
   which is what actually stops the picker from opening. */
.btn.is-disabled { opacity: 0.5; pointer-events: none; cursor: not-allowed; }

.share-link-row { display: flex; gap: 8px; margin-bottom: 8px; }
.share-link-row input {
  flex: 1;
  min-width: 0;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  border: 2px solid var(--border);
  font-family: inherit;
  font-size: 16px;
  background: var(--surface-alt);
  color: var(--text);
}
@media (max-width: 500px) {
  .share-link-row { flex-direction: column; }
}
.drop-submission-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
.drop-submission-card { text-align: center; }
.drop-submission-card video { width: 100%; aspect-ratio: 9 / 16; object-fit: cover; border-radius: var(--radius-md); background: #000; }
.drop-submission-meta { display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 8px; }
.drop-submission-meta img { width: 24px; height: 24px; border-radius: 50%; object-fit: cover; }
.drop-submission-meta a { font-size: 0.85rem; font-weight: 700; color: inherit; text-decoration: none; }

/* ---------- Drop Messages — admin review (mudrethope.php) ---------- */
.admin-drop-row { flex-direction: column; align-items: stretch; gap: 12px; }
.admin-drop-row-head { display: flex; align-items: center; flex-wrap: wrap; gap: 16px; }
.admin-drop-submissions { display: flex; flex-wrap: wrap; gap: 10px; padding-top: 10px; border-top: 1px solid var(--border); }
.admin-drop-submission-chip { display: flex; flex-direction: column; align-items: center; gap: 4px; width: 90px; }
.admin-drop-submission-chip video { width: 90px; height: 120px; object-fit: cover; border-radius: var(--radius-sm); background: #000; }
.admin-drop-submission-chip span { font-size: 0.72rem; color: var(--text-muted); }
/* Drop Messages / Content Review tabs only (data-dm-toggle-head /
   data-content-toggle-head) — their rows start collapsed by default. */
.admin-drop-row-head[data-dm-toggle-head],
.admin-drop-row-head[data-content-toggle-head] { cursor: pointer; }
.admin-drop-toggle-icon { color: var(--text-soft); transition: transform 0.15s ease; }
.admin-drop-row-head[data-dm-toggle-head].is-expanded .admin-drop-toggle-icon,
.admin-drop-row-head[data-content-toggle-head].is-expanded .admin-drop-toggle-icon { transform: rotate(90deg); }
.admin-drop-submissions.is-collapsed { display: none; }

/* ---------- Explore feed (explore.php) — mixed video + gallery posts,
   infinite scroll. Container-width on desktop (contained within a narrower
   column than the page's own 1180px .container, since these are 9:16 clips
   — full desktop width would look wrong), full-width edge-to-edge cards on
   mobile. Not a literal one-item-fills-the-viewport snap feed — that only
   really works for a single content type, and this feed is deliberately
   mixed (video + gallery), so it's a normal-scrolling feed instead. ---------- */
.explore-feed { display: flex; flex-direction: column; gap: 20px; max-width: 480px; margin: 0 auto; }
.explore-post { background: var(--surface); border-radius: var(--radius-md); box-shadow: var(--shadow-sm); overflow: hidden; }
.explore-post-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 14px; }
.explore-post-header-main { display: flex; align-items: center; gap: 10px; min-width: 0; }
.explore-post-header img { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; }
.explore-post-header a { font-weight: 700; color: var(--text); text-decoration: none; }
.explore-post-header .explore-post-meta { color: var(--text-muted); font-size: 0.78rem; }
/* Post/match date — upper right of the card, same row as the avatar/name so
   it never needs its own extra row. Shared by explore.php/feed.php's
   JS-rendered cards and profile.php's server-rendered post cards (all three
   reuse this exact .explore-post-header markup). */
.explore-post-date { color: var(--text-soft); font-size: 0.72rem; white-space: nowrap; flex-shrink: 0; cursor: pointer; }
.explore-post-video-wrap { position: relative; width: 100%; aspect-ratio: 9 / 16; background: #000; }
.explore-post-video-wrap video { width: 100%; height: 100%; object-fit: cover; display: block; }
.explore-post-mute-btn {
  position: absolute; bottom: 12px; right: 12px;
  background: rgba(0, 0, 0, 0.55); color: #fff; border: none; border-radius: 50%;
  width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.explore-post-caption { padding: 10px 14px 0; color: var(--text); font-size: 0.85rem; white-space: pre-wrap; }

/* profile.php's "Posts" card reuses the exact .explore-post card look above
   (same header/caption/image classes) instead of its own — see the
   .text-post-item/.explore-post split this replaced: posts used to be
   stacked inside one shared .detail-card, separated only by a border line;
   each is now its own card, properly separated by gap like the Explore
   feed, with the comment thread (unchanged functionality/markup) as
   padded content within that same card. */
.profile-posts-heading { font-size: 1.1rem; margin-bottom: 14px; }
.profile-posts-list { display: flex; flex-direction: column; gap: 16px; margin-bottom: 20px; }
.explore-post .post-comments { padding: 12px 14px 14px; }

/* Fact/roast cards (see EXPLORE_FACTS_* in includes/config.php) — same
   card shell as a real post for the plain (non-flippable) variant, but the
   gradient icon + "MatchUply Roasts" label stand in for a person's
   avatar/name so it's never mistaken for a real post. A fact written as a
   rhetorical question instead renders as a tap-to-flip card: the outer
   element is just a 3D "scene" (no card chrome of its own — the two faces
   below carry that), since giving both the scene and its faces the same
   border/shadow would visibly double up mid-rotation. */
.explore-post.explore-fact-post { border: 1px dashed var(--border); }
.explore-fact-icon {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--gradient-brand);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.95rem;
  flex-shrink: 0;
}
.explore-fact-label { font-weight: 700; color: var(--text); }
.explore-fact-post { animation: avatar-ring-pulse 2.4s ease-in-out infinite; }
.explore-fact-post .explore-post-caption { padding-bottom: 14px; font-style: italic; }

.explore-fact-post[data-fact-flippable] { cursor: pointer; perspective: 1200px; }
.explore-fact-flip-inner {
  position: relative;
  transition: transform 0.5s ease;
  transform-style: preserve-3d;
}
.explore-fact-post.is-flipped .explore-fact-flip-inner { transform: rotateY(180deg); }
.explore-fact-face {
  background: var(--surface);
  border: 1px dashed var(--border);
  box-shadow: var(--shadow-sm);
  backface-visibility: hidden;
}
.explore-fact-face-back { position: absolute; inset: 0; transform: rotateY(180deg); }
.explore-fact-tap-hint {
  padding: 0 14px 12px;
  font-size: 0.72rem;
  font-weight: 700;
  font-style: normal;
  color: var(--purple);
  display: flex;
  align-items: center;
  gap: 6px;
}
.explore-post-actions { display: flex; gap: 14px; padding: 10px 14px; align-items: center; }
.explore-post-actions button { background: none; border: none; cursor: pointer; color: var(--text-muted); font-size: 0.9rem; display: flex; align-items: center; gap: 6px; }
.explore-post-gallery-name { padding: 0 14px 10px; font-weight: 700; }
.explore-post-text-image { width: 100%; max-height: 480px; object-fit: cover; cursor: pointer; display: block; }
.explore-post-gallery-thumbs { display: grid; grid-template-columns: repeat(2, 1fr); gap: 2px; }
.explore-post-gallery-thumbs img, .explore-post-gallery-thumbs video { width: 100%; aspect-ratio: 1; object-fit: cover; cursor: pointer; display: block; }
.explore-post-gallery-more { position: relative; }
.explore-post-gallery-more::after {
  content: "+" attr(data-more); position: absolute; inset: 0; background: rgba(0, 0, 0, 0.5);
  color: #fff; display: flex; align-items: center; justify-content: center; font-size: 1.3rem; font-weight: 700;
}
.explore-sentinel { height: 1px; }
@media (max-width: 760px) {
  .explore-feed { max-width: 100%; gap: 0; }
  .explore-post-header{ padding: 12px 0; }
  .explore-post { border-radius: var(--radius-md); box-shadow: none; padding: 12px 20px; }
  .explore-fact-post{ margin-bottom: 8px;}
  .explore-fact-post .explore-post-header{ padding: 12px; }
  .explore-post-text-image{ max-height: calc(100vw - 80px); }
  .explore-post-caption, .explore-post-actions{ padding: 10px 0; }
  .explore-fact-flip-inner .explore-post-caption{ padding: 10px 14px; }
  .explore-post .match-card-stats{ justify-content: space-evenly; }
}

code{
  word-break: break-all;
}

/* ==========================================================================
   Falling snow background (dashboard.php's "Background Theme" preference,
   users.background_theme = 'snowflakes' — see includes/head.php and
   BACKGROUND_THEMES in includes/config.php). Entirely CSS-driven: the PHP
   side only decides whether to output a fixed set of 24 identical <span>
   elements at all; every bit of visual variety below (position, size,
   speed, start delay) comes from these nth-child rules, not inline styles
   or JS. Sits above normal page content — the point is flakes visibly
   drift over text/images, same as any "let it snow" site effect — but
   below the header/modals (z-index 90+/500 elsewhere in this file), and
   pointer-events:none throughout so it can never intercept a click.
   ========================================================================== */
.snowfall-overlay {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 40;
}
.snowflake {
  position: absolute;
  top: -10%;
  /* This app is light-theme-only (see CLAUDE.md) — plain white text on a
     mostly white/pale-blue background was nearly invisible (confirmed via
     a real screenshot while building this). A soft ice-blue fill plus a
     darker blue "outline" (stacked text-shadow offsets, the standard
     faux-stroke trick — box-shadow doesn't apply to text glyphs) reads
     clearly against this app's light surfaces without looking harsh. */
  color: #cfe8ff;
  text-shadow:
    -1px -1px 0 rgba(60, 110, 180, 0.55),
    1px -1px 0 rgba(60, 110, 180, 0.55),
    -1px 1px 0 rgba(60, 110, 180, 0.55),
    1px 1px 0 rgba(60, 110, 180, 0.55),
    0 2px 5px rgba(40, 80, 140, 0.35);
  user-select: none;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  /* animation-name/-duration/-delay are set per-flake below (each one is
     fully unique — see the nth-child rules), not here. */
}
@keyframes snowfall-a { 0% { transform: translateY(0) translateX(0); opacity: 0; } 8% { opacity: var(--peak-op, 1); } 50% { transform: translateY(55vh) translateX(22px); } 92% { opacity: var(--peak-op, 1); } 100% { transform: translateY(115vh) translateX(-8px); opacity: 0; } }
@keyframes snowfall-b { 0% { transform: translateY(0) translateX(0); opacity: 0; } 8% { opacity: var(--peak-op, 1); } 50% { transform: translateY(55vh) translateX(-16px); } 92% { opacity: var(--peak-op, 1); } 100% { transform: translateY(115vh) translateX(10px); opacity: 0; } }
@keyframes snowfall-c { 0% { transform: translateY(0) translateX(0); opacity: 0; } 8% { opacity: var(--peak-op, 1); } 50% { transform: translateY(55vh) translateX(6px); } 92% { opacity: var(--peak-op, 1); } 100% { transform: translateY(115vh) translateX(-20px); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .snowflake { animation: none; opacity: 0; }
}

/* Every flake gets its OWN left position, size, opacity, drift pattern,
   duration, AND delay — no two share the same full combination. Earlier
   this reused just 6 duration/delay pairs across 24 flakes (a repeating
   cycle), so every 4th flake was on an identical clock and visibly fell
   in lockstep with the others sharing its pair. Negative delays (rather
   than 0/positive) mean every flake starts already mid-fall on page load
   instead of all beginning at the top together and needing time to
   spread out. */
.snowflake:nth-child(1)  { left: 1%;  font-size: 10px; --peak-op: 0.75; animation-name: snowfall-a; animation-duration: 12.4s; animation-delay: -2.1s; }
.snowflake:nth-child(2)  { left: 5%;  font-size: 13px; --peak-op: 0.85; animation-name: snowfall-b; animation-duration: 9.8s;  animation-delay: -6.4s; }
.snowflake:nth-child(3)  { left: 9%;  font-size: 8px; --peak-op: 0.6;  animation-name: snowfall-c; animation-duration: 16.2s; animation-delay: -0.9s; }
.snowflake:nth-child(4)  { left: 13%; font-size: 15px; --peak-op: 0.95; animation-name: snowfall-a; animation-duration: 11.1s; animation-delay: -9.3s; }
.snowflake:nth-child(5)  { left: 17%; font-size: 11px; --peak-op: 0.8;  animation-name: snowfall-b; animation-duration: 14.7s; animation-delay: -3.6s; }
.snowflake:nth-child(6)  { left: 21%; font-size: 14px; --peak-op: 0.7;  animation-name: snowfall-c; animation-duration: 8.5s;  animation-delay: -12.1s; }
.snowflake:nth-child(7)  { left: 25%; font-size: 9px; --peak-op: 0.9;  animation-name: snowfall-a; animation-duration: 18.3s; animation-delay: -1.4s; }
.snowflake:nth-child(8)  { left: 29%; font-size: 12px; --peak-op: 0.65; animation-name: snowfall-b; animation-duration: 10.6s; animation-delay: -7.8s; }
.snowflake:nth-child(9)  { left: 33%; font-size: 10px; --peak-op: 1;    animation-name: snowfall-c; animation-duration: 15.9s; animation-delay: -4.2s; }
.snowflake:nth-child(10) { left: 37%; font-size: 14px; --peak-op: 0.78; animation-name: snowfall-a; animation-duration: 9.2s;  animation-delay: -13.5s; }
.snowflake:nth-child(11) { left: 41%; font-size: 9px; --peak-op: 0.88; animation-name: snowfall-b; animation-duration: 13.4s; animation-delay: -0.3s; }
.snowflake:nth-child(12) { left: 45%; font-size: 15px; --peak-op: 0.6;  animation-name: snowfall-c; animation-duration: 19.7s; animation-delay: -8.6s; }
.snowflake:nth-child(13) { left: 49%; font-size: 12px; --peak-op: 0.92; animation-name: snowfall-a; animation-duration: 11.8s; animation-delay: -5.1s; }
.snowflake:nth-child(14) { left: 53%; font-size: 13px; --peak-op: 0.7;  animation-name: snowfall-b; animation-duration: 8.9s;  animation-delay: -11.4s; }
.snowflake:nth-child(15) { left: 57%; font-size: 8px; --peak-op: 0.83; animation-name: snowfall-c; animation-duration: 16.8s; animation-delay: -2.7s; }
.snowflake:nth-child(16) { left: 61%; font-size: 16px; --peak-op: 0.68; animation-name: snowfall-a; animation-duration: 10.3s; animation-delay: -14.2s; }
.snowflake:nth-child(17) { left: 65%; font-size: 10px; --peak-op: 0.97; animation-name: snowfall-b; animation-duration: 13.9s; animation-delay: -6.9s; }
.snowflake:nth-child(18) { left: 69%; font-size: 14px; --peak-op: 0.72; animation-name: snowfall-c; animation-duration: 17.5s; animation-delay: -3.3s; }
.snowflake:nth-child(19) { left: 73%; font-size: 9px; --peak-op: 0.87; animation-name: snowfall-a; animation-duration: 9.6s;  animation-delay: -9.8s; }
.snowflake:nth-child(20) { left: 77%; font-size: 12px; --peak-op: 0.63; animation-name: snowfall-b; animation-duration: 14.2s; animation-delay: -1.6s; }
.snowflake:nth-child(21) { left: 81%; font-size: 10px; --peak-op: 0.93; animation-name: snowfall-c; animation-duration: 12.7s; animation-delay: -10.5s; }
.snowflake:nth-child(22) { left: 85%; font-size: 15px; --peak-op: 0.66; animation-name: snowfall-a; animation-duration: 18.9s; animation-delay: -4.7s; }
.snowflake:nth-child(23) { left: 89%; font-size: 9px; --peak-op: 0.9;  animation-name: snowfall-b; animation-duration: 11.4s; animation-delay: -7.2s; }
.snowflake:nth-child(24) { left: 93%; font-size: 14px; --peak-op: 0.77; animation-name: snowfall-c; animation-duration: 15.3s; animation-delay: -13.9s; }

/* ==========================================================================
   Bokeh Glow background (dashboard.php's "Background Theme" preference,
   users.background_theme = 'bokeh' — see includes/head.php and
   BACKGROUND_THEMES in includes/config.php). Soft blurred orbs in the
   site's own --pink/--purple brand gradient, drifting slowly upward —
   the "brand color" counterpart to Falling Snow, no icon glyph involved,
   just CSS shapes/gradients/blur. Same z-index/pointer-events posture as
   .snowfall-overlay (renders above page content, below header/modals,
   never intercepts a click), and the same "every orb gets its own full
   set of values, nothing shared" fix already applied there so no two
   orbs ever move in lockstep. */
.bokeh-overlay {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 40;
}
.bokeh-orb {
  position: absolute;
  bottom: -20%;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 35%, var(--pink), var(--purple));
  filter: blur(3px);
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}
@keyframes bokeh-a { 0% { transform: translate(0, 0) scale(0.9); opacity: 0; } 15% { opacity: var(--peak-op, 0.15); } 50% { transform: translate(24px, -55vh) scale(1.1); } 85% { opacity: var(--peak-op, 0.15); } 100% { transform: translate(-10px, -120vh) scale(0.85); opacity: 0; } }
@keyframes bokeh-b { 0% { transform: translate(0, 0) scale(1); opacity: 0; } 15% { opacity: var(--peak-op, 0.15); } 50% { transform: translate(-20px, -55vh) scale(0.9); } 85% { opacity: var(--peak-op, 0.15); } 100% { transform: translate(14px, -120vh) scale(1.15); opacity: 0; } }
@keyframes bokeh-c { 0% { transform: translate(0, 0) scale(1.05); opacity: 0; } 15% { opacity: var(--peak-op, 0.15); } 50% { transform: translate(10px, -55vh) scale(1); } 85% { opacity: var(--peak-op, 0.15); } 100% { transform: translate(-22px, -120vh) scale(0.95); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .bokeh-orb { animation: none; opacity: 0; }
}

.bokeh-orb:nth-child(1)  { left: 3%;  width: 60px;  height: 60px;  --peak-op: 0.14; animation-name: bokeh-a; animation-duration: 28s; animation-delay: -5s; }
.bokeh-orb:nth-child(2)  { left: 12%; width: 90px;  height: 90px;  --peak-op: 0.10; animation-name: bokeh-b; animation-duration: 34s; animation-delay: -18s; }
.bokeh-orb:nth-child(3)  { left: 20%; width: 45px;  height: 45px;  --peak-op: 0.18; animation-name: bokeh-c; animation-duration: 22s; animation-delay: -9s; }
.bokeh-orb:nth-child(4)  { left: 30%; width: 110px; height: 110px; --peak-op: 0.08; animation-name: bokeh-a; animation-duration: 40s; animation-delay: -2s; }
.bokeh-orb:nth-child(5)  { left: 38%; width: 55px;  height: 55px;  --peak-op: 0.20; animation-name: bokeh-b; animation-duration: 19s; animation-delay: -14s; }
.bokeh-orb:nth-child(6)  { left: 47%; width: 75px;  height: 75px;  --peak-op: 0.12; animation-name: bokeh-c; animation-duration: 31s; animation-delay: -22s; }
.bokeh-orb:nth-child(7)  { left: 55%; width: 95px;  height: 95px;  --peak-op: 0.09; animation-name: bokeh-a; animation-duration: 36s; animation-delay: -6s; }
.bokeh-orb:nth-child(8)  { left: 63%; width: 50px;  height: 50px;  --peak-op: 0.22; animation-name: bokeh-b; animation-duration: 17s; animation-delay: -11s; }
.bokeh-orb:nth-child(9)  { left: 70%; width: 130px; height: 130px; --peak-op: 0.07; animation-name: bokeh-c; animation-duration: 44s; animation-delay: -27s; }
.bokeh-orb:nth-child(10) { left: 78%; width: 65px;  height: 65px;  --peak-op: 0.16; animation-name: bokeh-a; animation-duration: 24s; animation-delay: -3s; }
.bokeh-orb:nth-child(11) { left: 85%; width: 85px;  height: 85px;  --peak-op: 0.11; animation-name: bokeh-b; animation-duration: 33s; animation-delay: -16s; }
.bokeh-orb:nth-child(12) { left: 90%; width: 40px;  height: 40px;  --peak-op: 0.24; animation-name: bokeh-c; animation-duration: 15s; animation-delay: -8s; }
.bokeh-orb:nth-child(13) { left: 95%; width: 100px; height: 100px; --peak-op: 0.10; animation-name: bokeh-a; animation-duration: 38s; animation-delay: -20s; }
.bokeh-orb:nth-child(14) { left: 8%;  width: 58px;  height: 58px;  --peak-op: 0.15; animation-name: bokeh-b; animation-duration: 21s; animation-delay: -12s; }

/* ==========================================================================
   Cherry Blossom background (users.background_theme = 'petals'). Petals
   fall, sway side to side, and rotate — same overlay/z-index/pointer-events
   posture as .snowfall-overlay, same "every element gets its own full,
   unique set of values" rule so nothing ever falls in visible lockstep. */
.petal-overlay {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 40;
}
.petal {
  position: absolute;
  top: -10%;
  color: var(--coral);
  text-shadow: 0 1px 3px rgba(214, 51, 108, 0.4);
  user-select: none;
  animation-timing-function: ease-in;
  animation-iteration-count: infinite;
}
@keyframes petal-a { 0% { transform: translateY(0) translateX(0) rotate(0deg); opacity: 0; } 8% { opacity: var(--peak-op, 1); } 30% { transform: translateY(34vh) translateX(26px) rotate(90deg); } 60% { transform: translateY(70vh) translateX(-14px) rotate(220deg); } 92% { opacity: var(--peak-op, 1); } 100% { transform: translateY(115vh) translateX(12px) rotate(340deg); opacity: 0; } }
@keyframes petal-b { 0% { transform: translateY(0) translateX(0) rotate(0deg); opacity: 0; } 8% { opacity: var(--peak-op, 1); } 30% { transform: translateY(34vh) translateX(-22px) rotate(-70deg); } 60% { transform: translateY(70vh) translateX(18px) rotate(-190deg); } 92% { opacity: var(--peak-op, 1); } 100% { transform: translateY(115vh) translateX(-10px) rotate(-310deg); opacity: 0; } }
@keyframes petal-c { 0% { transform: translateY(0) translateX(0) rotate(0deg); opacity: 0; } 8% { opacity: var(--peak-op, 1); } 30% { transform: translateY(34vh) translateX(10px) rotate(50deg); } 60% { transform: translateY(70vh) translateX(-20px) rotate(160deg); } 92% { opacity: var(--peak-op, 1); } 100% { transform: translateY(115vh) translateX(6px) rotate(280deg); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .petal { animation: none; opacity: 0; }
}

.petal:nth-child(1)  { left: 2%;  font-size: 16px; --peak-op: 0.75; animation-name: petal-a; animation-duration: 13.5s; animation-delay: -1.8s; }
.petal:nth-child(2)  { left: 6%;  font-size: 20px; --peak-op: 0.85; animation-name: petal-b; animation-duration: 10.2s; animation-delay: -7.1s; }
.petal:nth-child(3)  { left: 11%; font-size: 14px; --peak-op: 0.65; animation-name: petal-c; animation-duration: 17.4s; animation-delay: -3.4s; }
.petal:nth-child(4)  { left: 15%; font-size: 24px; --peak-op: 0.9;  animation-name: petal-a; animation-duration: 12.1s; animation-delay: -10.6s; }
.petal:nth-child(5)  { left: 19%; font-size: 18px; --peak-op: 0.7;  animation-name: petal-b; animation-duration: 15.8s; animation-delay: -4.9s; }
.petal:nth-child(6)  { left: 24%; font-size: 22px; --peak-op: 0.8;  animation-name: petal-c; animation-duration: 9.6s;  animation-delay: -13.2s; }
.petal:nth-child(7)  { left: 28%; font-size: 15px; --peak-op: 0.95; animation-name: petal-a; animation-duration: 19.3s; animation-delay: -2.5s; }
.petal:nth-child(8)  { left: 33%; font-size: 21px; --peak-op: 0.6;  animation-name: petal-b; animation-duration: 11.4s; animation-delay: -8.7s; }
.petal:nth-child(9)  { left: 37%; font-size: 17px; --peak-op: 0.88; animation-name: petal-c; animation-duration: 16.9s; animation-delay: -5.3s; }
.petal:nth-child(10) { left: 41%; font-size: 23px; --peak-op: 0.72; animation-name: petal-a; animation-duration: 10.7s; animation-delay: -14.8s; }
.petal:nth-child(11) { left: 46%; font-size: 16px; --peak-op: 0.83; animation-name: petal-b; animation-duration: 14.3s; animation-delay: -0.6s; }
.petal:nth-child(12) { left: 50%; font-size: 25px; --peak-op: 0.68; animation-name: petal-c; animation-duration: 18.6s; animation-delay: -9.9s; }
.petal:nth-child(13) { left: 54%; font-size: 19px; --peak-op: 0.93; animation-name: petal-a; animation-duration: 11.9s; animation-delay: -6.2s; }
.petal:nth-child(14) { left: 59%; font-size: 14px; --peak-op: 0.77; animation-name: petal-b; animation-duration: 15.1s; animation-delay: -12.4s; }
.petal:nth-child(15) { left: 63%; font-size: 22px; --peak-op: 0.86; animation-name: petal-c; animation-duration: 13.8s; animation-delay: -3.1s; }
.petal:nth-child(16) { left: 67%; font-size: 18px; --peak-op: 0.62; animation-name: petal-a; animation-duration: 17.2s; animation-delay: -11.5s; }
.petal:nth-child(17) { left: 72%; font-size: 26px; --peak-op: 0.91; animation-name: petal-b; animation-duration: 9.9s;  animation-delay: -7.6s; }
.petal:nth-child(18) { left: 76%; font-size: 15px; --peak-op: 0.79; animation-name: petal-c; animation-duration: 16.4s; animation-delay: -1.3s; }
.petal:nth-child(19) { left: 81%; font-size: 20px; --peak-op: 0.96; animation-name: petal-a; animation-duration: 12.6s; animation-delay: -15.7s; }
.petal:nth-child(20) { left: 85%; font-size: 17px; --peak-op: 0.71; animation-name: petal-b; animation-duration: 18.1s; animation-delay: -4.4s; }
.petal:nth-child(21) { left: 90%; font-size: 24px; --peak-op: 0.84; animation-name: petal-c; animation-duration: 10.4s; animation-delay: -9.2s; }
.petal:nth-child(22) { left: 94%; font-size: 21px; --peak-op: 0.67; animation-name: petal-a; animation-duration: 14.6s; animation-delay: -6.8s; }

/* ==========================================================================
   Twinkling Sky background (users.background_theme = 'twinkle'). The
   calmest option, deliberately: no translateX/translateY drift at all —
   each star is pinned at its own fixed top/left point and only pulses
   opacity/scale, via a --peak-op custom property (same technique
   .bokeh-orb already uses) so no two stars brighten in sync. */
.twinkle-overlay {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 40;
}
.twinkle-star {
  position: absolute;
  color: var(--gold);
  text-shadow: 0 0 4px rgba(245, 179, 1, 0.6);
  user-select: none;
  opacity: 0.12;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-name: twinkle-pulse;
}
@keyframes twinkle-pulse { 0%, 100% { opacity: 0.12; transform: scale(0.8); } 50% { opacity: var(--peak-op, 0.75); transform: scale(1.2); } }
@media (prefers-reduced-motion: reduce) {
  .twinkle-star { animation: none; opacity: 0.2; }
}

.twinkle-star:nth-child(1)  { top: 8%;  left: 5%;  font-size: 13px; --peak-op: 0.8;  animation-duration: 3.2s; animation-delay: -0.5s; }
.twinkle-star:nth-child(2)  { top: 15%; left: 22%; font-size: 18px; --peak-op: 0.65; animation-duration: 4.1s; animation-delay: -2.3s; }
.twinkle-star:nth-child(3)  { top: 6%;  left: 38%; font-size: 10px;  --peak-op: 0.9;  animation-duration: 2.8s; animation-delay: -1.1s; }
.twinkle-star:nth-child(4)  { top: 20%; left: 55%; font-size: 15px; --peak-op: 0.7;  animation-duration: 3.6s; animation-delay: -3.4s; }
.twinkle-star:nth-child(5)  { top: 12%; left: 70%; font-size: 11px;  --peak-op: 0.85; animation-duration: 4.5s; animation-delay: -0.9s; }
.twinkle-star:nth-child(6)  { top: 25%; left: 85%; font-size: 14px; --peak-op: 0.6;  animation-duration: 3.9s; animation-delay: -2.7s; }
.twinkle-star:nth-child(7)  { top: 30%; left: 10%; font-size: 16px; --peak-op: 0.75; animation-duration: 2.5s; animation-delay: -1.6s; }
.twinkle-star:nth-child(8)  { top: 35%; left: 30%; font-size: 10px;  --peak-op: 0.95; animation-duration: 4.8s; animation-delay: -3.9s; }
.twinkle-star:nth-child(9)  { top: 40%; left: 48%; font-size: 13px; --peak-op: 0.68; animation-duration: 3.1s; animation-delay: -0.3s; }
.twinkle-star:nth-child(10) { top: 45%; left: 63%; font-size: 15px; --peak-op: 0.82; animation-duration: 4.3s; animation-delay: -2.1s; }
.twinkle-star:nth-child(11) { top: 50%; left: 78%; font-size: 11px;  --peak-op: 0.72; animation-duration: 2.9s; animation-delay: -4.2s; }
.twinkle-star:nth-child(12) { top: 55%; left: 92%; font-size: 18px; --peak-op: 0.6;  animation-duration: 3.7s; animation-delay: -1.4s; }
.twinkle-star:nth-child(13) { top: 60%; left: 3%;  font-size: 14px; --peak-op: 0.88; animation-duration: 4.6s; animation-delay: -3.2s; }
.twinkle-star:nth-child(14) { top: 65%; left: 18%; font-size: 10px;  --peak-op: 0.65; animation-duration: 2.6s; animation-delay: -0.7s; }
.twinkle-star:nth-child(15) { top: 70%; left: 35%; font-size: 16px; --peak-op: 0.79; animation-duration: 4.0s; animation-delay: -2.9s; }
.twinkle-star:nth-child(16) { top: 5%;  left: 60%; font-size: 13px; --peak-op: 0.9;  animation-duration: 3.4s; animation-delay: -1.9s; }
.twinkle-star:nth-child(17) { top: 75%; left: 50%; font-size: 11px;  --peak-op: 0.7;  animation-duration: 4.9s; animation-delay: -0.4s; }
.twinkle-star:nth-child(18) { top: 80%; left: 68%; font-size: 15px; --peak-op: 0.83; animation-duration: 2.7s; animation-delay: -3.6s; }
.twinkle-star:nth-child(19) { top: 85%; left: 82%; font-size: 10px;  --peak-op: 0.6;  animation-duration: 3.8s; animation-delay: -2.4s; }
.twinkle-star:nth-child(20) { top: 18%; left: 95%; font-size: 14px; --peak-op: 0.92; animation-duration: 4.4s; animation-delay: -1.2s; }
.twinkle-star:nth-child(21) { top: 28%; left: 44%; font-size: 16px; --peak-op: 0.67; animation-duration: 3.0s; animation-delay: -4.5s; }
.twinkle-star:nth-child(22) { top: 38%; left: 8%;  font-size: 11px;  --peak-op: 0.77; animation-duration: 4.7s; animation-delay: -0.8s; }
.twinkle-star:nth-child(23) { top: 48%; left: 26%; font-size: 13px; --peak-op: 0.86; animation-duration: 2.4s; animation-delay: -2.6s; }
.twinkle-star:nth-child(24) { top: 58%; left: 58%; font-size: 15px; --peak-op: 0.63; animation-duration: 3.5s; animation-delay: -4.8s; }
.twinkle-star:nth-child(25) { top: 68%; left: 88%; font-size: 10px;  --peak-op: 0.94; animation-duration: 4.2s; animation-delay: -1.7s; }
.twinkle-star:nth-child(26) { top: 88%; left: 15%; font-size: 14px; --peak-op: 0.71; animation-duration: 2.3s; animation-delay: -3.0s; }

/* ==========================================================================
   Gentle Rain background (users.background_theme = 'rain'). Not a glyph —
   thin gradient bars falling fast with a slight diagonal drift, in the
   site's own blue brand tones (--pink is this app's blue, --purple its
   navy — see the palette note above .bokeh-orb). Kept thin/low-opacity/
   moderate-count on purpose ("gentle", not a dense downpour). */
.rain-overlay {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 40;
}
.raindrop {
  position: absolute;
  top: -10%;
  width: 2px;
  background: linear-gradient(to bottom, rgba(74, 144, 217, 0) 0%, rgba(74, 144, 217, 0.65) 45%, rgba(13, 59, 102, 0.8) 100%);
  border-radius: 2px;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
@keyframes rain-a { 0% { transform: translate(0, 0); opacity: 0; } 12% { opacity: var(--peak-op, 0.7); } 88% { opacity: var(--peak-op, 0.7); } 100% { transform: translate(-18px, 115vh); opacity: 0; } }
@keyframes rain-b { 0% { transform: translate(0, 0); opacity: 0; } 12% { opacity: var(--peak-op, 0.7); } 88% { opacity: var(--peak-op, 0.7); } 100% { transform: translate(18px, 115vh); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .raindrop { animation: none; opacity: 0; }
}

.raindrop:nth-child(1)  { left: 1%;  height: 18px; --peak-op: 0.65; animation-name: rain-a; animation-duration: 1.1s;  animation-delay: -0.3s; }
.raindrop:nth-child(2)  { left: 5%;  height: 24px; --peak-op: 0.75; animation-name: rain-b; animation-duration: 0.9s;  animation-delay: -0.7s; }
.raindrop:nth-child(3)  { left: 9%;  height: 20px; --peak-op: 0.55; animation-name: rain-a; animation-duration: 1.5s;  animation-delay: -1.1s; }
.raindrop:nth-child(4)  { left: 14%; height: 16px; --peak-op: 0.8;  animation-name: rain-b; animation-duration: 1.3s;  animation-delay: -0.2s; }
.raindrop:nth-child(5)  { left: 18%; height: 22px; --peak-op: 0.62; animation-name: rain-a; animation-duration: 0.95s; animation-delay: -1.4s; }
.raindrop:nth-child(6)  { left: 22%; height: 26px; --peak-op: 0.7;  animation-name: rain-b; animation-duration: 1.65s; animation-delay: -0.5s; }
.raindrop:nth-child(7)  { left: 27%; height: 19px; --peak-op: 0.85; animation-name: rain-a; animation-duration: 1.05s; animation-delay: -0.9s; }
.raindrop:nth-child(8)  { left: 31%; height: 17px; --peak-op: 0.58; animation-name: rain-b; animation-duration: 1.45s; animation-delay: -1.6s; }
.raindrop:nth-child(9)  { left: 35%; height: 23px; --peak-op: 0.72; animation-name: rain-a; animation-duration: 1.2s;  animation-delay: -0.4s; }
.raindrop:nth-child(10) { left: 40%; height: 21px; --peak-op: 0.66; animation-name: rain-b; animation-duration: 0.85s; animation-delay: -1.2s; }
.raindrop:nth-child(11) { left: 44%; height: 25px; --peak-op: 0.78; animation-name: rain-a; animation-duration: 1.55s; animation-delay: -0.6s; }
.raindrop:nth-child(12) { left: 48%; height: 18px; --peak-op: 0.6;  animation-name: rain-b; animation-duration: 1.0s;  animation-delay: -1.5s; }
.raindrop:nth-child(13) { left: 53%; height: 20px; --peak-op: 0.82; animation-name: rain-a; animation-duration: 1.35s; animation-delay: -0.1s; }
.raindrop:nth-child(14) { left: 57%; height: 24px; --peak-op: 0.68; animation-name: rain-b; animation-duration: 0.92s; animation-delay: -1.3s; }
.raindrop:nth-child(15) { left: 61%; height: 16px; --peak-op: 0.75; animation-name: rain-a; animation-duration: 1.6s;  animation-delay: -0.8s; }
.raindrop:nth-child(16) { left: 65%; height: 22px; --peak-op: 0.56; animation-name: rain-b; animation-duration: 1.15s; animation-delay: -1.7s; }
.raindrop:nth-child(17) { left: 70%; height: 26px; --peak-op: 0.8;  animation-name: rain-a; animation-duration: 0.98s; animation-delay: -0.35s; }
.raindrop:nth-child(18) { left: 74%; height: 19px; --peak-op: 0.63; animation-name: rain-b; animation-duration: 1.42s; animation-delay: -1.05s; }
.raindrop:nth-child(19) { left: 78%; height: 17px; --peak-op: 0.87; animation-name: rain-a; animation-duration: 1.08s; animation-delay: -0.55s; }
.raindrop:nth-child(20) { left: 83%; height: 23px; --peak-op: 0.7;  animation-name: rain-b; animation-duration: 1.28s; animation-delay: -1.45s; }
.raindrop:nth-child(21) { left: 87%; height: 21px; --peak-op: 0.6;  animation-name: rain-a; animation-duration: 0.88s; animation-delay: -0.25s; }
.raindrop:nth-child(22) { left: 91%; height: 25px; --peak-op: 0.77; animation-name: rain-b; animation-duration: 1.52s; animation-delay: -1.25s; }
.raindrop:nth-child(23) { left: 95%; height: 18px; --peak-op: 0.65; animation-name: rain-a; animation-duration: 1.18s; animation-delay: -0.65s; }
.raindrop:nth-child(24) { left: 98%; height: 20px; --peak-op: 0.73; animation-name: rain-b; animation-duration: 0.94s; animation-delay: -1.58s; }

/* ==========================================================================
   Fireflies background (users.background_theme = 'fireflies'). Unlike every
   overlay above, these don't fall or rise — each firefly is anchored at its
   own top/left point and wanders in a small loop around it (drift out, pulse
   brighter, drift back), so 0%/100% share the same transform/opacity for a
   seamless loop. Small glow via box-shadow rather than a text glyph. */
.firefly-overlay {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 40;
}
.firefly {
  position: absolute;
  border-radius: 50%;
  background: var(--gold);
  box-shadow: 0 0 6px 2px rgba(245, 179, 1, 0.85), 0 0 14px 5px rgba(245, 179, 1, 0.35);
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}
@keyframes firefly-a { 0% { transform: translate(0, 0) scale(0.6); opacity: 0; } 20% { transform: translate(10px, -8px) scale(1); opacity: var(--peak-op, 0.85); } 50% { transform: translate(-6px, -16px) scale(1.2); opacity: var(--peak-op, 0.85); } 80% { transform: translate(8px, -6px) scale(1); opacity: var(--peak-op, 0.85); } 100% { transform: translate(0, 0) scale(0.6); opacity: 0; } }
@keyframes firefly-b { 0% { transform: translate(0, 0) scale(0.6); opacity: 0; } 20% { transform: translate(-12px, -10px) scale(1); opacity: var(--peak-op, 0.85); } 50% { transform: translate(6px, -18px) scale(1.2); opacity: var(--peak-op, 0.85); } 80% { transform: translate(-8px, -4px) scale(1); opacity: var(--peak-op, 0.85); } 100% { transform: translate(0, 0) scale(0.6); opacity: 0; } }
@keyframes firefly-c { 0% { transform: translate(0, 0) scale(0.6); opacity: 0; } 20% { transform: translate(8px, 10px) scale(1); opacity: var(--peak-op, 0.85); } 50% { transform: translate(-10px, 18px) scale(1.2); opacity: var(--peak-op, 0.85); } 80% { transform: translate(6px, 6px) scale(1); opacity: var(--peak-op, 0.85); } 100% { transform: translate(0, 0) scale(0.6); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .firefly { animation: none; opacity: 0; }
}

.firefly:nth-child(1)  { top: 6%;  left: 4%;  width: 5px; height: 5px; --peak-op: 0.85; animation-name: firefly-a; animation-duration: 5.2s; animation-delay: -1.1s; }
.firefly:nth-child(2)  { top: 14%; left: 18%; width: 7px; height: 7px; --peak-op: 0.65; animation-name: firefly-b; animation-duration: 6.4s; animation-delay: -3.2s; }
.firefly:nth-child(3)  { top: 9%;  left: 33%; width: 6px; height: 6px; --peak-op: 0.9;  animation-name: firefly-c; animation-duration: 4.8s; animation-delay: -0.6s; }
.firefly:nth-child(4)  { top: 22%; left: 50%; width: 8px; height: 8px; --peak-op: 0.6;  animation-name: firefly-a; animation-duration: 7.1s; animation-delay: -4.4s; }
.firefly:nth-child(5)  { top: 17%; left: 66%; width: 5px; height: 5px; --peak-op: 0.95; animation-name: firefly-b; animation-duration: 5.9s; animation-delay: -2.3s; }
.firefly:nth-child(6)  { top: 28%; left: 82%; width: 7px; height: 7px; --peak-op: 0.7;  animation-name: firefly-c; animation-duration: 6.7s; animation-delay: -5.1s; }
.firefly:nth-child(7)  { top: 35%; left: 10%; width: 6px; height: 6px; --peak-op: 0.8;  animation-name: firefly-a; animation-duration: 8.3s; animation-delay: -1.8s; }
.firefly:nth-child(8)  { top: 41%; left: 27%; width: 9px; height: 9px; --peak-op: 0.55; animation-name: firefly-b; animation-duration: 4.5s; animation-delay: -3.6s; }
.firefly:nth-child(9)  { top: 38%; left: 45%; width: 5px; height: 5px; --peak-op: 0.92; animation-name: firefly-c; animation-duration: 7.6s; animation-delay: -0.9s; }
.firefly:nth-child(10) { top: 48%; left: 60%; width: 7px; height: 7px; --peak-op: 0.68; animation-name: firefly-a; animation-duration: 6.0s; animation-delay: -4.9s; }
.firefly:nth-child(11) { top: 55%; left: 75%; width: 6px; height: 6px; --peak-op: 0.88; animation-name: firefly-b; animation-duration: 5.4s; animation-delay: -2.7s; }
.firefly:nth-child(12) { top: 52%; left: 90%; width: 8px; height: 8px; --peak-op: 0.6;  animation-name: firefly-c; animation-duration: 8.9s; animation-delay: -6.2s; }
.firefly:nth-child(13) { top: 62%; left: 15%; width: 5px; height: 5px; --peak-op: 0.78; animation-name: firefly-a; animation-duration: 4.2s; animation-delay: -1.4s; }
.firefly:nth-child(14) { top: 70%; left: 35%; width: 7px; height: 7px; --peak-op: 0.9;  animation-name: firefly-b; animation-duration: 7.3s; animation-delay: -3.9s; }
.firefly:nth-child(15) { top: 65%; left: 55%; width: 6px; height: 6px; --peak-op: 0.65; animation-name: firefly-c; animation-duration: 5.7s; animation-delay: -0.3s; }
.firefly:nth-child(16) { top: 78%; left: 70%; width: 8px; height: 8px; --peak-op: 0.85; animation-name: firefly-a; animation-duration: 6.6s; animation-delay: -5.5s; }
.firefly:nth-child(17) { top: 85%; left: 22%; width: 5px; height: 5px; --peak-op: 0.72; animation-name: firefly-b; animation-duration: 8.1s; animation-delay: -2.0s; }
.firefly:nth-child(18) { top: 80%; left: 88%; width: 7px; height: 7px; --peak-op: 0.95; animation-name: firefly-c; animation-duration: 4.9s; animation-delay: -4.1s; }

/* ==========================================================================
   Autumn Leaves background (users.background_theme = 'leaves'). Same fall/
   sway/rotate shape as .petal, just a slower, lazier drift (leaves are
   heavier than flower petals) and a warm brown-orange palette instead of
   pink/coral. */
.leaf-overlay {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 40;
}
.leaf {
  position: absolute;
  top: -10%;
  color: #c9752c;
  text-shadow: 0 1px 3px rgba(140, 74, 20, 0.4);
  user-select: none;
  animation-timing-function: ease-in;
  animation-iteration-count: infinite;
}
@keyframes leaf-a { 0% { transform: translateY(0) translateX(0) rotate(0deg); opacity: 0; } 8% { opacity: var(--peak-op, 1); } 30% { transform: translateY(34vh) translateX(32px) rotate(140deg); } 60% { transform: translateY(70vh) translateX(-20px) rotate(280deg); } 92% { opacity: var(--peak-op, 1); } 100% { transform: translateY(115vh) translateX(16px) rotate(420deg); opacity: 0; } }
@keyframes leaf-b { 0% { transform: translateY(0) translateX(0) rotate(0deg); opacity: 0; } 8% { opacity: var(--peak-op, 1); } 30% { transform: translateY(34vh) translateX(-28px) rotate(-110deg); } 60% { transform: translateY(70vh) translateX(24px) rotate(-250deg); } 92% { opacity: var(--peak-op, 1); } 100% { transform: translateY(115vh) translateX(-14px) rotate(-390deg); opacity: 0; } }
@keyframes leaf-c { 0% { transform: translateY(0) translateX(0) rotate(0deg); opacity: 0; } 8% { opacity: var(--peak-op, 1); } 30% { transform: translateY(34vh) translateX(14px) rotate(80deg); } 60% { transform: translateY(70vh) translateX(-24px) rotate(200deg); } 92% { opacity: var(--peak-op, 1); } 100% { transform: translateY(115vh) translateX(10px) rotate(340deg); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .leaf { animation: none; opacity: 0; }
}

.leaf:nth-child(1)  { left: 2%;  font-size: 18px; --peak-op: 0.75; animation-name: leaf-a; animation-duration: 16.5s; animation-delay: -2.4s; }
.leaf:nth-child(2)  { left: 7%;  font-size: 22px; --peak-op: 0.85; animation-name: leaf-b; animation-duration: 13.2s; animation-delay: -8.1s; }
.leaf:nth-child(3)  { left: 12%; font-size: 16px; --peak-op: 0.65; animation-name: leaf-c; animation-duration: 20.4s; animation-delay: -4.6s; }
.leaf:nth-child(4)  { left: 17%; font-size: 26px; --peak-op: 0.9;  animation-name: leaf-a; animation-duration: 15.1s; animation-delay: -12.6s; }
.leaf:nth-child(5)  { left: 22%; font-size: 20px; --peak-op: 0.7;  animation-name: leaf-b; animation-duration: 18.8s; animation-delay: -6.9s; }
.leaf:nth-child(6)  { left: 27%; font-size: 24px; --peak-op: 0.8;  animation-name: leaf-c; animation-duration: 12.6s; animation-delay: -15.2s; }
.leaf:nth-child(7)  { left: 32%; font-size: 17px; --peak-op: 0.95; animation-name: leaf-a; animation-duration: 22.3s; animation-delay: -3.5s; }
.leaf:nth-child(8)  { left: 37%; font-size: 23px; --peak-op: 0.6;  animation-name: leaf-b; animation-duration: 14.4s; animation-delay: -10.7s; }
.leaf:nth-child(9)  { left: 42%; font-size: 19px; --peak-op: 0.88; animation-name: leaf-c; animation-duration: 19.9s; animation-delay: -6.3s; }
.leaf:nth-child(10) { left: 47%; font-size: 25px; --peak-op: 0.72; animation-name: leaf-a; animation-duration: 13.7s; animation-delay: -17.8s; }
.leaf:nth-child(11) { left: 52%; font-size: 18px; --peak-op: 0.83; animation-name: leaf-b; animation-duration: 17.3s; animation-delay: -1.6s; }
.leaf:nth-child(12) { left: 57%; font-size: 27px; --peak-op: 0.68; animation-name: leaf-c; animation-duration: 21.6s; animation-delay: -11.9s; }
.leaf:nth-child(13) { left: 62%; font-size: 21px; --peak-op: 0.93; animation-name: leaf-a; animation-duration: 14.9s; animation-delay: -7.2s; }
.leaf:nth-child(14) { left: 66%; font-size: 16px; --peak-op: 0.77; animation-name: leaf-b; animation-duration: 18.1s; animation-delay: -14.4s; }
.leaf:nth-child(15) { left: 70%; font-size: 24px; --peak-op: 0.86; animation-name: leaf-c; animation-duration: 16.8s; animation-delay: -4.1s; }
.leaf:nth-child(16) { left: 75%; font-size: 20px; --peak-op: 0.62; animation-name: leaf-a; animation-duration: 20.2s; animation-delay: -13.5s; }
.leaf:nth-child(17) { left: 79%; font-size: 28px; --peak-op: 0.91; animation-name: leaf-b; animation-duration: 12.9s; animation-delay: -9.6s; }
.leaf:nth-child(18) { left: 83%; font-size: 17px; --peak-op: 0.79; animation-name: leaf-c; animation-duration: 19.4s; animation-delay: -2.3s; }
.leaf:nth-child(19) { left: 88%; font-size: 22px; --peak-op: 0.96; animation-name: leaf-a; animation-duration: 15.6s; animation-delay: -18.7s; }
.leaf:nth-child(20) { left: 93%; font-size: 19px; --peak-op: 0.71; animation-name: leaf-b; animation-duration: 21.1s; animation-delay: -5.4s; }

/* ==========================================================================
   Floating Bubbles background (users.background_theme = 'bubbles'). Same
   rise-and-fade shape as .bokeh-orb (bottom -20% drifting up past -120vh),
   but rendered as translucent outlined circles with a highlight instead of
   blurred brand-color orbs, and a livelier side-to-side wobble. */
.bubble-overlay {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 40;
}
.bubble {
  position: absolute;
  bottom: -20%;
  border-radius: 50%;
  /* An all-white/pale-blue fill at low opacity (the first version of this)
     was nearly invisible against this app's light backgrounds — same
     "needs real contrast, not just a highlight" lesson as .snowflake's
     comment above. A visible brand-blue rim plus a stronger, more
     saturated blue in the gradient (still a highlight-heavy "soap bubble"
     look, just no longer washed out) fixes it. */
  background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.85), rgba(74, 144, 217, 0.4) 65%, rgba(13, 59, 102, 0.18) 100%);
  border: 1.5px solid rgba(74, 144, 217, 0.75);
  box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.75), 0 0 8px rgba(74, 144, 217, 0.5);
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}
@keyframes bubble-a { 0% { transform: translate(0, 0) scale(0.7); opacity: 0; } 12% { opacity: var(--peak-op, 0.18); } 45% { transform: translate(16px, -55vh) scale(1); } 80% { transform: translate(-14px, -95vh) scale(1.1); opacity: var(--peak-op, 0.18); } 100% { transform: translate(10px, -125vh) scale(1.2); opacity: 0; } }
@keyframes bubble-b { 0% { transform: translate(0, 0) scale(0.7); opacity: 0; } 12% { opacity: var(--peak-op, 0.18); } 45% { transform: translate(-18px, -55vh) scale(0.95); } 80% { transform: translate(12px, -95vh) scale(1.05); opacity: var(--peak-op, 0.18); } 100% { transform: translate(-10px, -125vh) scale(1.15); opacity: 0; } }
@keyframes bubble-c { 0% { transform: translate(0, 0) scale(0.75); opacity: 0; } 12% { opacity: var(--peak-op, 0.18); } 45% { transform: translate(8px, -55vh) scale(1.05); } 80% { transform: translate(-16px, -95vh) scale(0.95); opacity: var(--peak-op, 0.18); } 100% { transform: translate(14px, -125vh) scale(1.1); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .bubble { animation: none; opacity: 0; }
}

.bubble:nth-child(1)  { left: 3%;  width: 44px; height: 44px; --peak-op: 0.48; animation-name: bubble-a; animation-duration: 24s; animation-delay: -6s; }
.bubble:nth-child(2)  { left: 10%; width: 58px; height: 58px; --peak-op: 0.35; animation-name: bubble-b; animation-duration: 30s; animation-delay: -14s; }
.bubble:nth-child(3)  { left: 18%; width: 36px; height: 36px; --peak-op: 0.62; animation-name: bubble-c; animation-duration: 20s; animation-delay: -3s; }
.bubble:nth-child(4)  { left: 27%; width: 66px; height: 66px; --peak-op: 0.26; animation-name: bubble-a; animation-duration: 34s; animation-delay: -19s; }
.bubble:nth-child(5)  { left: 35%; width: 40px; height: 40px; --peak-op: 0.53; animation-name: bubble-b; animation-duration: 22s; animation-delay: -8s; }
.bubble:nth-child(6)  { left: 44%; width: 52px; height: 52px; --peak-op: 0.31; animation-name: bubble-c; animation-duration: 28s; animation-delay: -16s; }
.bubble:nth-child(7)  { left: 52%; width: 48px; height: 48px; --peak-op: 0.44; animation-name: bubble-a; animation-duration: 26s; animation-delay: -10s; }
.bubble:nth-child(8)  { left: 60%; width: 34px; height: 34px; --peak-op: 0.57; animation-name: bubble-b; animation-duration: 18s; animation-delay: -4s; }
.bubble:nth-child(9)  { left: 68%; width: 62px; height: 62px; --peak-op: 0.22; animation-name: bubble-c; animation-duration: 36s; animation-delay: -22s; }
.bubble:nth-child(10) { left: 76%; width: 42px; height: 42px; --peak-op: 0.40; animation-name: bubble-a; animation-duration: 24s; animation-delay: -11s; }
.bubble:nth-child(11) { left: 84%; width: 56px; height: 56px; --peak-op: 0.29; animation-name: bubble-b; animation-duration: 32s; animation-delay: -17s; }
.bubble:nth-child(12) { left: 90%; width: 38px; height: 38px; --peak-op: 0.55; animation-name: bubble-c; animation-duration: 19s; animation-delay: -2s; }
.bubble:nth-child(13) { left: 8%;  width: 50px; height: 50px; --peak-op: 0.33; animation-name: bubble-a; animation-duration: 29s; animation-delay: -13s; }
.bubble:nth-child(14) { left: 95%; width: 46px; height: 46px; --peak-op: 0.42; animation-name: bubble-b; animation-duration: 23s; animation-delay: -7s; }
.bubble:nth-child(15) { left: 58%; width: 64px; height: 64px; --peak-op: 0.20; animation-name: bubble-c; animation-duration: 38s; animation-delay: -20s; }
.bubble:nth-child(16) { left: 22%; width: 32px; height: 32px; --peak-op: 0.59; animation-name: bubble-a; animation-duration: 17s; animation-delay: -1s; }

/* Background Theme picker (dashboard.php) — a row of selectable option
   cards, same visual weight as .layout-option (create-match wizard)
   rather than a plain checkbox, since it's meant to read as "pick a
   theme," not "flip a switch" — room for more than one real theme later. */
.theme-picker { display: flex; gap: 12px; flex-wrap: wrap; }
.theme-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 14px 18px;
  min-width: 88px;
  border-radius: var(--radius-sm);
  border: 2px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
}
.theme-option-icon { font-size: 1.5rem; }
.theme-option-label { font-size: 0.8rem; font-weight: 700; }
.theme-option.is-active { border-color: var(--purple); color: var(--purple); }
.theme-option:hover { border-color: var(--purple); }

/* Color Palette (dashboard.php) — a two-tone swatch (outer = --bg, inner =
   --surface) standing in for .theme-option-icon's plain icon, since these
   options are literal colors, not effects, so showing the actual colors is
   more useful than a symbol. Reuses .theme-picker/.theme-option for layout. */
.color-theme-swatch {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.08);
  display: flex;
  align-items: center;
  justify-content: center;
}
.color-theme-swatch-inner {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.1);
}
.color-theme-swatch-none {
  background: var(--surface-alt);
  color: var(--text-muted);
  font-size: 0.85rem;
}