/* GetMyPhoto's guest-facing pages. Deliberately independent of
   style.css (which admin still uses): this is the Instabooth brand --
   four colours, no greys, heavy condensed caps, brush script, film
   grain -- not the photobooth-dashboard design system.

   Palette blocks resolve the same way theme.js does. Light is the bare
   root default; dark applies whenever the browser does not report a
   light preference; the data-theme blocks are the explicit user choice
   persisted in localStorage and win over both. theme.js drives the logo
   swap off the same resolution, so palette and artwork can never
   disagree.

   Swapping in the real licensed faces later: add @font-face blocks for
   Alfredino and Filmotype Keynote, repoint --font-display and
   --font-script, and set --display-stretch to 100% (real Alfredino is
   already condensed). No font-family literal appears anywhere else in
   this file or in any template. Two rules don't read --display-stretch
   at all and won't pick up that change: .display-m hardcodes 82% and
   .btn hardcodes 85%, both intentional per the spec's stretch table
   (display-m and buttons sit slightly wider than the headline stretch).
   Check those two by hand when swapping fonts. */

:root {
  /* --- Type ------------------------------------------------------- */
  /* Archivo is variable: wght 100-900, wdth 62-125%. Display sets
     wght 800 / font-stretch 78%; body uses the same family at 100%. */
  --font-display: 'Archivo', 'Archivo Narrow', 'Helvetica Neue', Arial, sans-serif;
  --font-body: 'Archivo', 'Helvetica Neue', Arial, sans-serif;
  --font-script: 'Yellowtail', 'Snell Roundhand', cursive;
  --font-mono: 'Space Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;
  --display-stretch: 78%;

  /* --- Texture ---------------------------------------------------- */
  /* Inline feTurbulence, desaturated, on a 160x160 tile -- no image
     asset, no extra request. Applied only by body::before. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='grain'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23grain)'/%3E%3C/svg%3E");

  /* --- Light palette (default) ------------------------------------ */
  /* --ink-display is black in light and cream in dark, so one headline
     rule serves both themes; the spec's light table folds it into --ink. */
  --bg: #FFE2BD;
  --surface: #FFFFFF;
  --wash: #FFEFDA;
  --ink: #000000;
  --ink-display: #000000;
  --ink-muted: rgba(0, 0, 0, 0.66);
  --rule: rgba(0, 0, 0, 0.10);
  --rule-strong: rgba(0, 0, 0, 0.28);
  --accent-fill: #B10808;
  --accent-ink: #B10808;
  --on-accent: #FFE2BD;
  --card-shadow: 0 2px 0 rgba(0, 0, 0, 0.16);
  --photo-shadow: 0 2px 0 rgba(0, 0, 0, 0.14);
  --photo-shadow-hover: 0 4px 0 rgba(0, 0, 0, 0.18);
  --focus-ring: rgba(177, 8, 8, 0.20);
  --grain-opacity: 0.18;
  --grain-blend: multiply;
}

/* Dark: pure black field, white/cream ink, edges rather than grey fills
   -- the brand's own BW treatment. Two reds, never interchanged:
   --accent-fill only ever backs a shape, --accent-ink only ever inks
   text. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #000000;
    --surface: #17120C;
    --wash: #000000;
    --ink: #FFFFFF;
    --ink-display: #FFE2BD;
    --ink-muted: rgba(255, 226, 189, 0.72);
    --rule: rgba(255, 226, 189, 0.28);
    --rule-strong: rgba(255, 226, 189, 0.40);
    --accent-fill: #C21212;
    --accent-ink: #EF3D30;
    --on-accent: #FFE2BD;
    --card-shadow: none;
    --photo-shadow: none;
    --photo-shadow-hover: none;
    --focus-ring: rgba(239, 61, 48, 0.24);
    --grain-opacity: 0.14;
    --grain-blend: screen;
  }
}

:root[data-theme="dark"] {
  --bg: #000000;
  --surface: #17120C;
  --wash: #000000;
  --ink: #FFFFFF;
  --ink-display: #FFE2BD;
  --ink-muted: rgba(255, 226, 189, 0.72);
  --rule: rgba(255, 226, 189, 0.28);
  --rule-strong: rgba(255, 226, 189, 0.40);
  --accent-fill: #C21212;
  --accent-ink: #EF3D30;
  --on-accent: #FFE2BD;
  --card-shadow: none;
  --photo-shadow: none;
  --photo-shadow-hover: none;
  --focus-ring: rgba(239, 61, 48, 0.24);
  --grain-opacity: 0.14;
  --grain-blend: screen;
}

:root[data-theme="light"] {
  --bg: #FFE2BD;
  --surface: #FFFFFF;
  --wash: #FFEFDA;
  --ink: #000000;
  --ink-display: #000000;
  --ink-muted: rgba(0, 0, 0, 0.66);
  --rule: rgba(0, 0, 0, 0.10);
  --rule-strong: rgba(0, 0, 0, 0.28);
  --accent-fill: #B10808;
  --accent-ink: #B10808;
  --on-accent: #FFE2BD;
  --card-shadow: 0 2px 0 rgba(0, 0, 0, 0.16);
  --photo-shadow: 0 2px 0 rgba(0, 0, 0, 0.14);
  --photo-shadow-hover: 0 4px 0 rgba(0, 0, 0, 0.18);
  --focus-ring: rgba(177, 8, 8, 0.20);
  --grain-opacity: 0.18;
  --grain-blend: multiply;
}

/* ====================================================================
   Page shell
   ==================================================================== */

* { box-sizing: border-box; }

/* The page colour lives here, not on body. The grain layer below sits at
   z-index:-1, which paints *behind* its parent's background box -- a
   painted body background would hide it completely. Putting the colour
   on the root also stops body's background propagating to the canvas,
   which is what gives mix-blend-mode something to blend against. */
html { background-color: var(--bg); }

body {
  margin: 0;
  min-height: 100vh;
  padding: 20px;
  background-color: transparent;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* Film grain: page field only, never card interiors or photos. Because
   cards, inputs, buttons and photos are opaque and paint above a
   z-index:-1 layer, the grain shows only in the gutters. Textured cream
   is the wall; the clean card is the print. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: var(--grain);
  background-size: 180px 180px;
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
}

.page {
  width: 100%;
  max-width: 30rem;
  margin: 0 auto;
}

.page.wide { max-width: 68rem; }

.card {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 14px;
  box-shadow: var(--card-shadow);
  padding: 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.site-footer {
  margin: 32px auto 0;
  max-width: 44ch;
  color: var(--ink-muted);
  font-size: 0.8125rem;
  line-height: 1.5;
  text-align: center;
}

/* ====================================================================
   Type scale -- the spec's table, one class per role
   ==================================================================== */

/* Each role is a self-contained rule rather than a grouped shorthand:
   the type-scale test reads one block per role, and a future edit to
   one size can't silently drag another role with it. */

.display-xl {
  margin: 0;
  max-width: 14ch;
  font-family: var(--font-display);
  font-weight: 800;
  font-stretch: var(--display-stretch);
  font-size: 2.25rem;
  line-height: 0.95;
  letter-spacing: 0.005em;
  text-transform: uppercase;
  color: var(--ink-display);
}

.display-l {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-stretch: var(--display-stretch);
  font-size: 1.75rem;
  line-height: 1.0;
  letter-spacing: 0.005em;
  text-transform: uppercase;
  color: var(--ink-display);
}

/* Display M and the button role sit slightly wider than the headline
   stretch -- condensation closes up badly at small sizes. */
.display-m {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-stretch: 82%;
  font-size: 1.25rem;
  line-height: 1.15;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  color: var(--ink-display);
}

.script-l {
  margin: 0;
  font-family: var(--font-script);
  font-weight: 400;
  font-size: 1.5rem;
  line-height: 1.25;
  color: var(--accent-ink);
}

.script-s {
  margin: 0;
  font-family: var(--font-script);
  font-weight: 400;
  font-size: 1.125rem;
  line-height: 1.3;
  color: var(--accent-ink);
}

.eyebrow {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.75rem;
  line-height: 1;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.body-copy {
  margin: 0;
  max-width: 60ch;
  font-size: 1rem;
  line-height: 1.55;
}

.body-strong { font-weight: 600; }

.small {
  margin: 0;
  font-size: 0.8125rem;
  line-height: 1.5;
}

.error {
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.5;
  color: var(--accent-ink);
}

.muted { color: var(--ink-muted); }

/* ====================================================================
   Card rhythm -- the exact gaps from the spec's page compositions
   ==================================================================== */

.card > * { margin-top: 0; margin-bottom: 0; }

.badge + .display-xl,
.badge + .display-l,
.venue-logo + .display-xl,
.venue-logo + .display-l { margin-top: 16px; }

.display-xl + .script-l,
.display-l + .script-l { margin-top: 8px; }

.script-l + .small,
.script-l + .body-copy,
.display-xl + .body-copy,
.display-l + .body-copy,
.display-l + .small { margin-top: 16px; }

/* The 20px gap before a card's CTA lives with the button rules in the
   components block, so the real .btn rule stays the first occurrence of
   that selector in the file -- the stylesheet tests brace-match on
   first occurrence, and this note must never itself quote the literal
   selector text or it becomes the first occurrence instead. */

/* ====================================================================
   Header
   ==================================================================== */

.guest-header { margin-bottom: 28px; }

.guest-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 12px;
}

.guest-logo-link { display: block; line-height: 0; }

/* The watermark PNGs carry exactly 5% transparent padding per side, so a
   34px box renders a ~30px optical wordmark at the 3.62:1 ink ratio
   (~110px wide). Never add padding here -- it would double the margin. */
.brand-logo {
  display: block;
  height: 34px;
  width: auto;
  padding: 0;
}

.theme-toggle {
  position: relative;
  flex: none;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  background: transparent;
  box-shadow: none;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

/* Visual circle stays 36px per the spec; the invisible overlay brings the
   hit area to the 44x44 minimum without changing a rendered pixel. */
.theme-toggle::after {
  content: "";
  position: absolute;
  inset: -4px;
}

/* Letterpress double rule: 3px, 4px gap, 1px. box-sizing: border-box
   means the 8px height minus 4px of borders leaves exactly the gap. */
.guest-rule {
  height: 8px;
  border-top: 3px solid var(--accent-fill);
  border-bottom: 1px solid var(--accent-fill);
}

/* ====================================================================
   Badge -- the oval lockup's geometry quoted once per page
   ==================================================================== */

.badge {
  display: inline-block;
  align-self: center;
  padding: 6px 14px;
  border-radius: 999px;
  background: var(--accent-fill);
  color: var(--on-accent);
}

/* ====================================================================
   Buttons -- stadiums, because a true ellipse can't survive responsive
   width changes while the same idea in stadium form can
   ==================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 52px;
  padding: 0 28px;
  border: 0;
  border-radius: 999px;
  font-family: var(--font-display);
  font-weight: 700;
  font-stretch: 85%;
  font-size: 1.0625rem;
  line-height: 1;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: transform 140ms ease-out, box-shadow 140ms ease-out, filter 140ms ease-out;
}

.btn-primary {
  background: var(--accent-fill);
  color: var(--on-accent);
}

.btn-primary:hover {
  filter: brightness(1.08);
  transform: translateY(-1px);
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
}

.btn-secondary {
  background: transparent;
  color: var(--ink);
  border: 2px solid var(--ink);
}

.btn-secondary:hover {
  transform: translateY(-1px);
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
}

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

.btn-block { width: 100%; }

.text-link {
  color: var(--accent-ink);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}

:focus-visible {
  outline: 2px solid var(--accent-fill);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .btn,
  .btn:hover,
  .btn:active { transform: none; }
}

/* ====================================================================
   In-card form rhythm -- 24px before the input group, 12px between
   fields, 20px before the CTA, 8px when an error sits above it
   ==================================================================== */

.card form {
  width: 100%;
  margin-top: 24px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

/* A CTA that isn't inside a form -- registered.html, not_found.html --
   is a direct child of the card and takes the same 20px gap. */
.card > .btn { margin-top: 20px; }

.card form .btn { margin-top: 20px; }
.card form .error { margin-top: 16px; }
.card form .error + .btn { margin-top: 8px; }

/* ====================================================================
   Code boxes -- printed ticket stub, real DOM cells rather than a
   single field with a background-drawn grid
   ==================================================================== */

/* Ten real 1-character inputs (2-char prefix, 8-char suffix) plus one
   static, non-interactive dash between them -- app/codes.py's own
   convention, same as the placeholder always showed. A single input
   with a CSS-drawn divider grid was tried first and dropped: the grid
   was math approximating the field's own width from font metrics (ch
   units), resolved in a separate pass from the field's actual layout,
   and the two could drift apart by a device pixel or more depending on
   the renderer/DPI -- invisible in one screenshot, visibly uneven cells
   (and a sliver of a 12th) on a real display. Real boxes in a flex row
   can't drift from themselves: the browser lays out N equal flex items
   in one pass, so the cells are exactly even by construction, on every
   renderer, at every DPI, with zero approximation anywhere. */
.code-boxes {
  display: flex;
  width: 100%;
  background-color: var(--wash);
  border: 2px solid var(--ink);
  border-radius: 6px;
  overflow: hidden;
}

.code-box,
.code-dash {
  flex: 1 1 0;
  min-width: 0;
  height: 60px;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 1.5rem;
  text-align: center;
  border-right: 1px solid var(--rule-strong);
}

.code-boxes > :last-child { border-right: none; }

.code-box {
  padding: 0;
  border-top: none;
  border-bottom: none;
  border-left: none;
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  caret-color: var(--accent-ink);
  text-transform: uppercase;
}

.code-box:focus {
  outline: none;
  background: var(--surface);
  box-shadow: inset 0 -3px 0 var(--accent-fill);
}

/* The dash is a label, not a field -- never typed, never focusable,
   always assumed at this fixed position (see the sync() note in
   index.html's script). flex-basis-1 keeps it visually the same width
   as a letter cell, matching the reference layout. */
.code-dash {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-muted);
}

/* ====================================================================
   Text fields and the consent row
   ==================================================================== */

.venue-logo {
  /* A fixed frame, not just a max-height cap: venues upload logos of
     wildly different native resolutions and aspect ratios (square marks,
     wide wordmarks, thin banners), and max-height alone only ever shrinks
     an oversized one -- it never scales a small source file up to use the
     space. Giving both axes a fixed size and letting object-fit letterbox
     the actual image inside that frame keeps every venue's logo reading
     as the same intentional size, regardless of the file's own pixels. */
  width: min(240px, 100%);
  height: 96px;
  object-fit: contain;
}

.field {
  width: 100%;
  height: 52px;
  padding: 0 16px;
  font-family: var(--font-body);
  /* 16px exactly: anything smaller makes iOS Safari zoom on focus. */
  font-size: 1rem;
  line-height: 1.4;
  color: var(--ink);
  background: var(--wash);
  border: 1px solid var(--rule-strong);
  border-radius: 8px;
}

.field::placeholder {
  /* No opacity knock-down here: these fields have no <label>, so the
     placeholder is the field's only accessible name on the form that
     collects the guest's PII. --ink-muted's own rgba alpha (0.66 light,
     0.72 dark) already clears 4.5:1 against --wash; stacking opacity on
     top of that dropped both themes below AA. See
     tests/test_guest_contrast.py for the pinned ratios. */
  color: var(--ink-muted);
}

.field:focus {
  outline: none;
  border-color: var(--accent-fill);
  box-shadow: 0 0 0 3px var(--focus-ring);
}

.field + .field { margin-top: 12px; }

.consent {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin-top: 12px;
  text-align: left;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--ink-muted);
}

.consent input[type="checkbox"] {
  flex: none;
  width: 20px;
  height: 20px;
  margin: 0;
  accent-color: var(--accent-fill);
}

/* ====================================================================
   Gallery -- prints laid out on the textured field, no card
   ==================================================================== */

/* gallery.html's <main> is the one guest <main> that isn't `.card` (no
   flex centering), so its direct children -- chiefly the venue-logo img
   -- fall back to the block default (start-aligned) instead of matching
   .gallery-title's text-align below. Scoped to `.page.wide main` because
   that combination is unique to gallery.html. */
.page.wide main { text-align: center; }

.gallery-title { text-align: center; }

.gallery-meta {
  margin-top: 8px;
  text-transform: uppercase;
  letter-spacing: 0.10em;
}

.gallery-zip {
  width: 100%;
  margin-top: 20px;
}

.venue-logo + .gallery-head { margin-top: 16px; }

.gallery {
  margin-top: 32px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px;
}

/* The photobooth strip's gutter rather than a polaroid: a mat with a
   deliberately wider bottom edge that houses the per-photo action, so
   the frame earns its keep instead of being decoration. */
.photo {
  position: relative;
  padding: 6px 6px 24px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 3px;
  box-shadow: var(--photo-shadow);
  transition: transform 120ms ease-out, box-shadow 120ms ease-out;
}

.photo img {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
}

.photo.main {
  grid-column: 1 / -1;
  padding: 10px 10px 28px;
}

.photo.main img { aspect-ratio: 3 / 2; }

/* Marks the two thumbnails that aren't just "another shot": the composed
   strip (every shot from the session printed as one sheet) and the one
   file that actually moves. Never both on the same photo -- the strip is
   always a still, the gif is never the main sheet. */
.photo-badge {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 1;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--accent-fill);
  color: var(--on-accent);
  font-size: 0.625rem;
}

.photo.main .photo-badge {
  top: 16px;
  left: 16px;
}

.photo-actions {
  position: absolute;
  left: 6px;
  right: 6px;
  bottom: 0;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.photo.main .photo-actions {
  left: 10px;
  right: 10px;
  height: 28px;
}

.photo-download {
  position: relative;
  display: block;
  height: 24px;
  line-height: 24px;
  color: var(--accent-ink);
  text-decoration: none;
}

.photo.main .photo-download {
  height: 28px;
  line-height: 28px;
}

/* The gutter is 24px by design, so the 44x44 minimum hit area comes from
   a transparent overlay rather than from making the mat taller. The link
   is sized to fill the gutter's full height first (24px, 28px for the
   main strip) -- without that, its box is only as tall as its own text
   (~14px), and the -10px vertical inset would land short of 44px even
   though .photo-actions centers it correctly within the gutter. */
.photo-download::after {
  content: "";
  position: absolute;
  inset: -10px -16px;
}

.photo-download:focus-visible {
  outline: 2px solid var(--accent-fill);
  outline-offset: 3px;
}

/* Touch devices can't hover, so the action is always visible there. */
@media (hover: hover) {
  .photo-actions {
    opacity: 0;
    transition: opacity 120ms ease-out;
  }

  .photo:hover .photo-actions,
  .photo:focus-within .photo-actions { opacity: 1; }

  .photo:hover {
    transform: translateY(-2px);
    box-shadow: var(--photo-shadow-hover);
  }
}

.photo:active { transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  .photo,
  .photo:hover,
  .photo:active { transform: none; }

  .photo { transition: box-shadow 120ms ease-out; }
}

/* ====================================================================
   Support: floating "need help?" button, its dialog, and the footer
   Privacy Policy link that rides along in the same partial
   ==================================================================== */

.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;
}

.support-fab {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 10;
  width: 48px;
  height: 48px;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  background: var(--surface);
  box-shadow: var(--card-shadow);
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

/* dialog[open] beats the UA default `display: block` without a fight --
   the grid centers the two possible bodies (form or "thanks") the same
   way regardless of which one is present. */
.support-dialog {
  padding: 24px;
  width: min(360px, calc(100vw - 48px));
  border: 1px solid var(--rule);
  border-radius: 14px;
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--card-shadow);
}

.support-dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
}

.support-dialog-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.support-message {
  height: 96px;
  padding: 12px 16px;
  resize: vertical;
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.4;
  color: var(--ink);
  background: var(--wash);
  border: 1px solid var(--rule-strong);
  border-radius: 8px;
}

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

.support-dialog-actions {
  display: flex;
  gap: 12px;
  margin-top: 4px;
}

.support-dialog-actions .btn { flex: 1; }

.support-footer-links {
  margin: 16px auto 0;
  text-align: center;
}

/* ====================================================================
   Desktop -- the system's only breakpoint
   ==================================================================== */

@media (min-width: 720px) {
  body { padding: 32px; font-size: 1.0625rem; }
  .card { padding: 40px; }
  .display-xl { font-size: 3.25rem; }
  .display-l { font-size: 2.5rem; }
  .display-m { font-size: 1.375rem; }
  .script-l { font-size: 1.875rem; }
  .body-copy { font-size: 1.0625rem; }
  .guest-header { margin-bottom: 40px; }
  .brand-logo { height: 40px; }
  .btn { font-size: 1.125rem; }
  .code-box,
  .code-dash { height: 68px; font-size: 1.875rem; }
  .gallery-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
  }
  .gallery-title { text-align: left; }
  .page.wide main { text-align: left; }
  .gallery-zip { width: auto; margin-top: 0; }
  .gallery {
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: 18px;
  }
}
