/* ==========================================================================
   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; }

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

.brand__mark {
  width: 42px;
  height: 42px;
  flex: none;
  overflow: visible;
  transition: transform 600ms var(--ease);
}

.brand__frame {
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-linecap: square;
}

.brand__lens {
  stroke: currentColor;
  stroke-width: 1.25;
  opacity: 0.85;
}

.brand__glint {
  stroke: var(--accent-text);
  stroke-width: 1.25;
  stroke-linecap: round;
}

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

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

/* ==========================================================================
   AWAIT
   Marks a passage the studio has to write or confirm before launch. It is
   styled to look deliberate rather than broken, because a client reading a
   staging link should be able to tell the difference between "not written
   yet" and "we forgot". Every one of these has a matching CLIENT REQUIRED
   comment in the source, and the README lists them.
   ========================================================================== */
.await {
  margin-top: var(--s-6);
  padding: var(--s-5) var(--s-5) var(--s-5) calc(var(--s-5) + 3px);
  border-left: 3px solid var(--accent);
  background: rgba(241, 237, 229, 0.035);
}

.await--wide { max-width: none; }

.await__label {
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--accent-text);
}

.await__text {
  margin-top: var(--s-3);
  max-width: 62ch;
  color: var(--on-paper-dim);
  font-size: var(--fs-small);
  line-height: 1.8;
}

/* 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 .await { margin-top: var(--s-6); }
.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; }
}
