/* =========================================================
   DESIGN TOKENS
   Change these and the whole site's look updates.
   ========================================================= */
:root {
  --bg: #0a0d08;
  --panel: #10140b;
  --panel-border: #202b16;
  --panel-raised: #182111;

  --grass: #5a9c3f;
  --grass-dark: #3f7429;
  /* "gold" now holds the site's primary green accent (buttons, links,
     highlighted amounts) — kept the variable names so we didn't have
     to touch every file that references them, just the color itself. */
  --gold: #6fd94a;
  --gold-dark: #4a9c2e;
  --emerald: #35b56a;
  --redstone: #c0392b;

  --text: #e9ede2;
  --text-dim: #a3ad93;
  --text-faint: #6c7660;

  --font-display: "Space Grotesk", sans-serif;
  --font-body: "Inter", sans-serif;
  --font-mono: "JetBrains Mono", monospace;

  --radius: 6px;
}

/* =========================================================
   RESET / BASE
   ========================================================= */
* { box-sizing: border-box; margin: 0; padding: 0; }

html { scroll-behavior: smooth; }

html, body { overflow-x: hidden; }

/* Sticky footer: body is a full-height flex column and <main> (see
   its rule further down) grows to fill whatever space the page's
   own content doesn't need, so .site-footer always sits flush
   against the bottom of the viewport on short pages (login,
   register, etc.) instead of floating wherever the content happens
   to end, while still being pushed down normally on tall pages. */
body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  background-image:
    radial-gradient(ellipse at top, rgba(106, 156, 70, 0.08), transparent 60%);
  color: var(--text);
  font-family: var(--font-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }

/* width:100% matters specifically on the five admin pages, where
   .wrap sits directly on <main> — a flex item of body (see the
   sticky-footer rule above). margin:0 auto there is an auto
   cross-axis margin in a column flex container, and per the flexbox
   spec, align-self:stretch is skipped whenever either cross-axis
   margin is auto — so without an explicit width, that <main> sized
   itself to fit its widest child (e.g. a wide table) instead of the
   viewport, silently dragging everything inside it wider than the
   screen on mobile. Everywhere else .wrap sits on an ordinary block
   inside <main>, where this is a no-op (already the default). */
.wrap {
  width: 100%;
  max-width: 900px;
  margin: 0 auto;
  padding: 0 24px;
}

button { font-family: inherit; cursor: pointer; }

/* visible keyboard focus everywhere */
a:focus-visible, button:focus-visible, input:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

/* =========================================================
   PAGE AMBIENT BACKGROUND (logged-in home page)
   A fixed layer behind everything — negative z-index on a
   position:fixed element paints behind body's normal in-flow
   content regardless of where in the DOM it sits, so it never has
   to fight header/main/footer for stacking order the way an
   absolutely-positioned layer nested in the page flow would.
   Reuses the hero's .floaty drift (below) spread thinner across
   the whole viewport, plus one slow "breathing" glow. Purely
   decorative — aria-hidden, switched off under reduced-motion.
   ========================================================= */
.page-ambient {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}

.page-floaty-1 { width: 16px; height: 16px; top: 12%; left: 6%; animation-delay: 0s; }
.page-floaty-2 { width: 12px; height: 12px; top: 38%; right: 8%; animation-delay: 2s; background: linear-gradient(160deg, var(--gold), var(--gold-dark)); }
.page-floaty-3 { width: 20px; height: 20px; top: 62%; left: 9%; animation-delay: 4s; }
.page-floaty-4 { width: 14px; height: 14px; top: 82%; right: 12%; animation-delay: 1.2s; background: linear-gradient(160deg, var(--gold), var(--gold-dark)); }
.page-floaty-5 { width: 18px; height: 18px; top: 25%; left: 48%; animation-delay: 3.3s; opacity: 0.18; }

.page-glow {
  position: absolute;
  top: 15%;
  left: 50%;
  width: 900px;
  height: 900px;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle, rgba(111, 217, 74, 0.05), transparent 70%);
  animation: glow-breathe 8s ease-in-out infinite;
}

/* Shared by .page-glow above and .auth-glow in auth.css — one
   definition so the two "breathing" glows (home page + login/
   register) can never drift out of sync in how they ease. */
@keyframes glow-breathe {
  0%, 100% { opacity: 0.6; transform: translate(-50%, -50%) scale(1); }
  50% { opacity: 1; transform: translate(-50%, -50%) scale(1.08); }
}

/* =========================================================
   HEADER
   ========================================================= */
.site-header {
  border-bottom: 1px solid var(--panel-border);
  background: rgba(20, 23, 15, 0.85);
  backdrop-filter: blur(6px);
  position: sticky;
  top: 0;
  z-index: 10;
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 64px;
}

.logo {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.15rem;
  letter-spacing: 0.01em;
  /* Allowed to shrink (it's the least important thing in the header
     row) and truncates with an ellipsis instead of wrapping to a
     second line if it doesn't fit — min-width:0 is what lets a flex
     child shrink below its own text's natural width at all. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.logo-cube { flex-shrink: 0; }

/* a tiny css-drawn cube, no image asset needed */
.logo-cube {
  width: 16px;
  height: 16px;
  background: var(--grass);
  border-top: 3px solid #8ec25f;
  border-left: 3px solid #8ec25f;
  border-right: 3px solid var(--grass-dark);
  border-bottom: 3px solid var(--grass-dark);
  box-sizing: content-box;
}

.main-nav { display: flex; gap: 28px; }

.main-nav a {
  color: var(--text-dim);
  font-size: 0.92rem;
  font-weight: 500;
  transition: color 0.15s ease;
  padding: 4px 0;
  border-bottom: 2px solid transparent;
}

.main-nav a:hover { color: var(--text); }

.main-nav a.active {
  color: var(--text);
  /* no border-bottom-color here — the sliding .nav-indicator span
     (added by nav-transitions.js) draws the line instead, so it
     can animate smoothly between links rather than jumping */
}

/* Groups the account icons + mobile hamburger together so
   .header-inner's 3-way space-between (logo / nav / this) holds up
   whether .main-nav is a horizontal row (desktop) or hidden behind
   the toggle (mobile — see the 640px breakpoint below). Never
   shrinks — the logo gives up space to this group first (see
   .logo's min-width:0 above), not the other way around, so these
   buttons never get squeezed into an unusable size. */
.header-right { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }

.nav-toggle-btn {
  display: none; /* shown only under the mobile breakpoint */
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  background: var(--panel-raised);
  border: 1px solid var(--panel-border);
  border-radius: 50%;
  color: var(--text-dim);
}

.nav-toggle-btn svg { width: 18px; height: 18px; }
.nav-toggle-btn:hover { border-color: var(--gold); color: var(--text); }

/* =========================================================
   HEADER ACCOUNT AREA (notification bell + profile menu)
   Filled in by auth-guard.js's renderAccountNav() once we
   know who's logged in.
   ========================================================= */
.header-account-inner { display: flex; align-items: center; gap: 10px; }

.notification-bell { position: relative; }

.notification-icon-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  background: var(--panel-raised);
  border: 1px solid var(--panel-border);
  border-radius: 50%;
  color: var(--text-dim);
}

.notification-icon-btn svg { width: 18px; height: 18px; }
.notification-icon-btn:hover { border-color: var(--gold); color: var(--text); }

.notification-badge {
  position: absolute;
  top: -2px;
  right: -2px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--redstone);
  color: #fff;
  font-size: 0.65rem;
  font-weight: 600;
  line-height: 16px;
  text-align: center;
}

.notification-dropdown {
  position: absolute;
  top: calc(100% + 8px);
  /* Right-anchored, same as .profile-dropdown — the bell sits well
     left of the viewport's right edge once the header has three
     icons in it, so a left-anchored 320px panel could run off the
     right edge of a narrow phone screen. Growing leftward from the
     bell instead always stays inside the header's own width. */
  right: 0;
  width: 320px;
  max-width: 80vw;
  max-height: 400px;
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 6px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  z-index: 20;
}

.notification-list { display: flex; flex-direction: column; gap: 2px; }

.notification-item {
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-radius: 4px;
  padding: 9px 10px;
  color: var(--text-dim);
}

.notification-item:hover { background: var(--panel-raised); }

.notification-item--unread {
  background: rgba(232, 195, 74, 0.08);
  color: var(--text);
}

.notification-item--unread:hover { background: rgba(232, 195, 74, 0.14); }

.notification-message { font-size: 0.85rem; line-height: 1.4; }
.notification-time { font-size: 0.72rem; color: var(--text-faint); }

.notification-empty {
  padding: 24px 10px;
  text-align: center;
  color: var(--text-faint);
  font-size: 0.85rem;
}

/* =========================================================
   PROFILE MENU (avatar button + dropdown)
   ========================================================= */
.profile-menu { position: relative; }

.profile-icon-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  background: var(--panel-raised);
  border: 1px solid var(--panel-border);
  border-radius: 50%;
  color: var(--text-faint); /* used by the outline-only default avatar */
  overflow: hidden;
}

.profile-icon-btn svg { width: 20px; height: 20px; }
.profile-icon-btn:hover { border-color: var(--gold); }

.profile-dropdown {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 160px;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 6px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  z-index: 20;
}

.profile-dropdown a,
.profile-dropdown button {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  color: var(--text-dim);
  font-family: var(--font-body);
  font-size: 0.88rem;
  padding: 9px 10px;
  border-radius: 4px;
}

.profile-dropdown a:hover,
.profile-dropdown button:hover {
  background: var(--panel-raised);
  color: var(--text);
}

.profile-dropdown #sign-out-btn { color: var(--redstone); }
.profile-dropdown #sign-out-btn:hover { background: rgba(192, 57, 43, 0.12); color: var(--redstone); }

/* =========================================================
   HERO
   ========================================================= */
.hero {
  padding: 72px 0 32px;
  position: relative;
  overflow: hidden;
}

.hero-inner { max-width: 640px; }

.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--emerald);
  margin-bottom: 14px;
}

h1 {
  font-family: var(--font-display);
  font-size: clamp(2rem, 4.5vw, 3rem);
  font-weight: 700;
  line-height: 1.12;
  letter-spacing: -0.01em;
  margin-bottom: 18px;
}

.hero-sub {
  color: var(--text-dim);
  font-size: 1.05rem;
  max-width: 54ch;
}

.hero-cta { display: inline-block; margin-top: 28px; }

/* a few drifting pixel blocks in the hero — decorative only,
   hidden from screen readers and switched off for anyone who's
   asked the OS to reduce motion */
.floaty {
  position: absolute;
  border-radius: 3px;
  background: linear-gradient(160deg, var(--emerald), var(--grass-dark));
  opacity: 0.35;
  animation: floaty-drift 6s ease-in-out infinite;
}

.floaty-1 { width: 22px; height: 22px; top: 18%; right: 12%; animation-delay: 0s; }
.floaty-2 { width: 14px; height: 14px; top: 62%; right: 22%; animation-delay: 1.5s; background: linear-gradient(160deg, var(--gold), var(--gold-dark)); }
.floaty-3 { width: 18px; height: 18px; top: 40%; right: 4%; animation-delay: 3s; }

@keyframes floaty-drift {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-14px); }
}

/* top/bottom-only — see .stats-strip's comment above for why this
   can't be a "56px 0 8px" shorthand when the element also carries
   .wrap's class */
.page-intro { padding-top: 56px; padding-bottom: 8px; }

/* =========================================================
   ABOUT THE GOAL (voxel bar lives here — signature element)
   Two-column split: copy on the left, the voxel progress card
   as the "visual" on the right. A row of individually "mined"
   blocks fills left to right — built from a real Minecraft
   resource-bar motif rather than a generic rounded progress pill.
   ========================================================= */
.about-goal {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 48px;
  align-items: center;
  /* top/bottom-only — see .stats-strip's comment above */
  padding-top: 24px;
  padding-bottom: 72px;
}

.about-goal-text .hero-sub { margin-bottom: 20px; }

.goal-card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 28px 28px 24px;
}

.goal-card-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 18px;
}

.goal-card-top h3 {
  font-family: var(--font-display);
  font-size: 1.3rem;
  font-weight: 700;
}

.goal-amounts {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  color: var(--text-dim);
}

.goal-amounts strong { color: var(--gold); font-weight: 500; }

.voxel-bar {
  display: grid;
  grid-template-columns: repeat(30, 1fr);
  gap: 3px;
  margin-bottom: 12px;
}

.voxel {
  aspect-ratio: 1;
  border-radius: 2px;
  background: var(--panel-raised);
  border: 1px solid var(--panel-border);
}

.voxel.filled {
  background: linear-gradient(160deg, #a7e879, var(--gold-dark));
  border-color: var(--gold-dark);
}

.goal-meta {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  font-size: 0.85rem;
  color: var(--text-faint);
}

/* Flex items shrink by default, and without this the "funded" /
   "Needed by ..." text wraps mid-phrase once its span gets
   squeezed on a narrow screen — nowrap keeps each piece on its
   own single line; the 640px breakpoint below shrinks the font
   instead, so it still fits rather than overflowing. */
.goal-meta span { white-space: nowrap; }

.goal-meta .highlight { color: var(--emerald); font-weight: 500; }

/* A slow, rotating "chasing light" ring — used on the spotlight
   goal card (the page's signature element) and the homepage shop
   ad's featured MVP tier, deliberately not on every card, so it
   still reads as "this one's special" rather than decoration.
   Classic technique: a wrapper with a few px of padding +
   overflow:hidden forms the ring gap, and an oversized conic-
   gradient pseudo-element rotates behind the (opaque) card, clipped
   down to just that gap. Animates a transform instead of the
   gradient's own angle, so it doesn't need @property / has wider
   browser support. */
.goal-card-glow {
  position: relative;
  border-radius: calc(var(--radius) + 3px);
  padding: 3px;
  overflow: hidden;
  isolation: isolate;
}

.goal-card-glow::before {
  content: "";
  position: absolute;
  top: -50%;
  left: -50%;
  width: 200%;
  height: 200%;
  background: conic-gradient(
    from 0deg,
    transparent 0%,
    transparent 78%,
    var(--emerald) 88%,
    var(--gold) 92%,
    var(--emerald) 96%,
    transparent 100%
  );
  animation: goal-glow-spin 5s linear infinite;
  z-index: 0;
}

.goal-card-glow .goal-card,
.goal-card-glow .shop-ad-card {
  position: relative;
  z-index: 1;
}

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

/* =========================================================
   STATS STRIP
   Sits right under the hero, styled thin and muted — the same
   slot a "trusted by" logo row would take on a marketing page.
   ========================================================= */
.stats-strip {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  /* top/bottom-only, deliberately NOT shorthand "padding: 28px 0" —
     this section also carries .wrap's class for its horizontal
     padding, and a shorthand here would silently zero that out
     (equal specificity, same property, later rule wins entirely).
     See the other section classes below for the same fix. */
  padding-top: 28px;
  padding-bottom: 28px;
  border-top: 1px solid var(--panel-border);
  border-bottom: 1px solid var(--panel-border);
  margin-bottom: 64px;
}

.stat { text-align: center; }

.stat-value {
  font-family: var(--font-mono);
  font-size: 1.3rem;
  color: var(--gold);
  margin-bottom: 4px;
}

.stat-label {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-faint);
}

/* =========================================================
   SECTION HEADINGS
   ========================================================= */
.section-heading {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 18px;
}

.section-heading h2 {
  font-family: var(--font-display);
  font-size: 1.4rem;
  font-weight: 700;
}

.see-all {
  font-size: 0.85rem;
  color: var(--text-dim);
  font-weight: 500;
}

.see-all:hover { color: var(--gold); }

.section-heading--center {
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 6px;
}

.accent-text { color: var(--gold); }

/* =========================================================
   KEY FEATURES (3-card grid)
   ========================================================= */
.key-features { margin-bottom: 72px; }

.feature-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 18px;
}

.feature-card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 26px 22px;
  text-align: center;
}

.feature-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--panel-raised);
  font-size: 1.3rem;
  margin-bottom: 14px;
}

.feature-card h3 {
  font-family: var(--font-display);
  font-size: 1.05rem;
  margin-bottom: 8px;
}

.feature-card p {
  color: var(--text-dim);
  font-size: 0.9rem;
}

/* =========================================================
   SHOP AD — homepage teaser for the Shop, an extra funding channel
   beyond plain dues (see .contribute-cta further down, which stays
   focused on donating). Prices come live from site_settings (see
   renderShopAd() in script.js) so a price change in Manage
   Contributions shows up here too. MVP gets the rotating glow ring
   (.goal-card-glow, shared with the spotlight goal card) as the
   featured tier — one eye-catching highlight, not all three cards
   competing for attention.
   ========================================================= */
.shop-ad { margin-bottom: 72px; }

.shop-ad-sub { max-width: none; text-align: center; margin-bottom: 32px; }

.shop-ad-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 18px;
  margin-bottom: 28px;
}

.shop-ad-card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 26px 22px;
  text-align: center;
  height: 100%;
}

/* Deliberately no resting border-color override here — the rotating
   glow ring (.goal-card-glow, wrapping this card) already marks it
   as the featured tier on its own. A static tinted border on top of
   that looked like a permanently "active"/highlighted state even
   with nothing hovered, which is exactly what looked wrong. Hover
   gets its own reaction instead of relying on a border-color change
   (the shared .shop-ad-card:hover rule's border-color: gold would've
   been a no-op here anyway): a warmer glow shadow, plus the
   surrounding ring spins faster while hovered. transform is reset to
   none — the shared rule's translateY(-4px) would otherwise lift this
   card's top edge into the wrapper's overflow:hidden (needed to clip
   the oversized glow-ring gradient), cutting it off. */
.shop-ad-card--featured:hover {
  transform: none;
}

/* The glow shadow itself lives on the wrapper, not the card — an
   element's own box-shadow is never clipped by its own
   overflow:hidden, but a *descendant's* shadow extending past the
   wrapper would be (same clipping the ring gradient relies on). Put
   on the card, this shadow got cut flat at the wrapper's edge. :has()
   scopes it to just the shop-ad wrapper, not every .goal-card-glow
   (e.g. the spotlight goal card doesn't get this shadow). */
.goal-card-glow:has(.shop-ad-card--featured):hover {
  box-shadow: 0 12px 32px rgba(232, 195, 74, 0.4);
}

.goal-card-glow:hover::before {
  animation-duration: 1.4s;
}

.shop-ad-icon { font-size: 1.8rem; margin-bottom: 10px; }

.shop-ad-card h3 {
  font-family: var(--font-display);
  font-size: 1.1rem;
  margin-bottom: 6px;
}

.shop-ad-price {
  font-family: var(--font-mono);
  font-size: 1.3rem;
  color: var(--gold);
  margin-bottom: 10px;
}

.shop-ad-price span { font-size: 0.75rem; color: var(--text-faint); font-family: var(--font-body); }

.shop-ad-card p { color: var(--text-dim); font-size: 0.88rem; }

.shop-ad-cta { text-align: center; }

/* =========================================================
   SHOUTOUTS (testimonial-shaped, real ledger messages)
   ========================================================= */
.shoutouts { margin-bottom: 72px; }

.shoutouts-sub { max-width: none; text-align: center; margin-bottom: 32px; }

.shoutout-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 18px;
}

.shoutout-card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.shoutout-quote {
  color: var(--text);
  font-size: 0.95rem;
  line-height: 1.5;
}

.shoutout-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  padding-top: 12px;
  border-top: 1px solid var(--panel-border);
}

.shoutout-name { font-weight: 500; }
.shoutout-amount { color: var(--emerald); }

.shoutouts-footer { text-align: center; margin-top: 24px; }

/* =========================================================
   LEDGER (contribution log)
   ========================================================= */
.ledger-preview { margin-bottom: 64px; }
.ledger-full { padding-bottom: 80px; }

.ledger {
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  overflow: hidden;
}

.ledger-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 16px;
  background: var(--panel);
  border-bottom: 1px solid var(--panel-border);
}

.ledger-row:last-child { border-bottom: none; }

.ledger-who {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
}

.ledger-name { font-weight: 600; }

.ledger-msg {
  color: var(--text-faint);
  font-size: 0.85rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ledger-right {
  display: flex;
  align-items: center;
  gap: 18px;
  font-family: var(--font-mono);
  font-size: 0.9rem;
  flex-shrink: 0;
}

/* Used only by rows that also show a purchase breakdown (what a
   contribution was actually for) — .ledger-row stays a plain single
   line everywhere else (expense lists, legacy-only ledger entries,
   anything with nothing to tag). With room to spare, this keeps
   everything on one line — name, then purpose tags centered in the
   middle, then date + amount — instead of leaving a lot of empty
   vertical space per row; it only drops to three stacked lines under
   the mobile breakpoint below, where there isn't enough width for
   all three to sit side by side without crowding. */
.ledger-row--stacked .ledger-tags { flex: 1; justify-content: center; }

/* My Contributions adds a 4th piece — a rejection reason — as a
   sibling of .ledger-who/.ledger-tags/.ledger-right when a
   submission was rejected. That doesn't fit the single-line layout
   above at any width (a full sentence crammed in beside name/tags/
   amount), so those specific rows stay stacked regardless of screen
   size instead of only dropping to it under the mobile breakpoint. */
.ledger-row--stacked:has(.ledger-msg) {
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
}

.ledger-row--stacked:has(.ledger-msg) .ledger-tags { flex: none; justify-content: flex-start; }
.ledger-row--stacked:has(.ledger-msg) .ledger-right { align-self: flex-end; }

.ledger-tags { display: flex; flex-wrap: wrap; gap: 5px; }

.ledger-date { color: var(--text-faint); }
.ledger-amount { color: var(--emerald); font-weight: 500; }

.ledger-controls { margin-bottom: 16px; }

/* Toggleable filter row (Contributors: Active/Inactive/VIP/MVP/
   Battlepass) — status filters are mutually exclusive (a member is
   one or the other), perk filters combine with OR (VIP+Battlepass
   shows anyone with either), and the two groups combine with AND;
   see matchesContributorFilters() in contributors.js. Each pill's
   active state borrows the exact color its .status-badge counterpart
   already uses, so the filter reads as "highlight this badge" rather
   than a separate visual language. */
.filter-pills { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }

.filter-pill {
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid var(--panel-border);
  background: var(--panel-raised);
  color: var(--text-dim);
  font-size: 0.82rem;
  font-weight: 500;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.filter-pill:hover { border-color: var(--gold-dark); color: var(--text); }

.filter-pill.active[data-filter="active"] { background: rgba(53, 181, 106, 0.15); border-color: var(--emerald); color: var(--emerald); }
.filter-pill.active[data-filter="inactive"] { background: var(--panel-border); border-color: var(--text-dim); color: var(--text); }
.filter-pill.active[data-filter="vip"] { background: rgba(224, 138, 60, 0.15); border-color: #e08a3c; color: #e08a3c; }
.filter-pill.active[data-filter="mvp"] { background: rgba(138, 95, 214, 0.15); border-color: #8a5fd6; color: #8a5fd6; }
.filter-pill.active[data-filter="battlepass"] { background: rgba(232, 195, 74, 0.15); border-color: var(--gold-dark); color: #e8c34a; }

#ledger-search {
  width: 100%;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  color: var(--text);
  padding: 10px 14px;
  border-radius: var(--radius);
  font-family: var(--font-body);
  font-size: 0.95rem;
}

#ledger-search::placeholder { color: var(--text-faint); }

.ledger-empty {
  padding: 32px 18px;
  text-align: center;
  color: var(--text-faint);
  background: var(--panel);
}

.expense-list { margin-bottom: 20px; }

/* small stat tiles — payment page's goal/share/remaining summary */
.summary-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
}

.summary-tile {
  background: var(--panel-raised);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 14px 16px;
}

.summary-tile-value {
  font-family: var(--font-mono);
  font-size: 1.25rem;
  color: var(--gold);
  margin-bottom: 4px;
}

.summary-tile-label {
  font-size: 0.78rem;
  color: var(--text-faint);
}

/* live checkout estimate on the payment page — rank/Legacy Pass
   add-ons plus any discount credit, on top of whatever "What's this
   for?" target is chosen */
.checkout-summary {
  margin: 16px 0;
  padding: 14px 16px;
  background: var(--panel-raised);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  font-size: 0.88rem;
}

.checkout-summary-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 4px 0;
  color: var(--text-dim);
}

.checkout-summary-row .discount { color: var(--emerald); }

.checkout-summary-row.total {
  border-top: 1px solid var(--panel-border);
  margin-top: 6px;
  padding-top: 10px;
  font-weight: 600;
  color: var(--text);
}

.gcash-qr {
  max-width: 220px;
  border-radius: 8px;
  margin-top: 8px;
}

/* Previous on the left, Next on the right */
.ledger-pagination {
  display: flex;
  justify-content: space-between;
  margin-top: 16px;
}

.btn-ghost {
  background: transparent;
  border: 1px solid var(--panel-border);
  color: var(--text);
  padding: 8px 14px;
  border-radius: var(--radius);
  font-size: 0.85rem;
}

.btn-ghost:hover { border-color: var(--gold); }

.btn-ghost:disabled {
  color: var(--text-faint);
  cursor: default;
}

.btn-ghost:disabled:hover { border-color: var(--panel-border); }

/* status pills — one class + a modifier, e.g. "status-badge status-badge--pending" */
.status-badge {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 500;
  text-transform: capitalize;
}

.status-badge--pending, .status-badge--open { background: rgba(232, 195, 74, 0.15); color: var(--gold); }
.status-badge--approved, .status-badge--active, .status-badge--finalized { background: rgba(53, 181, 106, 0.15); color: var(--emerald); }
.status-badge--rejected, .status-badge--denied, .status-badge--left { background: rgba(192, 57, 43, 0.15); color: var(--redstone); }
.status-badge--inactive { background: rgba(162, 173, 147, 0.15); color: var(--text-dim); }
.status-badge--member { background: var(--panel-raised); color: var(--text-dim); }
.status-badge--admin { background: rgba(232, 195, 74, 0.15); color: var(--gold); }
/* Rank badges REPLACE the member/admin role badge in the display
   when a rank is active — the person is still a `member` underneath,
   this is purely a display override (see contributors.js). Distinct
   colors so a rank + Battlepass badge shown side by side never look
   like the same kind of thing. */
.status-badge--vip { background: rgba(224, 138, 60, 0.15); color: #e08a3c; }
.status-badge--mvp { background: rgba(138, 95, 214, 0.15); color: #8a5fd6; }
/* Literal gold hex, not var(--gold) — that variable actually holds
   the site's green accent color now (see the --gold comment in the
   design tokens at the top of this file), so using it here rendered
   green instead of gold. */
.status-badge--battlepass { background: rgba(232, 195, 74, 0.15); color: #e8c34a; }

/* compact secondary buttons */
.btn-small {
  font-size: 0.8rem;
  padding: 6px 12px;
  border-radius: var(--radius);
  border: 1px solid var(--panel-border);
  background: transparent;
  color: var(--text);
}

.btn-small:hover { border-color: var(--gold); }

.btn-small.btn-approve { border-color: var(--emerald); color: var(--emerald); }
.btn-small.btn-approve:hover { background: rgba(53, 181, 106, 0.12); }

.btn-small.btn-reject { border-color: var(--redstone); color: var(--redstone); }
.btn-small.btn-reject:hover { background: rgba(192, 57, 43, 0.12); }

/* =========================================================
   GOALS GRID
   ========================================================= */
.goals { margin-bottom: 72px; }

.goal-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
}

.goal-mini {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 18px;
}

.goal-mini h4 {
  font-family: var(--font-display);
  font-size: 1.02rem;
  margin-bottom: 10px;
}

.mini-bar {
  height: 8px;
  background: var(--panel-raised);
  border-radius: 4px;
  overflow: hidden;
  margin-bottom: 8px;
}

.mini-bar-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--grass-dark), var(--emerald));
}

.goal-mini .goal-meta { font-size: 0.8rem; }

/* =========================================================
   CONTRIBUTE CTA
   ========================================================= */
.contribute-cta {
  background: var(--panel);
  border-top: 1px solid var(--panel-border);
  border-bottom: 1px solid var(--panel-border);
  padding: 64px 0;
}

.cta-inner { max-width: 480px; }

.contribute-cta h2 {
  font-family: var(--font-display);
  font-size: 1.8rem;
  margin-bottom: 14px;
}

.contribute-cta p { color: var(--text-dim); margin-bottom: 24px; }

.btn-gold {
  background: linear-gradient(160deg, #a7e879, var(--gold-dark));
  color: #0f2308;
  font-weight: 600;
  font-size: 0.95rem;
  border: none;
  padding: 13px 28px;
  border-radius: var(--radius);
  box-shadow: 0 2px 0 var(--gold-dark);
  transition: transform 0.1s ease;
}

.btn-gold:hover { transform: translateY(-1px); }
.btn-gold:active { transform: translateY(1px); box-shadow: none; }

/* =========================================================
   FOOTER
   ========================================================= */
.site-footer { padding: 32px 0 48px; flex-shrink: 0; }

.footer-inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  color: var(--text-faint);
  font-size: 0.85rem;
}

.footer-links { display: flex; gap: 20px; }
.footer-links a:hover { color: var(--gold); }

/* row of social icon links, e.g. Discord/Facebook — add more
   <a class="social-icon"> entries the same way to extend it */
.footer-social { display: flex; gap: 16px; }

.social-icon {
  display: inline-flex;
  color: var(--text-faint);
  transition: color 0.15s ease;
}

.social-icon:hover { color: var(--gold); }
.social-icon svg { width: 18px; height: 18px; display: block; }

/* =========================================================
   ABOUT PAGE
   ========================================================= */
/* top/bottom-only — see .stats-strip's comment above */
.about-section { padding-top: 8px; padding-bottom: 56px; }

.about-section h2 {
  font-family: var(--font-display);
  font-size: 1.4rem;
  font-weight: 700;
  margin-bottom: 8px;
}

.about-section-sub {
  color: var(--text-dim);
  font-size: 0.92rem;
  max-width: 60ch;
  margin-bottom: 22px;
}

.info-list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1px;
  background: var(--panel-border);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  overflow: hidden;
}

.info-item {
  background: var(--panel);
  padding: 16px 20px;
}

.info-item dt {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  margin-bottom: 4px;
}

.info-item dd {
  font-family: var(--font-mono);
  font-size: 0.95rem;
}

.addon-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
  margin-bottom: 14px;
}

.addon-credit {
  color: var(--text-faint);
  font-size: 0.82rem;
}

.founder-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
}

.founder-card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 18px;
  text-align: center;
}

.founder-name {
  font-family: var(--font-display);
  font-weight: 700;
  margin-bottom: 4px;
}

.founder-ign {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  color: var(--emerald);
  margin-bottom: 8px;
}

.founder-role {
  font-size: 0.8rem;
  color: var(--text-faint);
}

/* =========================================================
   SHOP PAGE
   ========================================================= */
/* top/bottom-only — see .page-intro's neighboring pattern; this
   section also carries .wrap's class for its horizontal padding,
   and a shorthand here would silently zero that out (same bug as
   .stats-strip below once had). */
.shop-section { padding-top: 8px; padding-bottom: 72px; }

.shop-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
  margin-bottom: 14px;
}

.shop-card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 22px;
}

.shop-card h2 {
  font-family: var(--font-display);
  font-size: 1.15rem;
  margin-bottom: 8px;
}

.shop-price {
  font-family: var(--font-mono);
  font-size: 1.2rem;
  color: var(--gold);
  margin-bottom: 10px;
}

.shop-description {
  color: var(--text-dim);
  font-size: 0.9rem;
  white-space: pre-line;
}

.shop-card--wide { margin-bottom: 28px; }
.shop-card--wide .see-all { display: inline-block; margin-top: 12px; }

/* Wide content (a 4-column table) needs to scroll inside its own
   container on a narrow screen rather than forcing the whole page
   to scroll sideways. */
.table-scroll { overflow-x: auto; }

.perks-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }

.perks-table th {
  text-align: center;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  padding: 10px;
  border-bottom: 1px solid var(--panel-border);
  white-space: nowrap;
}

.perks-table th:first-child { text-align: left; }

.perks-table td {
  padding: 10px;
  text-align: center;
  border-bottom: 1px solid var(--panel-border);
}

.perks-table td:first-child { text-align: left; color: var(--text); }

.perks-table tr:last-child td { border-bottom: none; }

.perk-note {
  display: block;
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--text-faint);
}

.perk-yes { color: var(--emerald); font-weight: 700; }
.perk-no { color: var(--text-faint); }

.perks-note { margin-top: 12px; }

.shop-cta { text-align: center; }

/* The "what you currently have" line above the shop cards — each
   active perk gets its own colored badge (see renderMyStatus() in
   shop.js) instead of being merged into one plain sentence, so
   rank/Legacy Pass and their expiry dates are each easy to pick out
   at a glance. */
#my-shop-status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  max-width: none;
}

.my-shop-label { color: var(--text-dim); font-size: 0.95rem; }

.my-shop-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.88rem;
  color: var(--text-dim);
}

.my-shop-empty { color: var(--text-dim); font-size: 1.05rem; }

/* =========================================================
   BATTLEPASS PAGE
   ========================================================= */
/* top/bottom-only, same reasoning as .shop-section above. */
.battlepass-section { padding-top: 8px; padding-bottom: 72px; }

/* A banner above the (still-visible) reward track, not a
   replacement for it — rewards stay visible either way, this just
   explains why they're all locked and links to where to fix that. */
.battlepass-no-pass {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 20px 24px;
  margin-bottom: 24px;
}

.battlepass-no-pass p { color: var(--text-dim); flex: 1; min-width: 220px; }

.battlepass-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
  gap: 10px;
}

.battlepass-day {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 12px 8px;
  text-align: center;
  transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
}

.battlepass-day-icon {
  font-size: 1.6rem;
  line-height: 1;
  margin-bottom: 6px;
}

.battlepass-day-number {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--text-faint);
  margin-bottom: 6px;
}

.battlepass-day-reward { font-size: 0.82rem; margin-bottom: 8px; min-height: 2.2em; }
.battlepass-day-state { font-size: 0.75rem; color: var(--text-faint); }

/* Locked days read as "not there yet" — dimmed and desaturated,
   same instinct as a grayed-out achievement icon. */
.battlepass-day--locked { opacity: 0.5; }
.battlepass-day--locked .battlepass-day-icon { filter: grayscale(1); }

.battlepass-day--claimed { opacity: 0.65; }

/* Claimable days get a gentle breathing glow so the one thing you
   can actually do stands out from the 30 that just sit there — plus
   the same hover-lift used for browsable cards elsewhere on the
   site (see the .feature-card group's comment), since these are
   genuinely clickable, unlike locked/claimed ones. */
.battlepass-day--claimable {
  border-color: var(--gold-dark);
  animation: battlepass-claimable-glow 2.2s ease-in-out infinite;
}

.battlepass-day--claimable:hover {
  transform: translateY(-4px);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.28);
}

@keyframes battlepass-claimable-glow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(232, 195, 74, 0.28); }
  50% { box-shadow: 0 0 0 6px rgba(232, 195, 74, 0); }
}

/* One-shot celebration the instant a claim succeeds — added by
   battlepass.js right before it reloads the grid into its settled
   "claimed" state, so the reward doesn't just silently change. */
.battlepass-day--just-claimed {
  animation: battlepass-claim-pulse 0.45s ease;
}

@keyframes battlepass-claim-pulse {
  0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(111, 217, 74, 0.55); }
  40% { transform: scale(1.08); box-shadow: 0 0 0 10px rgba(111, 217, 74, 0); }
  100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(111, 217, 74, 0); }
}

/* =========================================================
   CONTRIBUTORS PAGE
   ========================================================= */
/* top/bottom-only — see .stats-strip's comment above */
.contributors-section { padding-top: 8px; padding-bottom: 72px; }

.contributor-list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
  margin-top: 16px;
}

.contributor-card {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 16px;
}

.contributor-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--panel-raised);
  border: 1px solid var(--panel-border);
  color: var(--text-faint);
  overflow: hidden;
}

.contributor-avatar svg { width: 30px; height: 30px; }

.contributor-name { font-weight: 600; margin-bottom: 6px; }
.contributor-you { font-weight: 400; color: var(--gold); }
.contributor-meta { display: flex; gap: 6px; flex-wrap: wrap; }

/* =========================================================
   UPDATES / INSTALL PAGE
   ========================================================= */
/* top/bottom-only — see .stats-strip's comment above */
.updates-section { padding-top: 8px; padding-bottom: 72px; }

.install-panel {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 24px;
  margin: 16px 0 36px;
}

.install-panel-info { flex: 1; min-width: 220px; }
.install-panel-info .status-badge { margin-bottom: 8px; }

.install-note {
  color: var(--text-dim);
  font-size: 0.9rem;
  margin: 6px 0 0;
}

.changelog-entry {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 20px 22px;
  margin-bottom: 14px;
}

.changelog-entry-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 10px;
}

.changelog-version { font-family: var(--font-display); font-size: 1.05rem; }
.changelog-date { color: var(--text-dim); font-size: 0.85rem; }

.changelog-entry ul { margin: 0; padding-left: 18px; color: var(--text-dim); }
.changelog-entry li { margin-bottom: 4px; }

/* =========================================================
   SCROLL REVEAL + HOVER POLISH
   Applied via animations.js, which adds "is-visible" the first
   time an element scrolls into view. [data-reveal-group]'s
   children stagger in via nth-child delays — this works even for
   rows/cards a page's own script renders in later, since the
   selector below is a live CSS rule, not something animations.js
   has to reapply per element.
   ========================================================= */
[data-reveal] {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}
[data-reveal].is-visible { opacity: 1; transform: translateY(0); }

[data-reveal-group] > * {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.55s ease, transform 0.55s ease;
}
[data-reveal-group].is-visible > * { opacity: 1; transform: translateY(0); }

[data-reveal-group] > *:nth-child(2) { transition-delay: 0.07s; }
[data-reveal-group] > *:nth-child(3) { transition-delay: 0.14s; }
[data-reveal-group] > *:nth-child(4) { transition-delay: 0.21s; }
[data-reveal-group] > *:nth-child(5) { transition-delay: 0.28s; }
[data-reveal-group] > *:nth-child(n+6) { transition-delay: 0.34s; }

/* Card lift-on-hover — reserved for grid items you're actually
   scanning through (a "these are individual things" affordance),
   not single info panels like .auth-card or .goal-card where a
   hover lift would just be visual noise while someone's typing. */
.feature-card,
.goal-mini,
.shoutout-card,
.contributor-card,
.founder-card {
  transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
}

.feature-card:hover,
.shop-ad-card:hover,
.goal-mini:hover,
.shoutout-card:hover,
.contributor-card:hover,
.founder-card:hover {
  transform: translateY(-4px);
  border-color: var(--gold-dark);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.28);
}

/* Smoother hover on controls that previously changed instantly */
.btn-ghost, .btn-small, .notification-icon-btn, .profile-icon-btn, .see-all, .social-icon {
  transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease, transform 0.2s ease;
}

.btn-gold:hover {
  box-shadow: 0 4px 16px rgba(111, 217, 74, 0.35), 0 2px 0 var(--gold-dark);
}

/* =========================================================
   NAV TRANSITIONS (sliding underline + directional page slide)
   Wired by nav-transitions.js, only on the four pages sharing this
   fixed order: Home / Contributions / Contributors / About. The
   html.nav-enter-* starting state is applied synchronously by a
   tiny inline script at the very top of <body>, before <main> is
   even parsed — so <main> is born already offset, no transition
   plays on that first frame, and there's nothing to flash.
   ========================================================= */
.main-nav { position: relative; }

.nav-indicator {
  position: absolute;
  bottom: 0;
  height: 2px;
  background: var(--gold);
  transition: left 0.35s cubic-bezier(0.65, 0, 0.35, 1), width 0.35s cubic-bezier(0.65, 0, 0.35, 1);
  pointer-events: none;
}

main {
  flex: 1 0 auto;
  transition: transform 0.32s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.32s ease;
}

main.nav-exit-left { transform: translateX(-32px); opacity: 0; }
main.nav-exit-right { transform: translateX(32px); opacity: 0; }

html.nav-enter-left main { transform: translateX(-32px); opacity: 0; }
html.nav-enter-right main { transform: translateX(32px); opacity: 0; }

/* Voxel bar "pop-in" — the site's signature element gets its own
   flourish. Only filled blocks animate (they're the meaningful
   part); animation-delay is set per-block by script.js so blocks
   fill in left-to-right instead of all popping at once. */
@keyframes voxel-pop {
  from { opacity: 0; transform: scale(0.4); }
  to { opacity: 1; transform: scale(1); }
}

.voxel-pop { animation: voxel-pop 0.3s ease backwards; }

/* =========================================================
   MODAL — a centered floating panel over a dimmed backdrop, used
   for Manage Accounts' "Edit member" so it gets its own focused
   space instead of squeezing into the page layout inline. The panel
   itself just adds class="auth-card" (defined in account/auth.css)
   on top of this, reusing the exact same card look "My Profile"
   already has, rather than a separate visual style for one form.
   ========================================================= */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  padding: 24px;
  z-index: 100;
  overflow-y: auto;
}

.modal-overlay[hidden] { display: none; }

/* margin:auto (not align-items/justify-content on the overlay)
   centers this — auto margins in a flex container never go
   negative, so when the panel is taller than the viewport it starts
   flush at the top and scrolls normally, instead of align-items:
   center clipping its top edge the way it would here. */
.modal-panel {
  position: relative;
  width: 100%;
  max-width: 480px;
  margin: auto;
}

.modal-close-btn {
  position: absolute;
  top: 14px;
  right: 14px;
  background: none;
  border: none;
  color: var(--text-faint);
  font-size: 1.5rem;
  line-height: 1;
  padding: 4px 8px;
}

.modal-close-btn:hover { color: var(--text); }

.modal-cancel-btn { width: 100%; text-align: center; margin-top: 10px; }

/* =========================================================
   RESPONSIVE
   ========================================================= */
@media (max-width: 640px) {
  .about-goal { grid-template-columns: 1fr; }
  .stats-strip { grid-template-columns: 1fr; gap: 16px; }
  .goal-grid { grid-template-columns: 1fr; }
  .feature-grid { grid-template-columns: 1fr; }
  .shop-ad-grid { grid-template-columns: 1fr; }
  .shoutout-grid { grid-template-columns: 1fr; }
  .info-list { grid-template-columns: 1fr; }
  .addon-grid { grid-template-columns: 1fr; }
  .founder-grid { grid-template-columns: 1fr; }
  .contributor-list { grid-template-columns: 1fr; }
  .shop-grid { grid-template-columns: 1fr; }
  .ledger-row { flex-direction: column; align-items: flex-start; gap: 6px; }
  .ledger-right { align-self: flex-end; }
  /* Undoes the wide-screen centering above — once .ledger-row's
     column stacking (just above) kicks in, the tags need to wrap
     naturally at their own left edge instead of trying to center
     within a flex-grown middle "column" that no longer exists. */
  .ledger-row--stacked .ledger-tags { flex: none; justify-content: flex-start; }
  .install-panel { flex-direction: column; align-items: stretch; }
  .install-panel .btn-gold { width: 100%; text-align: center; }
  .battlepass-no-pass { flex-direction: column; align-items: stretch; }
  .battlepass-no-pass .btn-gold { width: 100%; text-align: center; }

  /* Shrinks rather than wraps — see .goal-meta's own comment above
     for why the text needs help fitting on one line at this width. */
  .goal-meta { font-size: 0.76rem; }
  .goal-mini .goal-meta { font-size: 0.72rem; }

  /* Collapse the horizontal nav into a hamburger-triggered dropdown
     — there's no room left for four text links plus the logo and
     account icons all on one row once the viewport gets this
     narrow. */
  .logo { font-size: 1.02rem; }

  .main-nav {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    gap: 0;
    background: var(--panel);
    border-bottom: 1px solid var(--panel-border);
    padding: 4px 24px 12px;
    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.35);
  }

  .main-nav.is-open { display: flex; }

  .main-nav a {
    padding: 12px 0;
    border-bottom: 1px solid var(--panel-border);
  }

  .main-nav a:last-child { border-bottom: none; }

  /* The sliding underline only makes sense for a horizontal row —
     in the stacked dropdown, fall back to the plain static
     border-bottom this site used before nav-transitions.js existed. */
  .nav-indicator { display: none; }
  .main-nav a.active { border-bottom-color: var(--gold); }

  .nav-toggle-btn { display: flex; }

  /* Both dropdowns normally anchor to the button that opens them
     (right:0 relative to their own small trigger button), which
     assumes that button sits at the outer edge of the header. On
     mobile it doesn't anymore — avatar/hamburger sit to the right
     of the bell — so a dropdown anchored to the bell specifically
     can overshoot off the LEFT edge of a narrow phone screen
     instead. Pinning both to the viewport itself sidesteps which
     button triggered it entirely — always fits, always fully
     visible, regardless of where the trigger sits in the header. */
  .notification-dropdown,
  .profile-dropdown {
    position: fixed;
    top: 70px;
    left: 12px;
    right: 12px;
    width: auto;
    max-width: none;
  }
}

/* reduced motion — deliberately does NOT cover this session's new
   scroll-reveal/hover/nav-transition polish (see animations.js and
   nav-transitions.js's top comments for why) */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .btn-gold { transition: none; }
  .floaty { animation: none; }
  .page-glow, .auth-glow { animation: none; }
  .goal-card-glow::before { animation: none; opacity: 0; }
}
