/* ── Allie & Mario ────────────────────────────────────────────────────────────
   Ported verbatim from the Claude Design source (`Allie and Mario.dc.html`).
   The <helmet> block of the design becomes the base layer below; everything
   else in the design lives as inline styles on the elements themselves, so
   this file stays deliberately thin.
   ──────────────────────────────────────────────────────────────────────────── */

/* Every colour in the site resolves to one of these. Edit the palette in
   `tools/palette.py` and re-run it — this block is generated, and the same
   block is written into the design source so the two cannot drift. */
/* palette:start — generated by tools/palette.py, do not edit by hand */
:root {
  --paper: oklch(0.955 0.016 84);
  --paper-a14: oklch(0.955 0.016 84 / 0.14);
  --paper-a28: oklch(0.955 0.016 84 / 0.28);
  --paper-a30: oklch(0.955 0.016 84 / 0.3);
  --paper-a35: oklch(0.955 0.016 84 / 0.35);
  --paper-a40: oklch(0.955 0.016 84 / 0.4);
  --paper-a42: oklch(0.955 0.016 84 / 0.42);
  --paper-a50: oklch(0.955 0.016 84 / 0.5);
  --paper-a55: oklch(0.955 0.016 84 / 0.55);
  --paper-a60: oklch(0.955 0.016 84 / 0.6);
  --paper-a70: oklch(0.955 0.016 84 / 0.7);
  --paper-a72: oklch(0.955 0.016 84 / 0.72);
  --paper-a85: oklch(0.955 0.016 84 / 0.85);
  --paper-a94: oklch(0.955 0.016 84 / 0.94);
  --paper-bright: oklch(0.98 0.01 84);
  --paper-bright-a05: oklch(0.98 0.01 84 / 0.05);
  --wine: oklch(0.30 0.128 22);
  --wine-a05: oklch(0.30 0.128 22 / 0.05);
  --wine-a35: oklch(0.30 0.128 22 / 0.35);
  --wine-a50: oklch(0.30 0.128 22 / 0.5);
  --wine-deep: oklch(0.28 0.12 22);
  --ink: oklch(0.20 0.02 30);
  --ink-a12: oklch(0.20 0.02 30 / 0.12);
  --ink-a14: oklch(0.20 0.02 30 / 0.14);
  --ink-a20: oklch(0.20 0.02 30 / 0.2);
  --ink-a24: oklch(0.20 0.02 30 / 0.24);
  --ink-soft: oklch(0.36 0.02 30);
  --ink-muted: oklch(0.38 0.02 30);
  --ink-dim: oklch(0.40 0.02 30);
  --ink-faint: oklch(0.44 0.02 30);
  --blush: oklch(0.88 0.06 40);
  --wine-hover: oklch(0.42 0.115 24);
  --terracotta: oklch(0.58 0.115 44);
  --alert: oklch(0.80 0.11 40);
  --note: oklch(0.86 0.09 44);
  --plate: oklch(0.36 0.10 23);
  --plate-alt: oklch(0.42 0.09 24);
}
/* palette:end */

html { scroll-behavior: smooth; }
body { margin: 0; background: var(--wine); -webkit-font-smoothing: antialiased; }
a { color: inherit; text-decoration: none; }
a:hover { color: var(--terracotta); }

@keyframes bf4 { 0%, 26.8% { opacity: 1; } 27.6%, 100% { opacity: 0; } }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes nudge { 0%, 62%, 100% { transform: translateY(0); } 78% { transform: translateY(5px); } }

@font-face { font-family: 'Silk Serif'; src: url('fonts/SilkSerif-Regular.otf') format('opentype'); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: 'Silk Serif'; src: url('fonts/SilkSerif-Italic.otf') format('opentype'); font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: 'Sacker Script'; src: url('fonts/SackerItalianScript.ttf') format('truetype'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Diatype'; src: url('fonts/Diatype-Regular.ttf') format('truetype'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Haas Grot Disp'; src: url('fonts/HaasGrotDisp-55.otf') format('opentype'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Haas Grot Disp'; src: url('fonts/HaasGrotDisp-65.otf') format('opentype'); font-weight: 500; font-display: swap; }

/* ── Interaction states ───────────────────────────────────────────────────────
   In the design source these are `style-hover` / `style-focus` attributes, which
   the design runtime applies by swapping inline styles. Inline styles outrank
   any selector, so the equivalent CSS has to carry `!important` to win the same
   way. Values are copied exactly from the source attributes.
   ──────────────────────────────────────────────────────────────────────────── */

/* style-focus on the gate's password field */
.gate-input:focus { border-color: var(--paper) !important; }

/* style-hover="background: var(--blush);" — gate submit */
.gate-submit:hover { background: var(--blush) !important; }

/* style-hover="opacity: 1; color: var(--paper);" — hero scroll cue */
.hero-scroll:hover { opacity: 1 !important; color: var(--paper) !important; }

/* style-hover="background: var(--blush); color: var(--wine-deep);" — nav RSVP */
.nav-rsvp:hover { background: var(--blush) !important; color: var(--wine-deep) !important; }

/* ── Nav: current section, and the RSVP button over a cream bar ───────────────
   Over the hero the button is cream on dark red, exactly like the hero's. Once
   the scroll pass turns the bar cream (`.nav-past`, set in app.js) that reads as
   nothing at all, so the button inverts.
   ──────────────────────────────────────────────────────────────────────────── */

.nav-past .nav-rsvp { background: var(--wine) !important; color: var(--paper) !important; }
.nav-past .nav-rsvp:hover { background: var(--wine-hover) !important; color: var(--paper) !important; }

/* The rule is reserved on every link, transparent until the section is current,
   so marking it moves nothing on the line. */
.nav-links a { border-bottom: 1px solid transparent; padding-bottom: 3px; }
.nav-links a.is-active { border-bottom-color: currentColor; }
.nav-links a.nav-rsvp { border-bottom: 0; }

/* The FAQ rows are <button>s; keep the caret/þtext colour inheriting on hover
   rather than picking up the global `a:hover` treatment. */
.faq-toggle { -webkit-tap-highlight-color: transparent; }

/* ── The "what to do" ledger ──────────────────────────────────────────────────
   build.py emits the unselected tab colours inline, because the design binds
   them through {{ t.bg }} / {{ t.fg }} / {{ t.border }} and a static build has no
   way to express a value that changes. Selection swaps them here, the same way
   the style-hover states above are handled — and for the same reason those need
   !important: inline styles outrank any selector.
   ──────────────────────────────────────────────────────────────────────────── */

.spot-tab[aria-selected="true"] {
  background: var(--paper) !important;
  color: var(--wine) !important;
  border-color: var(--paper) !important;
}
.spot-tab:hover { border-color: var(--paper) !important; }

/* The design carries its layout in inline styles, and an inline `display` beats
   the UA stylesheet's `[hidden] { display: none }` — without this an inactive
   panel's grid would still lay out and the tabs would appear to do nothing. */
[hidden] { display: none !important; }

/* Twenty-three links in one section, on a page where `a { text-decoration: none }`
   removes the only other affordance. Mark them, quietly. */
.spot-grid a {
  text-decoration: underline;
  text-decoration-color: var(--paper-a35);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
/* The global a:hover terracotta is low-contrast on the wine ground. */
.spot-grid a:hover { color: var(--note) !important; text-decoration-color: var(--note); }

.spot-tab:focus-visible,
.spot-grid a:focus-visible { outline: 2px solid var(--paper); outline-offset: 3px; }

/* ── Responsive layer ─────────────────────────────────────────────────────────
   The design is desktop-only: it carries its whole layout in inline styles and
   defines no behaviour below its 1328px content column. Below ~900px the
   210px sidebar grids and the three-column schedule rows collide, and the nav
   overruns its line. These rules restructure those four things and leave the
   design's type scale, colour and motion untouched. Inline styles outrank any
   selector, so overriding them requires !important.
   ──────────────────────────────────────────────────────────────────────────── */

/* The menu trigger exists in the markup at every width; it is only used below
   the breakpoint. */
.nav-toggle {
  display: none;
  /* The open menu is position:fixed, and positioned elements paint above static
     siblings regardless of DOM order — without its own positioning the trigger
     would be buried under the overlay it opened. */
  position: relative;
  z-index: 1;
  background: transparent;
  border: 0;
  padding: 0;
  color: inherit;
  font-family: 'Diatype', monospace;
  font-size: 13px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  cursor: pointer;
}

/* Seven links, the wordmark and the countdown share one line; below ~1180px the
   links tighten so the countdown keeps its place. `margin-left: auto` on the nav
   is what holds the links right: the countdown is a grid item, so anything that
   removes it (rather than merely narrowing it) hands the nav a wider track. It
   has to be a margin rather than `justify-self`, which would also become the
   containing block for the full-screen menu below the breakpoint. */
@media (max-width: 1180px) {
  .nav-links { font-size: 12px !important; gap: 16px !important; }
}
/* Three columns of ledger stop reading well before the main breakpoint. */
@media (max-width: 1100px) {
  .spot-grid { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
}

@media (max-width: 900px) {
  /* ── The gate ─────────────────────────────────────────────────────────────
     iOS Safari zooms the page in whenever a focused text input is under 16px,
     and app.js focuses this one on load — so the zoom lands on the very first
     screen a guest sees, clipping the wordmark and the line above the field off
     the right edge. 16px is the threshold, not a preference: at 16px Safari
     leaves the page alone, and the autofocus can stay (the keyboard still comes
     straight up). Desktop keeps the design's 14px.
     ──────────────────────────────────────────────────────────────────────── */
  .gate-input { font-size: 16px !important; }

  /* While the gate is up the page must not move behind it. The gate is
     position:fixed and opaque, so a swipe scrolls the document with nothing on
     screen to show that it happened, and the unlock then opens onto whatever
     section those swipes reached rather than the hero.

     `overflow: hidden` on the root is the usual way to say this and it does
     not work here: measured on iOS 26, the root computes to `hidden` and a
     swipe still took the document to 820. Refusing the gesture at the element
     under the finger does work. The field opts back in so the caret can still
     be dragged, which leaves a small surface where a swipe scrolls — `toTop`
     in app.js is what actually guarantees the end state, at every width. */
  html.gate-open #gate { touch-action: none; }
  html.gate-open .gate-input { touch-action: auto; }

  /* ── Copy: let the lines fill the measure ─────────────────────────────────
     Nineteen copy blocks carry `text-wrap: pretty` inline (one of them also
     `balance`, which the same selector catches). Chrome reads that as light
     orphan control, so it is invisible on the desktop the design was drawn
     for. Safari implements it as a whole-paragraph optimiser that minimises
     raggedness — and minimising rag in a 354px phone column means giving up a
     quarter of the measure. Measured on iOS 26 at 402pt: "Come kick off the
     weekend with us. Lots of tacos, lots of mezcal" set to lines of [265, 150]
     in a 354px column, stranding "Lots" at the end of a line 89px short of
     the edge. Reset, the same text sets to [352, 63].

     `text-wrap: wrap` is the shorthand that keeps the wrap mode and returns
     the *style* to auto, so lines fill greedily again. It has to be
     `!important` to outrank the inline shorthand, and it is matched on the
     inline value the way the reduced-motion block below matches on
     `animation:` — the design has no class to hang this on.
     ──────────────────────────────────────────────────────────────────────── */
  [style*="text-wrap: pretty"] { text-wrap: wrap !important; }

  /* ── Nav → full-screen menu ──────────────────────────────────────────── */
  .nav-toggle { display: block; }
  .nav-links { display: none !important; }

  body.nav-open .nav-links {
    display: flex !important;
    position: fixed;
    inset: 0;
    flex-direction: column;
    justify-content: center;
    /* The bar centres its items on the cross axis so the RSVP button's padding
       doesn't drag the labels off the baseline; at menu scale the list goes back
       to the page margin, and that inline value has to be overridden. */
    align-items: flex-start !important;
    gap: clamp(18px, 4vh, 34px) !important;
    padding: 0 clamp(24px, 3.9vw, 56px);
    box-sizing: border-box;
    background: var(--wine);
    color: var(--paper);
    font-family: 'Silk Serif', serif !important;
    font-size: clamp(30px, 8vw, 46px) !important;
    letter-spacing: 0.01em !important;
  }
  body.nav-open { overflow: hidden; }
  /* Past the hero the scroll pass gives the bar a backdrop-filter, and a filtered
     element becomes the containing block for its position:fixed descendants —
     which would trap the full-screen menu inside the 75px bar. Drop it while the
     menu is open; there is nothing behind the menu to blur anyway. */
  body.nav-open .nav { backdrop-filter: none !important; }
  /* Past the hero the scroll pass sets the bar's colour to near-black for the
     cream background; over the open menu that would be unreadable. */
  body.nav-open .nav-toggle { color: var(--paper) !important; }
  /* RSVP is a button in the bar; at menu scale it is another line of the list. */
  body.nav-open .nav-rsvp {
    background: transparent !important;
    color: inherit !important;
    padding: 0 !important;
    font-size: inherit !important;
    letter-spacing: inherit !important;
    /* The button stacks its "(coming soon)" sub-label; at menu scale the list is
       left-aligned, so the stack has to be too. */
    align-items: flex-start !important;
  }

  /* ── Hero ─────────────────────────────────────────────────────────────── */
  /* svh tracks the visible viewport, so the hero doesn't jump as mobile
     browsers show and hide their chrome. */
  #top { height: 100svh !important; min-height: 620px !important; }

  /* The three plates are placed in percentages tuned for a wide canvas. At
     phone width those percentages put one plate under the bar and two on top
     of the wordmark, so the plates are re-laid as a band: the photo-booth
     stack on the left, the other two stacked to its right, all three below the
     bar and none of them over any type.

     The band is described once here and the plates are positioned off it, so
     the three cannot drift apart:

       --band-top  clears the bar. The bar is 30px of padding either side of a
                   ~39px wordmark; 116px leaves a little air under it.
       --band-h    is capped in px so the photos don't balloon on a tall phone,
                   and is otherwise whatever the hero has left over once the
                   copy is paid for, so a short phone gives depth back to the
                   wordmark rather than crowding it. `max(100svh, 620px)` is
                   the hero's own height — the two values #top resolves between.
                   The 486px is what has to survive below the band: the 28px
                   gap, the tallest the copy block gets at phone width (247px),
                   24px of air, the scroll cue (17px), the hero's 54px foot,
                   and --band-top itself.

     Left / right edges resolve to the same page margin the wordmark and the
     nav use, so the band lines up with everything above and below it. The 55/45
     split (less a 10px gutter) keeps the booth stack roughly the portrait crop
     it has on the wide canvas. */
  #top {
    --band-top: 116px;
    --band-h: min(290px, calc(max(100svh, 620px) - 486px));
    --band-gap: 10px;
    --band-edge: clamp(24px, 3.9vw, 56px);
    --band-inner: calc(100% - 2 * var(--band-edge) - var(--band-gap));
  }
  .plate-a, .plate-b, .plate-c {
    max-width: none !important;
    bottom: auto !important;
  }
  /* The booth stack: left column, full band height. */
  .plate-a {
    top: var(--band-top) !important;
    left: var(--band-edge) !important;
    right: auto !important;
    width: calc(var(--band-inner) * 0.55) !important;
    height: var(--band-h) !important;
  }
  /* Right column, top half. Its exit vector is `right`, so it wants to be the
     plate nearest the right edge — from here it leaves the frame rather than
     sliding across the booth stack. */
  .plate-b {
    top: var(--band-top) !important;
    right: var(--band-edge) !important;
    left: auto !important;
    width: calc(var(--band-inner) * 0.45) !important;
    height: calc((var(--band-h) - var(--band-gap)) / 2) !important;
    /* The wide-canvas crop is a portrait one; these frames are landscape. */
    object-position: 46% 40% !important;
  }
  /* Right column, bottom half. */
  .plate-c {
    top: calc(var(--band-top) + (var(--band-h) + var(--band-gap)) / 2) !important;
    right: var(--band-edge) !important;
    left: auto !important;
    width: calc(var(--band-inner) * 0.45) !important;
    height: calc((var(--band-h) - var(--band-gap)) / 2) !important;
    object-position: 50% 62% !important;
  }
  /* The copy is centred in the first row of the stage, and the stage covers the
     whole hero — so reserving the band as top padding is what makes the
     wordmark unable to reach the photos, at any height, rather than merely not
     reaching them at the widths that happened to be checked. */
  .hero-stage {
    padding-top: calc(var(--band-top) + var(--band-h) + 28px) !important;
  }
  .hero-copy { width: 100% !important; }
  .hero-actions { width: auto !important; gap: clamp(20px, 5vw, 40px) !important; }

  /* ── Section grids: sidebar rail stacks above its content ─────────────── */
  .sec-grid {
    grid-template-columns: minmax(0, 1fr) !important;
    gap: clamp(22px, 5vw, 34px) !important;
  }
  .sec-rail { position: static !important; top: auto !important; }

  /* ── The ledger: one column, and tabs big enough for a thumb ──────────── */
  .spot-grid { grid-template-columns: minmax(0, 1fr) !important; }
  .spot-tab {
    flex: 1 1 auto !important;
    justify-content: center !important;
    padding: 15px 14px !important;
  }

  /* ── Schedule rows ────────────────────────────────────────────────────── */
  .sched-row {
    grid-template-columns: minmax(0, 1fr) !important;
    gap: 16px !important;
    padding: 34px 0 !important;
  }

  /* ── Footer ───────────────────────────────────────────────────────────────
     Wordmark left, meta right, on one line — the wide-canvas treatment, held
     together at phone width. It comes apart on its own because the row is
     `flex-wrap: wrap` around a 64px script wordmark: the meta block drops to a
     line of its own, where `space-between` has nothing to space it against and
     leaves it hard left, reading as loose content rather than a footer. Keep
     the line intact and bring the type down to footer scale.
     ──────────────────────────────────────────────────────────────────────── */
  .site-footer { padding-bottom: 44px !important; }
  .footer-inner {
    flex-wrap: nowrap !important;
    gap: 18px !important;
    padding-top: 26px !important;
  }
  /* The wordmark is the flexible half: it is the one that can afford to give up
     room, and `min-width: 0` is what lets it, since a flex item won't shrink
     below its content width without it. */
  .footer-mark {
    font-size: clamp(30px, 9.5vw, 40px) !important;
    min-width: 0 !important;
  }
  .footer-meta {
    flex: none !important;
    font-size: 10px !important;
    letter-spacing: 0.14em !important;
    line-height: 2 !important;
  }
}

/* Halving the hero's photo band needs the band to be deep enough that both
   halves still read as photographs. Under ~700px of viewport it isn't — the
   band is what the wordmark leaves over, and on a short phone that is not
   much — so the third frame steps out and the second takes the full depth,
   which is also closer to the portrait crop it has on the wide canvas.
   Legibility over frame count: the alternative is three slivers. */
@media (max-width: 900px) and (max-height: 699px) {
  .plate-c { display: none !important; }
  .plate-b {
    height: var(--band-h) !important;
    object-position: 46% 34% !important;
  }
}

/* Every other heading wraps onto a second line when it runs out of room. This
   one cannot — the rotating word is a single clipped line — so on the narrowest
   phones it has to shrink instead, or "WHAT TO EXPLORE" is cut off mid-word. */
@media (max-width: 374px) {
  #todo h2 { font-size: 26px !important; }
}

@media (max-width: 560px) {
  /* All three tabs on one line at phone width. */
  .spot-tab { padding: 14px 10px !important; letter-spacing: 0.1em !important; }
  [data-countdown] { font-size: 10px !important; letter-spacing: 0.14em !important; }
  /* Give the schedule's detail rows room: label and value on their own lines
     rather than squeezed onto one. */
  .sched-row > div:last-child > div { line-height: 2.1 !important; }
}

/* Honour the OS setting. The motion system in app.js already bails out of every
   scripted animation under `prefers-reduced-motion`; this switches off the two
   purely-declarative CSS animations (the hero photo-booth flicker and the
   scroll-cue nudge) so nothing is left moving. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  [style*="animation: bf4"],
  [style*="animation: nudge"] { animation: none !important; }
  /* With the flicker stopped, the stacked booth frames would all sit at
     opacity 1; keep only the topmost visible. */
  .booth-stack img { opacity: 0; }
  .booth-stack img:last-child { opacity: 1; }
}
