/* Generated by build/build.py from build/css. Do not edit: edit the
   source files and rebuild. Order is the cascade and matters. */

/* ===== fonts.css ===== */
/* --- Self-hosted webfaces, subset -----------------------------------------
   GENERATED by build/fonts.py. Do not edit: re-run that script.

   Subset to printable ASCII plus a margin of punctuation and symbols, which
   is what takes Newsreader from Google's 129 KB latin file to the size below.
   Both families are variable fonts, so one file covers every weight of a
   family and style; the separate rules point at the same file on purpose and
   the browser fetches it once.

   If copy is ever added in Devanagari or any non-Latin script, the character
   set in build/fonts.py has to grow first or those glyphs fall back. */

@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('/assets/fonts/newsreader-normal.54db7f1e.woff2') format('woff2');
}

@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/newsreader-normal.54db7f1e.woff2') format('woff2');
}

@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/newsreader-italic.c1551dc1.woff2') format('woff2');
}

@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('/assets/fonts/jost-normal.c9fef63c.woff2') format('woff2');
}

@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/jost-normal.c9fef63c.woff2') format('woff2');
}

@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/assets/fonts/jost-normal.c9fef63c.woff2') format('woff2');
}

/* ===== reset.css ===== */
/* ==========================================================================
   RESET
   ========================================================================== */

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

* { margin: 0; }

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

body { min-height: 100vh; }

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

input,
button,
textarea,
select { font: inherit; color: inherit; }

button {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

a { color: inherit; text-decoration: none; }

ul,
ol { list-style: none; padding: 0; }

fieldset { border: 0; padding: 0; margin: 0; }

summary::-webkit-details-marker { display: none; }

/* ===== tokens.css ===== */
/* --- Fallback faces, metric-matched -------------------------------------
   The webfonts arrive over the network. Until they do the browser paints
   Georgia and Arial, whose line boxes are not Newsreader's and Jost's, so the
   hero heading reflowed when the real faces swapped in and moved everything
   under it. Measured at 100px: Newsreader's ascent/descent is 74/26 against
   Georgia's 92/22, and Jost's is 107/38 against Arial's 91/21.

   ascent-override and descent-override are proportions of the em, so they are
   exact whichever local font actually backs the fallback -- which matters,
   because an Android phone has neither Georgia nor Arial and substitutes Noto.
   size-adjust corrects glyph width and is tuned to the first local() that
   resolves; it only affects where lines wrap, not their height.

   Re-measure both if either family is ever changed. */
@font-face {
  font-family: "Newsreader Fallback";
  src: local("Georgia"), local("Times New Roman"), local("Noto Serif"), local("Liberation Serif");
  size-adjust: 101.28%;
  ascent-override: 74%;
  descent-override: 26%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "Jost Fallback";
  src: local("Arial"), local("Helvetica"), local("Roboto"), local("Liberation Sans");
  size-adjust: 96.66%;
  ascent-override: 107%;
  descent-override: 38%;
  line-gap-override: 0%;
}

/* ==========================================================================
   TOKENS
   Every value the design system uses lives here. Component CSS never
   hard-codes a colour, size or duration.
   ========================================================================== */

:root {

  /* --- Colour: the ink side ---------------------------------------------
     The page opens at night, inside the wedding. */
  --ink:            #12100E;
  --ink-raised:     #1B1815;
  --on-ink:         #F1EDE5;
  /* Secondary text. Raised from #A29A8F, which measured 6.83:1 against
     the page black. This is 8.6:1, still clearly below the primary
     #F1EDE5 at 14:1 so the hierarchy holds. */
  --on-ink-dim:     #B5ADA1;
  --hair-ink:       rgba(241, 237, 229, 0.16);

  /* --- Colour: the second surface ---------------------------------------
     One surface, not two. The page is ink from the hero to the footer, and
     sections are separated by the hairline along their top edge rather than
     by a change of tone.

     The paper tokens are kept rather than deleted because every section, the
     header's surface detection and the print room are written against them.
     Pointing them at the ink values is a one-line change to go back to two
     surfaces, and leaves nothing to rewire if that is ever wanted. */
  --paper:          #12100E;
  /* Still lifted off the page: this is the album frame in the print room,
     which has to read as an object sitting on the surface rather than a
     hole cut in it. */
  --paper-shade:    #1B1815;
  --on-paper:       #F1EDE5;
  --on-paper-dim:   #B5ADA1;
  --hair-paper:     rgba(241, 237, 229, 0.16);

  /* --- Accent -----------------------------------------------------------
     Marigold, because that is the colour of the flowers at every wedding
     this studio shoots. Never used for body copy.

     Three values, because one cannot pass AA in both directions. The base
     marigold reaches only 4.32:1 against black and 4.12:1 against white,
     so it sits in the dead zone where no text colour on it clears 4.5:1
     and it clears 4.5:1 on no dark surface either. So:

       --accent        rules, borders, icons, focus rings, underlines.
                       Decorative only, where 3:1 is the bar. Never text,
                       never behind text.
       --accent-text   accent-coloured type on either dark surface.
                       6.02:1 on ink, 5.59:1 on paper.
       --accent-fill   solid fill sitting behind a cream label.
                       4.67:1 against #FFF6EF.

     Change one and re-measure. They are not interchangeable. */
  --accent:         #C25A1E;
  --accent-text:    #D9773B;
  --accent-fill:    #B65217;
  --accent-lift:    #D9773B;

  /* --- Type -------------------------------------------------------------- */
  --font-display:   "Newsreader", "Newsreader Fallback", "Iowan Old Style", Georgia, serif;
  --font-body:      "Jost", "Jost Fallback", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --weight-display: 400;

  --fs-hero:        clamp(2.6rem, 6.6vw, 6.5rem);
  --fs-mega:        clamp(2.25rem, 5vw, 4.5rem);
  --fs-h2:          clamp(1.875rem, 4vw, 3.5rem);
  --fs-h3:          clamp(1.375rem, 2.4vw, 2rem);
  --fs-body:        clamp(1rem, 1.05vw, 1.0625rem);
  --fs-small:       0.8125rem;
  --fs-label:       0.6875rem;

  --lh-body:        1.75;
  --ls-label:       0.22em;

  /* --- Space and layout --------------------------------------------------- */
  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-5: 1.5rem;
  --s-6: 2rem;
  --s-7: 2.5rem;
  --s-8: 3rem;
  --s-9: 4rem;
  --s-10: 5rem;

  --section-y:      clamp(5rem, 11vw, 10rem);
  --gutter:         clamp(1.25rem, 4.5vw, 4.5rem);
  --container:      1240px;
  --container-wide: 1680px;
  --measure:        62ch;
  --header-h:       84px;   /* 72px below 900, at the end of this file */

  /* --- Motion ------------------------------------------------------------- */
  --ease:           cubic-bezier(0.16, 1, 0.3, 1);
  --ease-io:        cubic-bezier(0.65, 0, 0.35, 1);
  --dur-hover:      400ms;
  --dur-reveal:     700ms;

  /* --- Photography ---------------------------------------------------------
     One grade for every photograph on the site. Tuned once, applied
     everywhere, which is what makes a mixed set read as one body of work. */
  --grade:          saturate(0.92) contrast(1.05) brightness(0.95);

  /* --- Layers -------------------------------------------------------------- */
  --z-sticky:  40;
  --z-drawer:  90;
  --z-header:  100;
  --z-bar:     150;
  --z-skip:    300;
}

/* The header is shorter on a phone. This lives here, with the token it
   changes, rather than in pages.css: the stylesheets load asynchronously and
   an override in a later file applies later than the value it overrides,
   which showed up as the header rendering 84px tall on a phone and then
   snapping to 72. */
@media (max-width: 900px) {
  :root { --header-h: 72px; }
}

/* ===== base.css ===== */
/* ==========================================================================
   BASE
   Element defaults only. Everything with a class lives in components.css
   or sections.css.
   ========================================================================== */

/* clip rather than hidden: it stops a stray transform from ever producing a
   sideways scrollbar, without turning the element into a scroll container,
   which would break every position: sticky on the page. */
html { overflow-x: clip; }

body {
  overflow-x: clip;
  background: var(--ink);
  color: var(--on-ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: 300;
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  line-height: 1;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

h1 { font-size: var(--fs-hero); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }

p { text-wrap: pretty; }

/* Focus is never removed, only replaced. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

/* Anchored headings clear the fixed header when linked to directly. */
[id] { scroll-margin-top: calc(var(--header-h) + 2rem); }

/* ===== components.css ===== */
/* ==========================================================================
   COMPONENTS
   Reusable blocks. Nothing here knows which page it is on.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Skip link
   -------------------------------------------------------------------------- */
/* The scroll sentinel the header watches to know it has left the top. It is
   an empty span, and an empty inline element still builds a line box, so
   without this it added a text-line of height to the top of every page until
   the stylesheets landed. It carries no appearance, only removal from flow. */
[data-top-sentinel] {
  position: absolute;
  top: 0;
  width: 1px;
  height: 1px;
}

.skip-link {
  position: absolute;
  top: var(--s-4);
  left: var(--s-4);
  z-index: var(--z-skip);
  padding: var(--s-3) var(--s-5);
  background: var(--accent-fill);
  color: #FFF6EF;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  transform: translateY(-250%);
  transition: transform var(--dur-hover) var(--ease);
}

.skip-link:focus { transform: none; }

/* --------------------------------------------------------------------------
   Eyebrow
   -------------------------------------------------------------------------- */
.eyebrow {
  font-family: var(--font-body);
  font-size: var(--fs-label);
  font-weight: 400;
  letter-spacing: var(--ls-label);
  line-height: 1;
  text-transform: uppercase;
}

.eyebrow--accent { color: var(--accent-text); }

.eyebrow--ruled {
  display: flex;
  align-items: center;
  gap: var(--s-3);
}

.eyebrow--ruled::before {
  content: "";
  display: block;
  width: 46px;
  height: 1px;
  background: var(--accent);
  flex: none;
}

/* --------------------------------------------------------------------------
   Section head
   -------------------------------------------------------------------------- */
.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-6);
}

.section-head__aside {
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--on-paper-dim);
  white-space: nowrap;
}

.section-lead {
  max-width: var(--measure);
  margin-top: var(--s-5);
  color: var(--on-paper-dim);
}

.section-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  margin-top: var(--s-7);
  color: var(--on-paper-dim);
  font-size: var(--fs-small);
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */
.btn {
  --btn-fill: var(--on-ink);
  --btn-fill-fg: var(--ink);
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  padding: 1.1rem 2rem;
  isolation: isolate;
  overflow: hidden;
  background: var(--accent-fill);
  color: #FFF6EF;
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  white-space: nowrap;
  transition: color var(--dur-hover) var(--ease);
}

/* The fill inverts against whatever the button sits on, or it goes
   near-black on near-black and disappears exactly when touched. */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--btn-fill);
  transform: scaleY(0);
  transform-origin: bottom center;
  transition: transform var(--dur-hover) var(--ease);
}

.btn:hover,
.btn:focus-visible { color: var(--btn-fill-fg); }

.btn:hover::before,
.btn:focus-visible::before { transform: scaleY(1); }

.btn__arrow {
  width: 15px;
  height: 15px;
  transition: transform var(--dur-hover) var(--ease);
}

.btn:hover .btn__arrow { transform: translateX(5px); }

/* Kept as an explicit modifier so a future light surface can override it. */
.btn--on-ink { --btn-fill: var(--on-ink); --btn-fill-fg: var(--ink); }

/* The action that closes a section. An outline rather than a filled button,
   so it never competes with the enquiry form. */
.btn-outline {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  padding: 0.85rem 1.5rem;
  border: 1px solid var(--accent);
  color: var(--accent-lift);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  white-space: nowrap;
  transition: background-color var(--dur-hover) var(--ease),
              color var(--dur-hover) var(--ease);
}

.btn-outline:hover,
.btn-outline:focus-visible { background: var(--accent-fill); color: #FFF6EF; }

.btn-outline__arrow {
  width: 14px;
  height: 14px;
  transition: transform var(--dur-hover) var(--ease);
}

.btn-outline:hover .btn-outline__arrow { transform: translateX(4px); }

/* Secondary: text with a rule that draws back on hover. */
.btn-quiet {
  position: relative;
  display: inline-block;
  padding-bottom: 0.45rem;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
}

.btn-quiet::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  opacity: 0.4;
  transform-origin: right center;
  transition: transform var(--dur-hover) var(--ease), opacity var(--dur-hover) var(--ease);
}

.btn-quiet:hover::after,
.btn-quiet:focus-visible::after { opacity: 1; transform: scaleX(0.35); }

/* --------------------------------------------------------------------------
   Inline link. A link inside running copy has to be obviously a link, so it
   takes the accent, a heavier weight and a rule that is already there rather
   than one that appears on hover.
   -------------------------------------------------------------------------- */
.link {
  color: var(--accent-lift);
  font-weight: 500;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.22em;
  text-decoration-color: rgba(217, 119, 59, 0.45);
  transition: color var(--dur-hover) var(--ease), text-decoration-color var(--dur-hover) var(--ease);
}

.link:hover,
.link:focus-visible {
  color: var(--accent-lift);
  text-decoration-color: currentColor;
}

/* --------------------------------------------------------------------------
   Photograph frame. The frame holds still, only the image moves.
   -------------------------------------------------------------------------- */
.frame {
  position: relative;
  overflow: hidden;
  background: var(--paper-shade);
}

.frame__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: var(--grade);
}

.frame--4x5 { aspect-ratio: 4 / 5; }
.frame--4x3 { aspect-ratio: 4 / 3; }
.frame--5x7 { aspect-ratio: 5 / 7; }

.frame--zoom .frame__img { transition: transform 900ms var(--ease); }
.frame--zoom:hover .frame__img { transform: scale(1.04); }

/* --------------------------------------------------------------------------
   Badge
   -------------------------------------------------------------------------- */
.note {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) 0 var(--s-2) var(--s-4);
  border-left: 2px solid var(--accent);
  color: var(--on-paper);
  font-size: var(--fs-body);
}

.note__icon {
  width: 18px;
  height: 18px;
  color: var(--accent);
  flex: none;
}

/* --------------------------------------------------------------------------
   Form field. Underline only, floating label, inline error.
   -------------------------------------------------------------------------- */
.field {
  position: relative;
  padding-top: 1.5rem;
  margin-bottom: 2.25rem;
}

.field__input {
  width: 100%;
  padding: 0.55rem 0 0.7rem;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--hair-paper);
  color: var(--on-paper);
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 300;
  transition: border-color var(--dur-hover) var(--ease);
}

textarea.field__input { resize: vertical; min-height: 3.5rem; }

.field__label {
  position: absolute;
  left: 0;
  top: 1.9rem;
  color: var(--on-paper-dim);
  font-size: 1rem;
  transform-origin: left top;
  transition: transform var(--dur-hover) var(--ease), color var(--dur-hover) var(--ease);
  pointer-events: none;
}

.field__input:focus { outline: none; border-bottom-color: var(--accent); }

.field__input:focus + .field__label,
.field__input:not(:placeholder-shown) + .field__label {
  transform: translateY(-1.75rem) scale(0.72);
  color: var(--accent-text);
}

.field__error {
  display: block;
  margin-top: var(--s-2);
  min-height: 1rem;
  color: var(--accent-text);
  font-size: var(--fs-small);
  opacity: 0;
  transition: opacity 300ms var(--ease);
}

.field--invalid .field__input { border-bottom-color: var(--accent); }
.field--invalid .field__error { opacity: 1; }

/* Choice pills, in place of a select. */
.picks { margin-bottom: 2.5rem; }

.picks__legend {
  display: block;
  margin-bottom: var(--s-4);
  color: var(--on-paper-dim);
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
}

.picks__set { display: flex; flex-wrap: wrap; gap: 0.6rem; }

.pick { position: relative; }

.pick__input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}

.pick__label {
  display: block;
  padding: 0.6rem 1.1rem;
  border: 1px solid var(--hair-paper);
  color: var(--on-paper-dim);
  font-size: var(--fs-small);
  transition: border-color var(--dur-hover) var(--ease),
              color var(--dur-hover) var(--ease),
              background-color var(--dur-hover) var(--ease);
}

.pick__input:hover + .pick__label { border-color: var(--on-paper-dim); color: var(--on-paper); }
.pick__input:checked + .pick__label { border-color: var(--accent-fill); background: var(--accent-fill); color: #FFF6EF; }
.pick__input:focus-visible + .pick__label { outline: 2px solid var(--accent); outline-offset: 3px; }

/* --------------------------------------------------------------------------
   Scroll progress
   -------------------------------------------------------------------------- */
.progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  z-index: var(--z-bar);
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   Entrance reveal. Fired once by IntersectionObserver.
   -------------------------------------------------------------------------- */
.reveal {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 900ms var(--ease), transform 900ms var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}

.reveal--in { opacity: 1; transform: none; }

.no-js .reveal { opacity: 1; transform: none; }

/* --------------------------------------------------------------------------
   Breadcrumb
   Sits in the hero on inner pages. The last item is text, not a link,
   because a link to the page you are on is a dead control.
   -------------------------------------------------------------------------- */
.crumbs { margin-bottom: var(--s-6); }

.crumbs__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--on-ink-dim);
}

.crumbs__item + .crumbs__item::before {
  content: "";
  display: inline-block;
  width: 14px;
  height: 1px;
  margin-right: var(--s-3);
  vertical-align: middle;
  background: var(--hair-ink);
}

.crumbs__item a { transition: color var(--dur-hover) var(--ease); }
.crumbs__item a:hover { color: var(--on-ink); }
.crumbs__item[aria-current] { color: var(--accent-text); }

/* --------------------------------------------------------------------------
   Section CTA
   Every section on a money page ends with one. It is a row rather than a
   block so the button never stretches to the column width.
   -------------------------------------------------------------------------- */
.section-cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  margin-top: var(--s-7);
}

/* --------------------------------------------------------------------------
   Definition rows
   Three near-identical shapes, kept apart because their proportions
   differ: compare is two even columns, specs is a narrow key against a
   wide value, moves runs the key inline with the sentence.
   -------------------------------------------------------------------------- */
.compare {
  margin-top: var(--s-6);
  border-top: 1px solid var(--hair-paper);
}

.compare__row {
  display: grid;
  grid-template-columns: 10rem 1fr;
  gap: var(--s-5);
  padding-block: var(--s-5);
  border-bottom: 1px solid var(--hair-paper);
}

.compare__term {
  font-family: var(--font-display);
  font-size: 1.125rem;
  letter-spacing: -0.01em;
  color: var(--accent-text);
}

.compare__def { color: var(--on-paper-dim); font-size: var(--fs-small); line-height: 1.7; }

.specs { margin-top: var(--s-6); }

.specs__item {
  display: grid;
  grid-template-columns: 11rem 1fr;
  gap: var(--s-5);
  padding-block: var(--s-4);
  border-top: 1px solid var(--hair-paper);
  font-size: var(--fs-small);
}
.specs__item:last-child { border-bottom: 1px solid var(--hair-paper); }

.specs__k { color: var(--accent-text); letter-spacing: 0.04em; }
.specs__v { color: var(--on-paper-dim); line-height: 1.6; }

.moves { margin-top: var(--s-6); }

.moves__item {
  padding-block: var(--s-4);
  border-top: 1px solid var(--hair-ink);
  color: var(--on-ink-dim);
  font-size: var(--fs-small);
  line-height: 1.7;
}
.moves__item:last-child { border-bottom: 1px solid var(--hair-ink); }

.moves__k {
  display: inline-block;
  min-width: 7rem;
  margin-right: var(--s-3);
  color: var(--accent-text);
  letter-spacing: 0.04em;
}

/* --------------------------------------------------------------------------
   Named places
   A list where the place name has to be scannable on its own, because
   people look for the one town they recognise before reading the line.
   -------------------------------------------------------------------------- */
.places { margin-block: var(--s-5) var(--s-6); }

.places__item {
  position: relative;
  padding-left: var(--s-6);
  padding-block: var(--s-2);
  color: var(--on-paper-dim);
  font-size: var(--fs-small);
  line-height: 1.7;
}

.places__item::before {
  content: "";
  position: absolute;
  top: 1.05em;
  left: 0;
  width: 12px;
  height: 1px;
  background: var(--accent);
}

.places__name { color: var(--on-paper); }

/* --------------------------------------------------------------------------
   Emphasis
   A paragraph that has to be read before the ones under it, without
   becoming a heading and breaking the outline.
   -------------------------------------------------------------------------- */
.lede-strong {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--weight-display);
  line-height: 1.3;
  letter-spacing: -0.015em;
  margin-bottom: var(--s-5);
}

/* A note with no box around it. The bordered version reads as a button,
   which is the mistake this variant exists to avoid. */
.note--bare {
  border: 0;
  border-left: 2px solid var(--accent);
  border-radius: 0;
  padding: var(--s-2) 0 var(--s-2) var(--s-5);
  background: none;
}

/* ===== sections.css ===== */
/* ==========================================================================
   SECTIONS
   ========================================================================== */

/* --------------------------------------------------------------------------
   Header. Colour follows whatever surface is under it.
   -------------------------------------------------------------------------- */
/* The bar is exactly --header-h tall and centres whatever is in it, rather
   than being padding plus whatever its contents happen to measure. Every
   sticky offset and every scroll-margin-top on the site is derived from that
   token, so the two drifting apart puts anchored headings under the header.
   Sizing from the token means changing the brand lockup cannot cause it. */
.header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: var(--z-header);
  display: flex;
  align-items: center;
  min-height: var(--header-h);
  color: var(--on-ink);
  transition: color 600ms var(--ease), background-color var(--dur-hover) var(--ease);
}

/* Both surfaces are dark, so the type does not invert. The class stays
   because the bar still has to match whichever dark it is sitting on. */
.header--paper { color: var(--on-paper); }

/* Opaque rather than blurred. A backdrop filter on a fixed bar sitting over
   scrolling content is one of the most reliable ways to make a page stutter. */
.header--stuck { background: rgba(18, 16, 14, 0.97); }
.header--stuck.header--paper { background: rgba(18, 16, 14, 0.97); }

.header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-6);
}

.header__nav { display: flex; align-items: center; gap: clamp(1.1rem, 2vw, 2.25rem); }

.header__link {
  position: relative;
  font-size: 0.9375rem;
  letter-spacing: 0.01em;
  opacity: 0.72;
  transition: opacity var(--dur-hover) var(--ease);
}

.header__link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -6px;
  width: 100%;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur-hover) var(--ease);
}

.header__link:hover,
.header__link:focus-visible { opacity: 1; }

.header__link:hover::after,
.header__link:focus-visible::after,
.header__link[aria-current="page"]::after { transform: scaleX(1); }

.header__link[aria-current="page"] { opacity: 1; }

.header__call { display: inline-flex; align-items: center; gap: var(--s-2); }

/* The nav's one control. An outline rather than a fill, so it reads as the
   next step without shouting over the hero. */
.header__cta {
  display: inline-flex;
  align-items: center;
  padding: 0.6rem 1.2rem;
  border: 1px solid var(--accent);
  color: var(--accent-lift);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  white-space: nowrap;
  transition: background-color var(--dur-hover) var(--ease), color var(--dur-hover) var(--ease);
}

.header__cta:hover,
.header__cta:focus-visible { background: var(--accent-fill); color: #FFF6EF; }
/* Inherits the button's colour, so it flips to the light fill on hover
   instead of staying marigold on a marigold background and vanishing. */
.header__call-icon { width: 14px; height: 14px; }

.header__burger { display: none; width: 40px; height: 40px; }

/* Below 900 the links move into the drawer. Beside the base rules on purpose,
   not in pages.css: see the note on --header-h in tokens.css. With the
   override in the last stylesheet the full nav painted first and the header
   was 287px tall on a phone until it arrived. */
@media (max-width: 900px) {
  .header__nav { display: none; }
  .header__burger { display: block; }
}

.header__burger-bar {
  display: block;
  width: 24px;
  height: 1px;
  margin: 6px auto;
  background: currentColor;
  transition: transform var(--dur-hover) var(--ease), opacity 300ms var(--ease);
}

.header__burger[aria-expanded="true"] .header__burger-bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.header__burger[aria-expanded="true"] .header__burger-bar:nth-child(2) { opacity: 0; }
.header__burger[aria-expanded="true"] .header__burger-bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* --------------------------------------------------------------------------
   Brand lockup
   One component, used at two sizes: the header takes --sm, the footer the
   full size. Replaced wholesale by the client logo as an img with alt text
   when that file exists; it is never a heading.

   Three things stop it reading as a default serif wordmark. The mark is two
   tone, marigold frame against a lens in the text colour, so it is a mark
   rather than an icon. The name mixes the italic and roman of one family,
   which is a masthead device and costs no extra font file since both faces
   are already loaded. And the qualifier sits under the name behind a
   hairline rather than beside it on the baseline, which turns two words
   into a stacked lockup with a left edge.
   -------------------------------------------------------------------------- */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.8rem;
  line-height: 1;
}

/* The studio's logo, a raster. It was a drawn SVG that took its colours from
   --accent and so followed the palette; a PNG cannot, so the gold is now
   fixed. That is fine on this site because there is one surface and it is
   near black, but it is the thing to check first if the palette ever moves. */
.brand__mark {
  width: 42px;
  height: 42px;
  flex: none;
  transition: transform 600ms var(--ease);
}

.brand__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.4rem;
}

/* The body face, not the display serif. Capitals at a weight and a tracking
   the rest of the page never uses, which is what separates a wordmark from a
   heading that happens to sit in the corner. It also adds no font request:
   Jost 500 is already loaded for the buttons. */
.brand__name {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 1.05rem;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  white-space: nowrap;
  /* Tracking is applied to the right of every glyph including the last, so
     without this the lockup carries a quarter-em of dead space on its end
     and stops looking optically centred against the mark. */
  margin-right: -0.26em;
}

/* Second line, lighter and marigold, so the lockup has two weights and two
   colours in it rather than one of each. --accent-text rather than --accent:
   the base marigold reaches only 4.32:1 on this background and would fail AA
   at this size. See the accent block in tokens.css. */
.brand__sub {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 0.625rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--accent-text);
  margin-right: -0.24em;
}

/* Held back so it separates the two words without becoming a third one. It
   is aria-hidden, so this is a decorative mark rather than text and the
   contrast floor does not apply to it. */
.brand__pipe {
  opacity: 0.45;
  margin-inline: 0.1em;
}

/* Transform only, so the hover cannot repaint the mark every frame. */
.brand:hover .brand__mark,
.brand:focus-visible .brand__mark { transform: rotate(-4deg); }

/* Header size. Small enough that the bar keeps its height, which the
   sticky offsets and every scroll-margin-top depend on. */
.brand--sm { gap: 0.62rem; }
.brand--sm .brand__mark { width: 31px; height: 31px; }
.brand--sm .brand__text { gap: 0.3rem; }
.brand--sm .brand__name {
  font-size: clamp(0.78rem, 1vw, 0.9rem);
  letter-spacing: 0.2em;
  margin-right: -0.2em;
}
.brand--sm .brand__sub {
  font-size: 0.5rem;
  letter-spacing: 0.2em;
  margin-right: -0.2em;
}

/* --------------------------------------------------------------------------
   Mobile drawer
   -------------------------------------------------------------------------- */
.drawer {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 6rem var(--gutter) 3rem;
  background: var(--ink);
  opacity: 0;
  visibility: hidden;
  transition: opacity 500ms var(--ease), visibility 0s linear 500ms;
}

.drawer--open { opacity: 1; visibility: visible; transition-delay: 0s; }

.drawer__list { display: grid; gap: var(--s-4); }

.drawer__link {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: clamp(1.6rem, 7vw, 2.5rem);
  line-height: 1.05;
  color: var(--on-ink);
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 600ms var(--ease), transform 600ms var(--ease);
}

.drawer--open .drawer__link {
  opacity: 1;
  transform: none;
  transition-delay: calc(var(--i) * 60ms);
}

.drawer__foot {
  margin-top: var(--s-8);
  padding-top: var(--s-5);
  border-top: 1px solid var(--hair-ink);
  color: var(--on-ink-dim);
  font-size: var(--fs-small);
}

.drawer__foot a { color: var(--on-ink); }

/* --------------------------------------------------------------------------
   Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: 100dvh;
  display: flex;
  align-items: flex-end;
  padding-block: 8rem clamp(3rem, 8vh, 6rem);
  overflow: hidden;
  isolation: isolate;
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
  overflow: hidden;
  clip-path: inset(14% 16%);
  animation: hero-shutter 1.5s var(--ease-io) 0.15s forwards;
}

@keyframes hero-shutter { to { clip-path: inset(0%); } }

.hero__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 64% 46%;
  filter: var(--grade);
  transform: scale(1.14);
  animation: hero-settle 2.6s var(--ease) 0.15s forwards;
}

@keyframes hero-settle { to { transform: scale(1); } }

.hero__veil {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(96deg,
      rgba(18, 16, 14, 0.94) 0%,
      rgba(18, 16, 14, 0.84) 34%,
      rgba(18, 16, 14, 0.36) 66%,
      rgba(18, 16, 14, 0.10) 100%),
    linear-gradient(to top,
      rgba(18, 16, 14, 0.92) 0%,
      rgba(18, 16, 14, 0.18) 40%,
      rgba(18, 16, 14, 0.44) 100%);
}

/* About, Contact and Privacy do not get the full-viewport hero. On Contact
   in particular it would push the enquiry form under the fold, and the
   strategy file puts that form above the fold or immediately after the
   first section. */
.hero--short { min-height: 62svh; }

.hero__body { max-width: 62rem; }

.hero__eyebrow {
  color: var(--on-ink-dim);
  opacity: 0;
  animation: hero-rise 900ms var(--ease) 0.7s forwards;
}

.hero__title { margin-top: var(--s-5); line-height: 0.98; }

.hero__line {
  display: block;
  overflow: hidden;
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}

.hero__line-inner {
  display: block;
  transform: translateY(110%);
  animation: hero-lift 1.1s var(--ease) forwards;
}

.hero__line:nth-child(1) .hero__line-inner { animation-delay: 0.78s; }
.hero__line:nth-child(2) .hero__line-inner { animation-delay: 0.88s; }

@keyframes hero-lift { to { transform: translateY(0); } }
@keyframes hero-rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

.hero__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-7);
  margin-top: clamp(2rem, 4vw, 3.5rem);
  padding-top: clamp(1.5rem, 3vw, 2.5rem);
  border-top: 1px solid var(--hair-ink);
  opacity: 0;
  animation: hero-rise 1s var(--ease) 1.25s forwards;
}

.hero__lede { max-width: 42ch; color: var(--on-ink-dim); }
.hero__actions { display: flex; align-items: center; gap: var(--s-6); }

/* --------------------------------------------------------------------------
   The print room. Ink becomes paper, the photograph becomes a print, and
   the album it belongs to opens in the same place.
   -------------------------------------------------------------------------- */
.printroom { position: relative; height: 480vh; }

.printroom__stage {
  position: sticky;
  top: 0;
  height: 100dvh;
  overflow: hidden;
  isolation: isolate;
  --album-w: min(56vw, 47rem);
  --album-x: 60%;
  --album-y: 50%;
}

/* Rounded to an even number so that centring it lands on whole pixels. A
   layer sitting on a half pixel is resampled, and every photograph inside
   it goes soft. */
@supports (width: round(down, 10px, 2px)) {
  .printroom__stage { --album-w: round(down, min(56vw, 47rem), 2px); }
}

.printroom__paper { position: absolute; inset: 0; z-index: -3; background: var(--paper); }

/* Marks the point where the header has to switch from light type to dark. */
.printroom__zone { position: absolute; left: 0; top: 10%; bottom: 0; width: 1px; pointer-events: none; }

.printroom__copy {
  position: absolute;
  left: var(--gutter);
  top: 50%;
  translate: 0 -50%;
  max-width: 22rem;
  color: var(--on-paper);
}

.printroom__line { margin-top: var(--s-4); font-family: var(--font-display); font-size: var(--fs-h3); line-height: 1.05; letter-spacing: -0.02em; }
.printroom__note { margin-top: var(--s-4); color: var(--on-paper-dim); font-size: var(--fs-small); line-height: 1.8; }

.printroom__side {
  position: absolute;
  right: var(--gutter);
  bottom: calc(clamp(2.5rem, 7vh, 5rem) + 2.25rem);
  color: var(--on-paper-dim);
  text-align: right;
}

.printroom__count {
  position: absolute;
  right: var(--gutter);
  bottom: clamp(2.5rem, 7vh, 5rem);
  color: var(--on-paper-dim);
  font-variant-numeric: tabular-nums;
  opacity: 0;
}

/* The opening frame. A single plain image the exact size and shape of the
   album's right hand page, so scaling it up to fill the screen and back
   down is pixel identical to the page underneath it. The zoom happens here
   and never on the album, because scaling a perspective root forces its
   whole 3D subtree to re-rasterise on every frame. */
/* Described by its top left corner and its exact size, not by a centre and
   a percentage translate. The album's right hand page starts at --album-x
   and the album is --album-w by --album-w * 5/8, so this box is that page
   by construction. With no CSS translate in play, an identity transform
   puts the frame exactly on the page at every viewport, and the script only
   ever has to animate x, y and scale back to zero. */
.printroom__open {
  position: absolute;
  z-index: -1;
  top: calc(var(--album-y) - var(--album-w) * 5 / 16);
  left: var(--album-x);
  width: calc(var(--album-w) / 2);
  height: calc(var(--album-w) * 5 / 8);
  overflow: hidden;
  background: var(--paper-shade);
  transform-origin: 50% 50%;
  will-change: transform;
}

.printroom__open-img { width: 100%; height: 100%; object-fit: cover; filter: var(--grade); }

/* --------------------------------------------------------------------------
   The album
   -------------------------------------------------------------------------- */
.album {
  position: absolute;
  top: var(--album-y);
  left: var(--album-x);
  z-index: -2;
  display: flex;
  width: var(--album-w);
  aspect-ratio: 8 / 5;
  translate: -50% -50%;
  perspective: 2600px;
}

.album__page,
.album__stack { position: relative; width: 50%; height: 100%; }

.album__page { overflow: hidden; background: var(--paper-shade); }

.album__img { width: 100%; height: 100%; object-fit: cover; filter: var(--grade); }

.album__stack { transform-style: preserve-3d; }

.album__base { position: absolute; inset: 0; z-index: 0; overflow: hidden; background: var(--paper-shade); }

.album__leaf {
  position: absolute;
  inset: 0;
  transform-origin: left center;
  transform-style: preserve-3d;
}

.album__face {
  position: absolute;
  inset: 0;
  overflow: hidden;
  backface-visibility: hidden;
  background: var(--paper-shade);
}

.album__face--back { transform: rotateY(180deg); }

/* Rides the turn: strongest when the leaf is edge on. */
.album__shade {
  position: absolute;
  inset: 0;
  background: linear-gradient(to right, rgba(23, 20, 15, 0.55), rgba(23, 20, 15, 0) 55%);
  opacity: 0;
  pointer-events: none;
}

/* Both appear only once the album has settled at its own size. */
.album__gutter {
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 54px;
  z-index: 30;
  transform: translateX(-50%);
  opacity: 0;
  pointer-events: none;
  background: linear-gradient(to right,
    rgba(9, 8, 7, 0) 0%,
    rgba(9, 8, 7, 0.55) 48%,
    rgba(9, 8, 7, 0.55) 52%,
    rgba(9, 8, 7, 0) 100%);
}

.album__mount {
  position: absolute;
  inset: -18px;
  border: 1px solid rgba(241, 237, 229, 0.22);
  opacity: 0;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   Services. The list is the layout, with one anchored frame beside it.
   -------------------------------------------------------------------------- */
/* Every other section carries a rule along its top edge. This one is the
   first after the print room, so without it the album simply runs into the
   list with no seam. */
.services { padding-block: var(--section-y); border-top: 1px solid var(--hair-paper); }

.services__grid {
  display: grid;
  grid-template-columns: 5fr 7fr;
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
  margin-top: clamp(2.5rem, 5vw, 4rem);
}

.services__viewer { position: sticky; top: calc(var(--header-h) + 4vh); }

.services__stack {
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--paper-shade);
}

/* Frames are laid down over one another rather than crossfaded. The wipe
   is driven from script so the incoming frame is masked and released in
   the same frame; toggling a class blanks the panel for a beat first. */
.services__frame {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  transition: opacity 600ms var(--ease);
}

.services__frame:first-child,
.services__frame--on { opacity: 1; }

.services__stack--scripted .services__frame { transition: none; }

.services__frame-img { width: 100%; height: 100%; object-fit: cover; filter: var(--grade); }

.services__caption {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-4);
  color: var(--on-paper-dim);
}

.services__caption::before {
  content: "";
  display: block;
  width: 18px;
  height: 1px;
  background: var(--accent);
}

.services__list { border-top: 1px solid var(--hair-paper); }

.services__row {
  position: relative;
  display: grid;
  grid-template-columns: 3rem 1fr auto;
  align-items: baseline;
  gap: var(--s-2) var(--s-5);
  padding-block: clamp(0.9rem, 1.6vw, 1.4rem);
  border-bottom: 1px solid var(--hair-paper);
}

.services__num {
  font-size: var(--fs-label);
  letter-spacing: 0.12em;
  color: var(--on-paper-dim);
}

.services__name {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: clamp(1.25rem, 2.2vw, 1.875rem);
  line-height: 1.15;
  letter-spacing: -0.02em;
  transition: transform 600ms var(--ease);
}

.services__link::after { content: ""; position: absolute; inset: 0; }

.services__meta {
  grid-column: 2 / -1;
  max-width: 52ch;
  color: var(--on-paper-dim);
  font-size: var(--fs-small);
}

.services__go {
  grid-column: 3;
  grid-row: 1;
  align-self: center;
  width: 20px;
  height: 20px;
  color: var(--accent);
  opacity: 0;
  transform: translateX(-8px);
  transition: opacity var(--dur-hover) var(--ease), transform var(--dur-hover) var(--ease);
}

.services__row--active .services__num { color: var(--accent-text); }
.services__row--active .services__name { transform: translateX(10px); }
.services__row--active .services__go { opacity: 1; transform: none; }

.services__thumb {
  display: none;
  grid-column: 3;
  grid-row: 1 / span 2;
  width: 64px;
  aspect-ratio: 4 / 5;
  overflow: hidden;
}

.services__thumb-img { width: 100%; height: 100%; object-fit: cover; filter: var(--grade); }

/* --------------------------------------------------------------------------
   Enquiry
   -------------------------------------------------------------------------- */
.enquiry { padding-block: var(--section-y); border-top: 1px solid var(--hair-paper); }

/* Recessed into the page rather than flush with it, with the accent along
   the top edge. On a page this long the form has to announce itself. */
.enquiry__panel {
  position: relative;
  background: var(--ink);
  border: 1px solid var(--hair-paper);
  padding: clamp(1.75rem, 4vw, 3.5rem);
}

.enquiry__panel::before {
  content: "";
  position: absolute;
  left: -1px; right: -1px; top: -1px;
  height: 3px;
  background: var(--accent);
}

.enquiry__grid {
  display: grid;
  grid-template-columns: 5fr 7fr;
  gap: clamp(2.5rem, 6vw, 6rem);
  align-items: start;
}

.enquiry__title {
  margin-top: var(--s-4);
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  /* Smaller than a full width statement: the panel's padding takes a chunk
     out of this column, so display size has to come down with it. */
  font-size: clamp(1.75rem, 3vw, 2.75rem);
  line-height: 1.08;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
.enquiry__title em { font-style: italic; color: var(--accent-text); }
.enquiry__copy { max-width: 36ch; margin-top: var(--s-5); color: var(--on-paper-dim); }

.enquiry__direct {
  margin-top: var(--s-7);
  padding-top: var(--s-5);
  border-top: 1px solid var(--hair-paper);
  /* The phone number, the WhatsApp line and the address. For anyone who
     would rather call than fill in a form, this block is the whole point of
     the section, so it is not caption-sized. */
  font-size: 1rem;
}

.enquiry__direct-line { line-height: 1.7; }
.enquiry__direct-line + .enquiry__direct-line { margin-top: var(--s-3); }

.form__row { display: grid; grid-template-columns: 1fr 1fr; gap: 2.5rem; }
.form__foot { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-6); }
.form__note { color: var(--on-paper-dim); font-size: var(--fs-small); max-width: 34ch; }

.form__sent { display: none; padding: var(--s-6) 0; border-top: 1px solid var(--hair-paper); }
.form--sent .form__body { display: none; }
.form--sent .form__sent { display: block; }
.form__sent-note { margin-top: var(--s-3); color: var(--on-paper-dim); font-size: var(--fs-small); }

/* --------------------------------------------------------------------------
   Why families choose us
   -------------------------------------------------------------------------- */
.why { padding-block: var(--section-y); border-top: 1px solid var(--hair-paper); }

.why__grid {
  display: grid;
  grid-template-columns: 5fr 7fr;
  gap: clamp(2.5rem, 6vw, 6rem);
  align-items: start;
}

.why__media { display: grid; grid-template-columns: repeat(6, 1fr); gap: var(--s-5); }
.why__main { grid-column: 1 / 6; }
.why__sub { grid-column: 3 / 7; margin-top: -30%; }

/* The page colour is what separates the two prints, not a shadow. */
.why__sub .frame { border-top: 10px solid var(--paper); border-left: 10px solid var(--paper); }

.why__lead { max-width: 54ch; margin-top: var(--s-5); color: var(--on-paper-dim); }
.why__list { margin-top: clamp(2rem, 4vw, 3rem); }

.why__item {
  display: grid;
  grid-template-columns: 3rem 1fr;
  gap: var(--s-5);
  padding-block: clamp(1.1rem, 2.2vw, 1.75rem);
  border-top: 1px solid var(--hair-paper);
}

.why__item:last-child { border-bottom: 1px solid var(--hair-paper); }

.why__num { color: var(--accent-text); font-size: var(--fs-label); letter-spacing: var(--ls-label); padding-top: 0.45rem; }
.why__item-title { font-size: clamp(1.125rem, 1.8vw, 1.5rem); line-height: 1.2; }
.why__item-text { max-width: 54ch; margin-top: var(--s-2); color: var(--on-paper-dim); font-size: var(--fs-small); line-height: 1.75; }
.why__badge-row { margin-top: clamp(2rem, 4vw, 2.75rem); }

/* --------------------------------------------------------------------------
   Recent work. Vertical scroll pans the row sideways.
   -------------------------------------------------------------------------- */
.work {
  overflow: hidden;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(2rem, 4vw, 3.5rem);
  padding-block: clamp(4rem, 8vw, 7rem);
  border-top: 1px solid var(--hair-paper);
}

.work__viewport { overflow-x: auto; scroll-snap-type: x mandatory; }
.work__viewport--panned { overflow: hidden; scroll-snap-type: none; }

.work__track {
  display: flex;
  align-items: flex-end;
  gap: clamp(1.25rem, 2.4vw, 2.5rem);
  padding-inline: var(--gutter);
  width: max-content;
}

.work__item { scroll-snap-align: center; }
.work__figure { margin: 0; }

.work__img {
  height: min(46vh, 25rem);
  width: auto;
  object-fit: cover;
  filter: var(--grade);
  transition: opacity var(--dur-hover) var(--ease);
}

.work__item--tall .work__img { height: min(58vh, 32rem); }
.work__link:hover .work__img { opacity: 0.86; }

.work__caption {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-4);
  color: var(--on-paper-dim);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.work__caption::before { content: ""; display: block; width: 18px; height: 1px; background: var(--accent); }

/* --------------------------------------------------------------------------
   Cities. Ten places, set as type rather than listed as data.
   -------------------------------------------------------------------------- */
.cities { padding-block: var(--section-y); border-top: 1px solid var(--hair-paper); }

.cities__list {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.2rem 1.6rem;
  margin-top: clamp(2rem, 4vw, 3.5rem);
  line-height: 1.25;
}

.cities__item {
  position: relative;
  display: inline-flex;
  align-items: baseline;
  gap: 1.6rem;
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: clamp(1.5rem, 3.8vw, 3rem);
  line-height: 1.3;
  letter-spacing: -0.02em;
}

.cities__item::after {
  content: "";
  width: 5px;
  height: 5px;
  background: var(--accent);
  opacity: 0.55;
  transform: translateY(-0.35em) rotate(45deg);
}

.cities__item:last-child::after { display: none; }

.cities__mask { display: inline-block; overflow: hidden; padding-bottom: 0.1em; margin-bottom: -0.1em; }

.cities__word {
  display: inline-block;
  transform: translateY(105%);
  transition: transform 900ms var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}

.cities--in .cities__word { transform: none; }
.no-js .cities__word { transform: none; }

/* --------------------------------------------------------------------------
   How booking works
   -------------------------------------------------------------------------- */
.booking { padding-block: var(--section-y); border-top: 1px solid var(--hair-paper); }

.booking__list { position: relative; margin-top: clamp(3rem, 6vw, 5rem); }

.booking__spine {
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--hair-paper);
}

.booking__spine-fill {
  position: absolute;
  inset: 0;
  display: block;
  background: var(--accent);
  transform: scaleY(0);
  transform-origin: top center;
}

.booking__items { position: relative; }

.booking__item {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 7rem 1fr;
  align-items: center;
  padding-block: clamp(1rem, 2.4vw, 2rem);
}

/* The step number carries the column the photograph would carry elsewhere,
   so the alternating layout keeps its weight without asking for four more
   images the client would have to shoot. */
.booking__media {
  grid-column: 1;
  justify-self: end;
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: clamp(3.5rem, 9vw, 8rem);
  line-height: 0.9;
  letter-spacing: -0.04em;
  color: var(--on-paper);
  opacity: 0.2;
}
.booking__body { grid-column: 3; max-width: 28rem; }

.booking__item:nth-child(even) .booking__media { grid-column: 3; justify-self: start; }
.booking__item--on .booking__media { opacity: 0.4; color: var(--accent); transition: opacity 600ms var(--ease); }
.booking__item:nth-child(even) .booking__body { grid-column: 1; margin-left: auto; }

/* The marks light as they pass the viewport centre, so their transition runs
   during a scroll. background-color cannot be composited and would repaint
   four elements every frame of that scroll, so the fill is a pseudo-element
   that cross-fades on opacity instead. The mark carries no text, so there is
   nothing for the overlay to obscure or duplicate. */
.booking__mark {
  position: relative;
  grid-column: 2;
  justify-self: center;
  width: 11px;
  height: 11px;
  border: 1px solid var(--accent);
  background: var(--paper);
  transform: rotate(45deg);
  transition: transform 600ms var(--ease);
}

.booking__mark::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--accent);
  opacity: 0;
  transition: opacity 600ms var(--ease);
}

.booking__item--on .booking__mark { transform: rotate(45deg) scale(1.25); }
.booking__item--on .booking__mark::after { opacity: 1; }

.booking__step { display: block; color: var(--accent-text); font-size: var(--fs-label); letter-spacing: var(--ls-label); text-transform: uppercase; }
.booking__name { margin-top: var(--s-3); font-family: var(--font-display); font-weight: var(--weight-display); font-size: clamp(1.5rem, 3vw, 2.5rem); line-height: 1.05; letter-spacing: -0.02em; }
.booking__note { max-width: 38ch; margin-top: var(--s-3); color: var(--on-paper-dim); font-size: var(--fs-small); line-height: 1.75; }

/* --------------------------------------------------------------------------
   Reviews
   -------------------------------------------------------------------------- */
.reviews { padding-block: var(--section-y); border-top: 1px solid var(--hair-paper); }

.reviews__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  margin-top: clamp(2.5rem, 5vw, 4rem);
  background: var(--hair-paper);
  border-block: 1px solid var(--hair-paper);
}

.review { background: var(--paper); padding: clamp(1.5rem, 2.6vw, 2.25rem); }

/* The stars are one image to a screen reader, labelled once, rather than
   five separate graphics announced in a row. */
.review__stars {
  display: flex;
  gap: 3px;
  margin-bottom: var(--s-4);
  color: var(--accent-text);
}

.review__star { width: 14px; height: 14px; flex: none; }

 /* Set at reading size rather than display size, so it keeps a reading
    leading. This is the one display face on the page that should not be
    tight. */
.review__quote {
  font-family: var(--font-display);
  font-size: clamp(1.0625rem, 1.5vw, 1.25rem);
  line-height: 1.5;
  letter-spacing: -0.01em;
}

/* Where a review was written in another language and Google translated it,
   the card says so rather than presenting the translation as the words the
   reviewer typed. */
.review__note { text-transform: none; letter-spacing: 0.02em; opacity: 0.75; }

.review__by { margin-top: var(--s-4); color: var(--on-paper-dim); font-size: var(--fs-label); letter-spacing: 0.14em; text-transform: uppercase; }

/* --------------------------------------------------------------------------
   FAQ
   -------------------------------------------------------------------------- */
.faq { padding-block: var(--section-y); border-top: 1px solid var(--hair-paper); }

.faq__list { margin-top: clamp(2rem, 4vw, 3rem); border-top: 1px solid var(--hair-paper); }

.faq__item { border-bottom: 1px solid var(--hair-paper); }

.faq__q {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-5);
  padding-block: clamp(1.1rem, 2.2vw, 1.6rem);
  font-family: var(--font-display);
  font-size: clamp(1.125rem, 1.9vw, 1.5rem);
  line-height: 1.25;
  letter-spacing: -0.01em;
  cursor: pointer;
  list-style: none;
}

/* The question is a real h3 so it lands in the outline and can be lifted
   into an answer box, but it takes the summary's type, not the h3 default. */
.faq__q-text {
  font: inherit;
  letter-spacing: inherit;
  line-height: inherit;
  margin: 0;
}

.faq__sign {
  flex: none;
  width: 14px;
  height: 14px;
  position: relative;
  align-self: center;
}

.faq__sign::before,
.faq__sign::after {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  height: 1px;
  background: var(--accent);
  transition: transform var(--dur-hover) var(--ease);
}

.faq__sign::after { transform: rotate(90deg); }
.faq__item[open] .faq__sign::after { transform: rotate(0deg); }

.faq__a { max-width: 68ch; padding-bottom: clamp(1.25rem, 2.4vw, 1.75rem); color: var(--on-paper-dim); }

/* --------------------------------------------------------------------------
   Closing
   -------------------------------------------------------------------------- */
/* The one place on the page where a photograph sits behind type rather
   than beside it. Two scrims: a vertical one to seat it into the page and
   a left weighted one so the copy always has near-black underneath it. */
/* Every other section boundary on the site carries a hairline, and the
   closing block was the one that did not: on five of the six pages it is
   plain ink following plain ink, so it ran straight into the section above
   with nothing marking the join. */
.closing {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--ink);
  color: var(--on-ink);
  padding-block: clamp(6rem, 1vw, 12rem);
  border-top: 1px solid var(--hair-ink);
}

/* Except on the home page, where the block carries a photograph. The top
   edge of the image is already the divider, and a hairline drawn on top of
   it reads as a seam rather than a join. */
.closing:has(.closing__img) { border-top: 0; }

.closing__img {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 60%;
  filter: var(--grade);
}

.closing__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(100deg,
      rgba(18, 16, 14, 0.95) 0%,
      rgba(18, 16, 14, 0.90) 42%,
      rgba(18, 16, 14, 0.70) 72%,
      rgba(18, 16, 14, 0.55) 100%),
    linear-gradient(to bottom,
      rgba(18, 16, 14, 0.72) 0%,
      rgba(18, 16, 14, 0.34) 40%,
      rgba(18, 16, 14, 0.80) 100%);
}

/* An inner block, so the container keeps the page gutters and this keeps
   the measure. */
.closing__body { max-width: 46rem; }
.closing__title {
  margin-top: var(--s-4);
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: var(--fs-mega);
  line-height: 1.05;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
.closing__title em { font-style: italic; color: var(--accent-text); }
.closing__copy { max-width: 46ch; margin-top: var(--s-5); color: var(--on-ink-dim); }
.closing__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-6); margin-top: var(--s-7); }

/* --------------------------------------------------------------------------
   Footer. Four columns: the studio, the site, the services, and how to
   reach a person. No underlines anywhere in here; colour and hover carry it.
   -------------------------------------------------------------------------- */
.footer {
  background: var(--ink);
  color: var(--on-ink-dim);
  /* A step up from --fs-small. The footer carries the NAP, the service links
     and the areas covered, which is the most-read block on the site after the
     hero, and it was set at the same 13px as a photo caption. */
  font-size: 0.9375rem;
  line-height: 1.7;
  /* Asymmetric on purpose. The top needs the full section rhythm to
     separate the footer from the page above it; the bottom only has to sit
     under the last line of type, so it takes a fraction of that. */
  padding-top: clamp(3.5rem, 7vw, 6rem);
  padding-bottom: var(--s-6);
  border-top: 1px solid var(--hair-ink);
}

.footer__grid {
  display: grid;
  grid-template-columns: 1.6fr 1fr 1.2fr 1.4fr;
  gap: clamp(2rem, 4vw, 4rem);
}

/* The studio's tagline, directly under the lockup it belongs to. Display
   face and italic so it reads as a line the studio would say rather than as
   another sentence of body copy. */
.footer__tagline {
  margin-top: var(--s-4);
  font-family: var(--font-display);
  font-style: italic;
  font-weight: var(--weight-display);
  font-size: 1.125rem;
  line-height: 1.35;
  letter-spacing: -0.01em;
  color: var(--on-ink);
}

.footer__blurb {
  max-width: 40ch;
  margin-top: var(--s-5);
  line-height: 1.7;
}

.footer__social { display: flex; gap: var(--s-3); margin-top: var(--s-6); }

.social {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--paper);
  color: var(--on-ink-dim);
  transition: background-color var(--dur-hover) var(--ease), color var(--dur-hover) var(--ease);
}

.social__icon { width: 17px; height: 17px; }

.social:hover,
.social:focus-visible { background: var(--accent-fill); color: #FFF6EF; }

/* Link columns */
.footer__title {
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 500;
  /* Wide tracking needs less of it as the type grows, or the words start to
     read as separated letters rather than a label. */
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--on-ink);
  margin-bottom: var(--s-5);
}

.footer__list li + li { margin-top: var(--s-3); }

.footer__list a,
.footer__contact a {
  color: var(--on-ink-dim);
  text-decoration: none;
  transition: color var(--dur-hover) var(--ease);
}

.footer__list a:hover,
.footer__list a:focus-visible,
.footer__contact a:hover,
.footer__contact a:focus-visible { color: var(--accent-lift); }

/* Contact column */
.footer__contact-item {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: var(--s-3);
  align-items: start;
  line-height: 1.6;
}

.footer__contact-item + .footer__contact-item { margin-top: var(--s-4); }

.footer__contact-icon {
  width: 17px;
  height: 17px;
  margin-top: 0.28em;
  color: var(--accent);
  flex: none;
}

.footer__address { font-style: normal; line-height: 1.6; }

/* Areas, full width under the columns */
.footer__areas {
  margin-top: clamp(2.5rem, 5vw, 4rem);
  padding-top: var(--s-6);
  border-top: 1px solid var(--hair-ink);
}

.footer__areas-list { max-width: 76ch; line-height: 1.6; }

/* Base line */
.footer__base {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  justify-content: space-between;
  align-items: center;
  margin-top: var(--s-6);
  padding-top: var(--s-5);
  border-top: 1px solid var(--hair-ink);
}

/* Inline rather than flex, so the whole sentence wraps as one piece of
   text instead of breaking around the icon. */
.footer__credit { display: inline; }

.footer__heart {
  display: inline-block;
  width: 14px;
  height: 14px;
  margin-inline: 0.1em;
  vertical-align: -0.18em;
  color: #D93025;
}

.footer__credit a {
  color: var(--on-ink);
  text-decoration: none;
  transition: color var(--dur-hover) var(--ease);
}

.footer__credit a:hover,
.footer__credit a:focus-visible { color: var(--accent-lift); }

/* --------------------------------------------------------------------------
   Floating WhatsApp. Present at every width: on a page this long it is the
   shortest route to a person from wherever the visitor happens to be.
   -------------------------------------------------------------------------- */
.whatsapp {
  position: fixed;
  right: clamp(1rem, 2vw, 2rem);
  bottom: clamp(1rem, 2vw, 2rem);
  z-index: var(--z-sticky);
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: #25D366;
  color: #0B3D22;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45);
  transition: transform var(--dur-hover) var(--ease), background-color var(--dur-hover) var(--ease);
}

.whatsapp:hover,
.whatsapp:focus-visible { transform: translateY(-3px); background: #1FBE59; }

.whatsapp__icon { width: 28px; height: 28px; }

/* ==========================================================================
   MONEY PAGES
   Everything below is shared by the three service pages. The home page
   uses none of it.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Inner-page hero
   Shorter than home's. A full viewport of photograph is right when it is
   the first thing anyone sees on the site; on page two it just delays
   the content they clicked for.
   -------------------------------------------------------------------------- */
.hero--page { min-height: min(82vh, 46rem); }

/* Home's h1 is three words on two lines. These are eight words, and at the
   hero size they run to three, which breaks the two-line reveal and leaves
   the photograph with nowhere to show. One step down fits them. */
.hero--page .hero__title { font-size: var(--fs-mega); }

/* --------------------------------------------------------------------------
   Section index
   Fourteen sections is long enough that people give up without a map.
   The strip names the whole page, sticks under the header, and slides a
   marigold rule under whichever section is on screen.

   It is a plain list of anchors first. With no script the marigold rule
   never moves, and every link still works.
   -------------------------------------------------------------------------- */
.index {
  position: sticky;
  top: var(--header-h);
  z-index: 40;
  background: var(--ink);
  border-block: 1px solid var(--hair-ink);
}

/* The track scrolls sideways on narrow screens rather than wrapping to
   three rows, which would push the content down on every phone. */
/* The strip is wider than the viewport at every width, so both ends fade
   to signal that it carries on. Without it the cut-off word at the right
   edge reads as a layout bug rather than an invitation to scroll. */
.index__track {
  position: relative;
  --fade: clamp(1.5rem, 4vw, 3rem);
  mask-image: linear-gradient(to right, transparent 0, #000 var(--fade),
              #000 calc(100% - var(--fade)), transparent 100%);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.index__track::-webkit-scrollbar { display: none; }

.index__list {
  display: flex;
  gap: clamp(1.1rem, 2.4vw, 2.2rem);
  width: max-content;
  min-width: 100%;
  padding-inline: var(--gutter);
  padding-block: 0.95rem;
}

.index__link {
  display: block;
  white-space: nowrap;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--on-ink-dim);
  transition: color var(--dur-hover) var(--ease);
}

.index__link:hover,
.index__link--on { color: var(--on-ink); }

/* One element that slides, rather than a border on each link that would
   have to fade in and out fourteen times. Transform only, so the slide
   is composited. */
.index__mark {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 2px;
  width: 1px;
  background: var(--accent);
  transform-origin: 0 50%;
  transform: translateX(0) scaleX(0);
  transition: transform 420ms var(--ease);
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   Split
   The workhorse. Text on one side, photograph on the other, in two
   proportions so consecutive sections never look like the same layout.
   -------------------------------------------------------------------------- */
.split { padding-block: var(--section-y); border-top: 1px solid var(--hair-paper); }

.split__grid {
  display: grid;
  gap: clamp(2.5rem, 5vw, 5.5rem);
  align-items: start;
}

.split__grid--7-5 { grid-template-columns: 7fr 5fr; }
.split__grid--5-7 { grid-template-columns: 5fr 7fr; }

.split__body > p { margin-top: var(--s-5); color: var(--on-paper-dim); line-height: 1.85; }
.split__body > p:first-of-type { margin-top: var(--s-6); }

/* Every section h2 that sits under an eyebrow. Kept as one selector list
   rather than a rule each, because two of these previously had no rule at
   all and inherited no margin-top: the eyebrow's box bottom then touched the
   heading's box top, and since the base line-height of 1 makes both boxes
   hug their glyphs, the two lines of text ran into each other. A missing
   entry here is now a missing entry in an obvious list. */
.split__title,
.narrow__title,
.cards__title,
.styles__title,
.pricing__title,
.cities__title,
.faq__title,
.why__title {
  margin-top: var(--s-4);
  font-size: var(--fs-h2);
  line-height: 1.02;
}

.split__media img,
.stack__item img,
.pair__item img,
.trio__item img,
.band img,
.card__media img,
.style__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: var(--grade);
}

.split__media { position: sticky; top: calc(var(--header-h) + 5rem); }
.split__media img { aspect-ratio: 4 / 5; }

/* Two photographs down the side of a text column, offset so the pair
   does not read as one tall block. */
.stack { display: grid; gap: var(--s-5); }
.stack__item:nth-child(2) { margin-left: clamp(1.5rem, 5vw, 4rem); }
.stack__item:first-child img { aspect-ratio: 3 / 2; }
.stack__item:nth-child(2) img { aspect-ratio: 3 / 4; }

/* --------------------------------------------------------------------------
   Narrow
   A centred measure with nothing beside it. Used where the section is
   short enough that a photograph next to it would be filler.
   -------------------------------------------------------------------------- */
.narrow { padding-block: var(--section-y); border-top: 1px solid var(--hair-paper); }

.narrow__body { max-width: 46rem; margin-inline: auto; text-align: center; }
.narrow__body .eyebrow { justify-content: center; }
.narrow__body > p { margin-top: var(--s-5); color: var(--on-paper-dim); line-height: 1.85; }
.narrow__body > p:first-of-type { margin-top: var(--s-6); }
.narrow__body .section-cta { justify-content: center; }
.narrow__body .note { text-align: left; margin-top: var(--s-6); }

/* --------------------------------------------------------------------------
   Image groups
   -------------------------------------------------------------------------- */
.band { margin-top: clamp(2.5rem, 5vw, 4.5rem); }
.band--inset img { aspect-ratio: 21 / 9; }
.band__cap {
  margin-top: var(--s-3);
  color: var(--on-paper-dim);
  font-size: var(--fs-small);
}

.pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(1rem, 2.5vw, 2rem);
  margin-top: clamp(2.5rem, 5vw, 4.5rem);
}
.pair__item:first-child img { aspect-ratio: 3 / 4; }
.pair__item:nth-child(2) img { aspect-ratio: 3 / 4; margin-top: 0; }

.trio {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1rem, 2.5vw, 2rem);
  margin-top: clamp(2.5rem, 5vw, 4.5rem);
}
.trio__item img { aspect-ratio: 3 / 4; }
/* The middle frame drops so the row has a rhythm rather than a straight
   top edge. */
.trio__item:nth-child(2) { margin-top: clamp(1.5rem, 4vw, 3.5rem); }

/* --------------------------------------------------------------------------
   Editorial band
   Full bleed photograph, one line of display serif over it. Its only job
   is to break the reading rhythm between two long text sections.
   -------------------------------------------------------------------------- */
.quote {
  position: relative;
  display: flex;
  align-items: center;
  min-height: min(58vh, 34rem);
  padding-block: var(--s-10);
  overflow: hidden;
  isolation: isolate;
}

.quote__img {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: var(--grade);
}

.quote__veil {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(92deg, rgba(18, 16, 14, 0.92) 0%, rgba(18, 16, 14, 0.72) 46%, rgba(18, 16, 14, 0.34) 100%),
    linear-gradient(to top, rgba(18, 16, 14, 0.7) 0%, rgba(18, 16, 14, 0.2) 55%, rgba(18, 16, 14, 0.5) 100%);
}

.quote__line {
  max-width: 22ch;
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: var(--fs-mega);
  line-height: 1.08;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

/* A subheading inside a split or narrow body. It sits under the section h2,
   so it takes more space above than a paragraph and none of the h2's size. */
.split__sub,
.narrow__sub {
  margin-top: var(--s-7);
  font-size: clamp(1.125rem, 1.6vw, 1.375rem);
  line-height: 1.3;
  letter-spacing: 0;
}
.split__sub + .specs,
.split__sub + p,
.narrow__sub + p { margin-top: var(--s-4); }

/* --------------------------------------------------------------------------
   Cards
   -------------------------------------------------------------------------- */
.cards { padding-block: var(--section-y); border-top: 1px solid var(--hair-paper); }
.cards__lede {
  max-width: 60ch;
  margin-top: var(--s-6);
  color: var(--on-paper-dim);
  line-height: 1.85;
}

.cards__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(1.25rem, 2.5vw, 2.25rem);
  margin-top: clamp(2.5rem, 5vw, 4rem);
}

.cards__grid--pair { grid-template-columns: repeat(2, 1fr); }
.cards__grid--pair .card__media img { aspect-ratio: 3 / 2; }

/* People, not products. The 3:2 above suits the birthday cards, which show a
   set rather than a subject; on a portrait it crops the head off. Team cards
   keep the frame the portrait was taken in, and hold the top of it, because
   a face sits nearer the top than the centre. */
.cards__grid--people .card__media img {
  aspect-ratio: 4 / 5;
  object-position: 50% 20%;
}
.cards__grid--pair .card__text { max-width: 46ch; }

.cards__foot {
  max-width: 62ch;
  margin-top: clamp(2rem, 4vw, 3rem);
  color: var(--on-paper-dim);
  line-height: 1.85;
}

.card__media { overflow: hidden; }
.card__media img {
  aspect-ratio: 4 / 5;
  transition: transform 900ms var(--ease);
}
.card:hover .card__media img { transform: scale(1.04); }

.card__title {
  margin-top: var(--s-4);
  font-size: 1.25rem;
  line-height: 1.2;
}
.card__text {
  margin-top: var(--s-2);
  color: var(--on-paper-dim);
  font-size: var(--fs-small);
  line-height: 1.7;
}

/* --------------------------------------------------------------------------
   Styles
   Five narrow columns. Small on purpose: these have no search volume and
   are there to answer a question, not to carry the page.
   -------------------------------------------------------------------------- */
.styles { padding-block: var(--section-y); border-top: 1px solid var(--hair-paper); }

.styles__grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: clamp(1rem, 2vw, 1.75rem);
  margin-top: clamp(2.5rem, 5vw, 4rem);
}

.style__media { overflow: hidden; }
.style__media img { aspect-ratio: 3 / 4; }
.style__name { margin-top: var(--s-4); font-size: 1.125rem; }
.style__text {
  margin-top: var(--s-2);
  color: var(--on-paper-dim);
  font-size: var(--fs-small);
  line-height: 1.65;
}

/* --------------------------------------------------------------------------
   Pricing
   -------------------------------------------------------------------------- */
.pricing { padding-block: var(--section-y); border-top: 1px solid var(--hair-ink); }

.pricing__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1.25rem, 2.5vw, 2rem);
  margin-top: clamp(2.5rem, 5vw, 4rem);
  align-items: stretch;
}

.tier {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: clamp(1.75rem, 3vw, 2.5rem);
  border: 1px solid var(--hair-ink);
  background: var(--ink-raised);
}

/* The tier most people take is marked, because a row of three identical
   cards makes the reader do work the studio can do for them. */
.tier--lead { border-color: var(--accent); }

/* Out of flow and straddling the top border, so the three cards keep their
   price lines on the same baseline. In flow it pushed this one card's whole
   contents down and the row stopped reading as a comparison. */
.tier__flag {
  position: absolute;
  top: 0;
  left: clamp(1.75rem, 3vw, 2.5rem);
  transform: translateY(-50%);
  padding: 0.35rem 0.7rem;
  background: var(--accent-fill);
  color: #FFF6EF;
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.tier__name { font-size: var(--fs-h3); line-height: 1.1; }
.tier__for {
  margin-top: var(--s-3);
  color: var(--on-ink-dim);
  font-size: var(--fs-small);
  line-height: 1.6;
}

.tier__price {
  margin-top: var(--s-6);
  padding-top: var(--s-5);
  border-top: 1px solid var(--hair-ink);
}
.tier__from {
  display: block;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--on-ink-dim);
}
/* CLIENT DETAIL REQUIRED: the real figure replaces "On request". It is set
   in display serif so a number reads as the number when it arrives. */
.tier__figure {
  display: block;
  margin-top: var(--s-2);
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: 2rem;
  letter-spacing: -0.02em;
  color: var(--accent-text);
}

.tier__list { margin-top: var(--s-6); }
.tier__list li {
  position: relative;
  padding-left: var(--s-5);
  padding-block: var(--s-2);
  color: var(--on-ink-dim);
  font-size: var(--fs-small);
  line-height: 1.6;
}
.tier__list li::before {
  content: "";
  position: absolute;
  top: 1em;
  left: 0;
  width: 8px;
  height: 1px;
  background: var(--accent);
}

.tier__cta { margin-top: auto; align-self: flex-start; }
.tier__list + .tier__cta { margin-top: var(--s-7); }

/* The three cards have to compare, which means the price lines and the
   buttons sit on the same baselines however long each description runs.
   Flex cannot do that across siblings; subgrid can, by letting all three
   share one set of row heights. Where it is unsupported the flex rules
   above still stand and the cards merely sit a line apart. */
@supports (grid-template-rows: subgrid) {
  /* Five in-flow children per card: name, blurb, price, list, button.
     The flag is absolute and takes no row. */
  .pricing__grid { grid-template-rows: repeat(5, auto); }

  .tier {
    display: grid;
    grid-row: span 5;
    grid-template-rows: subgrid;
    row-gap: 0;
  }

  .tier__name { align-self: end; }
  .tier__cta { margin-top: var(--s-7); align-self: start; justify-self: start; }
  .tier__list + .tier__cta { margin-top: var(--s-7); }
}

.pricing__foot { margin-top: clamp(2.5rem, 5vw, 4rem); }
.pricing__sub { font-size: var(--fs-h3); }
.pricing__terms {
  max-width: 62ch;
  margin-top: var(--s-6);
  color: var(--on-ink-dim);
  font-size: var(--fs-small);
  line-height: 1.8;
}

/* --------------------------------------------------------------------------
   Reel
   The gallery band. It pans sideways as the page scrolls vertically,
   driven by ScrollTrigger. With no script, and under reduced motion, it
   is a normal horizontally scrolling strip with snap points, and every
   photograph is still reachable by keyboard and by touch.
   -------------------------------------------------------------------------- */
.reel { padding-block: var(--section-y); border-top: 1px solid var(--hair-ink); }
/* Display treatment is set here rather than inherited from the h2 element
   rule, because this is an h2 on one page and a p on another. */
.reel__title {
  margin-top: var(--s-4);
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: var(--fs-h2);
  line-height: 1.02;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
.reel__lede {
  max-width: 62ch;
  margin-top: var(--s-6);
  color: var(--on-ink-dim);
  line-height: 1.85;
}

.reel__viewport {
  margin-top: clamp(2.5rem, 5vw, 4rem);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
}
.reel__viewport::-webkit-scrollbar { display: none; }

.reel__track {
  display: flex;
  align-items: flex-start;
  gap: clamp(0.75rem, 1.6vw, 1.5rem);
  width: max-content;
  padding-inline: var(--gutter);
}

.reel__item {
  flex: none;
  width: clamp(15rem, 26vw, 24rem);
  scroll-snap-align: center;
}
.reel__item--wide { width: clamp(20rem, 38vw, 36rem); }
/* Alternate frames drop, so the strip has a horizon rather than a rule. */
.reel__item:nth-child(even) { margin-top: clamp(1.5rem, 4vw, 3.5rem); }

.shot img {
  width: 100%;
  height: clamp(18rem, 42vh, 30rem);
  object-fit: cover;
  filter: var(--grade);
}
.shot__cap {
  margin-top: var(--s-3);
  color: var(--on-ink-dim);
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* When the pan is driven by script the viewport must not also scroll on
   its own, or the two fight each other. */
.reel--panned .reel__viewport {
  overflow: hidden;
  scroll-snap-type: none;
}

/* --------------------------------------------------------------------------
   Lightbox
   PhotoSwipe ships its own stylesheet; these are the studio's overrides.
   -------------------------------------------------------------------------- */
.pswp { --pswp-bg: #0B0A09; --pswp-icon-color: var(--on-ink); --pswp-icon-color-secondary: #0B0A09; }

.pswp__caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: var(--s-5) var(--gutter) var(--s-6);
  background: linear-gradient(to top, rgba(11, 10, 9, 0.9), transparent);
  color: var(--on-ink);
  font-family: var(--font-body);
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: center;
  pointer-events: none;
}

/* A photograph that opens a viewer should say so before it is clicked. */
.shot__link { display: block; cursor: zoom-in; }

/* The line under a name on a team card. Sits between the name and the
   description, so it takes the label treatment rather than body copy. */
.card__role {
  margin-top: var(--s-2);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--accent-text);
}

/* A qualifying note after a list, quieter than the paragraphs around it. */
.split__aside {
  margin-top: var(--s-5);
  color: var(--on-paper-dim);
  font-size: var(--fs-small);
  line-height: 1.8;
}

/* A card's footnote, used on the team cards for what is still missing. */
.card__note {
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--hair-paper);
  color: var(--accent-text);
  font-size: var(--fs-small);
}

/* ==========================================================================
   PAGEHEAD
   For pages with no photograph at the top. Privacy is read rather than
   looked at, and a full-bleed hero would put four hundred pixels between
   someone and the clause they came for.
   ========================================================================== */
.pagehead {
  padding-top: calc(var(--header-h) + clamp(3rem, 7vw, 6rem));
  padding-bottom: clamp(2.5rem, 5vw, 4rem);
}

.pagehead__title {
  margin-top: var(--s-5);
  font-size: var(--fs-h2);
  line-height: 1.02;
}

.pagehead__lede {
  max-width: 54ch;
  margin-top: var(--s-5);
  color: var(--on-ink-dim);
  font-size: var(--fs-lede, 1.0625rem);
  line-height: 1.8;
}

.pagehead__meta {
  margin-top: var(--s-5);
  color: var(--on-ink-dim);
  font-size: var(--fs-small);
}

/* ==========================================================================
   LEGAL
   One measured column. Long-form reading, so the line length is shorter
   than the rest of the site and the space between headings is larger.
   ========================================================================== */
/* Less air above than a normal section: the page head already separates
   this from the top of the page, and a legal page should put the first
   clause where someone can see it. */
.legal { padding-block: clamp(2.25rem, 4.5vw, 3.5rem) var(--section-y); border-top: 1px solid var(--hair-paper); }

.legal__body { max-width: 68ch; }

.legal__body > p {
  margin-top: var(--s-5);
  color: var(--on-paper-dim);
  line-height: 1.85;
}

.legal__h {
  margin-top: var(--s-8);
  font-size: clamp(1.375rem, 2.4vw, 1.875rem);
  line-height: 1.15;
}

.legal__body > .legal__h:first-of-type { margin-top: var(--s-7); }

.legal__sub {
  margin-top: var(--s-6);
  font-size: clamp(1.05rem, 1.5vw, 1.2rem);
  line-height: 1.3;
  letter-spacing: 0;
}

.legal__body .section-cta { margin-top: var(--s-8); }

/* The cities list closing note, on the contact page. */
.cities__note {
  max-width: 62ch;
  margin-top: clamp(2rem, 4vw, 3rem);
  color: var(--on-paper-dim);
  font-size: var(--fs-small);
  line-height: 1.8;
}

/* ==========================================================================
   MAP
   The Google Business Profile embed. Sized by aspect ratio rather than the
   iframe's own width and height attributes, which are only there as a
   fallback: an iframe is replaced content and will happily be 600x450 on a
   360px phone otherwise.
   ========================================================================== */
.map {
  margin-top: clamp(2.5rem, 5vw, 4rem);
  border: 1px solid var(--hair-paper);
}

.map__frame {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  border: 0;
  /* Google's tiles are brighter than anything else on the page. This pulls
     them back toward the site's grade so the map does not read as a hole
     punched in the surface. */
  filter: saturate(0.85) contrast(1.02) brightness(0.88);
}

.map__cap {
  padding: var(--s-4) var(--s-5);
  border-top: 1px solid var(--hair-paper);
  color: var(--on-paper-dim);
  font-size: var(--fs-small);
}

@media (max-width: 640px) {
  .map__frame { aspect-ratio: 4 / 3; }
}

/* ===== pages.css ===== */
/* ==========================================================================
   PAGES
   Page-level composition and every responsive rule that belongs to a
   section rather than to a component.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Surfaces
   Two darks, alternating down every page. Not scoped to a page class: the
   home page was the only one when this was written, and every page since
   wants the same two surfaces in the same order.
   -------------------------------------------------------------------------- */
.surface--paper { background: var(--paper); color: var(--on-paper); }

/* ==========================================================================
   RESPONSIVE

   Five tiers, each doing one job:

     1180  the footer drops from four columns to two
     1024  every two column content layout becomes one
      900  the nav becomes a drawer and the timeline moves to a left spine
      640  section heads stack, grids become single column, buttons may wrap
      420  the tightest phones: type and padding step down once more

   A grid child defaults to min-width auto, which lets a long word push the
   track wider than the viewport. Every grid child on this page is therefore
   given min-width: 0, which is what stopped the booking column overflowing
   between 1024 and 820.
   ========================================================================== */

.services__grid > *,
.enquiry__grid > *,
.why__grid > *,
.booking__item > *,
.footer__grid > *,
.reviews__grid > *,
.split__grid > *,
.cards__grid > *,
.styles__grid > *,
.pricing__grid > *,
.compare__row > *,
.specs__item > *,
.pair > *,
.trio > * { min-width: 0; }

/* ---------- 1180 ---------------------------------------------------------- */
@media (max-width: 1180px) {
  .footer__grid { grid-template-columns: 1fr 1fr; }
  .footer__col--brand { grid-column: 1 / -1; }
}

/* ---------- 1024 ---------------------------------------------------------- */
@media (max-width: 1024px) {
  .printroom__stage { --album-w: 62vw; --album-x: 58%; }

  .services__grid { grid-template-columns: 1fr; }
  .services__viewer { display: none; }

  .enquiry__grid { grid-template-columns: 1fr; }
  .why__grid { grid-template-columns: 1fr; gap: var(--s-8); }
  .why__media { max-width: 34rem; }

  .reviews__grid { grid-template-columns: repeat(2, 1fr); }

  /* Every split becomes one column, and the photograph goes under the text
     rather than beside it. A sticky media column with nothing beside it
     just sticks to nothing. */
  .split__grid--7-5,
  .split__grid--5-7 { grid-template-columns: 1fr; }
  .split__media { position: static; max-width: 34rem; }

  /* The flipped splits put the photograph first at desktop width. Stacked,
     that buries the heading below an image, so the text comes back up. */
  .split__grid--flip .split__body { order: -1; }

  .cards__grid { grid-template-columns: repeat(2, 1fr); }
  .styles__grid { grid-template-columns: repeat(3, 1fr); }
  .pricing__grid { grid-template-columns: 1fr; row-gap: 2.5rem; }
  .tier--lead { order: -1; }
}

/* ---------- 900 ----------------------------------------------------------- */
@media (max-width: 900px) {
  /* --header-h moved to tokens.css and the nav and burger rules to
     sections.css, each beside the base value it overrides. All seven
     stylesheets load asynchronously, so an override sitting in the last of
     them applies later than the rule it is meant to beat. */

  .hero__img { object-position: 58% 42%; animation: none; transform: none; }
  .hero__media { animation: none; clip-path: none; }
  .hero__veil {
    background: linear-gradient(to top,
      rgba(18, 16, 14, 0.95) 0%,
      rgba(18, 16, 14, 0.72) 42%,
      rgba(18, 16, 14, 0.22) 74%,
      rgba(18, 16, 14, 0.50) 100%);
  }
  .hero__foot { flex-direction: column; align-items: flex-start; gap: var(--s-5); }
  .hero__actions { flex-wrap: wrap; gap: var(--s-4); }

  .printroom { height: 420vh; }
  /* Narrow enough that the mount line has somewhere to sit. At 94vw the
     album touched both edges and the outline was cropped away. */
  .printroom__stage { --album-w: 84vw; --album-x: 50%; }
  .printroom__stage { --album-y: 32%; }
  .album__mount { inset: -9px; border-color: rgba(241, 237, 229, 0.34); }
  .printroom__copy {
    top: auto;
    translate: none;
    left: var(--gutter);
    right: var(--gutter);
    bottom: 2.5rem;
    max-width: none;
  }
  .printroom__side { display: none; }
  .printroom__count { bottom: 1rem; }

  /* Baseline alignment lines the thumbnail up with the text baseline, which
     leaves it stranded at the top of the row. The text aligns to the top,
     the photograph to the middle of the row it belongs to. */
  .services__row {
    grid-template-columns: 2rem 1fr 64px;
    align-items: start;
    column-gap: var(--s-4);
  }
  .services__num { padding-top: 0.35em; }
  .services__meta { grid-column: 2 / 3; }
  .services__go { display: none; }
  .services__thumb { display: block; align-self: center; }

  .form__row { grid-template-columns: 1fr; gap: 0; }

  .why__media { grid-template-columns: 1fr; }
  .why__main, .why__sub { grid-column: 1 / -1; }
  .why__sub { display: none; }
  .why__item { grid-template-columns: 1fr; gap: var(--s-2); }

  .work { min-height: 0; }

  /* The index sits under a shorter header at this width. */
  .index__list { padding-block: 0.8rem; }

  .compare__row,
  .specs__item { grid-template-columns: 1fr; gap: var(--s-2); }
  .moves__k { display: block; min-width: 0; margin-right: 0; margin-bottom: var(--s-1); }

  .stack__item:nth-child(2) { margin-left: 0; }
  .trio__item:nth-child(2) { margin-top: 0; }
  .reel__item:nth-child(even) { margin-top: 0; }

  /* The timeline moves off centre and runs down the left edge. The column is
     an odd 21px and the mark is centred in it, so the mark centre lands on
     10.5 and the one pixel spine at left: 10 sits exactly under it. Start
     aligning the mark instead left it four pixels off the line. */
  .booking__spine { left: 10px; }
  .booking__item,
  .booking__item:nth-child(even) {
    grid-template-columns: 21px 1fr;
    gap: var(--s-5);
    align-items: start;
  }
  .booking__mark { grid-column: 1; grid-row: 1; margin-top: 0.6rem; justify-self: center; }
  .booking__media,
  .booking__item:nth-child(even) .booking__media {
    grid-column: 2; grid-row: 2;
    margin-top: var(--s-2);
    justify-self: start;
    font-size: 3rem;
  }
  .booking__body,
  .booking__item:nth-child(even) .booking__body {
    grid-column: 2; grid-row: 1;
    max-width: none;
    margin-left: 0;
  }
}

/* ---------- 640 ----------------------------------------------------------- */
@media (max-width: 640px) {
  /* A head that sits on one line at desktop has to stack, and its aside can
     no longer refuse to wrap. */
  .section-head {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-3);
  }
  .section-head__aside { white-space: normal; }

  /* Same for the row that closes a section. */
  .section-foot {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-5);
  }

  .reviews__grid { grid-template-columns: 1fr; }
  .footer__grid { grid-template-columns: 1fr; gap: var(--s-7); }
  .footer__base { justify-content: flex-start; }

  .enquiry__panel { padding: var(--s-6) var(--s-5); }

  /* Stacked fields do not need desktop's column gap between them as well. */
  .field { margin-bottom: var(--s-5); }
  .picks { margin-bottom: var(--s-6); }
  .form__foot { gap: var(--s-4); }

  /* Wide tracking is what pushes a label onto a second line. It comes down
     rather than the type size, so the buttons keep their weight. */
  .btn,
  .btn-outline,
  .header__cta { letter-spacing: 0.1em; }
  .btn { padding: 1rem 1.5rem; }
  .btn-outline { padding: 0.8rem 1.25rem; }

  .work__img { height: min(42vh, 20rem); }
  .work__item--tall .work__img { height: min(52vh, 25rem); }

  /* Three photographs across a phone is three postage stamps. */
  .trio { grid-template-columns: 1fr; }
  .pair { grid-template-columns: 1fr; }
  .cards__grid { grid-template-columns: 1fr; }
  .styles__grid { grid-template-columns: repeat(2, 1fr); }
  .band--inset img { aspect-ratio: 16 / 10; }
  .narrow__body { text-align: left; }
  .narrow__body .eyebrow { justify-content: flex-start; }
  .narrow__body .section-cta { justify-content: flex-start; }
  .quote { min-height: min(52vh, 26rem); }
  .quote__line { max-width: none; }
  .tier__figure { font-size: 1.75rem; }

  .cities__list { gap: 0.2rem 1.1rem; }
  .cities__item { gap: 1.1rem; }

  /* Enough to clear the floating WhatsApp button and no more. It sits 16px
     off the bottom and is 56px tall, so 4.5rem covers its whole reach. */
  .footer { padding-bottom: 4.5rem; }
}

/* ---------- 420 ----------------------------------------------------------- */
@media (max-width: 420px) {
  /* Long labels wrap rather than setting a floor on the page width. */
  .btn,
  .btn-outline {
    white-space: normal;
    text-align: left;
  }

  .btn { padding: 1rem 1.4rem; }
  .btn-outline { padding: 0.8rem 1.1rem; }

  .hero__actions { align-items: stretch; }
  .hero__actions .btn { justify-content: center; text-align: center; }

  .enquiry__panel { padding: var(--s-5) var(--s-4); }

  .services__row { grid-template-columns: 1.75rem 1fr 52px; column-gap: var(--s-3); }
  .services__thumb { width: 52px; }

  .printroom__stage { --album-w: 88vw; }

  .booking__media { font-size: 2.5rem; }

  .work__img { height: min(38vh, 17rem); }
  .work__item--tall .work__img { height: min(46vh, 21rem); }

  .styles__grid { grid-template-columns: 1fr; }
  .reel__item,
  .reel__item--wide { width: min(78vw, 20rem); }
  .shot img { height: min(46vh, 22rem); }

  .social { width: 38px; height: 38px; }
  .social__icon { width: 15px; height: 15px; }

  .whatsapp { width: 50px; height: 50px; }
  .whatsapp__icon { width: 25px; height: 25px; }
}

/* ==========================================================================
   FALLBACK
   No script, or motion reduced. The album cannot turn, so it lies out and
   every page is still on the screen.
   ========================================================================== */
.printroom--flat,
.no-js .printroom { height: auto; }

.printroom--flat .printroom__stage,
.no-js .printroom .printroom__stage {
  position: static;
  height: auto;
  padding-block: var(--section-y);
  overflow: visible;
}

.printroom--flat .printroom__paper,
.no-js .printroom .printroom__paper { position: absolute; inset: 0; }

.printroom--flat .printroom__open,
.printroom--flat .album__shade,
.printroom--flat .album__gutter,
.printroom--flat .album__mount,
.printroom--flat .printroom__count,
.no-js .printroom .printroom__open,
.no-js .printroom .album__shade,
.no-js .printroom .album__gutter,
.no-js .printroom .album__mount,
.no-js .printroom .printroom__count { display: none; }

.printroom--flat .album,
.no-js .printroom .album {
  position: relative;
  top: auto;
  left: auto;
  translate: none;
  transform: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--s-4);
  width: min(100%, var(--container-wide));
  margin-inline: auto;
  padding-inline: var(--gutter);
  aspect-ratio: auto;
  perspective: none;
}

.printroom--flat .album__page,
.printroom--flat .album__stack,
.printroom--flat .album__base,
.no-js .printroom .album__page,
.no-js .printroom .album__stack,
.no-js .printroom .album__base {
  position: static;
  width: auto;
  height: auto;
  aspect-ratio: 4 / 5;
}

.printroom--flat .album__stack,
.printroom--flat .album__leaf,
.no-js .printroom .album__stack,
.no-js .printroom .album__leaf { display: contents; }

.printroom--flat .album__face,
.no-js .printroom .album__face {
  position: static;
  transform: none;
  aspect-ratio: 4 / 5;
  backface-visibility: visible;
}

.printroom--flat .printroom__copy,
.printroom--flat .printroom__side,
.no-js .printroom .printroom__copy,
.no-js .printroom .printroom__side {
  position: static;
  translate: none;
  opacity: 1;
  max-width: none;
  text-align: left;
  margin-top: var(--s-6);
  padding-inline: var(--gutter);
}

/* ==========================================================================
   REDUCED MOTION. Everything off, everything in its final state.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0ms !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0ms !important;
  }

  .hero__media { clip-path: inset(0%); }
  .hero__img { transform: none; }
  .reveal { opacity: 1; transform: none; }
  .cities__word { transform: none; }
  .booking__spine-fill { transform: scaleY(1); }
  .progress { display: none; }
}

/* ===== utilities.css ===== */
/* ==========================================================================
   UTILITIES
   Single-purpose helpers. Nothing here is a component.
   ========================================================================== */

.u-container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.u-container--wide { max-width: var(--container-wide); }

.u-measure { max-width: var(--measure); }

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

.u-nowrap { white-space: nowrap; }

/* Below-the-fold sections skip layout and paint until they are near the
   viewport. The contain-intrinsic-size keeps the scrollbar honest. */
.u-defer {
  content-visibility: auto;
  contain-intrinsic-size: auto 1200px;
}
