/* JoynUs app chrome, marketing, auth, host dashboard. Loaded after tokens.css. */

/* Wordmark face, self-hosted so the logo never depends on a CDN or flashes a
   fallback. Dancing Script, SIL OFL (see static/fonts/DancingScript-OFL.txt). */
@font-face {
  font-family: "Dancing Script";
  src: url("../fonts/dancing-script-700.1b7f787af95f.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; padding: 0; }

html { -webkit-text-size-adjust: 100%; }
body {
  font-family: var(--font-body);
  color: var(--ink);
  background-color: var(--paper);
  background-image: var(--grain);
  background-attachment: fixed;
  line-height: 1.6;
  font-size: var(--step-0);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

::selection { background: var(--clay); color: var(--paper); }

a { color: var(--clay-dark); text-decoration: none; text-underline-offset: 3px; }
a:hover { text-decoration: underline; }

h1, h2, h3 { font-family: var(--font-display); font-weight: 540; line-height: 1.08; letter-spacing: -0.015em; color: var(--ink); }
h1 { font-size: var(--step-3); }
h2 { font-size: var(--step-2); }
h3 { font-size: var(--step-1); }
p { max-width: 68ch; }

/* ---- Layout shell ---- */
.site-nav {
  display: flex; align-items: center; gap: var(--sp-3);
  width: 100%; max-width: 1080px; margin: 0 auto; padding: var(--sp-3) var(--sp-3);
  border-bottom: 1px solid var(--line);
  position: relative;
}
.site-nav .nav-links { display: flex; align-items: center; gap: var(--sp-3); }
/* Hamburger toggle, hidden on desktop, shown on narrow screens. */
.site-nav .nav-toggle {
  display: none; flex-direction: column; justify-content: center; gap: 5px;
  width: 44px; height: 44px; padding: 0; background: none; border: none; cursor: pointer;
}
.site-nav .nav-toggle-bar {
  display: block; width: 24px; height: 2px; margin: 0 auto; background: var(--ink);
  border-radius: 2px; transition: none;
}
.site-nav .brand {
  font-family: "Dancing Script", var(--font-display), cursive;
  font-size: 1.85rem; font-weight: 700; line-height: 1;
  letter-spacing: 0; color: var(--ink); display: inline-flex; align-items: center; gap: 0.5rem;
}
.site-nav .brand:hover { text-decoration: none; }
.site-nav .brand .seal {
  width: 1.6rem; height: 1.6rem;
  background: url("../img/brand/seal-64.b4b8367f9b9f.png") center / contain no-repeat;
}
.site-nav .spacer { flex: 1; }
.site-nav a:not(.brand) { color: var(--ink-soft); font-size: var(--step--1); letter-spacing: 0.02em; }
.site-nav a:not(.brand):hover { color: var(--ink); text-decoration: none; }

main { flex: 1 0 auto; width: 100%; max-width: 1080px; margin: 0 auto; padding: var(--sp-5) var(--sp-3) var(--sp-6); }

/* small-caps label */
.eyebrow {
  font-size: var(--step--1); letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--clay-dark); font-weight: 600;
}

/* ---- Buttons ---- */
button, .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
  font-family: var(--font-body); font-size: var(--step-0); font-weight: 600;
  letter-spacing: 0.01em; line-height: 1;
  padding: 0.8em 1.4em; border-radius: var(--radius); border: 1px solid transparent;
  background: var(--ink); color: var(--paper); cursor: pointer;
  transition: transform .15s ease, box-shadow .15s ease, background .15s ease;
  text-decoration: none;
}
button:hover, .btn:hover { transform: translateY(-1px); box-shadow: var(--shadow); text-decoration: none; }
button:active, .btn:active { transform: translateY(0); }
.btn-primary { background: var(--clay); color: var(--paper); }
.btn-primary:hover { background: var(--clay-dark); }
.btn-ghost { background: transparent; color: var(--ink); border-color: var(--line-strong); }
.btn-ghost:hover { background: var(--paper-2); box-shadow: none; }
.btn-lg { font-size: var(--step-1); padding: 0.85em 1.7em; }
button:focus-visible, .btn:focus-visible, a:focus-visible { outline: none; box-shadow: var(--ring); }

/* ---- Forms ---- */
label { display: block; font-size: var(--step--1); font-weight: 600; color: var(--ink-soft); margin-bottom: var(--sp-1); }
input, textarea, select {
  font: inherit; width: 100%; color: var(--ink);
  background: var(--paper); border: 1px solid var(--line-strong);
  padding: 0.7em 0.85em; border-radius: var(--radius); margin-bottom: var(--sp-3);
  transition: border-color .15s ease, box-shadow .15s ease;
}
input::placeholder, textarea::placeholder { color: var(--ink-faint); }
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--clay); box-shadow: var(--ring); }
textarea { min-height: 6rem; resize: vertical; }
form p { margin: 0; max-width: none; }
.helptext, .help { font-size: var(--step--1); color: var(--ink-faint); font-weight: 400; }
.error, .errorlist { color: var(--danger); font-size: var(--step--1); list-style: none; margin-bottom: var(--sp-2); }

/* The `hidden` attribute must actually hide. The browser's own rule for it is
   `[hidden] { display: none }`, which any class setting display beats on
   specificity, so `el.hidden = true` on a `.modal-field` (display: flex) or any
   other laid-out element silently does nothing. That is not theoretical: the
   collaborator invite dialog hides its Allowance field when the role is set to
   designer, because views_collab forces that allowance to 0 whatever is posted,
   and the field stayed on screen inviting the host to type a number that would be
   discarded. Found by screenshotting the dialog rather than by reading the JS,
   which looked correct and was. */
[hidden] { display: none !important; }

/* ---- Notices (app-wide feedback surface, see templates/_messages.html) ---- */
.notices { display: grid; gap: var(--sp-2); margin-bottom: var(--sp-4); }
.notice {
  border: 1px solid var(--line); border-left-width: 4px;
  border-radius: var(--radius); padding: var(--sp-2) var(--sp-3);
  background: var(--paper-2); font-size: var(--step--1); max-width: none;
}
.notice-success { border-left-color: var(--ok); }
.notice-error { border-left-color: var(--danger); color: var(--danger); }

/* ---- Cards & panels ---- */
.card {
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: var(--sp-4); box-shadow: var(--shadow-sm);
}
.panel {
  background: linear-gradient(var(--paper), var(--paper-2));
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: var(--shadow); padding: clamp(1.5rem, 4vw, 3rem);
}
/* `.panel` carries no vertical margin of its own, so two panels stacked on one
   page touch along their borders and read as a single card. Every page that
   stacked them worked around it by hand (`mt-4` on the second one, or `mb-16px`
   on the first), and the pages added on 2026-08-10 did not: the partner
   dashboard, the design page and the platform partner admin all shipped with
   their cards touching. Supply the gap once, here, rather than remembering the
   utility on every new template.

   The value is exactly what `mt-4` gives, so a page that already carries it
   renders identically. `margin-top` is one property and not a sum, so two rules
   setting it cannot double the gap, and where the panel ABOVE carries a bottom
   margin instead the two margins collapse to the larger of the pair rather than
   adding up. So there is no page on which this widens a gap by more than
   bringing it up to --sp-4.

   `:where()` keeps the selector at zero specificity on purpose, so a single
   class still beats it: the legal pages wrap their panels in `.stack`, whose
   `> * + *` deliberately sets a tighter --sp-3, and that keeps winning. Anything
   that wants a different gap says so on the element, as before. */
:where(.panel + .panel) { margin-top: var(--sp-4); }
/* Except after an explicit `mb-16px`, which is a choice somebody already made.
   `wa_merge.html` numbers its panels as a sequence of steps and spaces them
   tighter on purpose. Collapsing takes the LARGER of the two margins, so the rule
   above would have widened those to 32px, and this change is supposed to stop
   cards touching rather than to restyle a page that was already spaced. Measured
   before and after, so this is a number and not a guess. */
.mb-16px + .panel { margin-top: 0; }
/* A narrower panel for single-column settings-style forms. */
.panel-narrow { max-width: 640px; }
.legal-stack { max-width: 680px; }
/* A compact panel for a single stat line (e.g. guests-page allowance summary). */
.panel-stats { max-width: 560px; margin-bottom: var(--sp-3); }
/* Centered single-column panel for the event create/edit form. */
.panel-form { max-width: 600px; margin: 0 auto; }
/* Muted intro paragraph directly under a panel/section heading. */
.panel-intro { margin: 0.25rem 0 var(--sp-3); }

/* ---- Auth card ---- */
.auth-wrap { min-height: calc(100dvh - 6rem); display: grid; place-items: center; }
.auth-card { width: min(440px, 100%); }
.auth-card .panel { position: relative; overflow: hidden; }
.auth-card .panel::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 4px;
  background: linear-gradient(90deg, var(--clay), var(--gold));
}
.auth-card h1 { font-size: var(--step-2); margin: 0.4rem 0 0.25rem; }
.auth-card .sub { color: var(--ink-soft); margin-bottom: var(--sp-4); }
.auth-card button { width: 100%; }

/* ---- Standalone confirm pages (unsubscribe, etc.) ---- */
.confirm-page { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: var(--sp-4); background: var(--paper); }
.confirm-card { max-width: 440px; width: 100%; text-align: center; }
.confirm-card h1 { font-size: var(--step-2); margin: 0 0 var(--sp-3); }
.confirm-card .lede { color: var(--ink-soft); margin: 0 0 var(--sp-4); }
.confirm-card .fine-print { margin: var(--sp-3) 0 0; font-size: var(--step--1); color: var(--ink-faint); }

/* ---- Hero (marketing) ---- */
.hero {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: clamp(var(--sp-4), 5vw, var(--sp-6));
  align-items: center;
  padding: clamp(2rem, 7vw, 5.5rem) 0 var(--sp-6);
}
.hero-copy { max-width: 34ch; }          /* the measure lives on the text column, not the section */
.hero h1 { font-size: var(--step-4); margin: var(--sp-3) 0; }
.hero h1 em { font-style: italic; color: var(--clay-dark); }
.hero-copy p { font-size: var(--step-1); color: var(--ink-soft); max-width: 44ch; margin-bottom: var(--sp-4); }
.hero-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.hero-note { font-size: var(--step--1); color: var(--ink-faint); margin-top: var(--sp-3); letter-spacing: 0.01em; }

/* Signature: a sample bilingual invitation card with a wax seal */
.invite-preview { display: flex; justify-content: center; perspective: 1200px; }
.invite-card {
  --invite-paper: #f6eddc;
  --invite-ink:   #2c1a0e;
  --invite-gold:  #c9ad72;
  position: relative;
  width: min(340px, 100%);
  aspect-ratio: 4 / 5;
  padding: clamp(1.4rem, 3.5vw, 2.2rem) clamp(1.2rem, 3vw, 1.9rem) 3.4rem;
  background:
    radial-gradient(ellipse at 50% 20%, #fdf8ee 0%, var(--invite-paper) 62%, #efe2ca 100%);
  color: var(--invite-ink);
  text-align: center;
  border-radius: 3px;
  box-shadow: 0 1px 0 var(--invite-gold) inset, var(--shadow-lg);
  outline: 1px solid var(--invite-gold);
  outline-offset: -10px;
  transform: rotate(1.4deg);
  transition: transform .4s cubic-bezier(.2,.7,.2,1);
  animation: invite-rise .9s cubic-bezier(.2,.7,.2,1) both;
}
.invite-preview:hover .invite-card { transform: rotate(0deg) translateY(-4px); }
/* Botanical sprigs framing the card, behind the text (same art as the real invite) */
.invite-sprig { position: absolute; height: 44%; width: auto; opacity: 0.9; z-index: 0; pointer-events: none; }
/* Sit just inside the gold frame (outline is 10px in) so they align to that margin, not the card edge */
.invite-sprig--tr { top: 13px; right: 13px; }
.invite-sprig--bl { bottom: 13px; left: 13px; }
.invite-body { position: relative; z-index: 1; }
.invite-flourish {
  display: block; font-family: var(--font-secondary); font-size: 0.72rem;
  letter-spacing: 0.16em; text-transform: uppercase; color: #8a7856; margin-bottom: 0.9rem;
}
.invite-kicker {
  font-family: var(--font-display); font-style: italic; font-size: 0.86rem;
  color: #6b5b43; max-width: none; margin: 0 auto 1rem; line-height: 1.35;
}
.invite-name { font-family: var(--font-display); font-weight: 540; font-size: clamp(1.7rem, 4.5vw, 2.15rem); line-height: 1.05; }
.invite-name-heb { font-family: var(--font-secondary); font-size: clamp(1.4rem, 4vw, 1.85rem); margin-top: 0.15rem; }
.invite-card-rule { display: block; width: 46px; height: 1px; background: var(--invite-gold); margin: 1rem auto; }
.invite-meta { font-family: var(--font-body); font-size: 0.82rem; color: #4a3f30; line-height: 1.5; }
.invite-meta-heb { font-family: var(--font-secondary); font-size: 0.82rem; color: #4a3f30; }
.wax-seal {
  position: absolute; left: 50%; bottom: -22px; transform: translateX(-50%);
  display: grid; place-items: center; width: 58px; height: 58px; border-radius: 50%;
  background: radial-gradient(circle at 38% 32%, #8a3242, #6b2737 70%);
  color: var(--invite-gold); font-family: var(--font-display); font-weight: 600; font-size: 0.82rem;
  border: 1.5px solid var(--gold); box-shadow: 0 6px 14px -6px rgba(107,39,55,0.7);
}
@keyframes invite-rise {
  from { opacity: 0; transform: rotate(1.4deg) translateY(18px); }
  to   { opacity: 1; transform: rotate(1.4deg) translateY(0); }
}

/* ---- How it works (numbered because it IS a sequence) ---- */
.section-head { font-size: var(--step-2); max-width: 20ch; margin-bottom: var(--sp-4); }
.steps { margin-top: var(--sp-5); border-top: 1px solid var(--line); padding-top: var(--sp-5); }
.step-list { list-style: none; display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.step { position: relative; padding-top: var(--sp-3); border-top: 2px solid var(--line-strong); }
.step-n { font-family: var(--font-display); font-size: var(--step-1); color: var(--clay); letter-spacing: 0.02em; }
.step h3 { font-size: var(--step-1); margin: var(--sp-1) 0; }
.step p { color: var(--ink-soft); font-size: var(--step-0); }

.feature-row { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); margin-top: var(--sp-5); border-top: 1px solid var(--line); padding-top: var(--sp-4); }
.feature-row h3 { font-size: var(--step-1); margin-bottom: var(--sp-1); }
.feature-row p { color: var(--ink-soft); font-size: var(--step-0); }

/* ---- Closing CTA ---- */
.cta-band {
  margin-top: var(--sp-4); padding: clamp(2rem, 5vw, 3.25rem);
  background: var(--paper-2); border: 1px solid var(--line); border-radius: var(--radius-lg);
  text-align: center; box-shadow: var(--shadow-sm);
}
.cta-band h2 { font-size: var(--step-2); max-width: 22ch; margin: 0 auto var(--sp-3); }

/* ---- Trust strip (home page), a deliberate privacy-pledge band with a
   shield-check motif, so the message reads as an intentional trust
   statement rather than an orphaned mid-page footnote. Lighter than the
   .cta-band below it (a warm tint + gold hairline, no shadow) so the two
   bands don't merge. ---- */
.trust-strip {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: var(--sp-2); text-align: center;
  margin-top: var(--sp-5); padding: var(--sp-3) var(--sp-4);
  background: var(--paper-2); border: 1px solid var(--gold);
  border-radius: var(--radius-lg);
}
.trust-strip__icon { width: 1.5em; height: 1.5em; color: var(--clay-dark); flex: none; }
.trust-strip p { margin: 0; max-width: 56ch; color: var(--ink-soft); font-size: var(--step--1); }
.trust-strip a { color: var(--clay-dark); white-space: nowrap; }

/* ---- Home: feature grid ---- */
.feature-grid-section { margin-top: var(--sp-5); border-top: 1px solid var(--line); padding-top: var(--sp-5); }
.feature-grid { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.feature-card h3 { font-size: var(--step-1); margin-bottom: var(--sp-1); }
.feature-card p { color: var(--ink-soft); font-size: var(--step-0); }
.badge-new {
  display: inline-block; font-size: var(--step--1); font-weight: 600;
  color: var(--clay-dark); background: var(--paper-2);
  border: 1px solid var(--line-strong); border-radius: 999px;
  padding: 0 0.55em; margin-left: 0.35em; letter-spacing: 0.05em; text-transform: uppercase;
}

/* ---- Home: spotlights ---- */
.spotlight {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-5); align-items: center;
  margin-top: var(--sp-5); border-top: 1px solid var(--line); padding-top: var(--sp-5);
}
.spotlight-text h2 { font-size: var(--step-2); margin-bottom: var(--sp-2); max-width: 18ch; }
.spotlight-text p { color: var(--ink-soft); font-size: var(--step-1); max-width: 44ch; }
.spotlight-card {
  list-style: none; margin: 0; padding: var(--sp-4);
  background: var(--paper-2); border: 1px solid var(--line); border-radius: var(--radius-lg);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.spotlight-card li {
  font-family: var(--font-display); font-size: var(--step-1); color: var(--ink);
  padding-left: var(--sp-3); position: relative;
}
.spotlight-card li::before {
  content: ""; position: absolute; left: 0; top: 0.7em;
  width: 0.5em; height: 0.5em; background: var(--gold); border-radius: 999px;
}
.spotlight--reverse .spotlight-text { order: 2; }

/* ---- Home: pricing ---- */
.pricing { text-align: center; margin-top: var(--sp-5); border-top: 1px solid var(--line); padding-top: var(--sp-5); }
.pricing-card {
  max-width: 32rem; margin: var(--sp-4) auto 0; background: var(--paper-2);
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: var(--sp-5) var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2); align-items: center;
}
.pricing-card p { color: var(--ink-soft); font-size: var(--step-0); max-width: 40ch; }
.pricing-card strong { color: var(--ink); }
.pricing-price { font-family: var(--font-display); font-size: var(--step-3); color: var(--clay-dark); }
.pricing-card .fine { color: var(--ink-faint); font-size: var(--step--1); }
.pricing-card .btn { margin-top: var(--sp-3); }

@media (max-width: 640px) {
  .spotlight { grid-template-columns: 1fr; gap: var(--sp-4); }
  .spotlight--reverse .spotlight-text { order: 0; }
}

/* Stack the hero on narrow viewports; card follows the copy */
@media (max-width: 720px) {
  .hero { grid-template-columns: 1fr; gap: var(--sp-5); padding-top: var(--sp-4); }
  .hero-copy { max-width: none; }
  .invite-preview { order: 2; }

  /* Collapse the nav into a hamburger: the links drop down as a full-width
     panel below the bar instead of overflowing it. */
  .site-nav .nav-toggle { display: flex; }
  .site-nav .nav-links {
    display: none; position: absolute; top: 100%; left: 0; right: 0; z-index: 100;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--paper); border-bottom: 1px solid var(--line);
    box-shadow: var(--shadow-sm); padding: var(--sp-2) var(--sp-3);
  }
  .site-nav .nav-links.open { display: flex; }
  .site-nav .nav-links a,
  .site-nav .nav-links form button {
    display: flex; align-items: center; width: 100%; min-height: 44px;
    padding: 0.5em 0; font-size: var(--step-0); text-align: left;
  }
  .site-nav .nav-links form { width: 100%; }
  .site-nav .nav-links form button { justify-content: flex-start; }
}
@media (prefers-reduced-motion: reduce) {
  .invite-card { animation: none; transition: none; }
}

/* ---- Page header ---- */
.page-head { display: flex; flex-wrap: wrap; align-items: end; gap: var(--sp-3); justify-content: space-between; margin-bottom: var(--sp-4); padding-bottom: var(--sp-3); border-bottom: 1px solid var(--line); }
.page-head h1 { font-size: var(--step-3); }

/* ---- Event grid ---- */
.event-grid { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.event-card { display: block; background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: var(--sp-3) var(--sp-3) var(--sp-4); box-shadow: var(--shadow-sm); transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease; }
.event-card:hover { transform: translateY(-2px); box-shadow: var(--shadow); border-color: var(--line-strong); text-decoration: none; }
.event-card h2 { font-size: var(--step-1); margin-bottom: var(--sp-1); }
.event-card .meta { color: var(--ink-faint); font-size: var(--step--1); }

/* status chips */
.chip { display: inline-block; font-size: 0.7rem; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 700; padding: 0.25em 0.6em; border-radius: 100px; }
.chip-draft { background: var(--paper-3); color: var(--ink-soft); }
.chip-published { background: var(--clay-tint); color: var(--clay-dark); }
.chip-ok { background: #e8ede6; color: var(--ok); }

/* ---- Tables ---- */
table { width: 100%; border-collapse: collapse; font-size: var(--step-0); }
thead th { font-size: var(--step--1); letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-faint); text-align: left; font-weight: 700; padding: 0.6rem 0.75rem; border-bottom: 2px solid var(--line-strong); }
td { padding: 0.7rem 0.75rem; border-bottom: 1px solid var(--line); }
tbody tr:hover { background: var(--paper-3); }

/* ---- Stats ---- */
.stats { display: grid; gap: var(--sp-2); grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); margin-bottom: var(--sp-4); }
.stat { background: var(--paper-2); border: 1px solid var(--line); border-radius: var(--radius); padding: var(--sp-3); }
.stat .n { font-family: var(--font-display); font-size: var(--step-2); display: block; }
.stat .k { font-size: var(--step--1); color: var(--ink-faint); letter-spacing: 0.06em; text-transform: uppercase; }

/* Tiles are links to the filtered guest list. Keep them reading as tiles,
   inherit the text colour, no underline, but give a clear hover and a
   visible keyboard focus ring. */
a.stat { display: block; color: inherit; text-decoration: none;
         transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease; }
a.stat:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); border-color: var(--clay); }
a.stat:focus-visible { outline: 2px solid var(--clay-dark); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { a.stat { transition: none; } a.stat:hover { transform: none; } }

/* ---- Button variants ---- */
.btn-sm { font-size: var(--step--1); padding: 0.5em 0.9em; }
/* Compact button for tight table-row actions (e.g. remove/revoke), smaller than .btn-sm. */
.btn-compact { padding: 0.4em 0.8em; font-size: var(--step--1); }
/* Touch devices: small buttons (copy-link, toolbar actions) still need a
   44px tap target, the compact padding alone leaves them ~30px tall. */
@media (pointer: coarse) {
  .btn-sm { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
}
.btn-link { background: none; border-color: transparent; color: var(--clay-dark); padding: 0.2em 0.4em; font-weight: 600; box-shadow: none; }
.btn-link:hover { background: var(--paper-2); box-shadow: none; transform: none; text-decoration: underline; }
.btn-danger-link { color: var(--danger); }
.btn-danger-link:hover { background: var(--clay-tint); color: var(--danger); box-shadow: none; transform: none; }

/* ---- Inline form (label + compact input on one line) ---- */
/* Cost report: the seat budget + per-head rate (templates/events/cost_report.html).
   Two short numbers, so they sit on one line with the button; the help text
   breaks to its own row underneath rather than stretching the inputs. */
.cost-form input { max-width: 11rem; }
.cost-form > p { flex-basis: 100%; margin: 0; font-size: var(--step--1); }

.inline-form { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: flex-end; }
.inline-form label { margin-bottom: 0; }
.inline-form input { width: auto; margin-bottom: 0; }

/* ---- Person chips (gender-coded; used on guests + seating pages) ---- */
.person-chip {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 0.2em 0.65em; border-radius: 999px; font-size: var(--step--1);
  /* 1.5px, and transparent on EVERY chip: the status ring is a border-color
     swap, so the width must be identical whether or not a chip has a status,
     otherwise attending chips would sit smaller than their neighbours and the
     row edges stop lining up. The half-pixel is deliberate, 1px read too faint
     against the pale fills, 2px was the heavy band this replaced. */
  margin: 2px; border: 1.5px solid transparent;
  text-decoration: none; color: var(--ink); font-weight: 500; cursor: pointer;
}
.person-chip:hover { text-decoration: none; opacity: 0.85; }
.person-chip-M { background: #dbeafe; }
.person-chip-F { background: #fce7f3; }
.person-chip-u, .person-chip- { background: var(--paper-3); }
/* RSVP status per person, not per household: a household where one person comes
   and three don't cannot be summarised by one chip. Gender owns the background,
   so status takes the border and a ring and the two never fight. color-mix
   because --rsvp-yes/--danger are variables and cannot be fed to rgba() as
   channels. Attending uses --rsvp-yes (neon), NOT --ok: --ok is the muted sage
   the success notices are built from, and repointing it would turn every notice
   on the site neon. Declined deliberately stays the muted brick --danger, it
   sits at 45% opacity as the de-emphasised state, and a matching neon red would
   make declines shout louder than acceptances.
   The .45 opacity on declined predates the ring and is unscoped on purpose: it
   applies on every view (filtered or not, see guests.html's rsvp_state use),
   same as it always has. The ring (border-color + box-shadow) is the new bit
   and only makes sense on the unfiltered list, where colour actually varies
   chip-to-chip, .guest-list--status is the ancestor guests.html adds only
   there (mirrors show_person_status). */
.person-chip.declined { opacity: 0.45; }
/* A thin line and a soft halo, no hard ring. An earlier version stacked a 3px
   ring on a 10px bloom, which on a list of twenty households read as a heavy
   band rather than a lit edge. The glow is a single blurred shadow with no
   spread, so it stays behind the chip instead of thickening it. */
.guest-list--status .person-chip.attending { border-color: var(--rsvp-yes);
  box-shadow: 0 0 7px 0 color-mix(in srgb, var(--rsvp-yes) 60%, transparent); }
/* No halo on declined: it is the de-emphasised state (45% opacity), and only
   the thing you're scanning for should glow. */
.guest-list--status .person-chip.declined { border-color: var(--danger); }
.person-chip.selected { border-color: var(--clay-dark); }
.person-chip.dragging { opacity: 0.4; }
.chip-remove { background: none; border: none; cursor: pointer; font-size: 1em; line-height: 1; padding: 0 0 0 4px; color: var(--ink-soft); }
.chip-remove:hover { color: var(--danger); }

.drag-over { outline: 2px dashed var(--clay); outline-offset: 3px; background: var(--clay-tint) !important; }

.dnd-toast {
  position: fixed; bottom: var(--sp-4); left: 50%; transform: translateX(-50%);
  background: var(--danger); color: var(--paper); padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius); font-size: var(--step--1); z-index: 1000;
  pointer-events: none;
}
/* Danger red is the default because every original caller was a failure. The
   htmx flows also report successes ("Added, it's hidden by your search"), and
   a success in the failure colour reads as a failure. */
.dnd-toast.is-info { background: var(--ink-soft); }

/* ---- Utilities ---- */
.muted { color: var(--ink-faint); }
/* Visually hidden but available to screen readers (skip-links, field labels
   whose purpose is otherwise clear from context). */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.d-inline { display: inline; }
.m-0 { margin: 0; }
.mt-0 { margin-top: 0; }
.mt-2 { margin-top: var(--sp-2); }

/* Checkboxes/radios must not inherit the text-input box styling (border, padding,
   radius), render them as native controls, not little bordered boxes. */
input[type="checkbox"], input[type="radio"] {
  width: auto; margin-bottom: 0; padding: 0; border: 0; border-radius: 0;
  background: none; box-shadow: none;
}

/* Stacked page-form field: label above a full-width control (contrast with
   .modal-field, which is the label-left/input-right layout for dialogs only). */
.field { margin-bottom: var(--sp-3); }
.field > label { display: block; margin-bottom: var(--sp-1); }
.field > input, .field > textarea, .field > select { width: 100%; margin-bottom: 0; }

/* Checkbox rows rendered by Django's {{ form.as_p }}: keep label + box inline
   instead of the label floating far from the control. */
form p:has(> input[type="checkbox"]) { display: flex; align-items: center; gap: var(--sp-2); }
form p:has(> input[type="checkbox"]) > label { display: inline-block; margin: 0; }

/* <label class="inline-check"> in a dialog: the checkbox and its wording on one
   line. Promoted out of questions_manage.html's page <style> block, where it was
   written as `.modal-form .inline-check` and therefore did nothing for the guests
   page's reassign dialog, which uses the same class. Measured in a browser there:
   display:block, align-items:normal, so the intended row with a gap was never
   applied and the only reason it did not look broken is the
   input[type=checkbox]{width:auto} rule above. Exactly the defect commit f00369d
   ("the new pages use classes that exist") exists to stop.

   Scoped to `dialog` rather than global on purpose. accounts/account_settings.html
   also uses .inline-check, on a full-page form, and that page is one of the ten
   tools/visual_regression.py pins, so a global rule would silently restyle it.
   Every .modal-form in questions_manage.html is inside a <dialog>, so that page
   renders identically with the page-level rule removed. */
dialog .inline-check { display: flex; align-items: center; gap: var(--sp-2); }
dialog .inline-check input { width: auto; margin: 0; }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
.mb-2 { margin-bottom: var(--sp-2); }
.mb-3 { margin-bottom: var(--sp-3); }
.mb-4 { margin-bottom: var(--sp-4); }
.mb-5 { margin-bottom: var(--sp-5); }
.my-3 { margin: var(--sp-3) 0; }
.ml-auto { margin-left: auto; }
.mx-auto { margin-left: auto; margin-right: auto; }
.py-4 { padding: var(--sp-4) 0; }
.p-4 { padding: var(--sp-4); }
.text-right { text-align: right; }
.text-center { text-align: center; }
.text-soft { color: var(--ink-soft); }
.text-danger { color: var(--danger); }
.cursor-pointer { cursor: pointer; }
.font-mono { font-family: monospace; }
.bg-clay-tint { background: var(--clay-tint, #f3e0d8); }

.stack > * + * { margin-top: var(--sp-3); }
.toolbar { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }

/* Panel header: title on the left, a primary action on the right.
   (Distinct from `.section-head`, which is the marketing section-title style.) */
.panel-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-4); }
.panel-head > :is(h1, h2, h3) { margin: 0; }

/* Grouped action bar: labelled clusters instead of one random row of buttons. */
.action-bar { display: flex; flex-wrap: wrap; gap: var(--sp-4) var(--sp-5); align-items: flex-start; margin-bottom: var(--sp-2); }
.action-group { display: flex; flex-direction: column; gap: var(--sp-2); }
.action-group-label { font-size: var(--step--1); text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-faint); font-weight: 600; }
.action-group .toolbar { gap: var(--sp-2); }
hr { border: none; border-top: 1px solid var(--line); margin: var(--sp-4) 0; }
/* Wider vertical rhythm for an hr separating a major page section (e.g. the
   "Previous messages" divider on the broadcast page). A class always beats
   the bare `hr` element selector, so no ordering/specificity concern here. */
.hr-lg { margin: var(--sp-5) 0; }

/* ---- Collaborators ---- */
.collab-list { display: flex; flex-direction: column; gap: var(--sp-2); }
.collab-subhead { font-size: var(--step--1); text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-faint); font-weight: 600; margin: var(--sp-4) 0 var(--sp-2); }
.collab-row { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; padding: var(--sp-2) var(--sp-3); background: var(--paper-2); border: 1px solid var(--line); border-radius: var(--radius); }
.collab-row .who { flex: 1 1 12rem; min-width: 0; font-weight: 600; overflow-wrap: anywhere; }
.collab-row .who.is-pending { font-weight: 500; color: var(--ink-soft); }
.collab-actions { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.allowance-field { display: inline-flex; align-items: center; gap: var(--sp-2); }
.allowance-field > label { margin: 0; font-size: var(--step--1); color: var(--ink-faint); font-weight: 600; }
.allowance-field > input { width: 4.5rem; margin-bottom: 0; text-align: center; }
.collab-empty { color: var(--ink-faint); }
.copy-link { font-family: var(--font-body); font-size: var(--step--1); background: var(--paper-2); border: 1px dashed var(--line-strong); border-radius: var(--radius); padding: 0.5rem 0.7rem; word-break: break-all; }
/* <pre class="copy-link copy-link-pre">, preformatted config snippets that need horizontal scroll instead of wrapping. */
.copy-link-pre { white-space: pre; overflow-x: auto; }

/* position:fixed + inset:0 + margin:auto re-centres modal dialogs; the global
   `* { margin:0 }` reset above otherwise strips the UA centering -> top-left. */
dialog { border: 1px solid var(--line); border-radius: var(--radius-lg); padding: var(--sp-4); box-shadow: var(--shadow-lg); width: min(520px, 92vw); max-width: 92vw; position: fixed; inset: 0; margin: auto; }
dialog::backdrop { background: rgba(34, 30, 24, 0.4); }
/* Modal headings run one step smaller than page headings; `.stack` on the form
   handles the vertical rhythm, so modal markup needs no inline spacing. */
/* All modal titles render at one size regardless of heading level (h2/h3). */
dialog h2, dialog h3 { font-size: var(--step-1); }
/* Some modal headings (e.g. seating's "Add tables") run one step smaller than
   the default dialog h2 with a fixed margin-bottom instead of relying on
   .stack; a single class beats the two-element `dialog h2` rule above. */
.modal-title-sm { font-size: var(--step-0); margin-bottom: var(--sp-3); }
/* Modal form rows: label left, input right-aligned to a common width so fields
   line up into a unified column. Use <label class="modal-field"><span>…</span><input></label>. */
.modal-field { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-2); }
.modal-field > span { flex: 0 0 38%; color: var(--ink-faint); font-size: 0.85rem; }
.modal-field > input, .modal-field > select { flex: 1 1 auto; min-width: 0; margin-bottom: 0; }
/* .modal-field is defined after .mb-3 in this file, so a same-specificity
   class="modal-field mb-3" tie would let .modal-field's own margin-bottom win
   regardless of markup order (broadcast.html needs the wider sp-3 gap). The
   compound selector forces the intended precedence. */
.modal-field.mb-3 { margin-bottom: var(--sp-3); }

.invite-rule { display: flex; align-items: center; gap: var(--sp-2); color: var(--ink-faint); font-size: var(--step--1); }
.invite-rule::before, .invite-rule::after { content: ""; height: 1px; flex: 1; background: var(--line); }

/* Rate-limited notice (accounts/rate_limited.html) */
.rate-limited-notice { max-width: 32rem; margin: 4rem auto; text-align: center; }

/* Branded error pages (404/403/400/500), a centred wax-seal motif. */
.error-page { text-align: center; max-width: 34rem; margin: clamp(2.5rem, 11vh, 6rem) auto; padding: 0 var(--sp-3); }
.error-seal { width: 5rem; height: 5rem; margin: 0 auto var(--sp-3);
  background: url("../img/brand/seal-128.66efc540e5c0.png") center / contain no-repeat;
  filter: drop-shadow(0 8px 22px rgba(34,30,24,0.18)); }
.error-code { display: block; font-family: var(--font-display); font-size: var(--step-0);
  letter-spacing: 0.32em; text-transform: uppercase; color: var(--gold); margin-bottom: var(--sp-2); }
.error-page h1 { font-size: var(--step-4); margin-bottom: var(--sp-2); }
.error-page h1 em { font-style: italic; color: var(--clay-dark); }
.error-page p { color: var(--ink-soft); font-size: var(--step-1); margin-bottom: var(--sp-4); max-width: 30ch; margin-left: auto; margin-right: auto; }
.error-actions { display: flex; gap: var(--sp-2); justify-content: center; flex-wrap: wrap; }

footer.site-foot { width: 100%; max-width: 1080px; margin: 0 auto; padding: var(--sp-4) var(--sp-3); border-top: 1px solid var(--line); color: var(--ink-faint); font-size: var(--step--1); display: flex; flex-wrap: wrap; gap: var(--sp-3); justify-content: space-between; }

.hero-music-credit { font-size: var(--step--1); color: var(--ink-faint); margin-top: var(--sp-2); text-align: center; }
.music-credit { font-size: var(--step--1); color: var(--ink-faint); text-align: center; margin-top: var(--sp-4); }

@media (max-width: 640px) {
  .hide-on-mobile { display: none !important; }
}

/* ---- Spacing / text utilities (raw px, these values don't line up with the
   --sp-*/--step-* scale, so they use an explicit "-Npx" suffix rather than
   the token-index naming (.mt-4 etc.) to avoid implying a scale relationship
   that isn't there.) ---- */
.mt-4px  { margin-top: 4px; }
.mt-6px  { margin-top: 6px; }
.mt-8px  { margin-top: 8px; }
.mt-12px { margin-top: 12px; }
.mb-4px  { margin-bottom: 4px; }
.mb-8px  { margin-bottom: 8px; }
.mb-10px { margin-bottom: 10px; }
.mb-12px { margin-bottom: 12px; }
.mb-16px { margin-bottom: 16px; }
/* Beats the page-local .wa-warning { margin-bottom: 12px } rule in wa_merge.html
   on specificity (two classes > one) regardless of source order. */
.wa-warning.mb-16px { margin-bottom: 16px; }
.gap-10px { gap: 10px; }
.text-80 { font-size: 0.8rem; }
.text-85 { font-size: 0.85rem; }
.text-875 { font-size: 0.875rem; }
.text-90 { font-size: 0.9rem; }
.text-step-neg1 { font-size: var(--step--1); }
/* Matches the fallback-only var(--color-muted, #6b7280) used throughout the
   wa_merge.html page-local <style> block, kept consistent with that palette
   rather than .muted's --ink-faint so colors stay pixel-identical. */
.wa-text-muted { color: var(--color-muted, #6b7280); }
.is-hidden { display: none; }

/* ---- WhatsApp merge page (templates/events/wa_merge.html) ---- */
.wa-step-title    { font-size: 1rem; margin-bottom: 12px; }
.wa-step-title-sm { font-size: 1rem; margin-bottom: 8px; }
.wa-msg-textarea {
  width: 100%; font-family: inherit; padding: 8px;
  border: 1px solid var(--color-border, #d1d5db); border-radius: 6px;
  font-size: 0.9rem; resize: vertical;
}
.wa-country-code-input {
  width: 60px; margin-left: 6px; padding: 2px 6px;
  border: 1px solid var(--color-border, #d1d5db); border-radius: 4px;
  font-size: 0.9rem;
}
.wa-delay-slider { width: 200px; }
/* Static portion of #selector-warning / #wa-notice; both start hidden and are
   toggled via el.style.display in wa-merge.js, so the display:none stays a
   bespoke inline style (removing it would leave them permanently hidden once
   the JS clears the inline style back to ''). */
.wa-selector-warning-text { font-size: 0.85rem; color: #dc2626; }
.wa-notice-box {
  font-size: 0.9rem; color: #92400e; background: #fef3c7;
  border: 1px solid #f59e0b; border-radius: 6px; padding: 8px 12px;
}

/* ---- Help page (templates/marketing/help.html) ---- */
.help-lede { color: var(--ink-soft); margin: 0; }
/* Two columns: a sticky nav on the left, the section cards on the right. */
.help-body {
  display: grid; grid-template-columns: 210px minmax(0, 1fr);
  gap: var(--sp-4); align-items: start;
}
.help-sections { min-width: 0; }
/* Numbering comes from a counter, not the markup, because sections are filtered
   per role: a designer sees a subset, and hardcoded numbers would read 1, 2, 11.
   The counter renumbers whatever is actually on the page. */
.help-sections { counter-reset: help-section; }
.help-sections .help-section h2 { counter-increment: help-section; }
.help-sections .help-section h2::before { content: counter(help-section) ". "; }

/* Jump-to: a sticky vertical menu that stays visible while scrolling. */
.help-jump {
  position: sticky; top: var(--sp-3);
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--sp-3); background: var(--paper-2);
  border: 1px solid var(--line); border-radius: var(--radius-lg);
}
.help-jump > strong {
  font-size: var(--step--1); text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink-faint); margin-bottom: var(--sp-1);
}
.help-jump a {
  font-size: var(--step--1); color: var(--ink-soft); text-decoration: none;
  padding: 0.4em 0.6em; border-radius: var(--radius);
}
.help-jump a:hover { background: var(--paper); color: var(--clay-dark); text-decoration: none; }

/* On narrow screens, drop the sidebar: nav becomes a wrap of pills on top. */
@media (max-width: 720px) {
  .help-body { grid-template-columns: 1fr; }
  .help-jump {
    position: static; flex-direction: row; flex-wrap: wrap; align-items: center;
    gap: var(--sp-2); margin-bottom: var(--sp-4);
  }
  .help-jump > strong { margin-bottom: 0; margin-right: var(--sp-1); }
  .help-jump a { border: 1px solid var(--line); background: var(--paper); border-radius: 100px; }
}

/* Each section is its own card. */
.help-section {
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: clamp(1.4rem, 3vw, 2rem); box-shadow: var(--shadow-sm);
  margin-bottom: var(--sp-4); scroll-margin-top: var(--sp-4);
}
.help-section h2 { font-size: var(--step-1); margin-bottom: var(--sp-2); }
/* Global h3 is --step-1, the same size as the section heading, a subheading
   inside a section has to sit below it, not beside it. */
.help-section h3 { font-size: var(--step-0); margin: var(--sp-3) 0 var(--sp-1); }
.help-section p { margin-bottom: var(--sp-2); }
.help-section ul { padding-left: 1.4em; margin-bottom: var(--sp-2); }
.help-section li { margin-bottom: var(--sp-1); }
.help-section > :last-child { margin-bottom: 0; }

/* ---- Contacts page (templates/accounts/contacts.html) ---- */
.error-banner {
  background: #fee2e2; border: 1px solid #fca5a5; border-radius: var(--radius);
  padding: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-3); color: #991b1b;
}

/* ---- Broadcast page (templates/events/broadcast.html) ---- */
.channel-row { display: flex; gap: var(--sp-3); align-items: center; font-size: var(--step--1); margin-bottom: var(--sp-3); }
.channel-option { display: flex; align-items: center; gap: var(--sp-1); font-weight: normal; }
.broadcast-history-title { font-size: var(--step-0); margin-bottom: var(--sp-3); }
.broadcast-date-cell { white-space: nowrap; font-size: var(--step--1); }

/* ---- Registry manage page (templates/events/registry_manage.html) ---- */
.table-scroll { overflow-x: auto; }
.row-actions { display: flex; flex-wrap: wrap; gap: .3rem; align-items: center; }

/* Gift registry manage page. These two classes were used from the day the page
   shipped but never had rules, so the table fell back to browser defaults. */
.registry-manage { display: flex; flex-direction: column; gap: 1rem; }
.registry-table { width: 100%; border-collapse: collapse; font-size: var(--step-0); }
.registry-table th,
.registry-table td { padding: 0.6rem 0.75rem; text-align: left; vertical-align: top;
  border-bottom: 1px solid var(--line-strong); }
.registry-table th { font-weight: 600; }

