/*
 * The wedding booking page.
 *
 * ── Where the look comes from ───────────────────────────────────────
 *
 * The studio's own reading of the first version: boring, nothing new,
 * doesn't feel modern, and the colour palette is boring. It was cream
 * and grey, which is what every booking form on the internet is, and
 * it put the price in the largest type on every card. Then, of the
 * one after it: catchier photographs, a more graphic and modern
 * design, and pictures that are crystal clear.
 *
 * So this is an editorial spread and not a form. White and blush, one
 * warm accent and no second hue. Big serif set left, generous paper,
 * thin gold rules, photographs overlapping flat blush blocks, and a
 * contact sheet across the page. The graphic language is composition —
 * overlap, asymmetry, scale, one rotated label, honest numerals —
 * because more colour would only compete with the photographs, and the
 * photographs are the product.
 *
 * The typefaces do not change. They are the Investment Guide's, they
 * are already paid for and already downloaded; what changed is scale.
 *
 * ── The rule the whole page is built on ─────────────────────────────
 *
 * Nothing sits on top of a photograph. No scrim, no gradient, no tint,
 * no type, no hover that touches the picture. A caption is white paper
 * laid over the corner of a print, which is a different thing. Every
 * photograph is drawn at a width its file can actually carry, because
 * "crystal clear" is arithmetic and the arithmetic is in `sizes`.
 *
 * Committed to the one theme deliberately. A page that flips to dark
 * under a system setting would show a couple two different studios
 * depending on the time of day, and the photographs are graded for
 * one of them.
 */

@font-face {
  font-family: 'Fraunces';
  src: url('/wedding/fonts/fraunces.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: 'Fraunces';
  src: url('/wedding/fonts/fraunces-italic.woff2') format('woff2');
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: 'Karla';
  src: url('/wedding/fonts/karla.woff2') format('woff2');
  font-weight: 200 800;
  font-display: swap;
}

:root {
  /* ── White and blush ──────────────────────────────────────────────
     Blush is a ground, never type. At #C98B84 it reads 2.71:1 against
     the page — lovely as a wash behind something and unreadable as
     words on it, so it tints surfaces, borders and the placeholder
     fields and is never asked to carry a sentence.

     Gold is the same story one step further on. It clears 3:1, which
     is enough for a large price and not enough for a caption, so it
     fills buttons and sets display figures and nothing smaller. Every
     small gold label in the dark version has moved to ink-soft. */
  --ground: #FDFBFA;
  --raised: #FAF2F0;
  --line: #EEE0DC;
  --ink: #241C1C;
  --ink-soft: #5A4E4C;
  /* #857472 was the obvious muted and misses body contrast at 4.30. */
  --muted: #7A6A68;
  --gold: #B08D57;
  --gold-soft: #9A7742;
  /* A third gold, for type below 18px.
     The brand gold is 3.0:1 on this page and #9A7742 only reaches 3.7
     — both fine filling a button or setting a $3,900, both unreadable
     as an 11px uppercase label. This one clears 4.96:1 and still reads
     as gold rather than as brown. Small gold text uses it; nothing
     else does. */
  --gold-ink: #856334;
  --blush: #C98B84;
  --blush-wash: #F5E2DF;
  /* The flat block a photograph is laid over. A ground and never type:
     it exists to be half-covered, and the sliver that shows is the
     only thing on the page doing the job a drop shadow used to. */
  --blush-block: #EFD3CE;

  /* The one shadow on the page, and only under a photograph that
     overlaps something. Spread −30 against a 60 blur means it barely
     reaches sideways at all — it lifts the print off the block
     underneath rather than drawing a box around it. */
  --shadow: 0 24px 60px -30px rgba(36, 28, 28, .35);

  /* Both darkened for a white ground, where the dark theme's values
     would have floated. 5.8:1 and 5.5:1 — read as text, not just as
     colour, which matters on the one screen where being wrong means
     choosing a day the studio cannot photograph. */
  --free: #4A6B4F;
  --taken: #A64B3E;

  --display: 'Fraunces', Georgia, 'Times New Roman', serif;
  --body: 'Karla', system-ui, -apple-system, sans-serif;

  --step: clamp(1.25rem, 3vw, 2.5rem);

  /* Lets a `<details>` animate to its own height rather than snapping
     open and shoving the shelf below it down a screen. */
  interpolate-size: allow-keywords;
  color-scheme: light;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; }

/* Gold, and on everything. A keyboard on a near-black page with no
   ring is a cursor nobody can find. */
:where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Said, not shown. The compare table's ticks and dashes are colour
   and shape; this is the same fact in words. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── A photograph ──────────────────────────────────────────────────
   The frame states the shape and the focal point; the picture fills
   it. `aspect-ratio` plus the file's own width and height on the
   `<img>` mean the box is the right size before a byte arrives, so
   nothing on the page moves when one does.

   The blush wash underneath is what shows for the instant before the
   picture paints. It is not a placeholder pattern: a field of fake
   grain where a photograph is about to be is a page apologising for
   itself. */
.photo {
  display: block;
  position: relative;
  overflow: hidden;
  aspect-ratio: var(--ratio);
  background: var(--blush-wash);
}
.photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The couple, not the sky above them. Written by the build script
     from the studio's own reading of each frame. */
  object-position: var(--fx) var(--fy);
}
/* A slot the manifest has never heard of costs a box of blush and
   nothing else — no broken-image icon, no collapsed row. */
.photo--none { background: var(--blush-wash); }

/* Every grid and flex item that holds a photograph, told that it may
   be narrower than the picture inside it. The automatic minimum is
   the file's own width, and a 1024px file in a 320px column is how a
   page ends up scrolling sideways for reasons nobody can see. */
.hero-words, .hero-art, .panel-art, .panel-body,
.day-side, .day-cal, .review-sums, .review-side { min-width: 0; }

/* ── A photograph on a block ───────────────────────────────────────
   The block is offset down and right and sits behind. Everything the
   page does graphically it does this way: two flat shapes that miss
   each other slightly. */
.plate { position: relative; }
.plate-block {
  position: absolute;
  inset: 1.25rem -1.25rem -1.25rem 1.25rem;
  background: var(--blush-block);
}
.plate-photo { position: relative; box-shadow: var(--shadow); }

/* ── The masthead ──────────────────────────────────────────────────
   A slim bar, and the whole of the page's furniture. The mark used to
   sit inside the hero photograph, where every window width cropped
   something around it. */
.masthead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding-bottom: clamp(1.5rem, 4vw, 2.5rem);
  border-bottom: 1px solid var(--line);
}
/* The mark is drawn from a 480px file, so it is sharp at any of
   these; the sizes are about presence, not resolution. On a phone the
   masthead is a strip the eye passes over on the way to the headline,
   and 112px there made it the headline. */
.masthead-mark { width: 76px; height: auto; }
@media (min-width: 40rem) { .masthead-mark { width: 92px; } }
@media (min-width: 64rem) { .masthead-mark { width: 112px; } }
/* The link around the mark adds no line box under it. */
.masthead-home { display: inline-flex; line-height: 0; }
.masthead-say {
  display: flex;
  gap: 1.5rem;
  margin: 0;
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: right;
}
.masthead-mail { display: none; color: var(--gold-ink); }
@media (min-width: 64rem) { .masthead-mail { display: inline; } }

/* The two dead ends — no slug, and an offer that will not load. */
.notice { text-align: center; padding: clamp(2rem, 6vw, 4rem) 0 0; }
.notice img { width: 112px; height: auto; }
.notice .lede { margin-left: auto; margin-right: auto; }

/* ── The hero ──────────────────────────────────────────────────────
   Words left, collage right, and the collage's own top edge flush
   with the top of the section. Inside `.page`, so it can never reach
   past the document: the version that did reach past it needed
   `100vw`, and `100vw` is wider than the page by the width of the
   scrollbar. */
.hero { padding-top: clamp(2rem, 5vw, 3.5rem); }
@media (min-width: 64rem) {
  .hero {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: var(--step);
    align-items: start;
  }
  .hero-words { grid-column: 1 / span 5; padding-top: .5rem; }
  .hero-art { grid-column: 6 / -1; }
}
.hero-say {
  color: var(--ink-soft);
  font-size: 14.5px;
  max-width: 30rem;
  margin: 1.6rem 0 0;
  padding-top: 1.2rem;
  border-top: 1px solid var(--line);
}

/* The collage. `width: 100%` and `overflow: hidden` are the two things
   standing between an overlapping layout and a page that scrolls
   sideways on a phone. */
.hero-art {
  width: 100%;
  overflow: hidden;
  margin-top: clamp(1.75rem, 5vw, 2.5rem);
  /* Room for the one shadow to land in. Clipped flush against the box
     it would read as a cut rather than as a print lying on paper. */
  padding-bottom: 1.75rem;
}
@media (min-width: 64rem) {
  .hero-art {
    display: grid;
    grid-template-columns: 1.75rem minmax(0, 1fr);
    gap: .75rem;
    margin-top: 0;
  }
}

/* Set up the left edge of the collage, in its own gutter so it is
   never laid over a photograph. Rotating type is allowed; rotating a
   picture is not. */
.hero-rot { display: none; }
@media (min-width: 64rem) {
  .hero-rot {
    display: block;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    align-self: end;
    font-size: 11px;
    letter-spacing: .18em;
    text-transform: uppercase;
    color: var(--muted);
    white-space: nowrap;
  }
}

/* The stage. The tall photograph is in normal flow and gives the box
   its height; the other three are placed against it. */
.hero-stage { position: relative; padding-bottom: 3.5rem; }
@media (min-width: 64rem) { .hero-stage { padding-bottom: 0; } }

.photo.hero-tall { aspect-ratio: 4 / 5; }
/* 4/5 of an 820px tablet is 1025px of photograph — taller than the
   screen it is on, which makes the whole first view one picture with
   no sign there is a page beneath it. Squarer here; the desktop column
   goes back to the frame's own shape. */
@media (min-width: 40rem) { .photo.hero-tall { aspect-ratio: 1 / 1; } }
@media (min-width: 64rem) {
  .photo.hero-tall { aspect-ratio: var(--ratio); width: 60%; margin-left: auto; }
}

.hero-wide {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 60%;
  box-shadow: var(--shadow);
}
@media (min-width: 64rem) {
  .hero-wide { right: auto; left: 0; bottom: 10%; width: 58%; }
}

/* Behind the wide one and missing it by 1.5rem, so what reads is a
   flat blush edge rather than a border. */
.hero-block {
  position: absolute;
  right: 1.5rem;
  bottom: 1.5rem;
  width: 60%;
  aspect-ratio: 3 / 2;
  background: var(--blush-block);
}
@media (min-width: 64rem) {
  .hero-block {
    right: auto;
    left: 1.5rem;
    top: 18%;
    bottom: calc(10% - 1.5rem);
    width: 58%;
    aspect-ratio: auto;
  }
}

/* Small, square, and off a corner. Hidden on a phone, where a third
   photograph in a 350px column is a postage stamp. Bottom-left on a
   tablet, where the stacked collage has a corner going spare; top-left
   on a desktop, where it lands on the blush block instead. */
.hero-detail { display: none; }
@media (min-width: 40rem) {
  .hero-detail {
    display: block;
    position: absolute;
    left: 0;
    bottom: 0;
    width: 26%;
  }
}
@media (min-width: 64rem) {
  .hero-detail { width: 24%; top: 8%; bottom: auto; }
}

/* The collage arrives a piece at a time. The resting state is the
   visible one, so the reduced-motion rule that deletes the animation
   leaves four photographs sitting exactly where they belong. */
@keyframes rise {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: none; }
}
.hero-stage > * { animation: rise 600ms ease-out both; }
.hero-tall { animation-delay: 0ms; }
.hero-block { animation-delay: 120ms; }
.hero-wide { animation-delay: 240ms; }
.hero-detail { animation-delay: 360ms; }
.hero-mark { animation-delay: 480ms; }

/* Over the tall photograph's own top-left corner, which on a desktop
   starts at 40% of the stage — not the stage's corner, where there is
   no photograph to sit on. `pointer-events: none` because it is a mark
   and not a control, and `aria-hidden` because the masthead above has
   already named the studio in text. */
.hero-mark {
  position: absolute;
  z-index: 2;
  top: 1.25rem;
  left: 1.25rem;
  width: 72px;
  height: auto;
  pointer-events: none;
}
@media (min-width: 64rem) {
  .hero-mark { left: calc(40% + 1.25rem); width: 96px; }
}

.eyebrow {
  font-size: 11px;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--gold-ink);
  margin: 0;
}
/* Pushed as far as the column will carry it. `opsz` and `SOFT` are
   Fraunces' own axes — at display size the optical-size axis is what
   stops a serif built for captions looking like a caption blown up. A
   file without them ignores the line. */
.display {
  font-family: var(--display);
  font-weight: 400;
  font-variation-settings: 'opsz' 144, 'SOFT' 40;
  line-height: .98;
  letter-spacing: -0.02em;
  text-wrap: balance;
  margin: .5rem 0 0;
  font-size: clamp(3rem, 8vw, 6.75rem);
}
/* One word turned, and the only gold in the headline. */
.display em { font-style: italic; color: var(--gold-ink); }
.lede {
  font-family: var(--display);
  font-style: italic;
  color: var(--ink-soft);
  max-width: 34rem;
  margin: 1.1rem 0 0;
  font-size: clamp(1.05rem, 2.2vw, 1.35rem);
}

.page { max-width: 68rem; margin: 0 auto; padding: var(--step); }
@media (min-width: 64rem) { .page { max-width: 84rem; } }
/* The masthead and the hero share a block of their own so the strip
   below can run the full width of the document between them. */
.page--top { padding-bottom: 0; }

/* ── The contact sheet ─────────────────────────────────────────────
   Outside `.page` and the width of the document, because a strip that
   stops at a column is a row of thumbnails. Eight across a desktop;
   on a phone it scrolls itself, which is the whole reason it is a
   strip and not a grid — the page must never be the thing that moves
   sideways. */
.strip { padding: clamp(2rem, 5vw, 3.25rem) 0 clamp(1.5rem, 4vw, 2.5rem); }
.strip-say {
  margin: 0 0 1.1rem;
  text-align: center;
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--muted);
}
.strip-row {
  display: flex;
  gap: 1rem;
  padding: 0 1rem 1rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* Or the first snap scrolls the padding away and the strip starts
     flush left while it still ends an inch short on the right. */
  scroll-padding-inline: 1rem;
  -webkit-overflow-scrolling: touch;
}
.strip-tile {
  flex: 0 0 38vw;
  /* A flex item's automatic minimum is the intrinsic width of the
     picture inside it, which for a 720px file is 720px. Eight of
     those is a document twice the width of the window. */
  min-width: 0;
  scroll-snap-align: start;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 500ms ease-out, transform 500ms ease-out;
}
.strip-tile[data-in='1'] { opacity: 1; transform: none; }
/* The stagger, as eight rules rather than eight inline delays: the
   markup stays free of styling and the strip can be reordered in the
   component without anybody editing JavaScript. */
.strip-tile:nth-child(1) { transition-delay: 0ms; }
.strip-tile:nth-child(2) { transition-delay: 60ms; }
.strip-tile:nth-child(3) { transition-delay: 120ms; }
.strip-tile:nth-child(4) { transition-delay: 180ms; }
.strip-tile:nth-child(5) { transition-delay: 240ms; }
.strip-tile:nth-child(6) { transition-delay: 300ms; }
.strip-tile:nth-child(7) { transition-delay: 360ms; }
.strip-tile:nth-child(8) { transition-delay: 420ms; }
/* Every second frame dropped, so the row has a rhythm instead of a
   ruled edge. A margin and not a transform — a transform on a frame
   is one refactor away from a transform on a photograph. */
.strip-tile:nth-child(even) { margin-top: 1.5rem; }
/* 38vw is two and a half tiles on a phone, which is exactly the hint
   that there are more to push at. The same fraction of a tablet is
   three big pictures and no longer reads as a contact sheet. */
@media (min-width: 40rem) { .strip-tile { flex: 0 0 25vw; } }
@media (min-width: 64rem) {
  .strip-row { overflow-x: visible; }
  .strip-tile { flex: 1 1 0; }
}

/* ── The steps ─────────────────────────────────────────────────────
   Numbered, because four steps in an order is a sequence and a
   sequence with numerals on it is one a couple can hold in their
   head. Left-aligned like everything else. */
.steps-wrap { margin: 0 0 clamp(1.75rem, 4vw, 2.75rem); }
.steps {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem clamp(1.25rem, 4vw, 3rem);
  margin: 0 0 .9rem;
  padding: 0;
  list-style: none;
}
.steps li {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  color: var(--muted);
  transition: color 300ms ease;
}
.step-no {
  font-family: var(--display);
  font-style: italic;
  font-size: 1.15rem;
  color: var(--gold-ink);
  font-variant-numeric: tabular-nums;
}
.step-say {
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
}
.steps li[data-on='1'] { color: var(--ink); }
.steps li[data-done='1'] { color: var(--gold-ink); }

/* The same fact as the numerals, for the eye that reads shapes first. */
.steps-rule { height: 1px; background: var(--line); }
.steps-rule span {
  display: block;
  height: 1px;
  background: var(--gold);
  width: 25%;
  transition: width 400ms ease;
}
.steps-wrap[data-step='0'] .steps-rule span { width: 25%; }
.steps-wrap[data-step='1'] .steps-rule span { width: 50%; }
.steps-wrap[data-step='2'] .steps-rule span { width: 75%; }
.steps-wrap[data-step='3'] .steps-rule span { width: 100%; }

/* ── A step ────────────────────────────────────────────────────────
   A rule and air, not a filled box. The fill was the last thing on
   the page still making a form of it. */
section.card,
.collections-step {
  position: relative;
  overflow: hidden;
  margin-bottom: 1rem;
}
section.card {
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: clamp(1.25rem, 3vw, 2.25rem);
}
/* The step's own numeral, at the size of a page number in a magazine
   and the colour of the blocks the photographs sit on. Decorative and
   said aloud nowhere: the steps above already carry it. */
.card-no {
  position: absolute;
  top: -1.25rem;
  right: .75rem;
  z-index: 0;
  font-family: var(--display);
  font-style: italic;
  font-size: 7rem;
  line-height: 1;
  color: var(--blush-block);
  pointer-events: none;
  user-select: none;
}
section.card > *:not(.card-no),
.collections-step > *:not(.card-no) { position: relative; z-index: 1; }

h2.section {
  font-family: var(--display);
  font-size: clamp(1.9rem, 4vw, 2.75rem);
  font-weight: 400;
  line-height: 1.06;
  letter-spacing: -0.015em;
  margin: 0 0 .35rem;
}
.section-note { color: var(--ink-soft); margin: 0 0 1.5rem; max-width: 44rem; }

/* ── Choosing a collection ─────────────────────────────────────────
   The head carries the one control that changes the whole view. */
.step-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.5rem;
}
.step-head .section-note { margin-bottom: 0; }
/* Out from under the step's own numeral, which lives in the same
   corner. A gold-edged pill with a blush numeral showing through it
   reads as a rendering fault. */
@media (min-width: 40rem) { .step-head { padding-right: 8.5rem; } }
.compare-toggle {
  font: inherit;
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  background: transparent;
  color: var(--gold-ink);
  border: 1px solid var(--gold-soft);
  border-radius: 999px;
  padding: .6rem 1.15rem;
  cursor: pointer;
  white-space: nowrap;
  transition: color 160ms ease, border-color 160ms ease, background-color 160ms ease;
}
.compare-toggle:hover { background: color-mix(in srgb, var(--gold) 12%, transparent); }
.compare-toggle[aria-pressed='true'] { background: var(--gold); border-color: var(--gold); color: #241C1C; }

/* Out through the ground and back. Both views mounted at once would
   leave a table of focusable cells sitting invisible behind the
   panels, which is a worse thing than a shorter dissolve. */
.views { transition: opacity 170ms ease; }
.views[data-fading='1'] { opacity: 0; }

/* A short gold rule, the name, and the count in words. A shelf
   heading is a caption in a magazine, not a table header. */
.shelf { margin-bottom: clamp(2.75rem, 6vw, 4.5rem); }
.shelf + .shelf { border-top: 1px solid var(--line); padding-top: clamp(2.5rem, 5vw, 3.5rem); }
.shelf-head { margin-bottom: clamp(1.75rem, 4vw, 2.75rem); }
.shelf-head::before {
  content: '';
  display: block;
  width: 2rem;
  height: 2px;
  background: var(--gold);
  margin-bottom: .9rem;
}
.shelf-name {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(1.6rem, 3vw, 2.2rem);
  line-height: 1.08;
  letter-spacing: -0.015em;
  color: var(--ink);
  margin: 0;
}
.shelf-count {
  margin: .3rem 0 0;
  font-size: 12px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ── A collection ──────────────────────────────────────────────────
   One spread each, separated by white rather than by a box. The fill
   and the border were the last of the form left in it: seven filled
   cards down a page is a list of products, and seven photographs with
   their captions beside them is a feature.

   Every second one turns, so the eye zig-zags down the shelf instead
   of running down a gutter. `grid-column` rather than `direction`,
   which would reverse the words inside as well. */
.panels { display: grid; gap: clamp(2.5rem, 6vw, 4.5rem); }

.panel {
  display: grid;
  gap: clamp(1.5rem, 3vw, 2.25rem);
  /* A transparent rule the chosen one turns gold, so choosing does
     not move a single pixel of the panel it is choosing. */
  border: 1px solid transparent;
  border-radius: 3px;
  padding: clamp(1rem, 2vw, 1.75rem);
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 380ms ease-out, transform 380ms ease-out,
    border-color 160ms ease;
}
.panel[data-in='1'] { opacity: 1; transform: none; }
.panel[data-on='1'] { border-color: var(--gold); }
@media (min-width: 40rem) {
  .panel {
    grid-template-columns: repeat(12, minmax(0, 1fr));
    /* Never smaller than the 1.25rem the caption card spills, or the
       card would run into the words beside it. */
    column-gap: clamp(1.75rem, 3vw, 2.75rem);
    align-items: start;
  }
  /* Both stay on the first row, and that is what makes the turn work
     rather than a nicety. Auto placement fills a row left to right and
     never goes back: on a turned panel the photograph takes columns 8
     to 12 first, so the words, asked for columns 1 to 7, found the
     cursor already past them and were given a second row instead. The
     panel grew by the height of its own photograph, and the price and
     the list of what is included sat underneath it with the whole left
     half of the panel empty beside them.

     It only ever showed on the turned panels, which is why the shelf
     looked right at a glance: the other five ask for 1 to 5 then 6 to
     12, which is the order auto placement was already going in. */
  .panel-art { grid-column: 1 / span 5; grid-row: 1; }
  .panel-body { grid-column: 6 / -1; grid-row: 1; }
  .panels > .panel:nth-of-type(even) .panel-art { grid-column: 8 / -1; }
  .panels > .panel:nth-of-type(even) .panel-body { grid-column: 1 / span 7; }
}

.panel-art { position: relative; max-width: 32rem; }
.panel-photo { box-shadow: var(--shadow); }

.panel-body {
  display: grid;
  gap: .95rem;
  align-content: start;
}

/* ── The caption card ──────────────────────────────────────────────
   White paper laid over the corner of the print and spilling off its
   inner edge. It is a button, because a couple who has read the panel
   and wants it should not have to go looking for one — and it is the
   only thing on this page allowed to cover a photograph, which it
   earns by being opaque paper rather than type on the picture. */
.panel-head {
  /* In flow with a negative top margin rather than pinned to the
     bottom of the frame: it overlaps the photograph by 1.25rem and no
     more, and the frame still owns the height of both — an absolute
     card would hang into the panel beneath it. `z-index` because a
     block background otherwise paints under a sibling's image. */
  position: relative;
  z-index: 1;
  margin: -1.25rem 1.5rem 0 0;
  font: inherit;
  color: inherit;
  background: var(--ground);
  border: 0;
  padding: 1rem 1.25rem 1.1rem;
  text-align: left;
  cursor: pointer;
  display: grid;
  gap: .3rem;
  transition: color 160ms ease;
}
/* Inset from the photograph's outer edge, spilling off its inner one,
   which is the side the words are on. */
@media (min-width: 40rem) {
  .panel-head { margin: -1.25rem -1.25rem 0 1.25rem; }
  .panels > .panel:nth-of-type(even) .panel-head { margin: -1.25rem 1.25rem 0 -1.25rem; }
}
/* Said on the card, never on the photograph. */
.panel-tag {
  justify-self: start;
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  background: var(--gold);
  color: var(--ink);
  padding: .15rem .6rem;
  border-radius: 2px;
}
/* The biggest type on the panel, and the price is not. */
.panel-name {
  font-family: var(--display);
  font-weight: 400;
  font-variation-settings: 'opsz' 144, 'SOFT' 40;
  font-size: clamp(2rem, 4vw, 3rem);
  line-height: 1;
  letter-spacing: -0.02em;
  margin: 0;
}
.panel-voice {
  font-family: var(--display);
  font-style: italic;
  font-size: clamp(1.02rem, 1.7vw, 1.15rem);
  color: var(--ink-soft);
  margin: 0;
  max-width: 34ch;
  text-wrap: balance;
}
/* The card changes; the photograph under it does not. */
.panel-head:hover .panel-name { color: var(--gold-ink); }

/* What the money buys, said as a difference.
   Every tier contains the one beneath it, so the lines that are not
   in the tier below are exactly what the step up is — and a couple
   reads one short list instead of diffing two long ones by eye. */
.panel-plus-head {
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--gold-ink);
  margin: 0 0 .5rem;
}
.panel-plus ul, .panel-all ul { margin: 0; padding: 0; list-style: none; display: grid; gap: .3rem; }
.panel-plus li, .panel-all li {
  position: relative;
  padding-left: 1.3rem;
  color: var(--ink-soft);
  font-size: 14.5px;
  line-height: 1.5;
}
.panel-plus li::before, .panel-all li::before {
  content: '✓';
  position: absolute;
  left: 0;
  top: 0;
  color: var(--gold);
  font-size: 12px;
}

.panel-all > summary,
.panel-tailor > summary {
  list-style: none;
  cursor: pointer;
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--gold-ink);
  display: flex;
  align-items: center;
  gap: .5rem;
}
.panel-all > summary::-webkit-details-marker,
.panel-tailor > summary::-webkit-details-marker { display: none; }
.panel-all > summary::after,
.panel-tailor > summary::after { content: '+'; font-size: 14px; line-height: 1; }
.panel-all[open] > summary::after,
.panel-tailor[open] > summary::after { content: '–'; }
.panel-all > summary:hover, .panel-tailor > summary:hover { color: var(--ink); }
.panel-all-in { padding-top: .7rem; }
.panel-tailor { border-top: 1px solid var(--line); padding-top: .95rem; }
/* A soft strip rather than a small gold line, so it is noticed (Tan,
   2026-09-16, chose it from three). Only this summary: "See everything
   included" keeps the shared look above. */
.panel-tailor > summary {
  justify-content: space-between;
  font-size: 12.5px;
  letter-spacing: .14em;
  color: var(--ink);
  background: color-mix(in srgb, var(--gold) 13%, var(--ground));
  border-radius: 8px;
  padding: .9rem 1rem;
}
.panel-tailor > summary::after { font-size: 20px; color: var(--gold-ink); }
.panel-tailor > summary:hover { background: color-mix(in srgb, var(--gold) 22%, var(--ground)); color: var(--ink); }

/* Opens to its own height rather than snapping, so the shelf below
   does not jump a screen under the reader's thumb. Browsers without
   `::details-content` drop the rule and open instantly, which is the
   behaviour this replaces. */
.panel-all::details-content,
.panel-tailor::details-content {
  block-size: 0;
  overflow: hidden;
  transition: block-size 300ms ease, content-visibility 300ms;
  transition-behavior: allow-discrete;
}
.panel-all[open]::details-content,
.panel-tailor[open]::details-content { block-size: auto; }

/* Last on the panel, and at the size of a sentence. Nothing about the
   figure is hidden — it simply stops being the first thing said. */
.panel-price {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .3rem .6rem;
  margin: .1rem 0 0;
  padding-top: .9rem;
  border-top: 1px solid var(--line);
  font-size: 1rem;
  color: var(--ink-soft);
}
.panel-amount { font-variant-numeric: tabular-nums; }
.panel-basis { color: var(--muted); }
.panel-hold { color: var(--muted); font-size: 13px; margin-left: auto; }

/* A figure being worked out on the server: the last true one stays where
   it is, dimmed, and the word beside it says why. Not a spinner and not
   a blank, because a couple looking at the place a price was needs to be
   told it is moving, not that it has gone. Opacity only, so the number
   never shifts and nothing reflows under a thumb. */
.panel-price[data-updating='1'] .panel-amount,
.rail-card[data-updating='1'] .amt,
.rail-card[data-updating='1'] .rail-compact dd {
  opacity: .45;
  transition: opacity .18s;
}

/* Ink, not gold. There are seven of these on the shelf and one gold
   button in the rail; seven golds would leave a couple hunting for
   which one carries on. Gold arrives on hover, which is the moment
   it means something. */
.panel-choose {
  justify-self: start;
  font: inherit;
  font-size: 12px;
  letter-spacing: .16em;
  text-transform: uppercase;
  background: var(--ink);
  color: var(--ground);
  border: 1px solid var(--ink);
  border-radius: 999px;
  padding: .8rem 1.7rem;
  cursor: pointer;
  transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
}
.panel-choose:hover {
  background: var(--gold);
  border-color: var(--gold);
  color: var(--ink);
}

/* ── The shelf as a table ──────────────────────────────────────────
   Its own scroller, so a narrow phone scrolls the table and never the
   document. The label column stays put: a row of ticks with its own
   label scrolled off the left is a row of nothing. */
.cmp-wrap {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--raised);
}
.cmp { border-collapse: collapse; width: 100%; min-width: 34rem; font-size: 14px; }
.cmp th, .cmp td {
  padding: .62rem .85rem;
  text-align: center;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.cmp tr:last-child th, .cmp tr:last-child td { border-bottom: 0; }
.cmp thead th {
  font-family: var(--display);
  font-weight: 400;
  font-size: 1.05rem;
  color: var(--ink);
  background: var(--raised);
}
.cmp tbody th[scope='row'] {
  text-align: left;
  font-weight: 400;
  color: var(--ink-soft);
  white-space: normal;
  min-width: 11rem;
}
.cmp th[scope='row'], .cmp .cmp-head {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--raised);
  text-align: left;
}
.cmp-yes { color: var(--gold-soft); font-size: 15px; }
.cmp-buy { color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.cmp-no { color: var(--muted); }
.cmp-hours { color: var(--ink); font-variant-numeric: tabular-nums; }
.cmp-price th, .cmp-price td {
  font-family: var(--display);
  font-size: 1.05rem;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  border-top: 1px solid var(--line);
}

/* ── The two steps with a photograph beside them ───────────────────
   The calendar and the totals are the work; the photograph is the
   reason anybody is doing it. A 20rem column each, and below 64rem
   the column stacks — capped at 32rem, because both files are 1024px
   wide and 1024px is crisp to 512 CSS pixels and invented after
   that. */
.day-grid, .review-grid { display: grid; gap: var(--step); }
.day-side, .review-side { max-width: 32rem; }
/* Stacked, the calendar goes first. It is the one control on this
   step, and the side column is a photograph and two fields a couple is
   told they do not need — put in front of the calendar they read as an
   obstacle between the page and the reason they opened it. */
.day-cal { order: 0; }
.day-side { order: 1; }
@media (min-width: 64rem) {
  .day-grid, .review-grid {
    grid-template-columns: minmax(0, 1fr) 20rem;
    align-items: start;
  }
  .day-side { grid-column: 2; grid-row: 1; max-width: none; }
  .day-cal { grid-column: 1; grid-row: 1; }
  .review-side { max-width: none; }
}

/* ── Fields ────────────────────────────────────────────────────── */
.fields { display: grid; gap: 1rem; }
@media (min-width: 40rem) { .fields.two { grid-template-columns: 1fr 1fr; } }
/* A field the width of a desktop is a line the eye loses its place
   in on the way back. */
.form-narrow { max-width: 40rem; }

label.field { display: block; }
label.field > span {
  display: block;
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: .35rem;
}
input, textarea, select {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: .7rem .8rem;
}
input::placeholder, textarea::placeholder { color: var(--muted); }
input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--gold) 28%, transparent);
}
textarea { min-height: 6rem; resize: vertical; }
.hint { color: var(--muted); font-size: 13px; margin-top: .35rem; }

/* ── The layout, and the price rail in it ──────────────────────────
   One column on a phone, where the rail falls to the bottom of the
   document and sticks there as the bar it has always been. Two from
   64rem, where there is room to keep the money beside the choosing.
   `align-items: start` is what lets the rail stick rather than
   stretch to the height of the row. */
.layout { display: grid; gap: var(--step); align-items: start; }
@media (min-width: 64rem) {
  .layout { grid-template-columns: minmax(0, 1fr) 21rem; }
}
.column { min-width: 0; }

/* Both bars in this bundle are `.rail`: this is the one Jackie's beauty
   page gets, and it is exactly what it has always been. Left here
   untouched, with the booking bar's rules added after it rather than
   replacing it — renaming this block took her bar's position away
   entirely and stopped it being a bar at all. */
.rail { position: sticky; bottom: 0; z-index: 5; }

/*
 * A bar that stays where it is put.
 *
 * Tan, 2026-09-18, looking at his own phone: "or just make it stay at
 * the bottom and not move along with the page", a minute after "make it
 * as small as possible". `sticky` did neither. Measured on a 393px
 * phone, it has three regimes in one step: clamped out of sight above
 * its container on the way down, pinned to the viewport through the
 * middle, and sitting where it falls at the end — so it appears,
 * vanishes and reappears while a couple scrolls one screen.
 *
 * `fixed` costs the page the height back. Without the reserve below,
 * the last thing on every screen sits under this bar for good: measured
 * at the end of the page, the closing line was covered with no reserve
 * and clear with it. That is the standard fault of this pattern and it
 * is the one that would come back as next week's complaint.
 *
 * `left`/`right` are 0 and the inset is re-applied here as padding,
 * because a fixed element escapes the container whose 20px it used to
 * sit inside. The ground behind it is what stops page content showing
 * through beside the card, and the hairline is what stops it bleeding
 * into whatever scrolls underneath.
 */
.wed-rail {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 5;
  padding: 0 20px env(safe-area-inset-bottom);
  background: var(--ground);
  border-top: 1px solid var(--line);
}
/*
 * The height the fixed bar takes out of the flow, given back so the
 * page's last line is not parked underneath it.
 *
 * `:has` because one stylesheet serves three pages out of one bundle:
 * this one, Jackie's beauty page and the meet reply. A bare `body` rule
 * reserved 180px on all three, and the meet page has no bar at all to
 * justify it. This reserves the space exactly where the bar exists.
 * (`:has` is Safari 15.4; the `color-mix` already used 27 times in this
 * file is 16.2, so this asks less of a browser than the file already
 * does.)
 *
 * 84 is the bar's 72 plus a hair, and the bar is 72 in every state now
 * that it is one line. It was 180 when the bar could reach 166, and
 * shrinking the bar left the reserve 108px too big on every screen —
 * the number needed to move with the thing it was measured against.
 */
@media (max-width: 63.99rem) {
  body:has(.wed-rail) { padding-bottom: 84px; }
}
@media (min-width: 64rem) {
  /* Jackie's, unchanged from live. */
  .rail { position: sticky; top: var(--step); bottom: auto; }
  /* Back to a column beside the form: in flow, no ground, no hairline. */
  .wed-rail {
    position: sticky;
    top: var(--step);
    bottom: auto;
    left: auto;
    right: auto;
    padding: 0;
    background: none;
    border-top: 0;
  }
}
/* On a phone this is the bottom bar, so the figures and the button
   share one row — stacking them costs about 90px of calendar, which
   is two weeks of it. From 64rem it becomes a column in the rail. */
.rail-card {
  background: var(--raised);
  color: var(--ink);
  border: 1px solid var(--line);
  border-top: 1px solid var(--gold);
  border-radius: 3px;
  padding: .7rem 1rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem 1.25rem;
  box-shadow: 0 -10px 30px rgba(0, 0, 0, .45);
}
/* On a desktop the box goes and a 2px gold rule holds it instead: the
   rail is a column in a spread, not a widget parked beside one. */
@media (min-width: 64rem) {
  .rail-card {
    display: block;
    background: transparent;
    border: 0;
    border-top: 2px solid var(--gold);
    border-radius: 0;
    padding: 1.1rem 0 0;
    box-shadow: none;
  }
}

/* The collection, with its own photograph at the size of a stamp. A
   column of figures forgets what was chosen; a picture of it does
   not. */
.rail-pick { align-items: center; }
.rail-thumb { flex: none; width: 56px; }
.rail-title {
  display: none;
  font-size: 10px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 .9rem;
}

/* Said once, at the top of the card, rather than by every line. */
.rail-updating {
  margin: 0 0 .6rem;
  font-size: 12px;
  color: var(--gold-ink);
}
/* A refusal reads the same size as the updating line rather than as an
   alarm: nothing is lost, the figures are the last true ones, and the
   only thing the couple has to do is press once. */
.rail-refused {
  margin: 0 0 .6rem;
  font-size: 12px;
  color: var(--ink-soft, var(--ink));
}
.rail-refused button {
  font: inherit;
  color: var(--gold-ink);
  background: none;
  border: 0;
  padding: 0;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

/* The itemised list: desktop only. */
.rail-lines { display: none; }
.rail-line {
  display: flex;
  gap: 1rem;
  align-items: baseline;
  padding: .45rem 0;
  font-size: 14px;
  line-height: 1.35;
  color: var(--ink-soft);
}
.rail-line .amt {
  margin-left: auto;
  font-family: var(--display);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--ink);
}
.rail-empty { opacity: .6; font-style: italic; }
.rail-tax { opacity: .75; }
.rail-total {
  display: flex;
  gap: 1rem;
  align-items: baseline;
  margin-top: .5rem;
  padding-top: .75rem;
  border-top: 1px solid var(--line);
  font-family: var(--display);
  font-size: 1.4rem;
}
.rail-total .amt {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  color: var(--gold-ink);
}
.rail-retainer {
  margin-top: .5rem;
  padding-top: .6rem;
  border-top: 1px solid var(--line);
}
.rail-retainer .amt { color: var(--gold-ink); }

/* The condensed figures: phones only. */
/* Three figures and a button do not fit across a 375px phone, so the
   bar takes two rows there. Kept tight, because every pixel it spends
   is a pixel of calendar it sits on top of. */
.rail-compact { display: flex; gap: .5rem 1.1rem; margin: 0; flex-wrap: wrap; }
.rail-compact div { display: grid; }
.rail-compact dt {
  font-size: 10px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--muted);
}
.rail-compact dd {
  margin: 0;
  font-family: var(--display);
  font-size: 1.05rem;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
  /* Gold-ink, not gold: this is 16.8px and the brand gold stops being
     readable below 18. */
  color: var(--gold-ink);
}

.rail-nav { display: flex; align-items: center; gap: .75rem; margin-left: auto; }
.rail-nav .go { order: 2; }

/*
 * The one line, and why it is a line rather than a list.
 *
 * A label in small caps above its value is two lines tall, so three
 * figures are a three line bar however true they are — which is what
 * Tan was still looking at when he wrote "the bar has to be even
 * SMALLER" about a screen where every figure was correct.
 *
 * So: value first, one line, and it gives way before the button does.
 * `min-width: 0` is what lets it shrink inside a flex row at all, and
 * the ellipsis is what it does instead of wrapping and taking the bar
 * back to two lines on a narrow phone. Continue never shrinks.
 */
.rail-say {
  display: flex;
  align-items: baseline;
  gap: .35rem;
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
  font-size: 13px;
  line-height: 1.25;
  color: var(--ink);
}
/*
 * The figure is the part that must never be cut.
 *
 * Measured at 320px: with one shrinking box, "Total $2,205" wanted 72px
 * in 54 and rendered as a clipped price — and half a price is worse
 * than no price, because it still looks like one. So the word gives way
 * first and the number does not shrink at all.
 */
.rail-say-label {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--muted);
}
.rail-say-value { flex: 0 0 auto; white-space: nowrap; }

/* Back is the way out, not the way on: a quiet control beside the
   button rather than a second one the same size as it. */
@media (max-width: 63.99rem) {
  /* One row means one row: the card must not wrap, or the line below
     goes back to being a second row and the bar doubles. */
  .wed-rail .rail-card { flex-wrap: nowrap; }
  .wed-rail .rail-nav { gap: .9rem; flex: 0 0 auto; }
  /* `.rail-nav` for weight: the plain `button.back` rule is further
     down this file, and a media query adds no specificity of its own. */
  .wed-rail .rail-nav button.back {
    border: 0;
    padding: .2rem 0;
    font-size: 11px;
    color: var(--muted);
  }
  .wed-rail .rail-nav button.back:hover { color: var(--ink); }
}

@media (min-width: 64rem) {
  .rail-title { display: block; }
  .rail-lines { display: block; }
  .rail-compact { display: none; }
  .rail-say { display: none; }
  .rail-nav { flex-direction: column; align-items: stretch; margin: 1.1rem 0 0; }
  .rail-nav .go { order: 0; text-align: center; }
  .rail-nav .back { order: 1; text-align: center; }
}

button.go {
  font: inherit;
  font-size: 12px;
  letter-spacing: .16em;
  text-transform: uppercase;
  background: var(--gold);
  color: #241C1C;
  border: 1px solid var(--gold);
  border-radius: 999px;
  padding: .8rem 1.6rem;
  cursor: pointer;
  white-space: nowrap;
  transition: filter 140ms ease;
}
button.go:hover:not(:disabled) { filter: brightness(1.08); }
button.go:disabled { opacity: .4; cursor: default; }

button.back {
  font: inherit;
  font-size: 12px;
  letter-spacing: .16em;
  text-transform: uppercase;
  background: transparent;
  color: var(--ink);
  border: 1px solid color-mix(in srgb, var(--ink) 35%, transparent);
  border-radius: 999px;
  padding: .8rem 1.3rem;
  cursor: pointer;
}
button.back:hover { border-color: var(--ink); }

/* ── Dates ─────────────────────────────────────────────────────── */
.month { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.month .dow {
  font-size: 10px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
  padding-bottom: .35rem;
}
.day {
  aspect-ratio: 1;
  border: 1px solid var(--line);
  background: var(--ground);
  border-radius: 3px;
  font: inherit;
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  cursor: pointer;
  display: grid;
  place-items: center;
}
/* Free: green, and clickable. The border is gold rather than green
   because gold is what everything touchable on this page is edged
   with, and the numeral already carries the colour that means yes. */
.day[data-state='free'] {
  color: var(--free);
  background: color-mix(in srgb, var(--free) 10%, var(--ground));
  border-color: color-mix(in srgb, var(--gold) 45%, transparent);
  font-weight: 600;
}
.day[data-state='free']:hover {
  border-color: var(--gold);
  background: color-mix(in srgb, var(--free) 20%, var(--ground));
}

/* Taken: red, and struck through.
   A couple deserves to see that the day exists and is spoken for,
   not a hole in the month — and the strike says "gone" to anyone who
   cannot see the red at all. */
.day[data-state='taken'] {
  color: var(--taken);
  background: color-mix(in srgb, var(--taken) 12%, var(--ground));
  border-color: color-mix(in srgb, var(--taken) 32%, transparent);
  cursor: not-allowed;
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}

/* Outside the window: quiet. Not a refusal, just not on sale yet. */
.day[data-state='off'] {
  color: var(--muted);
  background: transparent;
  border-color: transparent;
  cursor: default;
  opacity: .5;
}

.day[data-on='1'],
.day[data-state='free'][data-on='1'] {
  background: var(--gold);
  color: #241C1C;
  border-color: var(--gold);
  text-decoration: none;
}

/* ── The legend ──────────────────────────────────────────────────
   The colours are named in words as well as shown, because a colour
   code nobody states is a colour code half the room has to guess. */
.legend {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem 1.1rem;
  list-style: none;
  margin: .9rem 0 0;
  padding: 0;
  font-size: 12px;
  color: var(--ink-soft);
}
.legend li { display: flex; align-items: center; gap: .4rem; }
.legend .swatch {
  width: 13px;
  height: 13px;
  border-radius: 2px;
  border: 1px solid var(--line);
}
.legend .swatch[data-state='free'] {
  background: color-mix(in srgb, var(--free) 45%, var(--ground));
  border-color: color-mix(in srgb, var(--gold) 55%, transparent);
}
.legend .swatch[data-state='taken'] {
  background: color-mix(in srgb, var(--taken) 45%, var(--ground));
  border-color: color-mix(in srgb, var(--taken) 55%, transparent);
}
.legend .swatch[data-state='off'] { background: transparent; border-style: dashed; }

.month-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: .6rem;
}
.month-head strong { font-family: var(--display); font-weight: 400; font-size: 1.15rem; }

/* ── Jumping to a month ──────────────────────────────────────────────
   The title is the control, so it has to read as pressable without
   becoming furniture. No border and no fill: it earns its affordance
   from the gold on hover and the caret, which is the same restraint the
   rest of the page uses on its links. */
.month-head .month-jump {
  font: inherit;
  background: transparent;
  border: 0;
  border-radius: 3px;
  /* The arrows beside it are 34px circles, and that rule reaches this
     button too. Width and height have to be given back explicitly:
     specificity only settles properties that are actually declared, and
     leaving these out left the title as a circle with its own border
     correctly removed. */
  width: auto;
  height: auto;
  min-height: 34px;
  padding: .2rem .5rem;
  cursor: pointer;
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  gap: .4rem;
}
.month-head .month-jump::after {
  content: '';
  width: 6px; height: 6px;
  border-right: 1.2px solid var(--gold-ink);
  border-bottom: 1.2px solid var(--gold-ink);
  transform: rotate(45deg) translate(-1px, -1px);
  transition: transform 160ms ease;
}
.month-head .month-jump[aria-expanded='true']::after { transform: rotate(225deg) translate(-2px, -2px); }
.month-head .month-jump:hover strong, .month-head .month-jump:focus-visible strong { color: var(--gold-ink); }

.jump {
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--raised);
  padding: .9rem;
  margin-bottom: .8rem;
}
.jump-years { display: flex; gap: .4rem; flex-wrap: wrap; }
.jump-year {
  font: inherit;
  font-size: 13px;
  letter-spacing: .04em;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: .25rem .7rem;
  cursor: pointer;
  color: var(--ink-soft);
}
.jump-year[data-on='1'] { border-color: var(--gold); color: var(--ink); background: var(--ground); }

/* 12px, so --muted is allowed to carry it. */
.jump-note {
  margin: .75rem 0 .4rem;
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}

.jump-months { display: grid; grid-template-columns: repeat(3, 1fr); gap: .4rem; }
.jump-month {
  font: inherit;
  background: var(--ground);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: .5rem .25rem;
  cursor: pointer;
  color: var(--ink);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .15rem;
  /* Comfortably past 44 on a phone, which is where this is used. */
  min-height: 46px;
}
.jump-month:disabled { opacity: .38; cursor: default; }
.jump-month[data-on='1'] { border-color: var(--gold); }
.jump-abbr { font-size: 13px; letter-spacing: .06em; }
/* The count is the point of the panel, so it gets the display face and
   the gold. `--gold-ink` rather than `--gold`: at this size the brand
   gold is 3.0:1 and unreadable. */
.jump-free {
  font-family: var(--display);
  font-size: 15px;
  line-height: 1;
  color: var(--gold-ink);
}
/* A month with nothing left says so by going quiet rather than red.
   Red is the language of a day somebody already has. */
/* The word is rendered as the value now, not appended to it: as a
   pseudo element it sat after the count and read "0full". */
.jump-month[data-none='1'] .jump-free {
  color: var(--muted);
  font-family: var(--body);
  font-size: 11px;
  letter-spacing: .06em;
}

.jump-next {
  margin-top: .7rem;
  width: 100%;
  font: inherit;
  font-size: 13px;
  background: transparent;
  border: 1px solid var(--gold);
  border-radius: 3px;
  padding: .55rem .6rem;
  cursor: pointer;
  color: var(--gold-ink);
}
.jump-next:hover, .jump-next:focus-visible { background: var(--ground); }

.month-head button {
  font: inherit;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 999px;
  width: 34px; height: 34px;
  cursor: pointer;
  color: var(--ink);
}
.month-head button:hover:not(:disabled) { border-color: var(--gold); color: var(--gold); }
.month-head button:disabled { opacity: .3; cursor: default; }

/* ── The optional account ────────────────────────────────────────────
   Set apart from the review above it by a rule rather than a box: it is
   an aside, not a step, and a panel here would read as something else
   the couple has to deal with before they can sign. */
.account-offer {
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
}
.account-offer-lead {
  font-family: var(--display);
  font-size: 1.05rem;
  color: var(--ink);
  margin: 0 0 .5rem;
}
.account-offer .hint { margin-top: .5rem; }
.account-offer strong { color: var(--ink); font-weight: 500; }
/* Shaped like `button.back`, the page's quiet action, because that is
   what this is: available, and not the thing being asked for. Written
   out rather than sharing a class, since those rules are scoped to
   `button` and this has to be a link. */
.account-offer .account-link {
  display: inline-block;
  font: inherit;
  font-size: 12px;
  letter-spacing: .16em;
  text-transform: uppercase;
  text-decoration: none;
  background: transparent;
  color: var(--ink);
  border: 1px solid color-mix(in srgb, var(--ink) 35%, transparent);
  border-radius: 999px;
  padding: .8rem 1.3rem;
}
.account-offer .account-link:hover,
.account-offer .account-link:focus-visible { border-color: var(--ink); }

.error {
  background: color-mix(in srgb, var(--taken) 16%, var(--raised));
  border: 1px solid color-mix(in srgb, var(--taken) 45%, transparent);
  color: color-mix(in srgb, var(--taken) 55%, var(--ink));
  padding: .8rem 1rem;
  border-radius: 3px;
  margin-bottom: 1rem;
}

.review-line {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: .55rem 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink-soft);
}
.review-line:last-of-type { border-bottom: 0; }
.review-line .amt { font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--ink); }
.review-total {
  display: flex;
  justify-content: space-between;
  padding-top: .9rem;
  margin-top: .5rem;
  border-top: 1px solid var(--gold-soft);
  font-family: var(--display);
  font-size: 1.3rem;
}
.review-total .amt { color: var(--gold-ink); font-variant-numeric: tabular-nums; }

.closing {
  text-align: center;
  color: var(--muted);
  font-size: 13px;
  margin: clamp(2rem, 5vw, 3.5rem) 0 0;
  padding: 2rem 0 3rem;
  border-top: 1px solid var(--line);
}

/* ── Tailoring a collection ────────────────────────────────────────
   Quiet on purpose. This sits inside a collection panel and must not
   compete with it: the collection is the product, and these are
   adjustments to it. A panel that looked like a configurator would
   invite a couple to price the parts. */
.tailor {
  margin-top: .9rem;
  padding: 1.15rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: color-mix(in srgb, var(--ground) 55%, var(--raised));
}
.tailor-head h3 {
  font-family: var(--display);
  font-weight: 400;
  font-size: 1.15rem;
  margin: 0 0 .3rem;
}
.tailor-head p { margin: 0 0 1rem; color: var(--ink-soft); font-size: 14px; }

.tailor-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: .8rem 0;
  border-top: 1px solid var(--line);
}
.tailor-row--toggle { cursor: pointer; }
.tailor-row input[type='checkbox'] {
  accent-color: var(--gold);
  width: 18px;
  height: 18px;
  flex: none;
}
.tailor-label { display: grid; gap: .1rem; min-width: 0; }
.tailor-name { font-family: var(--display); font-size: 1.02rem; }
.tailor-note { color: var(--muted); font-size: 13px; }
.tailor-amount {
  margin-left: auto;
  color: var(--ink-soft);
  font-size: 13px;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.stepper { margin-left: auto; display: flex; align-items: center; gap: .5rem; }
.stepper button {
  font: inherit;
  font-size: 1.1rem;
  line-height: 1;
  width: 34px;
  height: 34px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--raised);
  color: var(--ink);
  cursor: pointer;
}
.stepper button:hover:not(:disabled) { border-color: var(--gold); color: var(--gold); }
.stepper button:disabled { opacity: .3; cursor: default; }
.stepper-value {
  font-family: var(--display);
  font-variant-numeric: tabular-nums;
  min-width: 5.5rem;
  text-align: center;
}

/* The offer, not a refusal. Gold rather than red: a couple has not
   done anything wrong, they have described a different collection. */
.tailor-switch {
  margin-top: 1rem;
  padding: .9rem 1rem;
  border: 1px solid var(--gold-soft);
  border-radius: 3px;
  background: color-mix(in srgb, var(--gold) 12%, var(--raised));
}
.tailor-switch p { margin: 0 0 .7rem; font-size: 14px; line-height: 1.45; }
.tailor-switch button {
  font: inherit;
  font-size: 12px;
  letter-spacing: .14em;
  text-transform: uppercase;
  background: var(--gold);
  color: #241C1C;
  border: 1px solid var(--gold);
  border-radius: 999px;
  padding: .6rem 1.2rem;
  cursor: pointer;
}

/* An adjustment in the rail: subordinate to the collection above it. */
.rail-adjust { opacity: .8; font-size: 13px; padding-left: .8rem; }

/* Inside a panel there is not room for a label and its price on one
   line, so the amount drops below when it has to; the label is the
   thing being read. */
.tailor-row--toggle { flex-wrap: wrap; }
.tailor-row--toggle .tailor-label { flex: 1 1 11rem; }
.tailor-row--toggle .tailor-amount { flex: 0 1 auto; margin-left: auto; padding-left: 1.7rem; }

/* ── The three groups in the panel ─────────────────────────────────
   What can be bought, what is already there, and what needs a larger
   collection. Run together they read as one list, and the last group
   looks like something broken rather than something to aspire to. */
.tailor-group {
  margin: 1rem 0 .3rem;
  font-size: 10px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
}
.tailor-have, .tailor-up { border-top: 1px solid var(--line); }
.tailor-row--have, .tailor-row--up { border-top: 0; padding: .45rem 0; }
.tailor-row--have { color: var(--ink-soft); }
.tailor-row--have .tick { color: var(--gold-ink); flex: none; }
.tailor-row--up { opacity: .85; }

/* Not a disabled control — a signpost. A greyed row with no way out
   tells a couple the studio does not do this at all. */
.tailor-jump {
  margin-left: auto;
  font: inherit;
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  background: transparent;
  border: 1px solid var(--gold-soft);
  border-radius: 999px;
  padding: .35rem .75rem;
  color: var(--gold-ink);
  cursor: pointer;
  white-space: nowrap;
}
.tailor-jump:hover { border-color: var(--gold); background: color-mix(in srgb, var(--gold) 14%, transparent); }

.tailor-cap { flex-basis: 100%; margin: .3rem 0 0; font-size: 12px; color: var(--muted); }
.tailor-needs { font-style: italic; }

/* The credit, said where it is earned. Gold rather than green: it is
   not money off, and anything that reads like a discount here sets up
   a disappointment at the total. */
.tailor-credit {
  margin-top: .9rem;
  padding: .8rem .9rem;
  border: 1px solid var(--gold-soft);
  border-radius: 3px;
  background: color-mix(in srgb, var(--gold) 10%, var(--raised));
}
.tailor-credit p { margin: 0; font-size: 13px; line-height: 1.5; }

/* ── Stillness ─────────────────────────────────────────────────────
   Asked for, and given completely: nothing rises, nothing fades,
   nothing opens on a timer. The panels have to be told they are shown
   regardless, because their resting state is invisible. */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
  /* Both of these rest invisible and are shown by an observer, so
     deleting the transition is not enough — they have to be told. The
     hero collage does not appear here: its resting state is already
     the visible one and the deleted animation is what hid it. */
  .panel, .strip-tile { opacity: 1 !important; transform: none !important; }
  .views { opacity: 1 !important; }
  .panel-all::details-content,
  .panel-tailor::details-content { transition: none !important; }
}

/* ── The address that goes on the agreement ────────────────────────────
 *
 * Shaped like `.account-offer`, which is the block immediately below
 * it: a rule, a display line, the fields, a hint. Two asides on one
 * screen that were built differently would read as two designs.
 */
.review-address {
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
  max-width: 40rem;
}
.review-address-lead {
  font-family: var(--display);
  font-size: 1.05rem;
  color: var(--ink);
  margin: 0 0 .5rem;
}
.review-address .hint { margin-top: .6rem; }

/* Street across the top, city and postal code beside each other. The
   street is the long one and a postal code is six characters, so an
   even split would waste the row. */
.fields.address { grid-template-columns: 1fr; margin-top: 1.1rem; }
@media (min-width: 40rem) {
  .fields.address { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); }
  .fields.address > label:first-child { grid-column: 1 / -1; }
}

/* The line read back to them, in the words the invoice will print. */
.address-preview {
  margin: .9rem 0 0;
  font-size: 13px;
  color: var(--muted);
}
.address-preview strong {
  color: var(--ink-soft);
  font-weight: 500;
}

/* The escape hatch for a couple whose postal code is not Canadian.
   12px, so `--gold-ink` rather than the brand gold: 3.0:1 fills a
   button and is unreadable as a label. */
.address-more {
  margin-top: .5rem;
  padding: 0;
  font: inherit;
  font-size: 12px;
  color: var(--gold-ink);
  background: none;
  border: 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.address-more:hover { color: var(--ink); }

/* A refusal under the box it belongs to, rather than a banner at the
   top of the screen listing which of five fields is wrong. */
.field-error {
  display: block;
  margin-top: .35rem;
  font-size: 13px;
  color: var(--taken);
}
label.field[data-bad='1'] input {
  border-color: color-mix(in srgb, var(--taken) 55%, var(--line));
}

/* ── A first meeting ──────────────────────────────────────────────────
 *
 * Built from what is already here. The panel is a `.card`, the day grid
 * is `.month` and `.day` with two new states, the windows are the
 * `.jump-year` pill at a tappable size, and the place rows are the only
 * genuinely new shape. Nothing new sits on a photograph, no new colour
 * is introduced, and blush stays a ground.
 */

/* The doors. A text link, never a second button beside Continue: gold
   is what this page uses for the one thing it wants pressed. */
.meet-door { margin: 0 0 .9rem; flex-basis: 100%; }
.meet-door-ask {
  margin: 0 0 .45rem;
  font-size: 13px;
  color: var(--muted);
}
/* The second door on the page. Outlined in the studio's gold rather
   than filled: Continue stays the one obvious way on, and this reads as
   an invitation beside it instead of a rival. One line of type inside
   it, so the button is a button; the reassurance sits under it, where a
   caption belongs, and the whole thing stays a thumb's height. */
.meet-first {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: .6rem 1.4rem;
  font: inherit;
  font-size: 12px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--gold-ink);
  background: none;
  border: 1px solid var(--gold);
  border-radius: 999px;
  cursor: pointer;
  transition: background .18s, color .18s, border-color .18s;
}
/* Filled, it takes the page's own ink rather than white: white on this
   gold reads at 3.09:1, the ink at 5.40:1, both measured from the
   rendered colours (QA, 2026-09-17). */
.meet-first:hover,
.meet-first:focus-visible { background: var(--gold); border-color: var(--gold); color: var(--ink); }
.meet-door-note {
  margin: .45rem 0 0;
  font-size: 12px;
  color: var(--muted);
}

/* The steps bar recedes while the panel is open rather than pretending
   there is a fifth step. */
.steps-wrap[data-aside='1'] { opacity: .5; }

.meet-card { max-width: 44rem; }
.meet-group { margin-top: 1.75rem; }

/* How would you like to meet. Two tiles, and the gold rule is how this
   page already says "chosen" so no fill is needed. */
.meet-tiles { display: grid; gap: .75rem; margin-top: .7rem; }
@media (min-width: 40rem) { .meet-tiles { grid-template-columns: 1fr 1fr; } }
.choice {
  display: grid;
  gap: .2rem;
  padding: 1rem 1.1rem;
  min-height: 64px;
  text-align: left;
  font: inherit;
  background: var(--ground);
  border: 1px solid var(--line);
  border-radius: 3px;
  cursor: pointer;
}
.choice:hover { border-color: var(--gold-soft); }
.choice[aria-pressed='true'] { border-color: var(--gold); }
.choice strong {
  font-family: var(--display);
  font-weight: 400;
  font-size: 1.15rem;
  color: var(--ink);
}
.choice span { font-size: 14px; color: var(--ink-soft); }

/* Where. One question with three answers, as rows rather than tiles:
   the third expands into a field and the first into a list, and a grid
   of boxes that grow is a layout that jumps. */
.where-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: .9rem;
  align-items: start;
  padding: .85rem 0;
  border-top: 1px solid var(--line);
  cursor: pointer;
}
.where-row input[type='radio'] {
  width: 18px;
  height: 18px;
  margin-top: .15rem;
  accent-color: var(--gold);
}
.where-name {
  display: block;
  font-family: var(--display);
  font-size: 1.05rem;
  color: var(--ink);
}
.where-note { display: block; font-size: 13px; color: var(--muted); }
.where-far {
  font-size: 13px;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.where-in {
  grid-column: 1 / -1;
  padding: .25rem 0 .5rem 1.9rem;
}
.where-sub { padding: .6rem 0; border-top-color: var(--blush-block); }
.where-state { font-size: 13px; color: var(--ink-soft); margin: .5rem 0 0; }

/* The days. Two states, one of them struck through, so no legend is
   needed and no colour carries the meaning on its own. Deliberately
   not the free/taken palette: that green means "yours to book" and
   nothing here is being booked. */
.meet-month { max-width: 26rem; margin-top: .7rem; }
.day[data-state='meet-open'] { color: var(--ink); }
.day[data-state='meet-open']:hover { border-color: var(--gold); }
.day[data-state='meet-busy'] {
  color: var(--muted);
  opacity: .5;
  border-color: transparent;
  background: transparent;
  text-decoration: line-through;
  cursor: not-allowed;
}
.day[data-state='meet-open'][data-on='1'],
.day[data-state='meet-busy'][data-on='1'] {
  background: var(--gold);
  border-color: var(--gold);
  color: #fff;
  font-weight: 600;
}

/* The windows. The `.jump-year` pill at a size a thumb can hit. */
.meet-windows { margin-top: 1.5rem; }
.pills { display: flex; gap: .55rem; flex-wrap: wrap; }
.pill {
  font: inherit;
  font-size: 14px;
  min-height: 44px;
  padding: .55rem 1rem;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 3px;
  color: var(--ink);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: .45rem;
}
.pill:hover:not([disabled]) { border-color: var(--gold-soft); }
.pill[data-on='1'] { border-color: var(--gold); background: var(--raised); }
.pill em { font-style: normal; font-size: 12px; color: var(--muted); }
.pill[disabled] { opacity: .38; cursor: default; text-decoration: line-through; }

.meet-nav {
  display: flex;
  gap: .75rem;
  align-items: center;
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
}

/* The review step's door, shaped like the account offer it sits above. */
.meet-ask { margin-top: .5rem; }

/* The address block nested inside the panel: no rule and no heading,
   because the question it sits under is already both. */
.review-address.bare {
  margin-top: .8rem;
  padding-top: 0;
  border-top: 0;
}

/* ── Jackie's booking link, /beauty/<slug> ──────────────────────────
   Built from the wedding booking's own classes: the masthead, the
   numbered cards, `.day-grid`, `.fields` and `label.field`. What is left
   here is only what that page has no need for. */

/* Two more day states. Gold marks a day that is partly booked, because
   it is still a day a client can choose. */
.day[data-state='some'] {
  color: var(--ink);
  background: color-mix(in srgb, var(--gold) 16%, var(--ground));
  border-color: color-mix(in srgb, var(--gold) 55%, transparent);
  font-weight: 600;
}
.day[data-state='some']:hover { border-color: var(--gold); }

/* A day off: struck through like a booked day, but neutral rather than
   red, because nobody has it. Jackie is simply not working. */
.day[data-state='closed'] {
  color: var(--muted);
  background: transparent;
  border-color: var(--line);
  cursor: not-allowed;
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}

.legend .swatch[data-state='some'] {
  background: color-mix(in srgb, var(--gold) 16%, var(--ground));
  border: 1px solid color-mix(in srgb, var(--gold) 55%, transparent);
}
.legend .swatch[data-state='closed'] {
  background: transparent;
  border: 1px solid var(--line);
}

/* The words above the first card, spaced the way the wedding hero is. */
.beauty-intro { padding: clamp(2rem, 6vw, 4rem) 0 clamp(1.5rem, 4vw, 2.5rem); }
.beauty-intro .lede { max-width: 40rem; }

.beauty-h3 {
  font-family: var(--display);
  font-weight: 400;
  font-size: 1.25rem;
  color: var(--ink);
  margin: 0 0 0.6rem;
}

/* What the chosen day holds, in the day grid's side column beside the
   calendar, where the wedding step keeps its fields. */
.beauty-day {
  padding: 1.1rem 1.25rem;
  background: var(--raised);
  border: 1px solid var(--line);
}
.beauty-open { margin: 0 0 1rem; color: var(--ink-soft); }
.beauty-hours { list-style: none; margin: 0 0 1rem; padding: 0; }
.beauty-hours li {
  padding: 0.45rem 0;
  border-top: 1px solid var(--line);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.beauty-hours li:last-child { border-bottom: 1px solid var(--line); }

/* --taken is a text colour already on this page (a booked day's number),
   so a clash warning in it reads as the same fact. */
.beauty-warn { color: var(--taken); margin: 0.75rem 0 0; }

/* ── The redesign: cards in one column, the price beside them ────────
   Wider than the wedding page, because on a desktop this column shares
   its row with the rail and the calendar still wants the chosen day's
   hours beside it. */
.page.beauty { max-width: 80rem; }

/* How a client knows her time: when to start, or when to be ready. */
.beauty-mode { margin: 1rem 0 .25rem; }
.beauty-mode .pill { font-size: 13px; min-height: 40px; padding: .45rem .8rem; }
.beauty-where { display: block; margin-top: 1.25rem; max-width: 32rem; }

/* The day at a glance. Grey for the hours already booked, gold for the
   stretch she is asking for, red where the two overlap. */
.beauty-strip { margin: 1rem 0 .25rem; }
.beauty-strip-bar {
  position: relative;
  height: 14px;
  background: var(--blush-wash);
  border-radius: 999px;
  overflow: hidden;
}
.beauty-strip-busy { position: absolute; top: 0; bottom: 0; background: var(--ink-soft); opacity: .35; }
.beauty-strip-you { position: absolute; top: 0; bottom: 0; background: var(--gold); opacity: .9; }
.beauty-strip-you[data-clash='1'] { background: var(--taken); }
.beauty-strip-ticks { position: relative; height: 1.25rem; margin-top: .3rem; }
.beauty-strip-ticks span {
  position: absolute;
  transform: translateX(-50%);
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
}
.beauty-strip-ticks span:last-child { transform: translateX(-100%); }

/* Who is getting ready: what it is for, then one row per person. */
.beauty-occasions { margin-bottom: 1.25rem; }
.beauty-people { list-style: none; margin: 0; padding: 0; }
.beauty-person { padding: 1rem 0; border-top: 1px solid var(--line); }
.beauty-person:last-child { border-bottom: 1px solid var(--line); }
.beauty-person-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem .9rem;
  margin-bottom: .7rem;
}
.beauty-person-label { font-family: var(--display); font-size: 1.2rem; color: var(--ink); }
.beauty-person-name {
  font: inherit;
  font-size: 15px;
  flex: 1 1 11rem;
  min-width: 0;
  max-width: 18rem;
  padding: .5rem .7rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--ground);
  color: var(--ink);
}
.beauty-person-remove {
  font: inherit;
  font-size: 13px;
  margin-left: auto;
  min-height: 44px;
  padding: 0 .25rem;
  background: none;
  border: 0;
  color: var(--ink-soft);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.beauty-person-none { margin: .6rem 0 0; font-size: 14px; color: var(--ink-soft); }

/* A service is a chip a thumb can hit, with its price beside its name.
   One that cannot be booked online yet is outlined in dots and says so. */
.beauty-chips { display: flex; flex-wrap: wrap; gap: .5rem; }
.beauty-chip {
  font: inherit;
  font-size: 14px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .5rem .95rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}
.beauty-chip:hover { border-color: var(--gold-soft); }
.beauty-chip em {
  font-style: normal;
  font-size: 13px;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}
.beauty-chip[data-on='1'] { border-color: var(--gold); background: var(--raised); }
.beauty-chip[data-on='1']::before { content: '✓'; font-size: 13px; color: var(--gold-ink); }
.beauty-chip[data-ask='1'] { border-style: dotted; }
.beauty-chip .beauty-chip-ask { color: var(--gold-ink); }

/* Adding people is a count per role, with the wedding page's stepper. */
.beauty-add { margin-top: 1.75rem; }
.beauty-add-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: .45rem 0;
  border-top: 1px solid var(--line);
}
.beauty-add-row:last-of-type { border-bottom: 1px solid var(--line); }
.beauty-add-row > span { color: var(--ink); }
.beauty-add-row .stepper-value { min-width: 2.5rem; }
.beauty-hint { margin: 1rem 0 0; font-size: 14px; color: var(--ink-soft); max-width: 40rem; }

/* The rail, where it says more than the wedding rail does: a line per
   person, the second artist, Jackie's time, and a figure that may be a
   range, which wraps rather than running out of the column. */
.beauty-rail .rail-val { margin-left: auto; text-align: right; color: var(--ink); }
.beauty-rail .rail-total { flex-wrap: wrap; }
.beauty-rail .rail-total .amt { white-space: normal; text-align: right; }
.beauty-rail .rail-note { margin: .45rem 0 0; font-size: 13px; line-height: 1.45; color: var(--ink-soft); }
.rail-more {
  font: inherit;
  font-size: 13px;
  min-height: 40px;
  padding: .4rem .85rem;
  margin-left: auto;
  background: none;
  border: 1px solid var(--line);
  border-radius: 3px;
  color: var(--ink);
  cursor: pointer;
}
/* On a phone the itemised list opens above the bar as a sheet, and never
   taller than half the screen. */
@media (max-width: 63.99rem) {
  .beauty-rail .rail-card.open .rail-lines {
    display: block;
    order: -1;
    flex-basis: 100%;
    max-height: 50vh;
    overflow: auto;
  }
}
@media (min-width: 64rem) {
  .rail-more { display: none; }
}

/* On a phone the name takes its own line under the person, so Remove
   stays beside the role rather than falling to a line of its own, and
   each service is a full row with its price at the right edge, the way
   a menu is read on a narrow screen. */
@media (max-width: 39.99rem) {
  .beauty-person-head { display: grid; grid-template-columns: 1fr auto; }
  .beauty-person-name { grid-column: 1 / -1; grid-row: 2; max-width: none; }
  .beauty-person-remove { margin-left: 0; }
  .beauty-chips { flex-direction: column; }
  .beauty-chip { width: 100%; }
  .beauty-chip em:first-of-type { margin-left: auto; }
  /* The bar keeps the two figures a client is watching and the button
     that opens the rest, on one row. How many people is on the card she
     is filling in, and a second row of bar is a row of calendar lost. */
  .beauty-rail .rail-compact div:nth-child(2) { display: none; }
  .beauty-rail .rail-card { flex-wrap: nowrap; }
  .beauty-rail .rail-card.open { flex-wrap: wrap; }
}

/* ── Jackie's page on a phone ───────────────────────────────────────
   Scoped to `.page.beauty`: the wedding page shares every class below and
   was not the page being fitted. */
@media (max-width: 39.99rem) {
  /* A rem off each side of the page and 14px inside a card give the
     calendar the width for 44px days on a 390px phone, the size a thumb
     hits first time. At 360 they come to 40, up from 37. */
  .page.beauty { padding-left: 1rem; padding-right: 1rem; }
  .page.beauty .card { padding-left: .875rem; padding-right: .875rem; }
  /* At 7rem the numeral ran through each card's heading and the line
     under it. Smaller, with the heading kept out of its corner, it is
     still the spread's big italic number, and the note starts below it. */
  .page.beauty .card-no { font-size: 4.25rem; top: -.9rem; right: .5rem; }
  .page.beauty .card > h2.section { padding-right: 3.75rem; }
  /* Everything a thumb presses is at least 44px. The month's title is
     left out of the arrows' rule by name, or it becomes a 44px circle, the
     same trap the rule for it above describes. */
  .page.beauty .month-head button:not(.month-jump) { width: 44px; height: 44px; }
  .page.beauty .month-head .month-jump { min-height: 44px; }
  .page.beauty .stepper button { width: 44px; height: 44px; }
  .page.beauty .stepper-value { min-width: 2.5rem; }
  .page.beauty .beauty-mode .pill,
  .page.beauty .rail-more { min-height: 44px; }
  /* Under 16px, Safari on an iPhone zooms into a field when it is tapped
     and leaves the page zoomed. */
  .page.beauty .beauty-person-name { font-size: 16px; }
  /* The small tracked labels come up to 12px, the smallest size the muted
     colour is readable at. `:where` keeps this below the Chinese rules,
     which set their own sizes. */
  .page.beauty :where(label.field > span, .beauty-section-label, .eyebrow, .masthead-say, .rail-compact dt, .month .dow) {
    font-size: 12px;
  }
}
/* While the rail is the bar along the bottom of the screen. */
@media (max-width: 63.99rem) {
  /* At .45 black the bar laid a grey band over the fields scrolling under
     it, on a ground that is nearly white. */
  .page.beauty .rail-card { box-shadow: 0 -8px 24px rgba(36, 28, 28, .16); }
  /* Opened, the sheet already ends on the total, so the bar's own figures
     under it said the same thing twice. */
  .page.beauty .rail-card.open .rail-compact { display: none; }
}

/* ── The wedding page on a phone ─────────────────────────────────────
   The fitting Jackie's page had above, for the faults an audit found
   here too at 390: each card's 7rem numeral on its heading, 34px month
   arrows and a 36px month title, 10 and 11px labels in the muted colour,
   and the bar's grey band. Kept off Jackie's page, which has its own
   block, and nothing here is a class the meeting reply page draws. */
@media (max-width: 39.99rem) {
  .page:not(.beauty) .card-no { font-size: 4.25rem; top: -.9rem; right: .5rem; }
  /* The collection card's heading sits in .step-head with its toggle, so
     that is what keeps out of the numeral's corner there. */
  .page:not(.beauty) .card > h2.section,
  .page:not(.beauty) .card > .step-head { padding-right: 3.75rem; }
  .page:not(.beauty) .month-head button:not(.month-jump) { width: 44px; height: 44px; }
  .page:not(.beauty) .month-head .month-jump { min-height: 44px; }
  .page:not(.beauty) :where(label.field > span, .eyebrow, .masthead-say, .rail-compact dt, .month .dow, .step-say) {
    font-size: 12px;
  }
  /* The contact sheet's caption lives outside .page, so the rule above
     cannot reach it; only the wedding page draws the strip. */
  .strip-say { font-size: 12px; }
}
@media (max-width: 63.99rem) {
  .page:not(.beauty) .rail-card { box-shadow: 0 -8px 24px rgba(36, 28, 28, .16); }
}

.beauty-notes { display: block; margin-top: 1rem; }
.beauty-notes textarea { width: 100%; box-sizing: border-box; }

.beauty-foot { margin-top: 1.5rem; }
.beauty-send {
  font: inherit;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: #241C1C;
  background: var(--gold);
  border: 1px solid var(--gold);
  border-radius: 3px;
  padding: 0.85rem 1.6rem;
  cursor: pointer;
}
.beauty-send:disabled { opacity: 0.45; cursor: not-allowed; }
@media (max-width: 39.99rem) {
  .beauty-send { width: 100%; }
}

a.beauty-send { display: inline-block; text-decoration: none; text-align: center; }

/* ── Jackie's page in Vietnamese and Chinese ────────────────────────
   Karla and the Fraunces file above carry Latin letters only, so a
   Vietnamese word set in them borrows its accented letters one at a
   time from whatever the phone has, and "điểm" arrives in two
   typefaces. The gallery already solved this for the studio's
   Vietnamese clients, and these are its files: Fraunces's own
   Vietnamese letters, which join the face above by unicode-range and
   are only fetched by a page that uses one, and Be Vietnam Pro for body
   text, used whole, so that a word is never split between two designs.

   Chinese uses the phone's own fonts. A Chinese webfont is megabytes,
   and every phone that can read Chinese already has a good one.
   Fraunces and Karla stay first in those stacks so that "Jackie",
   "GST" and the prices keep the page's own letters. */
@font-face {
  font-family: 'Fraunces';
  src: url('/wedding/fonts/fraunces-vi.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
    U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Be Vietnam Pro';
  src: url('/wedding/fonts/bevietnampro-latin.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+2000-206F, U+2122, U+2212, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Be Vietnam Pro';
  src: url('/wedding/fonts/bevietnampro-vi.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
    U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

html[lang='vi'] { --body: 'Be Vietnam Pro', system-ui, -apple-system, sans-serif; }
html[lang='zh-Hans'] {
  --display: 'Fraunces', 'Songti SC', 'Noto Serif CJK SC', 'Source Han Serif SC', 'PingFang SC', serif;
  --body: 'Karla', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC', 'Source Han Sans SC', sans-serif;
}
html[lang='zh-Hant'] {
  --display: 'Fraunces', 'Songti TC', 'Noto Serif CJK TC', 'Source Han Serif TC', 'PingFang TC', serif;
  --body: 'Karla', 'PingFang TC', 'Microsoft JhengHei', 'Noto Sans CJK TC', 'Source Han Sans TC', sans-serif;
}

/* Chinese has no capitals to space out, and tracking set for a line of
   capitals pulls a line of characters apart until it stops reading as
   words. Italic is the same problem: a slanted character is a distorted
   one, not an emphasis. It is one rule over everything rather than an
   override per label because every label on the page was written for
   capitals, and missing one leaves a single spaced out line of Chinese.

   The labels set at 10 and 11px are sized for capitals, and a character
   needs more room than a capital does to stay legible. */
html[lang^='zh'] body * { letter-spacing: 0 !important; text-transform: none !important; }
html[lang^='zh'] .display em { font-style: normal; }

/* The lede is set in Fraunces italic, and there is no italic file with
   Vietnamese letters in it, so every accented letter in an italic
   Vietnamese sentence was drawn in Georgia instead. Upright Fraunces has
   them all. Chinese has no italic to lose, only a slanted copy. */
html:is([lang='vi'], [lang^='zh']) .lede { font-style: normal; }
html[lang^='zh'] :is(.masthead-say, .eyebrow, label.field > span, .rail-title, .rail-compact dt, .month .dow) {
  font-size: 13px;
}

/* Vietnamese capitals stack their tone marks, and at 10px the marks run
   into each other. */
html[lang='vi'] :is(.rail-title, .rail-compact dt, .month .dow) { font-size: 11px; }

/* "Chi tiết" is two words where "Details" is one, and the button folded
   it onto two lines, which made the bar along the bottom of a phone
   taller in Vietnamese than in English. */
.rail-more { white-space: nowrap; }

/* The languages the page can be read in, each written in itself and
   tagged with its own `lang`, so a phone shapes 简体中文 as Chinese and a
   screen reader says Tiếng Việt as Vietnamese on a page that is in
   English. Tall enough to tap: these are the first thing a client who
   cannot read the page needs to hit. */
.beauty-langs {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0 1.25rem;
  margin: .5rem 0 0;
}
.beauty-langs button {
  appearance: none;
  min-height: 44px;
  padding: 0;
  background: none;
  border: 0;
  border-bottom: 1px solid transparent;
  font: inherit;
  font-size: 14px;
  color: var(--ink-soft);
  cursor: pointer;
}
.beauty-langs button[aria-pressed='true'] { color: var(--ink); border-bottom-color: var(--gold); }
.beauty-langs [lang='vi'] { font-family: 'Be Vietnam Pro', system-ui, sans-serif; }
.beauty-langs [lang='zh-Hans'] { font-family: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC', sans-serif; }
.beauty-langs [lang='zh-Hant'] { font-family: 'PingFang TC', 'Microsoft JhengHei', 'Noto Sans CJK TC', sans-serif; }

/* ── A bride's own link ─────────────────────────────────────────────
   Whose wedding this is, said once under the headline on the raised
   ground with the rail's gold rule, so it reads as something the studio
   already knows rather than something she has to fill in. The couple's
   name is in the display face because it is the one line on the page
   written about her rather than to her. Gold ink on the raised ground is
   4.9:1, so the eyebrow stays legible at 11px. */
.beauty-wedding {
  margin-top: 1.5rem;
  padding: 1rem 1.25rem 1.1rem;
  max-width: 40rem;
  background: var(--raised);
  border-top: 1px solid var(--gold);
}
.beauty-wedding .eyebrow { margin: 0 0 .25rem; }
.beauty-wedding-name { margin: 0; font-family: var(--display); font-size: 1.6rem; line-height: 1.2; color: var(--ink); }
.beauty-wedding-day { margin: .1rem 0 .6rem; color: var(--ink-soft); }
.beauty-wedding p:last-child { margin-bottom: 0; color: var(--ink-soft); }
.beauty-wedding-gone { margin: 1.25rem 0 0; max-width: 40rem; color: var(--ink-soft); font-size: 14.5px; }
.beauty-included { margin: .1rem 0 .6rem; font-size: 13px; color: var(--gold-ink); }

/* ── The note, read back as points ──────────────────────────────────
   Under the box it came from, on the raised ground with the gold rule
   the wedding card uses, because it is the same gesture: the page saying
   back what it understood. Stale points stay on screen, dimmed, so a
   client who changes one word does not lose the list she was checking. */
.beauty-look { margin-top: .9rem; }
.beauty-look[data-state='ready'],
.beauty-look[data-state='stale'] {
  padding: .9rem 1.1rem 1rem;
  background: var(--raised);
  border-top: 1px solid var(--gold);
}
.beauty-look-title {
  margin: 0 0 .4rem;
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--gold-ink);
}
.beauty-look-points { margin: 0; padding-left: 1.1rem; color: var(--ink); }
.beauty-look-points li { margin: .2rem 0; }
.beauty-look[data-state='stale'] .beauty-look-points { color: var(--muted); }
.beauty-look-note { margin: .6rem 0 0; font-size: 13px; color: var(--ink-soft); }
.beauty-look-button {
  appearance: none;
  min-height: 44px;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  font-size: 14px;
  color: var(--gold-ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.beauty-look-working { margin: 0; font-size: 14px; color: var(--ink-soft); }
/* A thin gold line crossing under the words while the model answers: it
   moves, so the page reads as working, and it is a hairline, so nothing
   jumps when the points arrive. */
.beauty-look-working::after {
  content: '';
  display: block;
  height: 1px;
  margin-top: .5rem;
  background: linear-gradient(90deg, transparent, var(--gold), transparent) no-repeat;
  background-size: 40% 100%;
  animation: beauty-look-sweep 1.4s ease-in-out infinite;
}
@keyframes beauty-look-sweep {
  from { background-position: -40% 0; }
  to { background-position: 140% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .beauty-look-working::after { animation: none; background: var(--gold); opacity: .4; }
}
html[lang^='zh'] .beauty-look-title { font-size: 13px; }

.beauty-send-as { margin: 1rem 0 .75rem; }
.beauty-back { margin-top: 1.5rem; }
/* The way back to the studio's site under an estimate, tall enough to tap
   and set in the gold that reads at body size. */
.beauty-home { margin: 1rem 0 0; }
.beauty-home a {
  display: inline-block;
  min-height: 44px;
  padding: .7rem .5rem;
  box-sizing: border-box;
  color: var(--gold-ink);
  text-underline-offset: 3px;
}

/* ── A person's choices, in the sections of Jackie's list ────────────
   Makeup, hair and add ons, each labelled the way the rail labels its
   rows. The hair label says one style, because a second one swaps the
   first and a client should not have to find that out by tapping.
   Extensions wait for a hair style and look it. */
.beauty-section + .beauty-section { margin-top: .75rem; }
.beauty-section-label {
  margin: 0 0 .4rem;
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
}
html[lang^='zh'] .beauty-section-label { font-size: 13px; }
.beauty-chip:disabled { opacity: .45; cursor: default; }
.beauty-travel { margin-top: .6rem; }
/* The places Google offers for where a party gets ready: the wedding page's
   where rows, as buttons, because picking one is an action. */
.beauty-places { margin-top: .5rem; border-bottom: 1px solid var(--line); }
.beauty-place {
  display: block;
  width: 100%;
  text-align: left;
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  border-top: 1px solid var(--line);
  padding: .7rem 0;
  cursor: pointer;
}
.beauty-place:hover .where-name { color: var(--gold-ink); }
.beauty-place:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
/* Google's terms ask for its name beside anything of its shown without a map. */
.beauty-attribution { display: block; margin: .4rem 0 .5rem; font-size: 12px; color: var(--muted); }
.where-state .beauty-attribution { display: inline; margin: 0; }

/* Answering a meeting offer, meet.jsx.
 * Gold fills the button and marks the choice; small gold text uses
 * --gold-ink, and blush is not used as type. */
.meet-reply .mr-card { max-width: 36rem; margin: 0 auto; padding: 2.5rem 1.25rem 4rem; }
/* The page's own size: the wedding hero's display type is too loud for a short answer. */
.meet-reply .display { font-size: clamp(2.1rem, 5vw, 3rem); line-height: 1.08; }
.meet-reply .mr-mark { display: block; width: 56px; height: auto; margin-bottom: 1.75rem; }
.meet-reply .lede { color: var(--ink-soft); }
.mr-note { border-left: 2px solid var(--line); padding-left: .9rem; color: var(--ink-soft); margin: 1rem 0 0; }
.mr-options { display: grid; gap: .75rem; margin-top: 1.5rem; }
.mr-option { display: grid; gap: .2rem; text-align: left; padding: 1rem 1.1rem; background: var(--ground); border: 1px solid var(--line); border-radius: 3px; color: var(--ink); font: inherit; cursor: pointer; }
.mr-option:hover { border-color: var(--gold); }
.mr-option.is-on { border-color: var(--gold); background: var(--raised); box-shadow: inset 3px 0 0 var(--gold); }
.mr-option:focus-visible, .mr-chip:focus-visible, .mr-button:focus-visible, .mr-link:focus-visible { outline: 2px solid var(--gold-ink); outline-offset: 2px; }
.mr-when { font-weight: 600; }
.mr-where { color: var(--ink-soft); font-size: .95rem; }
.mr-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; margin-top: 1.5rem; }
.mr-button { background: var(--gold); color: #fff; border: 0; border-radius: 3px; padding: .85rem 1.4rem; font: inherit; font-weight: 600; cursor: pointer; }
.mr-button:disabled { opacity: .55; cursor: default; }
.mr-link { background: none; border: 0; padding: 0; font: inherit; color: var(--gold-ink); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.mr-link:disabled { opacity: .6; cursor: default; }
.mr-quiet { color: var(--muted); font-size: .9rem; margin-top: 1.1rem; }
.mr-error { color: var(--taken); margin-top: 1rem; }
.mr-label { margin: 1.4rem 0 .5rem; font-size: .8rem; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft); }
.mr-chips { display: flex; flex-wrap: wrap; gap: .5rem; }
.mr-chip { background: var(--ground); border: 1px solid var(--line); border-radius: 999px; padding: .5rem .9rem; font: inherit; color: var(--ink); cursor: pointer; }
.mr-chip.is-on { border-color: var(--gold); background: var(--raised); }
.mr-chip:disabled { opacity: .6; cursor: default; }
.mr-hours { color: var(--ink-soft); font-size: .85em; }
.mr-input { display: block; width: 100%; box-sizing: border-box; margin-top: .6rem; padding: .7rem .8rem; border: 1px solid var(--line); border-radius: 3px; font: inherit; color: var(--ink); background: var(--ground); }
.mr-input:focus { outline: 2px solid var(--gold-ink); outline-offset: 1px; }
/* The collection they are reading, named beside its price. A different
   weight from a total they have chosen, so the two can never be read as
   the same thing. */
.rail-reading span:first-child { color: var(--muted); }


/* Hidden only where `railShows` says so, and only on a phone: a desktop
   has a column for it and always has. The rule is in rail.js; this is
   only how it is drawn. */
@media (max-width: 63.99rem) {
  .rail[data-show='0'] { display: none; }
}

/* At the end of the page rather than in the bar. Centred, because it is
   no longer one item in a list of figures; it is its own invitation. */
.meet-door-end { margin: 2.2rem auto 0; text-align: center; max-width: 30rem; }
