/* Shared building blocks for every page. Theme files set the colours and the display font. */

@font-face {
  font-family: 'Alegreya Sans';
  src: url('/assets/fonts/alegreya-sans-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Alegreya Sans';
  src: url('/assets/fonts/alegreya-sans-400-italic.woff2') format('woff2');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: 'Alegreya Sans';
  src: url('/assets/fonts/alegreya-sans-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Alegreya Sans';
  src: url('/assets/fonts/alegreya-sans-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --font-body: 'Alegreya Sans', 'Segoe UI', system-ui, sans-serif;
  --font-display: var(--font-body);

  /* Major-third scale from a 19px body */
  --text-sm: 1rem;
  --text-base: 1.1875rem;
  --text-lg: 1.484rem;
  --text-xl: 1.855rem;
  --text-2xl: clamp(1.9rem, 7vw, 2.32rem);
  --text-hero: clamp(2.75rem, 12.5vw, 5.75rem);
  --leading: 1.55;

  --gutter: clamp(1.25rem, 5vw, 2.5rem);
  --measure: 34rem;
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;
  --space-8: 4.5rem;

  --radius-field: 12px;
  --radius-card: 22px;
  --tap: 44px;

  /* Motion: a strong ease-out for things arriving, an ease-in-out for things moving on screen */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);

  /* Theme colours; each theme file overrides these */
  --ink: #222;
  --page: #fff;
  --accent: #333;
  --accent-hover: #111;
  --on-accent: #fff;
  --surface: #fff;
  --surface-ink: #222;
  --surface-muted: #555;
  --field-bg: #fff;
  --field-border: #888;
  --choice-selected: #eee;
  --chip-border: #888;
  --wish-bg: #fff;
  --wish-ink: #222;
  --wish-muted: #555;
  --footer-bg: #222;
  --footer-ink: #fff;
  --tip-bg: #222;
  --tip-ink: #fff;
  --map-placeholder: #eee;
}

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading);
  color: var(--ink);
  background: var(--page);
  overflow-x: clip;
}

img,
svg,
canvas,
picture {
  display: block;
  max-width: 100%;
}

img {
  height: auto;
}

[hidden] {
  display: none !important;
}

h1,
h2,
h3 {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.12;
  margin: 0 0 var(--space-4);
  text-wrap: balance;
}

h2 {
  font-size: var(--text-2xl);
}

h3 {
  font-size: var(--text-lg);
}

p {
  margin: 0 0 var(--space-4);
  text-wrap: pretty;
}

a {
  color: inherit;
  text-underline-offset: 0.15em;
}

:focus-visible {
  outline: 3px solid currentColor;
  outline-offset: 3px;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Sections. Each theme sets --from and --to so the background moves from day to night. */
.section {
  padding: var(--space-8) var(--gutter);
  background: linear-gradient(var(--from, var(--page)), var(--to, var(--page)));
}

.section > * {
  max-width: var(--measure);
  margin-inline: auto;
}

.section > h2 {
  margin-bottom: var(--space-5);
}

/* Each block tucks 1px under the next, so where two meet at a fraction of a pixel no hairline of the page
   background shows between them (their colours already match at the join) */
.section,
.title-card {
  margin-bottom: -1px;
}

/* The title card also tucks 1px up over the hero, covering the hero's last, partly drawn row of pixels
   (positioned, so it paints above the positioned hero) */
.title-card {
  position: relative;
  margin-top: -1px;
}

/* Fade-ins (src/js/reveal.js). It adds .reveal-on (only when motion is welcome) and marks each block
   .is-in the first time it scrolls into view; blocks arriving together are staggered by
   --reveal-delay. Without JavaScript nothing is hidden. */
.reveal-on .reveal {
  transition:
    opacity 700ms var(--ease-out) var(--reveal-delay, 0ms),
    transform 900ms var(--ease-out) var(--reveal-delay, 0ms);
}

.reveal-on .reveal:not(.is-in) {
  opacity: 0;
  transform: translateY(12px);
}

/* The letter rises a little further, as if drawn from its envelope */
.reveal-on .letter.reveal:not(.is-in) {
  transform: translateY(28px);
}

/* The fine rule under the title draws outwards once the title has arrived */
.reveal-on .title-card .hero__title::after {
  transition: transform 900ms var(--ease-out) 300ms;
}

.reveal-on .title-card .hero__title:not(.is-in)::after {
  transform: scaleX(0);
}

/* Hero */
.hero {
  position: relative;
}

.hero__text {
  position: relative;
  max-width: 42rem;
  margin-inline: auto;
  padding: var(--space-7) var(--gutter) var(--space-6);
  text-align: center;
}

.hero__title {
  font-size: var(--text-hero);
  line-height: 1.02;
  margin-bottom: var(--space-5);
}

.hero__when {
  font-size: var(--text-lg);
  line-height: 1.3;
  margin-bottom: var(--space-2);
}

.hero__where {
  margin: 0;
}

/* The full-screen hero (both pages): the owners' clip fills the screen and src/js/hero-scrub.js plays it
   as guests scroll. The scene keeps the clip's shape and is sized to cover the screen: phones held upright
   get the tall clip with a little cropped from the sides; where a screen needs cropping top to bottom,
   more goes from the bottom than the top. Each theme sets --hero-bg (shown while the first frame
   arrives), --mist (the colour the title card opens with) and the cue's colour and shadow. */
.hero--full {
  position: relative;
  height: 100vh;
  height: 100svh;
  overflow: clip;
  background: var(--hero-bg, var(--page));
}

.hero--full .hero__scene {
  position: absolute;
  top: 25%;
  left: 50%;
  width: max(100%, calc(100vh * 16 / 9));
  width: max(100%, calc(100svh * 16 / 9));
  aspect-ratio: 16 / 9;
  transform: translate(-50%, -25%);
}

@media (orientation: portrait) {
  .hero--full .hero__scene {
    width: max(100%, calc(100vh * 9 / 16));
    width: max(100%, calc(100svh * 9 / 16));
    aspect-ratio: 9 / 16;
  }
}

.hero--full .hero__frame,
.hero--full .hero__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* Where the clip meets the title card: a soft fade into the colour the title card opens with, so there
   is no line. On the clouds page it gathers as the dawn completes (hero-scrub.js). */
.hero--full .hero__mist {
  position: absolute;
  inset: auto 0 0;
  z-index: 2;
  height: var(--mist-height, 14%);
  background: linear-gradient(transparent, var(--mist));
  pointer-events: none;
}

/* Scroll cue: a fine chevron that bobs until the guest starts to scroll (hero-scrub.js fades it) */
.hero--full .hero__cue {
  position: absolute;
  left: 50%;
  bottom: var(--cue-bottom, 1.25rem);
  z-index: 3;
  width: 1.75rem;
  height: 1.75rem;
  margin-left: -0.875rem;
  pointer-events: none;
  filter: var(--cue-shadow, none);
}

/* The bob lives on the chevron, so hero-scrub.js can still fade the cue itself out */
.hero--full .hero__cue::after {
  content: '';
  position: absolute;
  inset: 0.3rem 0.45rem 0.6rem;
  border-right: 2px solid var(--cue-color, currentColor);
  border-bottom: 2px solid var(--cue-color, currentColor);
  transform: rotate(45deg);
  animation: hero-cue 2s var(--ease-in-out) infinite;
}

@keyframes hero-cue {
  0%,
  100% {
    transform: translateY(0) rotate(45deg);
    opacity: 0.75;
  }

  50% {
    transform: translateY(6px) rotate(45deg);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero--full .hero__cue {
    display: none; /* the still picture needs no scrolling */
  }
}

/* Letter */
.letter {
  padding: var(--space-6) var(--space-5) var(--space-6);
  border-radius: 3px;
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.1), 0 18px 40px -18px rgb(0 0 0 / 0.35);
}

.letter__heading {
  font-size: var(--text-2xl);
}

.letter__signoff {
  margin: var(--space-6) 0 0;
  font-style: italic;
}

.letter__names {
  font-family: var(--font-display);
  font-style: normal;
  font-size: var(--text-lg);
}

/* Stages and countdown */
.stage {
  text-align: center;
}

.stage__heading {
  margin-bottom: var(--space-5);
}

.countdown {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  justify-content: center;
}

.countdown__unit {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.countdown__num {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 7vw, 2.4rem);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.countdown__label {
  font-size: var(--text-sm);
  font-weight: 700;
  margin-top: var(--space-1);
}

/* Venues */
.venues__date {
  font-size: var(--text-lg);
  font-weight: 700;
}

.venue {
  margin-bottom: var(--space-5);
  padding: var(--space-5);
  border-radius: var(--radius-card);
  background: var(--surface);
  color: var(--surface-ink);
}

.venue__role {
  margin-bottom: var(--space-1);
}

.venue__time {
  font-weight: 700;
  margin-bottom: var(--space-3);
}

.venue__name {
  font-size: var(--text-lg);
  line-height: 1.3;
  margin-bottom: var(--space-1);
}

.venue__address {
  color: var(--surface-muted);
}

.venue__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.venue__map {
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: 14px;
  background: var(--map-placeholder);
}

.venue__map iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

.venues__calendar {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-3);
  margin: var(--space-6) auto 0;
}

.gift-note {
  margin: 0 auto;
  font-size: var(--text-lg);
  line-height: 1.45;
  text-align: center;
}

/* Buttons */
.btn,
.chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  border-radius: 999px;
  font: inherit;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
  transition: background-color 0.15s ease, transform 0.15s ease;
}

.btn {
  padding: 0.65rem 1.5rem;
  border: 2px solid transparent;
  font-weight: 700;
}

.btn:active,
.chip:active {
  transform: translateY(1px);
}

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

.btn--primary:hover {
  background: var(--accent-hover);
}

.btn--secondary {
  background: transparent;
  color: inherit;
  border-color: currentColor;
}

.btn--small {
  padding: 0.4rem 1.1rem;
  font-size: var(--text-sm);
}

.btn--ghost {
  background: transparent;
  color: inherit;
  border-color: currentColor;
}

.chip {
  padding: 0.35rem 1rem;
  border: 2px solid var(--chip-border);
  background: transparent;
  color: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
}

.chip:hover {
  background: var(--choice-selected);
}

/* Forms */
.panel {
  padding: var(--space-6) var(--space-5);
  border-radius: var(--radius-card);
  background: var(--surface);
  color: var(--surface-ink);
}

.rsvp__by {
  margin-bottom: var(--space-6);
}

.field {
  min-width: 0;
  margin: 0 0 var(--space-5);
  padding: 0;
  border: 0;
}

label,
legend {
  display: block;
  margin-bottom: var(--space-1);
  padding: 0;
  font-weight: 700;
}

.hint {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  color: var(--surface-muted);
}

input[type='text'],
input[type='tel'],
input[type='number'],
textarea {
  width: 100%;
  min-height: var(--tap);
  padding: 0.6rem 0.85rem;
  font: inherit;
  font-size: max(1rem, 16px); /* 16px or more stops phones zooming in on focus */
  font-variant-numeric: lining-nums; /* full-height figures for phone numbers and counts */
  color: var(--surface-ink);
  background: var(--field-bg);
  border: 2px solid var(--field-border);
  border-radius: var(--radius-field);
}

textarea {
  min-height: 5.5rem;
  resize: vertical;
}

input:focus-visible,
textarea:focus-visible {
  outline: 3px solid currentColor;
  outline-offset: 1px;
}

.choices {
  display: grid;
  gap: var(--space-2);
}

.choice {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 52px;
  margin: 0;
  padding: 0.6rem 1rem;
  border: 2px solid var(--field-border);
  border-radius: var(--radius-field);
  background: var(--field-bg);
  font-weight: 500;
  cursor: pointer;
}

.choice input {
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
  accent-color: var(--accent);
}

.choice:has(input:checked) {
  border-color: var(--accent);
  background: var(--choice-selected);
}

.choice:has(input:focus-visible) {
  outline: 3px solid currentColor;
  outline-offset: 2px;
}

.choice input:focus-visible {
  outline: none;
}

.steppers {
  display: grid;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

/* One row per count: the label on the left, the buttons on the right */
.steppers .field {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin: 0;
}

.steppers label {
  margin: 0;
}

.stepper__controls {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.stepper__btn {
  flex: none;
  width: var(--tap);
  height: var(--tap);
  border: 2px solid var(--field-border);
  border-radius: 50%;
  background: var(--field-bg);
  color: var(--surface-ink);
  font: inherit;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
}

.stepper input {
  width: 3.25rem;
  padding-inline: 0.25rem;
  font-size: 1.25rem;
  font-weight: 700;
  text-align: center;
  appearance: textfield;
}

.stepper input::-webkit-outer-spin-button,
.stepper input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.form__status {
  min-height: 1.5em;
  margin: var(--space-3) 0 0;
}

.field__error {
  margin: var(--space-2) 0 0;
  color: var(--error, #a3261a);
  font-size: var(--text-sm);
  font-weight: 700;
}

[aria-invalid='true'] {
  border-color: var(--error, #a3261a) !important;
}

/* The bot trap: off screen, out of the tab order, hidden from screen readers */
.trap {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.rsvp-result__heading {
  font-size: var(--text-xl);
}

.rsvp-result__heading:focus,
h1[tabindex='-1']:focus {
  outline: none; /* headings the page moves focus to for screen readers; they aren't controls */
}

.rsvp-result__note {
  color: var(--surface-muted);
}

/* A wish the guest has just posted rises into place (a response to their action) */
.wish.is-new {
  animation: wish-rise 0.6s ease-out both;
}

@keyframes wish-rise {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
}

/* The wishes wall */
.form--wish {
  margin-bottom: var(--space-7);
}

.wish-list {
  display: grid;
  gap: var(--space-5);
  margin-top: 0;
  margin-bottom: 0;
  padding: 0;
  list-style: none;
}

.wish {
  position: relative;
  isolation: isolate;
  padding: var(--space-5);
  border-radius: var(--radius-card);
  background: var(--wish-bg);
  color: var(--wish-ink);
}

.wish__text {
  margin-bottom: var(--space-2);
  white-space: pre-line;
}

.wish__meta {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--wish-muted);
}

.wish__name {
  font-weight: 700;
}

.wishes__empty {
  font-style: italic;
}

/* Messenger tip */
.messenger-tip {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--gutter);
  background: var(--tip-bg);
  color: var(--tip-ink);
  font-size: var(--text-sm);
  line-height: 1.35;
}

.messenger-tip p {
  flex: 1;
  margin: 0;
}

/* The upload teaser: empty frames fanned out where guests' uploads will appear (Release 2). The fan is
   laid out here in CSS, so it shows without JavaScript and for guests who turn off motion;
   src/js/photo-fan.js reads the same numbers to fan the frames out and spread them. Each frame's place
   comes from --d, its distance from the middle (-1 to 1). Phones show the middle five frames. */
.section--photos {
  text-align: center;
}

.photos__opens {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
}

.photo-fan {
  --w: clamp(108px, 34vw, 132px); /* frame width */
  --spread: 0.86; /* how far the outer frames sit from the middle, in frame widths */
  --tilt: 14deg; /* the outer frames' turn */
  --drop: 0.14; /* how much lower the outer frames sit, in frame widths */
  position: relative;
  isolation: isolate; /* a lifted frame never rises above the page's sticky tip */
  max-width: none;
  height: calc(var(--w) * (1.22 + var(--drop)) + 3.5rem);
  margin-top: var(--space-6);
  margin-inline: calc(-1 * var(--gutter)); /* edge to edge, so the outer frames reach the screen sides */
  overflow-x: clip;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

@media (min-width: 640px) {
  .photo-fan {
    --w: clamp(140px, 19vw, 188px);
    --spread: 1.62;
    --tilt: 21deg;
    --drop: 0.2;
  }
}

.photo-frame {
  --d2: calc(var(--d) * var(--d));
  position: absolute;
  top: 2rem; /* room for a frame to lift */
  left: 50%;
  z-index: var(--z);
  display: flex;
  flex-direction: column;
  width: var(--w);
  height: calc(var(--w) * 1.22);
  padding: calc(var(--w) * 0.07) calc(var(--w) * 0.07) 0; /* a percentage here would follow the fan's width */
  transform: translateX(-50%) translate(calc(var(--d) * var(--spread) * var(--w)), calc(var(--d2) * var(--drop) * var(--w)))
    rotate(calc(var(--d) * var(--tilt))) scale(calc(1 - 0.2244 * var(--d2)));
}

.photo-frame__window {
  display: grid;
  flex: 1;
  place-items: center;
}

.photo-frame__icon {
  width: 30%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.2;
  stroke-linejoin: round;
}

.photo-frame__label {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 22%;
  margin: 0 calc(var(--w) * -0.05); /* the label may use the frame's side margins, so it stays on one line */
  font-family: var(--font-display);
  font-size: clamp(0.72rem, calc(var(--w) * 0.075), 0.95rem);
  line-height: 1.1;
  white-space: nowrap;
}

/* Phones: five frames */
.photo-frame[data-slot='0'],
.photo-frame[data-slot='6'] {
  display: none;
}

.photo-frame[data-slot='1'] { --d: -1; --z: 1; }
.photo-frame[data-slot='2'] { --d: -0.5; --z: 2; }
.photo-frame[data-slot='3'] { --d: 0; --z: 10; }
.photo-frame[data-slot='4'] { --d: 0.5; --z: 2; }
.photo-frame[data-slot='5'] { --d: 1; --z: 1; }

/* Wider screens: all seven */
@media (min-width: 640px) {
  .photo-frame[data-slot='0'],
  .photo-frame[data-slot='6'] {
    display: flex;
  }

  .photo-frame[data-slot='0'] { --d: -1; --z: 1; }
  .photo-frame[data-slot='1'] { --d: -0.6667; --z: 2; }
  .photo-frame[data-slot='2'] { --d: -0.3333; --z: 3; }
  .photo-frame[data-slot='4'] { --d: 0.3333; --z: 3; }
  .photo-frame[data-slot='5'] { --d: 0.6667; --z: 2; }
  .photo-frame[data-slot='6'] { --d: 1; --z: 1; }
}

/* The page's companion (src/js/companion.js): the cub or the dove. It zigzags down the page between
   spots that alternate right and left, always in free space outside forms and text: on a card's top
   edge, above a heading or note, or beside the footer seal. Each theme sets its size and shadow. */
.companion-host {
  position: relative;
}

.companion-spot {
  position: absolute;
  width: var(--companion-w);
  height: var(--companion-h);
  pointer-events: none;
}

/* Feet on the top edge of a card, near one corner */
.companion-spot--card {
  bottom: calc(100% - 2px);
}

.companion-spot--card.companion-spot--right {
  right: 1.25rem;
}

.companion-spot--card.companion-spot--left {
  left: 1.25rem;
}

/* Above a heading or note, at one side */
.companion-spot--above {
  bottom: 100%;
}

.companion-spot--above.companion-spot--right {
  right: 0;
}

.companion-spot--above.companion-spot--left {
  left: 0;
}

/* Beside the seal, which stays centred */
.companion-perch {
  position: relative;
  display: block;
  width: fit-content;
  margin-inline: auto;
}

.companion-spot--beside {
  left: calc(100% + 0.5rem);
  bottom: 0;
}

/* The smallest phones: a little closer to the seal, so it stays on screen */
@media (max-width: 359px) {
  .companion-spot--beside {
    left: calc(100% + 0.125rem);
  }
}

.companion {
  position: absolute;
  inset: 0;
  width: var(--companion-w);
  height: var(--companion-h);
  pointer-events: auto;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  filter: var(--companion-shadow, none);
}

.companion--flying {
  position: fixed;
  inset: 0 auto auto 0;
  z-index: 20;
  pointer-events: none;
  will-change: transform;
}

.companion img {
  width: 100%;
  height: 100%;
}

/* The poses face right; mirrored when it's flying left, or resting on the right facing in */
.companion--left img {
  transform: scaleX(-1);
}

/* Footer */
.site-footer {
  padding: var(--space-7) var(--gutter) var(--space-8);
  overflow-x: clip; /* the companion resting beside the seal never makes the page scroll sideways */
  text-align: center;
  background: var(--footer-bg);
  color: var(--footer-ink);
}

.site-footer__seal {
  width: 84px;
  margin: 0 auto var(--space-3);
}

.site-footer__hosts {
  font-family: var(--font-display);
  font-size: var(--text-lg);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
