
/* ---------- Animated page-to-page navigation (native browser feature, no JS) ----------
   Supported browsers cross-fade + slide between full page loads automatically;
   unsupported browsers just navigate normally with zero difference. */
@view-transition {
  navigation: auto;
}
/* Solid backdrop behind the transition layer so no unpainted (white) gap
   can show through while old/new snapshots fade and slide. */
html::view-transition {
  background-color: #0a1220;
}
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) {
    animation: 180ms ease-in both vt-fade-out, 180ms ease-in both vt-slide-out;
  }
  ::view-transition-new(root) {
    animation: 220ms ease-out both vt-fade-in, 220ms ease-out both vt-slide-in;
  }
}
@keyframes vt-fade-out { to { opacity: 0; } }
@keyframes vt-fade-in { from { opacity: 0; } }
@keyframes vt-slide-out { to { transform: translateX(-14px); } }
@keyframes vt-slide-in { from { transform: translateX(14px); } }

/* ---------- Tokens: dark theme, committed (not a light theme with dark chrome) ---------- */
:root {
  --bg-0: #0a1220;
  --bg-1: #0f1b30;
  --panel: #17233c;
  --panel-2: #1f2e4d;
  --border: #304060;
  --ink: #f4efe2;
  --muted: #b0a894;
  --red: #e2564a;
  --red-deep: #b3231c;
  --red-dark: #8a1a15;
  --green: #46b874;
  --green-deep: #1e5631;
  --green-dark: #163f24;
  --gold: #e6c368;
  --gold-deep: #c99a2e;
  --input-bg: #faf6ea;
  --input-ink: #241a14;
  --input-border: #cdbf9d;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

* { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  background: var(--bg-0);
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-family: var(--font);
  color: var(--ink);
  background:
    radial-gradient(1.5px 1.5px at 12% 22%, rgba(255,255,255,0.5), transparent),
    radial-gradient(1.5px 1.5px at 78% 12%, rgba(255,255,255,0.4), transparent),
    radial-gradient(1px 1px at 90% 48%, rgba(255,255,255,0.35), transparent),
    radial-gradient(1.5px 1.5px at 35% 65%, rgba(255,255,255,0.3), transparent),
    radial-gradient(1px 1px at 6% 78%, rgba(255,255,255,0.35), transparent),
    radial-gradient(1.5px 1.5px at 58% 35%, rgba(255,255,255,0.3), transparent),
    radial-gradient(1px 1px at 95% 82%, rgba(255,255,255,0.35), transparent),
    linear-gradient(180deg, var(--bg-1) 0%, var(--bg-0) 55%, #060a14 100%);
  font-size: 20px;
  line-height: 1.55;
}

/* ---------- String-light garland: normal document flow on purpose — see
   the comment in partials/head.ejs for why this isn't position:fixed. ---------- */
.light-garland {
  width: 100%;
  height: 46px;
  position: relative;
  z-index: 2;
}
.light-garland svg { display: block; width: 100%; height: 100%; overflow: visible; }
.garland-wire { stroke: #2a2015; stroke-width: 2; }
/* Garland-only 4-color cycle (adds green) — scoped here so the tree's own
   red/gold/blue .tree-light cycle elsewhere on the page is untouched. */
.light-garland .tree-light:nth-of-type(4n+1) { fill: #e0453c; filter: drop-shadow(0 0 4px rgba(224,69,60,0.9)); }
.light-garland .tree-light:nth-of-type(4n+2) { fill: var(--gold); filter: drop-shadow(0 0 4px rgba(230,195,104,0.9)); }
.light-garland .tree-light:nth-of-type(4n+3) { fill: #4a90d9; filter: drop-shadow(0 0 4px rgba(74,144,217,0.9)); }
.light-garland .tree-light:nth-of-type(4n+4) { fill: var(--green); filter: drop-shadow(0 0 4px rgba(70,184,116,0.9)); }

/* ---------- CSS-only snow: no JS involved anywhere in this effect ---------- */
.snow-layer {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 1;
}
.flake {
  position: absolute;
  top: -12%;
  color: #ffffff;
  text-shadow: 0 0 6px rgba(120,160,220,0.9), 0 0 2px rgba(255,255,255,0.9);
  animation-name: snow-fall;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  user-select: none;
}
@keyframes snow-fall {
  0%   { transform: translateY(-10vh) translateX(0) rotate(0deg); }
  100% { transform: translateY(112vh) translateX(24px) rotate(80deg); }
}

/* ---------- RSVP "yes" celebration burst — public/js/celebrate.js ---------- */
.celebrate-burst {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 600;
  overflow: hidden;
}
.celebrate-piece {
  position: absolute;
  top: -10%;
  animation-name: celebrate-fall;
  animation-timing-function: ease-in;
  animation-fill-mode: forwards;
  user-select: none;
}
@keyframes celebrate-fall {
  0% { transform: translateY(0) rotate(0deg); opacity: 1; }
  100% { transform: translateY(115vh) rotate(360deg); opacity: 0; }
}

/* ---------- CSS-only Santa flyby: long loop, no JS scheduling ---------- */
.santa-flyby {
  position: fixed;
  top: 9vh;
  left: 0;
  width: 230px;
  pointer-events: none;
  z-index: 501;
  filter: drop-shadow(0 4px 8px rgba(0,0,0,0.45));
  animation: santa-cycle 65s linear infinite;
}
.santa-flyby img { display: block; width: 100%; height: auto; }
/* A single smooth rise-and-fall arc, banking gently up then back down —
   linear timing across enough waypoints to shape the curve itself, rather
   than easing each short segment (which read as a stutter/twitch before). */
@keyframes santa-cycle {
  0%      { transform: translate(-40vw, 10vh) rotate(-6deg) scale(0.85); opacity: 0; }
  4%      { opacity: 1; }
  10%     { transform: translate(15vw, -6vh) rotate(4deg) scale(1.05); }
  15%     { transform: translate(72.5vw, -9vh) rotate(0deg) scale(1.1); }
  20%     { transform: translate(130vw, 10vh) rotate(-6deg) scale(0.85); opacity: 1; }
  21%     { opacity: 0; }
  100%    { transform: translate(130vw, 10vh) rotate(-6deg) scale(0.85); opacity: 0; }
}
@media (max-width: 600px) { .santa-flyby { width: 165px; } }

/* ---------- CSS-only snow drift: settles in once on load, then sits static ---------- */
.snow-drift {
  position: fixed;
  left: 0;
  right: 0;
  bottom: -1px;
  width: 100%;
  height: clamp(46px, 9vh, 100px);
  pointer-events: none;
  z-index: 0;
  transform-origin: bottom center;
  filter: drop-shadow(0 -2px 14px rgba(255,255,255,0.12));
  animation: drift-settle 1.6s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.snow-drift svg { display: block; width: 100%; height: 100%; }
@keyframes drift-settle {
  0%   { transform: scaleY(0); opacity: 0; }
  60%  { transform: scaleY(1.08); opacity: 1; }
  100% { transform: scaleY(1); opacity: 1; }
}

@keyframes twinkle {
  0%, 100% { opacity: 0.4; transform: scale(0.82); }
  50% { opacity: 1; transform: scale(1.15); }
}

.wrap {
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  padding: 28px 20px 60px;
  position: relative;
  z-index: 2;
}

/* ---------- Type ---------- */
h1 {
  font-weight: 800;
  font-size: 2.5rem;
  letter-spacing: -0.01em;
  color: var(--red);
  margin: 4px 0 8px;
  line-height: 1.15;
  text-align: center;
  text-shadow: 0 0 24px rgba(230,195,104,0.25), 0 2px 8px rgba(0,0,0,0.4);
}
h2 {
  font-weight: 800;
  font-size: 1.3rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--gold);
  margin: 34px 0 14px;
  padding-bottom: 10px;
  text-align: center;
  border-bottom: 3px dashed rgba(230,195,104,0.35);
}
h2::before { content: "\2744"; display: inline-block; margin-right: 8px; color: #fff; }
h3 { font-size: 1.15rem; font-weight: 700; margin: 20px 0 8px; color: var(--ink); }

p { margin: 10px 0; }
a { color: var(--gold); }

.subtitle { color: var(--muted); font-size: 1.15rem; margin-bottom: 24px; text-align: center; }
.invite .subtitle { text-align: inherit; }

/* ---------- Panels: quiet grouping for forms/admin, not boxed SaaS cards ---------- */
.card {
  background: var(--panel);
  border: none;
  border-top: 3px solid var(--gold);
  border-radius: 10px;
  padding: 24px 20px;
  margin-bottom: 20px;
  box-shadow: 0 10px 24px rgba(0,0,0,0.28);
  color: var(--ink);
}
.card .muted { color: var(--muted); }

/* ---------- Landing page: a Partiful-style invite feed, not a dashboard ---------- */
.poster {
  position: relative;
  text-align: center;
  background: linear-gradient(165deg, var(--panel-2) 0%, var(--panel) 70%);
  border: 2px dashed var(--gold-deep);
  border-radius: 20px;
  padding: 32px 24px 28px;
  box-shadow: 0 16px 34px rgba(0,0,0,0.4);
  transform: rotate(-0.6deg);
}
.poster-badge {
  display: inline-block;
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #3a2b00;
  background: linear-gradient(180deg, var(--gold), var(--gold-deep));
  padding: 7px 16px;
  border-radius: 999px;
  transform: rotate(-2deg);
  box-shadow: 0 4px 10px rgba(0,0,0,0.25);
}
.poster-title { margin: 18px 0 6px; }
.poster-sub { color: var(--muted); font-size: 1.1rem; text-align: center; margin-bottom: 22px; }
.poster-cta { max-width: 360px; margin-left: auto; margin-right: auto; }
.poster .invite-hint { max-width: 420px; margin-left: auto; margin-right: auto; text-align: center; }

.welcome-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}
.welcome-row .poster-cta { max-width: none; margin: 0; width: auto; padding-left: 28px; padding-right: 28px; }
.welcome-back-text { font-size: 1.15rem; font-weight: 700; color: var(--ink); }

.poster-stickers {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 26px;
}
.sticker {
  display: inline-block;
  font-size: 0.95rem;
  font-weight: 700;
  padding: 8px 16px;
  border-radius: 999px;
  box-shadow: 0 4px 10px rgba(0,0,0,0.25);
}
.sticker-red { background: linear-gradient(180deg, var(--red), var(--red-dark)); color: #fff; transform: rotate(-3deg); }
.sticker-green { background: linear-gradient(180deg, var(--green), var(--green-dark)); color: #fff; transform: rotate(2.5deg); }

.invite-tree { display: block; max-width: 190px; margin: 22px auto 0; }
.tree-svg { width: 100%; height: auto; display: block; }
.tree-tier-1 { fill: #3a8f5c; }
.tree-tier-2 { fill: #2c7849; }
.tree-tier-3 { fill: #1e5631; }
.tree-trunk { fill: #4a3324; }
.tree-star { fill: var(--gold); filter: drop-shadow(0 0 6px rgba(230,195,104,0.9)); }
.tree-light {
  transform-box: fill-box;
  transform-origin: center;
  animation: twinkle 1.8s ease-in-out infinite;
  animation-delay: calc(var(--i) * 0.28s);
}
.tree-light:nth-of-type(6n+1) { fill: #e0453c; filter: drop-shadow(0 0 4px rgba(224,69,60,0.9)); }
.tree-light:nth-of-type(6n+2) { fill: var(--gold); filter: drop-shadow(0 0 4px rgba(230,195,104,0.9)); }
.tree-light:nth-of-type(6n+3) { fill: #4a90d9; filter: drop-shadow(0 0 4px rgba(74,144,217,0.9)); }
.tree-light:nth-of-type(6n+4) { fill: #e0453c; filter: drop-shadow(0 0 4px rgba(224,69,60,0.9)); }
.tree-light:nth-of-type(6n+5) { fill: var(--gold); filter: drop-shadow(0 0 4px rgba(230,195,104,0.9)); }
.tree-light:nth-of-type(6n+6) { fill: #4a90d9; filter: drop-shadow(0 0 4px rgba(74,144,217,0.9)); }

.feed { margin-top: 40px; }

/* ---------- Countdown: FlipDown.js (vendored, see public/js/flipdown.js)
   theme + integration overrides. The flip mechanics themselves live in the
   library; this just reskins its default look to match our palette and
   drops it straight onto the page with no card wrapper around it. ---------- */
/* The vendored library gives .flipdown a fixed width/height sized for a
   2-digit day count. Our party is 3 digits out (100+ days), which overflows
   that fixed width — the floated 4th group (secs) then wraps, and because
   the container's height is also fixed it doesn't grow to contain that
   wrapped row, so it visually collides with whatever comes after it.
   Flexbox fixes both at once: wrapping is clean and the container's height
   sizes to fit however many rows it actually needs. */
.flipdown {
  width: auto;
  max-width: 100%;
  height: auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  row-gap: 10px;
  margin: 0 auto 28px;
}
.flipdown.flipdown__theme-christmas {
  font-family: var(--font);
}
.flipdown.flipdown__theme-christmas .rotor-group-heading:before {
  color: var(--muted);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.flipdown.flipdown__theme-christmas .rotor-group:nth-child(n+2):nth-child(-n+3):before,
.flipdown.flipdown__theme-christmas .rotor-group:nth-child(n+2):nth-child(-n+3):after {
  background-color: var(--gold-deep);
}
.flipdown.flipdown__theme-christmas .rotor,
.flipdown.flipdown__theme-christmas .rotor-top,
.flipdown.flipdown__theme-christmas .rotor-leaf-front {
  color: var(--ink);
  background-color: var(--panel);
}
.flipdown.flipdown__theme-christmas .rotor-bottom,
.flipdown.flipdown__theme-christmas .rotor-leaf-rear {
  color: var(--ink);
  background-color: var(--panel-2);
}
.flipdown.flipdown__theme-christmas .rotor:after {
  border-top: solid 1px var(--border);
}
/* Weak/old hardware skips this the same as the snow/Santa decorations —
   countdown.js also bails out before ever initializing FlipDown for them,
   this just covers the (rare) case the class is added after render. */
:root.low-power #flipdown { display: none; }
@media (prefers-reduced-motion: reduce) {
  .flipdown .rotor-leaf.flipped { transition: none; }
}

.feed-eyebrow {
  text-align: center;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gold);
  opacity: 0.9;
  margin-bottom: 18px;
}
.feed-card {
  display: flex;
  align-items: center;
  gap: 16px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 16px 18px;
  margin-bottom: 14px;
  box-shadow: 0 8px 18px rgba(0,0,0,0.22);
}
.feed-icon {
  flex-shrink: 0;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
}
.feed-icon-gold { background: rgba(230,195,104,0.18); }
.feed-icon-red { background: rgba(226,86,74,0.18); }
.feed-icon-green { background: rgba(70,184,116,0.18); }
.feed-body h3 { margin: 0 0 4px; font-size: 1.1rem; }
.feed-body p { margin: 0; color: var(--muted); font-size: 0.98rem; }

/* Mobile: announcements go above everything else, so a reminder is visible
   with zero scrolling instead of buried under every detail card. Desktop
   moves it into a side rail instead — see the min-width:860px override
   below, which redefines grid-template-areas/-columns for wider screens. */
.portal-layout {
  display: grid;
  grid-template-areas: "rail" "main";
  align-items: start;
  gap: 24px;
}
.portal-main { grid-area: main; }
.portal-rail { grid-area: rail; }

@media (min-width: 860px) {
  .poster { padding: 44px 48px 40px; }
  .poster-cta { display: inline-block; width: auto; padding-left: 40px; padding-right: 40px; }
  .feed-card { padding: 20px 24px; }

  .landing-layout {
    display: grid;
    grid-template-columns: 1.1fr 0.9fr;
    gap: 40px;
    align-items: center;
  }
  .landing-layout .poster {
    align-self: start;
    position: sticky;
    top: 24px;
  }
  .landing-layout .feed { margin-top: 0; }

  /* Portal pages (individual + household): announcements sit in a right
     rail next to the main content instead of below it, so they're
     glanceable without scrolling past everything else first. */
  .portal-layout {
    grid-template-areas: "main rail";
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 32px;
  }
  .portal-rail {
    position: sticky;
    top: 24px;
  }
  /* Collapsed-by-default only earns its keep on mobile, where a long post
     would otherwise push the guest's own RSVP status down the page. In the
     side rail there's room to just show everything. */
  .portal-rail .announcement-disclosure { display: none; }
  .portal-rail .announcement-static { display: block; }
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-block;
  width: 100%;
  text-align: center;
  font-size: 1.25rem;
  font-family: inherit;
  font-weight: 700;
  padding: 18px 20px;
  border-radius: 12px;
  border: none;
  cursor: pointer;
  text-decoration: none;
  margin: 8px 0;
  box-sizing: border-box;
  transition: transform 0.08s ease, box-shadow 0.08s ease;
}
.btn-primary {
  background: linear-gradient(180deg, var(--red), var(--red-dark));
  color: #fff;
  box-shadow: 0 3px 0 #6f130f, 0 8px 18px rgba(179,35,28,0.35);
}
.btn-primary:hover { filter: brightness(1.08); }
.btn-primary:active { transform: translateY(2px); box-shadow: 0 1px 0 #6f130f; }

.btn-secondary {
  background: linear-gradient(180deg, var(--green), var(--green-dark));
  color: #fff;
  box-shadow: 0 3px 0 #0f2e19, 0 8px 18px rgba(30,86,49,0.35);
}
.btn-secondary:hover { filter: brightness(1.08); }
.btn-secondary:active { transform: translateY(2px); box-shadow: 0 1px 0 #0f2e19; }

.btn-outline { background: var(--panel-2); color: var(--ink); border: 2px solid var(--border); }
.btn-small { width: auto; font-size: 1rem; padding: 10px 18px; }
.btn-danger { background: var(--panel-2); color: var(--red); border: 2px solid var(--red); }

.btn-yes {
  background: linear-gradient(180deg, var(--green), var(--green-dark));
  color: #fff;
  box-shadow: 0 3px 0 #0f2e19, 0 8px 18px rgba(30,86,49,0.35);
}
.btn-yes:hover { filter: brightness(1.08); }
.btn-yes:active { transform: translateY(2px); box-shadow: 0 1px 0 #0f2e19; }

.btn-no {
  background: linear-gradient(180deg, var(--red), var(--red-dark));
  color: #fff;
  box-shadow: 0 3px 0 #6f130f, 0 8px 18px rgba(179,35,28,0.35);
}
.btn-no:hover { filter: brightness(1.08); }
.btn-no:active { transform: translateY(2px); box-shadow: 0 1px 0 #6f130f; }

.stack { display: flex; flex-direction: column; gap: 10px; }
.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.row > * { flex: 1; }

/* ---------- Form controls: kept on a light surface for maximum legibility ---------- */
label { display: block; font-weight: 700; margin: 14px 0 6px; color: var(--ink); }
.plus-one-label { display: block; font-weight: 400; font-size: 0.85rem; color: var(--muted); margin: 10px 0 4px; }

/* ---------- Plus-one tag input: type-to-search, tap to add, like an email "To:" field ---------- */
.tag-input {
  position: relative;
  border: 2px solid var(--input-border);
  border-radius: 10px;
  background: var(--input-bg);
  padding: 8px;
}
.tag-input-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.tag-input-chips:not(:empty) {
  margin-bottom: 6px;
}
.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--panel-2);
  color: var(--ink);
  padding: 6px 6px 6px 14px;
  border-radius: 999px;
  font-size: 0.98rem;
  max-width: 100%;
}
.tag-chip span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tag-chip button {
  background: none;
  border: none;
  color: var(--ink);
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
  padding: 6px;
  min-width: 30px;
  min-height: 30px;
  border-radius: 50%;
  flex-shrink: 0;
}
.tag-chip button:hover, .tag-chip button:active { background: rgba(0,0,0,0.12); }
/* The suggested pill IS the button (see plus-one-tags.js) so the whole thing
   is tappable, not just the "+". Needs the usual button resets since it's a
   real <button> wearing .tag-chip's styling. */
.tag-chip-suggested {
  background: transparent;
  border: 2px dashed var(--border);
  color: var(--muted);
  font: inherit;
  cursor: pointer;
  padding: 6px 12px 6px 14px;
  text-align: left;
}
.tag-chip-suggested .tag-chip-add {
  color: var(--green);
  font-weight: 700;
  font-size: 1.15rem;
  line-height: 1;
  margin-left: 2px;
}
.tag-chip-suggested:hover, .tag-chip-suggested:active {
  background: rgba(70,184,116,0.18);
  border-color: var(--green);
  color: var(--ink);
}
.tag-input-field {
  border: none;
  background: transparent;
  width: 100%;
  padding: 10px 6px;
  font-size: 1.1rem;
  font-family: inherit;
  color: var(--input-ink);
  outline: none;
}
.tag-suggestions {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  z-index: 30;
  background: var(--input-bg);
  border: 2px solid var(--gold-deep);
  border-radius: 10px;
  margin-top: 6px;
  max-height: 280px;
  overflow-y: auto;
  list-style: none;
  padding: 0;
  box-shadow: 0 10px 24px rgba(0,0,0,0.35);
}
.tag-suggestion {
  padding: 13px 16px;
  font-size: 1.05rem;
  color: var(--input-ink);
  cursor: pointer;
  border-bottom: 1px solid var(--input-border);
}
.tag-suggestion:last-child { border-bottom: none; }
.tag-suggestion:hover, .tag-suggestion:active { background: rgba(0,0,0,0.06); }
.tag-suggestion .household { color: var(--muted); font-size: 0.9rem; }
.tag-suggestion.add-new { font-weight: 700; color: var(--red-dark); }
.no-js .tag-input { display: none; }
input[type=text], input[type=password], input[type=date], input[type=email], textarea, select {
  width: 100%;
  font-size: 1.15rem;
  font-family: inherit;
  padding: 14px;
  border: 2px solid var(--input-border);
  border-radius: 10px;
  background: var(--input-bg);
  color: var(--input-ink);
}
input:focus, textarea:focus, select:focus {
  outline: 3px solid var(--gold);
  outline-offset: 1px;
  border-color: var(--gold-deep);
}
textarea { min-height: 90px; resize: vertical; }

.checkbox-row { display: flex; align-items: center; gap: 12px; padding: 8px 0; }
.checkbox-row input[type=checkbox],
.checkbox-row input[type=radio] {
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  accent-color: var(--red);
}
.checkbox-row label { margin: 0; font-weight: 400; font-size: 1.15rem; color: var(--ink); }

.checkbox-row.attend-yes input[type=radio] { accent-color: var(--green); }
.checkbox-row.attend-yes label { color: var(--green); font-weight: 700; }
.checkbox-row.attend-no input[type=radio] { accent-color: var(--red); }
.checkbox-row.attend-no label { color: var(--red); font-weight: 700; }

.guest-block {
  border: 2px solid var(--border);
  border-radius: 12px;
  padding: 16px;
  margin-bottom: 16px;
  background: var(--panel-2);
}
.guest-block.attending { border-color: var(--green); background: #16301f; }

.pill {
  display: inline-block;
  padding: 5px 14px;
  border-radius: 999px;
  font-size: 0.95rem;
  font-weight: 700;
}
.pill-yes { background: #16301f; color: #8fe0ac; }
.pill-no { background: #341715; color: #f0a89f; }
.pill-pending { background: #362c12; color: #e8c96b; }

/* A row in a grouped list (household "Who's Coming"): every row, toggle or
   plain, shares the same chevron-slot + text layout so names all line up on
   the same left edge — a plain row just leaves the slot empty instead of
   getting a real triangle. */
.who-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  border-radius: 8px;
  font-size: 1.05rem;
}
.who-chevron {
  display: inline-block;
  width: 14px;
  flex-shrink: 0;
  color: var(--gold);
  font-size: 1.1rem;
  transition: transform 0.15s ease;
}
.who-row-toggle {
  cursor: pointer;
  list-style: none;
  font-weight: 700;
  color: var(--ink);
  background: var(--panel-2);
  margin: 4px 0;
}
.who-row-toggle::-webkit-details-marker { display: none; }
.who-row-toggle:hover { background: var(--panel); }
details[open] > .who-row-toggle .who-chevron { transform: rotate(90deg); }


.summary-status {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-size: 1.3rem;
  font-weight: 800;
  text-align: center;
  padding: 6px 0 18px;
}
.summary-rows { display: flex; flex-direction: column; }
.summary-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}
.summary-row:last-child { border-bottom: none; }
.summary-row .label { font-weight: 700; color: var(--ink); }
.summary-row .value { color: var(--muted); text-align: right; }

.banner {
  background: var(--green-deep);
  color: #fff;
  padding: 14px 18px;
  border-radius: 12px;
  margin-bottom: 20px;
  font-weight: 700;
  text-align: center;
}
/* Neutral variant for "we can't show this right now" notices. Deliberately not
   green (.banner's default reads as success) and not red (.error reads as an
   accusation) - this should land as a mild, unremarkable glitch. */
.banner.banner-quiet {
  background: var(--panel-2);
  border: 1px solid var(--border);
  color: var(--muted);
  font-weight: 400;
}
.banner.live {
  background: linear-gradient(90deg, var(--gold-deep), var(--gold));
  color: #3a2b00;
  animation: pulse 2s infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.75; } }

.muted { color: var(--muted); font-size: 1rem; }

.wifi-box {
  background: linear-gradient(160deg, #0d1526, #070c17);
  color: #fff;
  border-radius: 12px;
  padding: 20px;
  text-align: center;
  border: 1px solid var(--border);
}
.wifi-box .value { font-size: 1.6rem; font-weight: 700; letter-spacing: 1px; margin-top: 4px; word-break: break-word; color: var(--gold); }
.wifi-box .label { color: #a89e8d; font-size: 0.95rem; text-transform: uppercase; letter-spacing: 1px; }
.wifi-qr {
  background: var(--input-bg);
  display: inline-block;
  padding: 10px;
  border-radius: 10px;
  margin-top: 18px;
  line-height: 0;
}
.wifi-qr svg { display: block; width: 150px; height: 150px; }
.wifi-qr-caption { color: #a89e8d; font-size: 0.85rem; margin: 10px 0 0; }

table { width: 100%; border-collapse: collapse; font-size: 1rem; }
th, td { text-align: left; padding: 8px 6px; border-bottom: 1px solid var(--border); }

.link-box {
  display: flex;
  gap: 8px;
  align-items: center;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
  font-family: ui-monospace, "SF Mono", Consolas, monospace;
  font-size: 0.9rem;
  overflow-x: auto;
  white-space: nowrap;
  color: var(--ink);
}

.top-nav { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.top-nav a { text-decoration: none; font-weight: 700; }

html.nav-loading, html.nav-loading a, html.nav-loading button { cursor: progress; }

.portal-rail h2 { font-size: 1.15rem; margin: 0 0 12px; }
.announcement-disclosure .announcement-list { margin-top: 14px; }
.announcement-static { display: none; }
.announcement-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 16px; }
.announcement-item { padding-bottom: 16px; border-bottom: 1px solid var(--border); }
.announcement-item:last-child { border-bottom: none; padding-bottom: 0; }
.announcement-date { font-size: 0.8rem; }
.announcement-text { margin: 6px 0 0; white-space: pre-wrap; }
.announcement-photo { width: 100%; border-radius: 10px; margin-top: 10px; display: block; }

.tag-santa {
  background: linear-gradient(160deg, #2a2013, #1c150c);
  border: 2px dashed var(--gold);
  border-radius: 12px;
  padding: 18px;
  text-align: center;
}
.tag-santa .name { font-size: 1.6rem; color: var(--gold); font-weight: 700; }

/* Secret Santa reveal, styled as a wrapped present with a gift tag tied to
   it — the ribbon is pure CSS (two crossed bars + a bow emoji), the tag is
   a separate opaque card layered on top so the ribbon never runs behind the
   name and hurts readability. The settle animation is a one-time flourish
   (like the box just landed under the tree); it never hides or delays the
   name itself, so it stays readable immediately even with motion off. */
.gift-reveal {
  position: relative;
  background: linear-gradient(155deg, var(--red-deep), var(--red-dark));
  border-radius: 14px;
  padding: 48px 16px 20px;
  overflow: hidden;
  animation: gift-settle 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}
.gift-reveal::before,
.gift-reveal::after {
  content: '';
  position: absolute;
  background: linear-gradient(180deg, var(--gold), var(--gold-deep));
}
.gift-reveal::before { top: 0; bottom: 0; left: 50%; width: 12px; margin-left: -6px; }
.gift-reveal::after { left: 0; right: 0; top: 50%; height: 12px; margin-top: -6px; }
.gift-reveal .bow {
  position: absolute;
  top: 6px;
  left: 8px;
  font-size: 2rem;
  line-height: 1;
  transform: rotate(-45deg);
}
.gift-tag {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px 18px;
  text-align: center;
  box-shadow: 0 6px 14px rgba(0, 0, 0, 0.35);
}
.gift-tag .name { font-size: 1.6rem; color: var(--gold); font-weight: 700; }
@keyframes gift-settle {
  0% { transform: translateY(-16px) scale(0.94); opacity: 0; }
  65% { transform: translateY(3px) scale(1.015); opacity: 1; }
  100% { transform: translateY(0) scale(1); }
}
:root.low-power .gift-reveal,
:root.low-power .gift-reveal .bow {
  animation: none;
}

.error { background: #341715; color: #f0a89f; padding: 12px 16px; border-radius: 10px; margin-bottom: 16px; font-weight: 700; }
.success { background: #16301f; color: #8fe0ac; padding: 12px 16px; border-radius: 10px; margin-bottom: 16px; font-weight: 700; }

@media (min-width: 600px) { .btn { width: auto; } }

@media (min-width: 860px) {
  .wrap { max-width: 780px; padding-top: 16px; }
  h1 { font-size: 3.2rem; }
  h2 { font-size: 1.5rem; }
  body { font-size: 21px; }
  /* Flowing columns, not a grid — a grid locks both columns to the same row
     heights, so a short card next to a tall one leaves dead space beneath it.
     Multi-column layout lets each card just stack under whatever's above it
     in its own column instead. */
  .detail-grid { column-count: 2; column-gap: 32px; }
  .detail-section { break-inside: avoid; -webkit-column-break-inside: avoid; }

  /* The landing page is a hero, not a form/reading page — let it actually
     use the screen instead of sitting in the same narrow column as forms. */
  .wrap.wrap-wide { max-width: 1160px; }

  /* Admin needs more room than a form page for the stat grid and guest
     tables, but a dashboard read at full hero width just leaves everything
     — text lines, the sign-up rows, the composer — stretched out and sparse. */
  .wrap.wrap-admin { max-width: 900px; }

  /* Admin-only: same flowing-column idea as .detail-grid. Each card holds a
     full table, so it needs real room — 2 columns here, not 3, so a
     half-width laptop window doesn't squeeze the table into a mess. */
  .admin-guest-grid { column-count: 2; column-gap: 24px; }
  .admin-guest-grid > .card {
    break-inside: avoid;
    -webkit-column-break-inside: avoid;
    /* A card is only as wide as its column — without this, a long email
       address can force the table wider than that, spilling visibly into
       the neighboring column instead of staying inside its own card. */
    overflow-x: auto;
  }
  .admin-guest-grid table { table-layout: fixed; }
  /* Only Name/Email are genuinely long-content columns — RSVP/+1/emoji
     columns hold short fixed labels ("pending", "yes", a checkmark) and
     don't need a share of the squeeze, let alone getting ellipsized. */
  .admin-guest-grid th:nth-child(1), .admin-guest-grid td:nth-child(1) { width: 16%; }
  .admin-guest-grid th:nth-child(2), .admin-guest-grid td:nth-child(2) { width: 30%; }
  .admin-guest-grid th:nth-child(3), .admin-guest-grid td:nth-child(3) { width: 22%; }
  .admin-guest-grid th:nth-child(4), .admin-guest-grid td:nth-child(4) { width: 20%; }
  .admin-guest-grid th:nth-child(5), .admin-guest-grid td:nth-child(5),
  .admin-guest-grid th:nth-child(6), .admin-guest-grid td:nth-child(6) { width: 6%; }
  .admin-guest-grid td:nth-child(1), .admin-guest-grid td:nth-child(2) {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

}

/* ---------- Admin dashboard: a dense tool for one person, not a festive
   invite — quieter type, no snowflake/dashed-border decoration, cards that
   read as data rows rather than gift tags. Scoped entirely under
   .admin-shell so none of it leaks onto guest-facing pages. ---------- */
.admin-shell h1 { text-align: left; text-shadow: none; }
.admin-header { margin-bottom: 20px; }
.admin-header .subtitle { text-align: left; margin-bottom: 0; }

.admin-shell h2 {
  text-align: left;
  text-transform: none;
  letter-spacing: normal;
  font-size: 1.1rem;
  color: var(--ink);
  border-bottom: 1px solid var(--border);
  padding-bottom: 8px;
  margin: 36px 0 14px;
  /* The quick-nav pills are sticky at top:0 — without this, jumping to a
     section lands its heading directly underneath the bar, invisible. */
  scroll-margin-top: 64px;
}
.admin-shell h2::before { content: none; }

.admin-shell .card { border-top: none; border: 1px solid var(--border); }

.admin-quicknav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 24px;
  position: sticky;
  top: 0;
  z-index: 5;
  padding: 10px 0;
  background: rgba(10,18,32,0.9);
  backdrop-filter: blur(6px);
}
.admin-quicknav a {
  color: var(--muted);
  text-decoration: none;
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--panel);
}
.admin-quicknav a:hover { color: var(--ink); border-color: var(--gold-deep); }

.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  margin-bottom: 8px;
}
.stat-tile {
  border-radius: 12px;
  padding: 18px 14px;
  text-align: center;
  border: 1px solid var(--border);
  background: var(--panel);
}
.stat-num { font-size: 2rem; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1; }
.stat-label { color: var(--muted); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; margin-top: 6px; }
.stat-yes { background: linear-gradient(160deg, rgba(70,184,116,0.16), var(--panel) 70%); border-color: rgba(70,184,116,0.35); }
.stat-yes .stat-num { color: var(--green); }
.stat-no { background: linear-gradient(160deg, rgba(226,86,74,0.14), var(--panel) 70%); border-color: rgba(226,86,74,0.32); }
.stat-no .stat-num { color: var(--red); }
.stat-plusones { background: linear-gradient(160deg, rgba(230,195,104,0.14), var(--panel) 70%); border-color: rgba(230,195,104,0.32); }

.announcement-form { display: flex; flex-direction: column; gap: 16px; }
.announcement-form textarea { min-height: 90px; }
.announcement-form-side { display: flex; flex-direction: column; gap: 12px; }
.announcement-form-side .btn { margin-top: 4px; }

.admin-signup-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.admin-signup-row:last-child { border-bottom: none; }
.admin-signup-main { flex: 1; min-width: 0; }
.admin-signup-main > span:first-child { font-weight: 700; }

.admin-household-title { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
/* margin-right:auto keeps the name+count together on the left and pushes the
   copy button to the right edge, now that this row has three children. */
.admin-household-count { font-size: 0.85rem; font-weight: 400; margin-right: auto; }
.btn-copy-link {
  flex-shrink: 0;
  font: inherit;
  font-size: 0.78rem;
  font-weight: 700;
  line-height: 1;
  padding: 7px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--muted);
  cursor: pointer;
}
.btn-copy-link:hover, .btn-copy-link:focus-visible {
  border-color: var(--gold);
  color: var(--ink);
}
.btn-copy-link.copied {
  border-color: var(--green);
  background: rgba(70, 184, 116, 0.18);
  color: var(--green);
}

.admin-guest-grid table th {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  text-align: left;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.admin-guest-grid table td { padding: 8px 6px; border-bottom: 1px solid var(--border); }
.admin-guest-grid table tr:last-child td { border-bottom: none; }
.admin-guest-grid table tr:hover td { background: var(--panel-2); }

@media (min-width: 640px) {
  .stat-grid { grid-template-columns: repeat(4, 1fr); }
  .announcement-form { flex-direction: row; }
  .announcement-form-message { flex: 1; display: flex; flex-direction: column; }
  .announcement-form-message textarea { flex: 1; }
  .announcement-form-side { width: 240px; flex-shrink: 0; }
}

@media (min-width: 1300px) {
  .wrap.wrap-wide { max-width: 1360px; }
  .landing-layout { grid-template-columns: 1fr 1fr; gap: 64px; }
}

@media (prefers-reduced-motion: reduce) {
  .banner.live { animation: none; }
  .bulb { animation: none; opacity: 0.85; }
  /* Snow and the sleigh stay visible even under reduced-motion: gentle falling
     snow isn't the kind of motion that setting is meant to suppress, and the
     user explicitly wants it visible regardless. Just slow it down a bit. */
  .santa-flyby { animation-duration: 100s; }
  .flake { animation-duration: 20s !important; }
  .snow-drift { animation: none; }
  .gift-reveal { animation: none; }
}

/* ---------- Automatic degradation for weak/old hardware ----------
   `low-power` is added to <html> by an inline script in <head> based on
   navigator.hardwareConcurrency / deviceMemory / connection.saveData — a
   best-effort hint, not a guarantee, but enough to skip the decorative
   layers before the browser ever has to paint them on a machine that'll
   struggle with 50 animated flakes and a filtered drop-shadow sprite. */
:root.low-power .snow-layer,
:root.low-power .santa-flyby {
  display: none;
}
:root.low-power .snow-drift { animation: none; }
:root.low-power .tree-light { animation: none; opacity: 0.9; }
:root.low-power .banner.live { animation: none; }
:root.low-power ::view-transition-old(root),
:root.low-power ::view-transition-new(root) {
  animation: none !important;
}

.tag-input-hint { color: var(--gold-deep); font-size: 0.92rem; margin: 6px 2px 0; }
.plus-one-help {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--panel-2);
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 700;
  cursor: help;
  vertical-align: middle;
}
