/* ==========================================================================
   Poultney Wedding — the design system
   Engraved invitation, not a wedding template.

   Every colour, size and duration in this file is a token. Nothing else in
   the codebase names a colour. The accent is one attribute on <html>, so
   Will and Paige can flick between all six on the real pages and land one.
   ========================================================================== */

/* ---- Type -------------------------------------------------------------- */
/* Cormorant Garamond is a DISPLAY face — 0.62 cap/x-height ratio. It sets
   beautifully at 72px and turns to grey mush at 17px, which is the single
   most common way this aesthetic goes wrong. So: Cormorant for display only,
   Spectral (0.68 ratio) for anything you actually read.

   Cormorant SC is a separate family because Google Fonts strips the `smcp`
   OpenType feature from its subsets — `font-variant-caps: small-caps` would
   give synthetic small caps, where the browser scales capitals down and the
   stems go thin. On a page built out of small-caps eyebrows that is the
   difference between engraved and Word document. */

:root {
  --t-mono:    0.6875rem;  /* 11px — eyebrow, small caps */
  --t-fine:    0.8125rem;  /* 13px — captions, footnotes */
  --t-base:    1.0625rem;  /* 17px — body */
  --t-lead:    1.25rem;    /* 20px — standfirst */
  --t-h4:      1.5rem;
  --t-h3:      2rem;
  --t-h2:      3rem;
  --t-h1:      4.5rem;
  --t-display: 7rem;

  --serif:  "Cormorant Garamond", Garamond, "Times New Roman", serif;
  --smcp:   "Cormorant SC", Garamond, serif;
  --text:   "Spectral", Georgia, "Times New Roman", serif;
}

/* ---- Colour ------------------------------------------------------------ */
/* Pure #000 on #FFF is 21:1 and looks like a browser default. Cotton stock
   is warm and ink is never truly black. */

:root {
  --cream:      #F7F4EE;   /* ground — Crane Lettra ecru */
  --paper:      #FBF9F5;   /* raised card stock */
  --ink:        #14110E;   /* warm off-black — 17.14:1 on cream */
  --ink-elev:   #1D1916;
  --body:       #4A443C;   /* 8.76:1 */
  --meta:       #6B635A;   /* 5.38:1 */
  --hairline:   #DCD5C7;   /* 1.33:1 — a rule, not a border */
  --hairline-2: #CFC7B7;
  --ink-body:   #F2EDE3;   /* 16.12:1 on ink */
  --ink-meta:   #B5ADA2;   /* 8.48:1 on ink */
  --ink-rule:   #332D27;

  --good:       #2F5D3A;
  --warn:       #8A6414;
  --bad:        #8C2F2F;
}

/* No single hex clears AA on cream AND on ink — claret is 10.10:1 on cream
   and 1.70:1 on ink, which is invisible. So each accent is a PAIR, and both
   halves swap together from the one attribute. */
:root                      { --accent:#6E1F2B; --accent-lift:#A85259; }
[data-accent="claret"]     { --accent:#6E1F2B; --accent-lift:#A85259; }
[data-accent="green"]      { --accent:#14452F; --accent-lift:#35855E; }
[data-accent="sapphire"]   { --accent:#1B2A4A; --accent-lift:#6784B4; }
[data-accent="brass"]      { --accent:#806530; --accent-lift:#C2A25C; }
[data-accent="sage"]       { --accent:#5E6A4E; --accent-lift:#93A183; }
[data-accent="aubergine"]  { --accent:#46284C; --accent-lift:#946C9B; }

/* Surface roles resolve the pair. Components only ever use --ac. */
:root {
  --bg: var(--cream); --bg-raised: var(--paper);
  --fg: var(--ink); --fg-body: var(--body); --fg-meta: var(--meta);
  --rule: var(--hairline); --ac: var(--accent);
}
[data-ground="ink"] {
  --bg: var(--ink); --bg-raised: var(--ink-elev);
  --fg: var(--ink-body); --fg-body: var(--ink-body); --fg-meta: var(--ink-meta);
  --rule: var(--ink-rule); --ac: var(--accent-lift);
}

/* Tints derive automatically — no extra tokens to maintain. */
.wash { background: color-mix(in oklab, var(--ac) 9%, var(--bg-raised)); }

/* ---- Space ------------------------------------------------------------- */
:root {
  --s1:.5rem; --s2:1rem; --s3:1.5rem; --s4:2rem;
  --s5:3rem;  --s6:4.5rem; --s7:7rem; --s8:10rem;
  --measure: 34rem;   /* the invitation card */
  --col: 68rem;
  --col-wide: 82rem;
}

/* ---- Motion ------------------------------------------------------------ */
/* One curve, three durations. Nothing bounces, nothing parallaxes.
   14px of travel, not 40 — the distance is the tell. Long travel reads as a
   template; 14px reads as paper settling. */
:root {
  --ease-rise: cubic-bezier(.22,.61,.36,1);
  --ease-fade: cubic-bezier(.4,0,.2,1);
  --dur-fast: 240ms;
  --dur-base: 620ms;
  --dur-slow: 900ms;
}

/* ==========================================================================
   Base
   ========================================================================== */

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

/* The browser hides [hidden] from its own stylesheet, which ANY author rule
   beats. One class rule setting `display` on a hidden overlay is enough to
   paint it over the whole page and eat every click — which is exactly what
   happened in the planner. Stated once, here, so it cannot happen again. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg-body);
  font-family: var(--text);
  font-size: var(--t-base);
  font-weight: 300;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Paper tooth — one turbulence layer at 3%. Enough that the cream isn't
   flat, not enough to notice. */
body::after {
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 1;
  opacity: .030; mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}
@media print { body::after { display: none; } }

h1, h2, h3, h4, .display {
  font-family: var(--serif);
  font-weight: 300;
  color: var(--fg);
  margin: 0;
  text-wrap: balance;
  font-variant-numeric: oldstyle-nums;
}
h1 { font-size: var(--t-h1); line-height: 1.02; letter-spacing: .005em; }
h2 { font-size: var(--t-h2); line-height: 1.12; letter-spacing: .008em; }
h3 { font-size: var(--t-h3); line-height: 1.20; letter-spacing: .010em; }
h4 { font-size: var(--t-h4); line-height: 1.30; letter-spacing: .012em; }

.display {
  font-size: clamp(3rem, 10vw, var(--t-display));
  line-height: .98; letter-spacing: .005em; font-style: italic;
}

p { max-width: 65ch; margin: 0 0 var(--s3); }
p:last-child { margin-bottom: 0; }
.lead { font-size: var(--t-lead); line-height: 1.55; color: var(--fg-body); }
.fine { font-size: var(--t-fine); line-height: 1.6; color: var(--fg-meta); }

/* The eyebrow is the load-bearing detail. Real small caps, and the tracking
   rule that separates this from a template: letter-spacing is inversely
   proportional to size. 0.34em at 11px, 0.005em at 72px. */
.eyebrow {
  font-family: var(--smcp);
  font-size: var(--t-mono);
  font-weight: 400;
  letter-spacing: .34em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--fg-meta);
  margin: 0 -.34em 0 0;   /* wide tracking adds space after the last letter */
}
.eyebrow--ac { color: var(--ac); }

.date {
  font-family: var(--smcp);
  font-size: var(--t-fine);
  letter-spacing: .30em;
  text-transform: uppercase;
  font-variant-numeric: lining-nums;
  color: var(--fg-meta);
}

/* Anything in a column of figures must line up. */
.schedule td, .tnum { font-variant-numeric: tabular-nums lining-nums; }

a { color: var(--ac); }

/* Underlines grow from the left rather than fading — the one flourish worth
   having. Links stay underlined on hover/focus so colour is never the only
   thing carrying meaning. */
.link {
  color: var(--fg); text-decoration: none;
  background-image: linear-gradient(var(--ac), var(--ac));
  background-size: 0% 1px; background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size var(--dur-fast) var(--ease-fade);
  padding-bottom: .15em;
}
.link:hover, .link:focus-visible { background-size: 100% 1px; }

/* Body copy links keep a permanent underline — inside a paragraph, a
   grow-on-hover underline is invisible until you touch it. */
.prose a { color: var(--fg); text-decoration-color: var(--ac); text-underline-offset: .18em; }

:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--ac); outline-offset: 3px;
}

img { max-width: 100%; height: auto; display: block; }

hr { border: 0; height: 1px; background: var(--rule); margin: 0; }

/* ==========================================================================
   Layout
   The air ratios are what make it read expensive:
     - space above a heading is 3x the space below it
     - section padding (7rem) is 4x the largest internal gap (2rem)
     - the invitation panel runs a 1:5 padding-to-width ratio
   ========================================================================== */

.wrap { max-width: var(--col); margin-inline: auto; padding-inline: var(--s4); }
.wrap--wide { max-width: var(--col-wide); }
.wrap--narrow { max-width: 46rem; }

section { padding-block: var(--s7); }
section + section { border-top: 1px solid var(--rule); }

.stack { display: flex; flex-direction: column; }
.stack > * + * { margin-top: var(--gap, var(--s3)); }

.grid {
  display: grid; gap: var(--s5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
}

/* Card internals key on the CARD's width, never the viewport — a card in a
   two-up grid is half the width its viewport breakpoint assumes. */
.card { container-type: inline-size; }

/* ==========================================================================
   Components
   ========================================================================== */

/* The invitation panel. Centred text is normally a mistake; here it is
   correct — but ONLY inside this panel. Every other page is left-aligned.
   Centring the FAQ is what makes wedding sites unreadable. */
.invite {
  max-width: var(--measure); margin-inline: auto;
  background: var(--bg-raised);
  padding: var(--s7) var(--s5);
  text-align: center;
  display: flex; flex-direction: column; align-items: center;
  gap: var(--s4);
  position: relative;
}
/* The plate mark — the impression the printing plate leaves in the cotton. */
.invite::before {
  content: ""; position: absolute; inset: var(--s3);
  border: 1px solid var(--rule); pointer-events: none;
}
.invite > * { position: relative; }

/* Ink pressed into paper: a bright edge below catching light from above,
   a faint dark halo over. Must be almost subliminal. */
.letterpress {
  text-shadow: 0 1px 0 rgba(255,255,255,.55), 0 -1px 1px rgba(20,17,14,.10);
}
[data-ground="ink"] .letterpress {
  text-shadow: 0 1px 0 rgba(0,0,0,.5), 0 -1px 1px rgba(255,255,255,.10);
}

.rule--flourish { width: 4.5rem; height: 1px; background: var(--ac); margin-inline: auto; border: 0; }

.orn { display: block; width: 180px; height: 12px; margin-inline: auto; color: var(--ac); }

/* A thin circle, a wide-tracked W P, nothing else. No interlocking script,
   no laurel wreath. */
.monogram {
  width: 76px; aspect-ratio: 1; display: grid; place-items: center;
  border: 1px solid var(--ac); border-radius: 50%;
  font-family: var(--serif); font-weight: 300;
  font-size: 1.75rem; letter-spacing: .06em; color: var(--ac);
  padding-left: .06em;   /* optical centring against the tracking */
  flex: none;
}

/* Buttons are engraved plates, not buttons. Square corners, wide small caps,
   fill on hover. 48px minimum height — the iPad-in-a-lap posture is
   imprecise and half this guest list is over sixty. */
.btn {
  appearance: none; cursor: pointer; background: transparent;
  border: 1px solid var(--ac); color: var(--ac);
  font-family: var(--smcp);
  font-size: var(--t-mono); letter-spacing: .26em; text-transform: uppercase;
  padding: .95rem 1.7rem; border-radius: 0;
  min-height: 48px;
  display: inline-flex; align-items: center; justify-content: center;
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-fade),
              background var(--dur-fast) var(--ease-fade),
              border-color var(--dur-fast) var(--ease-fade);
}
.btn:hover { background: var(--ac); color: var(--bg-raised); }
.btn--solid { background: var(--ac); color: var(--bg-raised); }
.btn--solid:hover { background: transparent; color: var(--ac); }
.btn--quiet { border-color: var(--rule); color: var(--fg-meta); }
.btn--quiet:hover { background: transparent; border-color: var(--ac); color: var(--ac); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }
.btn.is-busy { opacity: .6; pointer-events: none; }

/* ---- Navigation -------------------------------------------------------- */
/* Opaque bar with a 1px bottom rule. No blur, no glass — that reads iOS 2021
   and costs a repaint on every scroll frame. */
.nav {
  position: sticky; top: 0; z-index: 20;
  background: var(--bg-raised);
  border-bottom: 1px solid var(--rule);
}
.nav__in {
  max-width: var(--col-wide); margin-inline: auto;
  padding: var(--s2) var(--s4);
  display: flex; align-items: center; gap: var(--s4);
}
.nav__brand {
  font-family: var(--smcp); font-size: var(--t-mono);
  letter-spacing: .3em; text-transform: uppercase;
  color: var(--fg); text-decoration: none; margin-right: auto;
  white-space: nowrap;
}
/* Seven items in one row, no dropdowns, no hamburger on tablet — but they
   must never wrap into two ragged lines, which is what a flex-wrap nav does
   at exactly the widths an iPad uses. Below the point where they fit, the
   row scrolls sideways instead: every item stays reachable, the brand stays
   put, and nothing reflows. */
.nav__links {
  display: flex; gap: var(--s3); align-items: center;
  flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  margin-inline: calc(var(--s2) * -1);
  padding-inline: var(--s2);
}
.nav__links::-webkit-scrollbar { display: none; }
.nav__links a { white-space: nowrap; }
.nav__links a {
  font-family: var(--smcp); font-size: var(--t-mono);
  letter-spacing: .22em; text-transform: uppercase;
  color: var(--fg-meta); text-decoration: none;
  padding: .5rem 0; min-height: 44px; display: inline-flex; align-items: center;
  border-bottom: 1px solid transparent;
  transition: color var(--dur-fast) var(--ease-fade), border-color var(--dur-fast) var(--ease-fade);
}
.nav__links a:hover, .nav__links a[aria-current="page"] {
  color: var(--fg); border-bottom-color: var(--ac);
}

/* ---- The change banner ------------------------------------------------- */
.banner {
  background: color-mix(in oklab, var(--ac) 10%, var(--bg-raised));
  border-bottom: 1px solid var(--rule);
  padding: var(--s2) var(--s4);
  text-align: center; font-size: var(--t-fine);
  color: var(--fg);
}

/* ---- Schedule ---------------------------------------------------------- */
.schedule { width: 100%; border-collapse: collapse; }
.schedule tr + tr td { border-top: 1px solid var(--rule); }
.schedule td { padding: var(--s3) 0; vertical-align: baseline; }
.schedule .t {
  font-family: var(--smcp); letter-spacing: .12em; text-transform: uppercase;
  color: var(--ac); white-space: nowrap; padding-right: var(--s4);
  width: 1%;
}
.schedule .what { color: var(--fg); }
.schedule .what small { display: block; color: var(--fg-meta); font-size: var(--t-fine); }

/* ---- Forms ------------------------------------------------------------- */
/* Labels above fields, always. Placeholder-only labels vanish the moment you
   start typing and older users lose their place. 16px minimum on inputs so
   iOS does not zoom the page when they are focused. */
.field { display: block; margin-bottom: var(--s3); }
.field__label {
  display: block;
  font-family: var(--smcp); font-size: var(--t-mono);
  letter-spacing: .2em; text-transform: uppercase;
  color: var(--fg-meta); margin-bottom: var(--s1);
}
.field__hint { display: block; font-size: var(--t-fine); color: var(--fg-meta); margin-bottom: var(--s1); }

.input, .select, .textarea {
  width: 100%;
  font-family: var(--text); font-size: max(16px, var(--t-base));
  font-weight: 300; line-height: 1.5;
  color: var(--fg); background: var(--bg-raised);
  border: 1px solid var(--rule); border-radius: 0;
  padding: .8rem .9rem;
  min-height: 48px;
  transition: border-color var(--dur-fast) var(--ease-fade);
}
.input:focus, .select:focus, .textarea:focus { border-color: var(--ac); }
.textarea { min-height: 7rem; resize: vertical; }

/* A refusal is rendered against the input that caused it, and clears itself
   on the next keystroke. Stow printed "that username is taken" under the
   first password box, 382px below the field it referred to. */
.in-bad { border-color: var(--bad); }
.fld-bad {
  color: var(--bad); font-size: var(--t-fine);
  margin-top: .35rem; display: block;
}

/* ---- Pills and chips --------------------------------------------------- */
.pill {
  display: inline-flex; align-items: center; gap: .4em;
  font-family: var(--smcp); font-size: var(--t-mono);
  letter-spacing: .16em; text-transform: uppercase;
  padding: .3rem .7rem;
  border: 1px solid var(--rule); color: var(--fg-meta);
  white-space: nowrap;
}
.pill--good { border-color: var(--good); color: var(--good); }
.pill--warn { border-color: var(--warn); color: var(--warn); }
.pill--bad  { border-color: var(--bad);  color: var(--bad); }
.pill--ac   { border-color: var(--ac);   color: var(--ac); }

/* ---- Motion ------------------------------------------------------------ */
@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}
.reveal { animation: rise var(--dur-base) var(--ease-rise) both; }
.reveal:nth-child(2) { animation-delay: 90ms; }
.reveal:nth-child(3) { animation-delay: 180ms; }
.reveal:nth-child(4) { animation-delay: 270ms; }

/* Never park content at opacity:0 waiting on an observer — a guest on bad
   reception at the venue gets a blank page. The hidden state is gated on the
   observer being alive. */
.js-reveal .reveal-on-scroll { opacity: 0; transform: translateY(14px); }
.reveal-on-scroll.is-in {
  opacity: 1; transform: none;
  transition: opacity var(--dur-base) var(--ease-rise),
              transform var(--dur-base) var(--ease-rise);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
  .js-reveal .reveal-on-scroll { opacity: 1; transform: none; }
}

/* ---- Accent picker ----------------------------------------------------- */
/* Lives on the site until they land one, then comes off from the admin. */
.accents { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; justify-content: center; }
.accents button {
  width: 34px; height: 34px; border-radius: 50%; cursor: pointer;
  border: 1px solid var(--rule); padding: 0;
  transition: transform var(--dur-fast) var(--ease-rise), box-shadow var(--dur-fast) var(--ease-fade);
}
.accents button[aria-pressed="true"] {
  box-shadow: 0 0 0 1px var(--bg), 0 0 0 2px var(--fg);
  transform: scale(1.1);
}

/* ==========================================================================
   Phone
   Guest-side RSVP and day-of use are phone-first, so these are the defaults,
   not the adaptation.
   ========================================================================== */

@media (max-width: 640px) {
  :root { --t-h1: 3rem; --t-h2: 2.25rem; --t-display: 3.6rem; --s7: 4.5rem; --s6: 3rem; }
  .wrap { padding-inline: var(--s3); }
  .invite { padding: var(--s6) var(--s3); }
  /* On a phone the brand and seven links cannot share a row at all. The
     brand goes above, the links scroll under it. Still no hamburger — a
     menu behind a button is one more thing for a guest to find. */
  .nav__in { padding-inline: var(--s3); gap: var(--s1); flex-direction: column; align-items: stretch; }
  .nav__brand { margin: 0 0 var(--s1); text-align: center; }
  .nav__links { gap: var(--s3); justify-content: flex-start; }
  .nav__links a { min-height: 38px; padding-block: .25rem; }
  .schedule .t { display: block; padding-right: 0; padding-bottom: .2rem; width: auto; }
  .schedule td { display: block; }
  .schedule tr + tr td:first-child { border-top: 1px solid var(--rule); padding-top: var(--s3); }
  .schedule tr + tr td { border-top: 0; padding-top: 0; }
  .btn { width: 100%; }
  .btn--inline { width: auto; }
}

/* Safe areas — a sticky Save under the iPhone home indicator is unreachable. */
.sticky-actions {
  position: sticky; bottom: 0;
  background: var(--bg-raised); border-top: 1px solid var(--rule);
  padding: var(--s2) var(--s3);
  padding-bottom: calc(var(--s2) + env(safe-area-inset-bottom));
}

/* ==========================================================================
   Print
   Someone's mother will print this and stick it on the fridge.
   ========================================================================== */

@media print {
  :root { --bg: #fff; --bg-raised: #fff; --fg: #000; --fg-body: #222; --fg-meta: #555; --rule: #bbb; }
  .nav, .accents, .btn, .no-print { display: none !important; }
  section { padding-block: var(--s4); break-inside: avoid; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .8em; color: #555; }
  .invite::before { border-color: #bbb; }
}

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

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--bg-raised); color: var(--fg); padding: var(--s2) var(--s3);
  border: 1px solid var(--ac);
}
.skip:focus { left: var(--s2); top: var(--s2); }

/* The way in, in the footer. Quiet enough that a guest reads past it,
   present enough that Will and Paige can find it. */
.keyhole {
  display: inline-flex; color: var(--fg-meta); opacity: .5;
  transition: opacity var(--dur-fast) var(--ease-fade), color var(--dur-fast) var(--ease-fade);
  padding: .4rem;                 /* a 15px icon is not a 44px tap target on its own */
  margin: -.4rem;
}
.keyhole:hover, .keyhole:focus-visible { opacity: 1; color: var(--ac); }
