/* ==========================================================================
   The Detail Co. — main.css
   Mobile detailing, Gainesville FL. Static site, no build step.

   CONTENTS
   00a View transitions (must sit above everything — @view-transition is a
       top-level rule and browsers without it simply skip it)
   00b Self-hosted fonts
   01  Tokens
   02  Reset & base
   03  Typography
   04  Layout utilities (.wrap, .section, rules)
   05  Micro-labels & links
   05b Social glyphs (inline SVG, one per network)
   06  Buttons
   07  Image slots (placeholder system)
   07b Image frames (the mat)
   08  Header & mobile menu
   09  Hero
   10  Packages ledger
   10b Ledger disclosure
   11  Moments row (the four steps of a visit)
   12  Rail (recent work, in their words — the drifting run)
   13  Quote row (about page)
   14  Button row (the home page's two ways in)
   15  Closing CTA
   16  Footer
   17  Page head & split (inner pages)
   18  Compare (before/after slider)
   19  The guide (one question at a time, on a porcelain sheet)
   19b The guide inside the enquiry modal
   20  Notes strip
   20b Enquiry modal
   20c Booking panel (the Bookedin calendar, in a frame)
   21  Motion & reveal
   22  Motion layer (breathing photographs, rising headlines, drawn
       hairlines, settling dots, the logo's twinkle)
   23  Reduced motion
   ========================================================================== */


/* 00a  VIEW TRANSITIONS
   Same-origin navigations cross-fade instead of blinking. The fixed header
   is captured under a name of its own so it stays put while the rest of the
   page changes underneath it. Browsers without view transitions ignore all
   of this and navigate the way they always did.
   ------------------------------------------------------------------------ */
@view-transition { navigation: auto; }

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 280ms;
  animation-timing-function: cubic-bezier(.2, .7, .2, 1);
}


/* 00b  SELF-HOSTED FONTS
   Cormorant Garamond (OFL, variable weight) for display.
   Reflow Sans (Envato Elements licence) for body and UI.
   ------------------------------------------------------------------------ */
@font-face { font-family: "Cormorant Garamond"; font-style: normal; font-weight: 300 700; font-display: swap; src: url("/assets/fonts/CormorantGaramond-variable.woff2") format("woff2"); }
@font-face { font-family: "Cormorant Garamond"; font-style: italic; font-weight: 300 700; font-display: swap; src: url("/assets/fonts/CormorantGaramond-italic-variable.woff2") format("woff2"); }
@font-face { font-family: "Reflow Sans"; font-style: normal; font-weight: 300; font-display: swap; src: url("/assets/fonts/ReflowSans-Light.woff2") format("woff2"); }
@font-face { font-family: "Reflow Sans"; font-style: normal; font-weight: 400; font-display: swap; src: url("/assets/fonts/ReflowSans-Regular.woff2") format("woff2"); }
@font-face { font-family: "Reflow Sans"; font-style: normal; font-weight: 500; font-display: swap; src: url("/assets/fonts/ReflowSans-Medium.woff2") format("woff2"); }
@font-face { font-family: "Reflow Sans"; font-style: normal; font-weight: 600; font-display: swap; src: url("/assets/fonts/ReflowSans-SemiBold.woff2") format("woff2"); }

/* 01  TOKENS
   ------------------------------------------------------------------------ */
:root {
  /* Base */
  --ink: #070a14;
  --ink-2: #0c1020;
  --ink-3: #121732;

  /* Text */
  --porcelain: #ece9e1;
  --porcelain-60: rgba(236, 233, 225, .60);
  --porcelain-40: rgba(236, 233, 225, .40);
  --porcelain-20: rgba(236, 233, 225, .20);

  /* Accent — swap this one line to A/B a gold brand */
  --accent: #3b62ff;
  --accent-2: #7d98ff;
  --accent-deep: #1c2f8f;

  /* Hairlines */
  --rule: rgba(236, 233, 225, .12);

  /* The sheet — the one place the page turns over.
     Every form on the site is a sheet of porcelain laid on the ink, so the
     questions read like something printed rather than something typed into
     the dark. Navy carries the text; the cobalt accent is shared with the
     rest of the site and needs no second value. */
  --sheet: #ece9e1;
  --sheet-ink: #0f1c2e;
  --sheet-ink-60: rgba(15, 28, 46, .62);
  --sheet-ink-40: rgba(15, 28, 46, .42);
  --sheet-rule: rgba(15, 28, 46, .22);
  --sheet-rule-soft: rgba(15, 28, 46, .11);
  --sheet-wash: rgba(15, 28, 46, .035);
  --sheet-accent-wash: rgba(59, 98, 255, .06);

  /* Picture mat: the band of ink between a frame's hairline and the photo.
     Whole pixels on purpose — a fractional value leaves a seam where the
     mat meets the picture. Full-bleed pictures set this to 0 locally. */
  --mat: 12px;
  --mat-bg: var(--ink);
  --mat-edge: rgba(236, 233, 225, .09);

  /* Type families */
  --serif: "Cormorant Garamond", "Iowan Old Style", Georgia, "Times New Roman", serif;
  --sans: "Reflow Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;

  /* Type scale */
  --fs-display: clamp(52px, 7.2vw, 112px);
  --fs-h2: clamp(36px, 4.4vw, 64px);
  --fs-h3: clamp(24px, 2.4vw, 34px);
  --fs-lead: clamp(21px, 2.2vw, 30px);
  --fs-body: 17px;
  --fs-small: 15px;
  --fs-micro: 11px;

  /* Spacing. The home page carries about half the copy it used to, so the
     section rhythm came down a step to match — still generous, no longer
     leaving 320px of nothing between two short blocks. */
  --section-y: clamp(88px, 10vw, 140px);
  --wrap: 1280px;
  --gutter: 32px;

  /* Motion */
  --ease: cubic-bezier(.2, .7, .2, 1);
  --dur: 700ms;

  /* Header height (used for scroll offsets) */
  --header-h: 84px;
}

/* The home page carries three blocks where it used to carry eight, and each
   one is denser than what it replaced. It gets a tighter rhythm than the
   inner pages so the whole thing reads as one run rather than a stack. */
body.is-home { --section-y: clamp(72px, 8vw, 112px); }

@media (max-width: 760px) {
  :root {
    --gutter: 20px;
    --header-h: 68px;
    --mat: 10px;
  }
}


/* 02  RESET & BASE
   ------------------------------------------------------------------------ */
*,
*::before,
*::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 24px);
}

body {
  margin: 0;
  background: var(--ink);
  color: var(--porcelain);
  font-family: var(--sans);
  font-size: var(--fs-body);
  font-weight: 300;
  line-height: 1.7;
  letter-spacing: .005em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

/* Grain — one fixed noise overlay for texture. Never interactive. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  opacity: .035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 140px 140px;
}

/* Body scroll lock while the mobile menu is open */
body.is-locked { overflow: hidden; }

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

h1, h2, h3, h4, p, figure, blockquote, ul, ol, dl, dd {
  margin: 0;
  padding: 0;
}

ul, ol { list-style: none; }

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

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

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

hr {
  border: 0;
  height: 1px;
  background: var(--rule);
  margin: 0;
}

::selection {
  background: var(--accent);
  color: #fff;
}

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

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

.skip-link {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 200;
  transform: translateY(-110%);
  background: var(--accent);
  color: #fff;
  padding: 14px 22px;
  font-size: var(--fs-micro);
  letter-spacing: .18em;
  text-transform: uppercase;
}

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


/* 03  TYPOGRAPHY
   ------------------------------------------------------------------------ */
.display,
.h2,
.h3,
.serif {
  font-family: var(--serif);
  font-weight: 400;
  letter-spacing: -.01em;
}

.display {
  font-size: var(--fs-display);
  line-height: .96;
  letter-spacing: -.022em;
}

.h2 {
  font-size: var(--fs-h2);
  line-height: 1.02;
  letter-spacing: -.018em;
}

.h3 {
  font-size: var(--fs-h3);
  line-height: 1.12;
}

.lead {
  font-family: var(--serif);
  font-size: var(--fs-lead);
  line-height: 1.38;
  font-weight: 400;
}

em,
.italic {
  font-style: italic;
  font-weight: 400;
}

.body-sm {
  font-size: var(--fs-small);
  line-height: 1.72;
  color: var(--porcelain-60);
}

.muted { color: var(--porcelain-60); }

.measure { max-width: 62ch; }
.measure-sm { max-width: 46ch; }
.measure-serif { max-width: 22ch; }


/* 04  LAYOUT UTILITIES
   ------------------------------------------------------------------------ */
.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.wrap--flush { padding-inline: 0; }

.section { padding-block: var(--section-y); }
.section--tight { padding-block: clamp(64px, 8vw, 104px); }
.section--flush-top { padding-top: 0; }
.section--flush-bottom { padding-bottom: 0; }
.section--alt { background: var(--ink-2); }

.rule { height: 1px; background: var(--rule); }
.rule--accent { background: var(--accent); height: 2px; }

.stack > * + * { margin-top: 20px; }
.stack-lg > * + * { margin-top: clamp(28px, 3vw, 44px); }

/* Section header: hairline, optional label, heading. Everything stays flush
   left so headings line up with the grids beneath them. */
.section-head {
  padding-top: 26px;
  border-top: 1px solid var(--rule);
}

.section-head > .eyebrow { margin-bottom: 22px; }

.section-head__body { max-width: 62ch; }
.section-head__body > * + * { margin-top: 22px; }

/* Section footer: a note at left, a link at right, on a hairline. */
.section-foot {
  margin-top: clamp(36px, 4vw, 56px);
  padding-top: 26px;
  border-top: 1px solid var(--rule);
  display: flex;
  flex-wrap: wrap;
  gap: 18px 32px;
  align-items: center;
  justify-content: space-between;
}


/* 05  MICRO-LABELS & LINKS
   ------------------------------------------------------------------------ */
.eyebrow {
  display: block;
  font-family: var(--sans);
  font-size: var(--fs-micro);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--porcelain-40);
}

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

.link-arrow {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--porcelain);
  padding-bottom: 6px;
  border-bottom: 1px solid var(--porcelain-20);
  transition: color .4s var(--ease), border-color .4s var(--ease);
}

.link-arrow::after {
  content: "→";
  letter-spacing: 0;
  transition: transform .4s var(--ease);
}

.link-arrow:hover,
.link-arrow:focus-visible {
  color: var(--accent-2);
  border-bottom-color: var(--accent-2);
}

.link-arrow:hover::after { transform: translateX(5px); }

.link-plain {
  border-bottom: 1px solid var(--porcelain-20);
  padding-bottom: 2px;
  transition: border-color .4s var(--ease), color .4s var(--ease);
}

.link-plain:hover,
.link-plain:focus-visible {
  color: var(--accent-2);
  border-bottom-color: var(--accent-2);
}


/* 05b  SOCIAL GLYPHS
   One anchor, one inline SVG, one visually hidden word. The glyph is drawn
   in hairlines of currentColor at the same weight as the hamburger and the
   close cross, so a network's mark arrives in the site's own line weight
   rather than as a downloaded badge. Size is set by the context that holds
   it: 20px in the header, 22px in the footer and the mobile menu.

   Adding a network is adding one more <a class="social"> beside the first —
   nothing here is Instagram-specific.
   ------------------------------------------------------------------------ */
.social {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  color: var(--porcelain-60);
  transition: color .35s var(--ease);
}

.social svg {
  display: block;
  width: var(--glyph, 20px);
  height: var(--glyph, 20px);
}

.social:hover,
.social:focus-visible { color: var(--porcelain); }

.site-header__actions .social { margin-right: 4px; }

.mobile-menu__foot .social,
.site-footer__list .social { --glyph: 22px; }


/* 06  BUTTONS
   ------------------------------------------------------------------------ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 17px 30px;
  border: 1px solid transparent;
  border-radius: 0;
  font-family: var(--sans);
  font-size: var(--fs-micro);
  font-weight: 600;
  line-height: 1;
  letter-spacing: .16em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color .4s var(--ease), border-color .4s var(--ease), color .4s var(--ease);
}

.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

.btn--primary:hover,
.btn--primary:focus-visible {
  background: var(--accent-2);
  border-color: var(--accent-2);
  color: var(--ink);
}

.btn--ghost {
  background: transparent;
  border-color: var(--porcelain-20);
  color: var(--porcelain);
}

.btn--ghost:hover,
.btn--ghost:focus-visible {
  border-color: var(--porcelain);
  background: rgba(236, 233, 225, .05);
}

.btn--sm { padding: 13px 22px; }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: center;
}


/* 07  IMAGE SLOTS
   Placeholder shows before load and whenever the file is missing.
   ------------------------------------------------------------------------ */
.slot {
  position: relative;
  overflow: hidden;
  aspect-ratio: var(--ratio, 16 / 9);
  background: var(--ink-2);
  isolation: isolate;
}

.slot[data-ratio="21/9"]  { --ratio: 21 / 9; }
.slot[data-ratio="2/1"]   { --ratio: 2 / 1; }
.slot[data-ratio="16/9"]  { --ratio: 16 / 9; }
.slot[data-ratio="3/2"]   { --ratio: 3 / 2; }
.slot[data-ratio="4/3"]   { --ratio: 4 / 3; }
.slot[data-ratio="1/1"]   { --ratio: 1 / 1; }
.slot[data-ratio="3/4"]   { --ratio: 3 / 4; }
.slot[data-ratio="4/5"]   { --ratio: 4 / 5; }
.slot[data-ratio="fill"]  { --ratio: auto; height: 100%; }

/* The designed placeholder: navy gradient + one faint diagonal sweep.
   It fills the window inside the mat, never the mat itself. */
.slot::before {
  content: "";
  position: absolute;
  inset: var(--mat);
  z-index: 0;
  background:
    linear-gradient(118deg, transparent 28%, rgba(125, 152, 255, .10) 45%, rgba(125, 152, 255, .025) 55%, transparent 72%),
    linear-gradient(158deg, #131936 0%, #0a0e1e 58%, #070a14 100%);
}

.slot img {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity .8s var(--ease), transform 900ms var(--ease);
}

/* Only hide images when JS is present to un-hide them. */
.has-js .slot img { opacity: 0; }
.has-js .slot.is-loaded img { opacity: 1; }
.slot.is-missing img { opacity: 0 !important; }

.slot__label {
  position: absolute;
  z-index: 4;
  left: calc(var(--mat) + 16px);
  bottom: calc(var(--mat) + 14px);
  right: calc(var(--mat) + 16px);
  font-family: var(--sans);
  font-size: var(--fs-micro);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--porcelain);
  opacity: .4;
  pointer-events: none;
  transition: opacity .5s var(--ease);
}

.slot.is-loaded .slot__label { opacity: 0; }

.slot--cover {
  position: absolute;
  inset: 0;
  aspect-ratio: auto;
  height: 100%;
}


/* 07b  IMAGE FRAMES
   Every photograph on the site is matted: a hairline around the figure, a
   band of ink between that hairline and the picture, and a second, fainter
   hairline sitting directly on the picture edge.

   The mat is painted OVER the image rather than around it. That keeps the
   figure's data-ratio box exact — so every grid still lines up — and it
   means the 1.03 hover scale is clipped by the mat instead of creeping into
   it. Full-bleed pictures (the hero, and the two layers inside a
   before/after slider) set --mat to 0 and opt out.
   ------------------------------------------------------------------------ */
.slot { border: 1px solid var(--rule); }

.slot::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  box-shadow:
    inset 0 0 0 var(--mat) var(--mat-bg),
    inset 0 0 0 calc(var(--mat) + 1px) var(--mat-edge);
}

/* Inside a mat the picture always eases. .slot--zoom stays valid markup —
   it now says what every framed figure already does. */
.slot img { transform: scale(1.001); }

.slot:hover img,
a:hover .slot img,
a:focus-visible .slot img,
button:hover .slot img,
button:focus-visible .slot img { transform: scale(1.03); }

/* On the alternate ink the mat takes that ground instead. */
.section--alt .slot,
.site-footer .slot { --mat-bg: var(--ink-2); }

/* --- full-bleed exceptions -------------------------------------------- */
.hero__media .slot,
.slot--cover {
  --mat: 0px;
  border: 0;
}

/* A slider's two layers are matted once, by the slider. They keep --mat so
   their placeholders and labels still sit inside that one window. */
.compare__layer .slot { border: 0; }

.hero__media .slot::after,
.compare__layer .slot::after,
.slot--cover::after { content: none; }

.hero__media .slot:hover img,
.compare__layer .slot:hover img { transform: none; }

/* --- the before/after slider is matted as one picture ------------------ */
.compare::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 6;
  pointer-events: none;
  box-shadow:
    inset 0 0 0 var(--mat) var(--mat-bg),
    inset 0 0 0 calc(var(--mat) + 1px) var(--mat-edge);
}


/* 08  HEADER & MOBILE MENU
   ------------------------------------------------------------------------ */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color .5s var(--ease), border-color .5s var(--ease), backdrop-filter .5s var(--ease);
  /* Captured on its own during a page cross-fade, so the one fixed element
     on the page does not flicker while the rest of it changes. */
  view-transition-name: header;
}

.site-header.is-scrolled {
  background: rgba(7, 10, 20, .88);
  -webkit-backdrop-filter: saturate(140%) blur(14px);
  backdrop-filter: saturate(140%) blur(14px);
  border-bottom-color: var(--rule);
}

.site-header__inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 24px;
  width: 100%;
}

.wordmark {
  font-family: var(--serif);
  font-size: 23px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -.005em;
  white-space: nowrap;
  justify-self: start;
}

.wordmark em { font-style: italic; }

/* The header lockup is inlined rather than linked, so the small cobalt
   shine inside it can be animated (see §22). Sized by height, with the
   aspect ratio stated outright so the width never depends on how a browser
   treats `width: auto` on an inline <svg>. */
.wordmark svg {
  display: block;
  height: 28px;
  width: auto;
  aspect-ratio: 200.137 / 30;
  transition: opacity .35s var(--ease);
}
.wordmark:hover svg,
.wordmark:focus-visible svg { opacity: .8; }
@media (max-width: 480px) { .wordmark svg { height: 24px; } }

.site-nav {
  display: flex;
  align-items: center;
  gap: clamp(20px, 2.6vw, 40px);
  justify-self: center;
}

.site-nav a {
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--porcelain-60);
  padding-block: 6px;
  transition: color .35s var(--ease);
}

.site-nav a:hover,
.site-nav a:focus-visible { color: var(--porcelain); }

.site-nav a.is-current {
  color: var(--porcelain);
  box-shadow: inset 0 -1px 0 var(--accent);
}

.site-header__actions {
  display: flex;
  align-items: center;
  gap: 14px;
  justify-self: end;
}

/* Hamburger: two hairlines, no icon font. */
.nav-toggle {
  display: none;
  width: 44px;
  height: 44px;
  margin-right: -12px;
  position: relative;
}

.nav-toggle span {
  position: absolute;
  left: 11px;
  width: 22px;
  height: 1px;
  background: var(--porcelain);
  transition: transform .4s var(--ease), opacity .3s var(--ease);
}

.nav-toggle span:nth-child(1) { top: 19px; }
.nav-toggle span:nth-child(2) { top: 25px; }

.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(3px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { transform: translateY(-3px) rotate(-45deg); }

.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--ink);
  display: flex;
  flex-direction: column;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .45s var(--ease), visibility .45s var(--ease);
}

.mobile-menu.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.mobile-menu__inner {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding: var(--header-h) var(--gutter) 40px;
}

.mobile-menu a.mobile-menu__link {
  display: block;
  font-family: var(--serif);
  font-size: clamp(38px, 11vw, 58px);
  font-weight: 400;
  line-height: 1.18;
  letter-spacing: -.02em;
  color: var(--porcelain);
  padding-block: 8px;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .5s var(--ease), transform .5s var(--ease), color .3s var(--ease);
}

.mobile-menu.is-open a.mobile-menu__link {
  opacity: 1;
  transform: none;
  transition-delay: calc(80ms + var(--i, 0) * 55ms);
}

.mobile-menu a.mobile-menu__link.is-current { color: var(--accent-2); }

.mobile-menu__foot {
  margin-top: 44px;
  padding-top: 28px;
  border-top: 1px solid var(--rule);
  display: flex;
  flex-wrap: wrap;
  gap: 16px 28px;
  align-items: center;
}

@media (max-width: 940px) {
  .site-nav { display: none; }
  .nav-toggle { display: block; }
  .site-header__inner { grid-template-columns: 1fr auto; }
  .site-header__actions .btn--primary { display: none; }
}

@media (min-width: 941px) {
  .mobile-menu { display: none; }
}


/* 09  HERO
   ------------------------------------------------------------------------ */
.hero {
  position: relative;
  min-height: clamp(620px, 96vh, 1000px);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  overflow: hidden;
  padding-top: calc(var(--header-h) + 40px);
}

/* z-index stays auto so this does not open a stacking context — the slot
   label needs to reach above the scrim. */
.hero__media {
  position: absolute;
  inset: 0;
}

.hero__media .slot {
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
  /* Dropped so the slot label can sit above the scrim while the photo
     is still missing. It hides itself as soon as the image loads. */
  isolation: auto;
}

/* Sits at the top here — the bottom-left corner belongs to the facts strip. */
.hero__media .slot__label {
  z-index: 4;
  top: calc(var(--header-h) + 16px);
  bottom: auto;
}

.hero__media .slot img {
  transform: scale(1.06);
  transition: opacity 1s var(--ease), transform 6s var(--ease);
}

body.is-ready .hero__media .slot img { transform: scale(1); }

/* Optional looping clip over the hero still. Fades in once it is actually
   playing, so the photo is what everyone sees first. Off on phones and
   under reduced-motion (main.js never starts it there either). */
.hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1.4s var(--ease);
  pointer-events: none;
}
.hero__video.is-playing { opacity: 1; }
@media (max-width: 767px) { .hero__video { display: none; } }
@media (prefers-reduced-motion: reduce) { .hero__video { display: none; } }

.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(180deg, rgba(7, 10, 20, .82) 0%, rgba(7, 10, 20, .34) 32%, rgba(7, 10, 20, .72) 74%, var(--ink) 100%),
    linear-gradient(92deg, rgba(7, 10, 20, .80) 0%, rgba(7, 10, 20, .18) 62%, transparent 100%);
}

.hero__body {
  position: relative;
  z-index: 2;
  width: 100%;
  padding-bottom: clamp(40px, 6vw, 72px);
}

.hero__eyebrow { margin-bottom: clamp(22px, 3vw, 34px); }

.hero__title {
  font-family: var(--serif);
  font-weight: 400;
  font-size: var(--fs-display);
  line-height: .96;
  letter-spacing: -.022em;
  max-width: 16ch;
}

.hero__title .line {
  display: block;
  overflow: hidden;
}

.hero__title .line > span {
  display: block;
  transform: translateY(102%);
  transition: transform 900ms var(--ease);
  transition-delay: calc(var(--i, 0) * 80ms);
}

body.is-ready .hero__title .line > span { transform: translateY(0); }

.hero__title em { color: var(--porcelain); }

.hero__actions {
  margin-top: clamp(32px, 4vw, 48px);
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .8s var(--ease) .42s, transform .8s var(--ease) .42s;
}

body.is-ready .hero__actions {
  opacity: 1;
  transform: none;
}

/* Facts along the bottom edge, on a hairline. */
.hero__facts {
  position: relative;
  z-index: 2;
  border-top: 1px solid var(--rule);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  opacity: 0;
  transition: opacity .9s var(--ease) .6s;
}

body.is-ready .hero__facts { opacity: 1; }

.hero__fact {
  padding: 22px 0;
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--porcelain-60);
}

.hero__fact + .hero__fact {
  border-left: 1px solid var(--rule);
  padding-left: clamp(16px, 3vw, 40px);
}

@media (max-width: 700px) {
  .hero { min-height: 88vh; }
  .hero__title { max-width: 100%; }
  .hero__facts { grid-template-columns: 1fr; }
  .hero__fact { padding: 13px 0; }
  .hero__fact + .hero__fact {
    border-left: 0;
    border-top: 1px solid var(--rule);
    padding-left: 0;
  }
}


/* 10  PACKAGES LEDGER
   One comparison grid. Column-major markup + subgrid keeps rows aligned on
   desktop and lets each package stack into its own readable list on mobile.
   ------------------------------------------------------------------------ */
.ledger-intro {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-end;
  gap: 24px;
  margin-bottom: clamp(40px, 5vw, 64px);
}

.ledger {
  display: grid;
  grid-template-columns: minmax(0, 1.45fr) repeat(3, minmax(0, 1fr));
  /* 1 head + 3 group headings + 14 inclusion rows */
  grid-template-rows: repeat(18, auto);
  column-gap: clamp(16px, 2.4vw, 40px);
}

.ledger__labels,
.ledger__col {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: 1 / -1;
  min-width: 0;
}

/* --- head ------------------------------------------------------------- */
.ledger__corner,
.ledger__head {
  border-top: 1px solid var(--rule);
  padding-top: 22px;
  padding-bottom: 26px;
}

.ledger__col--featured .ledger__head {
  border-top: 2px solid var(--accent);
  margin-top: -1px;
}

.ledger__col--featured {
  background: linear-gradient(180deg, rgba(59, 98, 255, .07) 0%, rgba(59, 98, 255, .015) 34%, transparent 62%);
}

.ledger__name {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(22px, 2.1vw, 30px);
  line-height: 1.06;
  letter-spacing: -.015em;
}

.ledger__price {
  margin-top: 8px;
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--porcelain-60);
}

.ledger__tag {
  margin-bottom: 10px;
  color: var(--accent-2);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
}

.ledger__tag--empty { visibility: hidden; }

.ledger__book {
  display: inline-block;
  margin-top: 18px;
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--porcelain);
  border-bottom: 1px solid var(--porcelain-20);
  padding-bottom: 5px;
  transition: color .35s var(--ease), border-color .35s var(--ease);
}

.ledger__book:hover,
.ledger__book:focus-visible {
  color: var(--accent-2);
  border-bottom-color: var(--accent-2);
}

/* --- group headings ---------------------------------------------------- */
.ledger__group {
  align-self: end;
  padding-top: 30px;
  padding-bottom: 12px;
  font-family: var(--sans);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--porcelain-40);
}

/* Spacer sitting in the package columns at each group row. */
.ledger__gap { align-self: end; }
.ledger__gap-label { display: none; }

/* --- rows -------------------------------------------------------------- */
.ledger__label,
.ledger__cell {
  border-top: 1px solid var(--rule);
  padding-block: 15px;
  min-width: 0;
}

.ledger__label {
  font-size: var(--fs-small);
  line-height: 1.45;
  color: var(--porcelain);
  padding-right: 20px;
}

.ledger__cell {
  display: flex;
  align-items: flex-start;
}

.ledger__cell-label { display: none; }

/* The filled dot. Empty cells stay empty — no dashes, no crosses. */
.ledger__dot {
  display: block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--porcelain);
  margin-top: 8px;
  flex: none;
}

.ledger__col--featured .ledger__dot { background: var(--accent-2); }

.ledger__cell.is-out .ledger__dot {
  background: none;
  box-shadow: inset 0 0 0 1px var(--porcelain-20);
}

/* --- reading one package at a time ------------------------------------- */
/* The table is three grids stacked (heads, the differences, the full list),
   so hovering a column has to be told which column it is. main.js numbers
   the columns and puts the live index on the host; the wash and the brighter
   dots then run the whole height of that package. Pointer devices only —
   there is nothing to hover on a phone, where the table is already stacked
   one package to a block. */
@media (hover: hover) and (pointer: fine) and (min-width: 901px) {
  .ledger__col { transition: background-color .5s var(--ease); }
  .ledger__dot { transition: background-color .4s var(--ease), box-shadow .4s var(--ease); }

  [data-hot="0"] .ledger__col[data-col="0"],
  [data-hot="1"] .ledger__col[data-col="1"],
  [data-hot="2"] .ledger__col[data-col="2"] { background-color: rgba(125, 152, 255, .05); }

  [data-hot="0"] .ledger__col[data-col="0"] .ledger__dot,
  [data-hot="1"] .ledger__col[data-col="1"] .ledger__dot,
  [data-hot="2"] .ledger__col[data-col="2"] .ledger__dot { background: var(--accent-2); }

  [data-hot="0"] .ledger__col[data-col="0"] .ledger__cell.is-out .ledger__dot,
  [data-hot="1"] .ledger__col[data-col="1"] .ledger__cell.is-out .ledger__dot,
  [data-hot="2"] .ledger__col[data-col="2"] .ledger__cell.is-out .ledger__dot {
    background: none;
    box-shadow: inset 0 0 0 1px rgba(125, 152, 255, .42);
  }
}

/* .section-foot (section 04) is the shared footer row for this and other
   sections; the ledger needs no footer rule of its own. */

@media (max-width: 900px) {
  /* Stacked list per package. */
  .ledger {
    display: block;
    grid-template-columns: none;
    grid-template-rows: none;
  }

  .ledger__labels { display: none; }

  .ledger__col {
    display: block;
    grid-template-rows: none;
    background: none;
    padding-bottom: 4px;
  }

  .ledger__col + .ledger__col { margin-top: 46px; }

  .ledger__head {
    border-top: 1px solid var(--rule);
    padding-top: 20px;
    padding-bottom: 20px;
  }

  .ledger__col--featured .ledger__head { border-top: 2px solid var(--accent); }

  .ledger__tag--empty { display: none; }

  .ledger__gap {
    display: block;
    padding-top: 26px;
    padding-bottom: 10px;
  }

  .ledger__gap-label {
    display: block;
    font-size: var(--fs-micro);
    font-weight: 500;
    letter-spacing: .18em;
    text-transform: uppercase;
    color: var(--porcelain-40);
  }

  .ledger__cell {
    align-items: baseline;
    gap: 14px;
    padding-block: 11px;
  }

  .ledger__cell-label {
    display: block;
    font-size: var(--fs-small);
    line-height: 1.45;
  }

  .ledger__cell.is-out {
    color: var(--porcelain-40);
    opacity: .55;
  }

  /* Marker to the left of the text so the column of dots stays straight. */
  .ledger__dot {
    order: -1;
    margin-top: 7px;
  }
}


/* 10b  LEDGER DISCLOSURE
   The home page shows the heads and the interior group; the remaining two
   groups live in a panel that opens on a row height animation. The closing
   rule of the table is the toggle itself, so the panel grows above it and
   the ledger never looks unfinished.
   ------------------------------------------------------------------------ */
.ledger-more {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  visibility: hidden;
  transition:
    grid-template-rows .6s var(--ease),
    opacity .45s var(--ease),
    visibility 0s linear .6s;
}

.ledger-more__inner {
  overflow: hidden;
  min-height: 0;
}

.ledger-more.is-open {
  grid-template-rows: 1fr;
  opacity: 1;
  visibility: visible;
  transition:
    grid-template-rows .6s var(--ease),
    opacity .55s var(--ease) .1s,
    visibility 0s;
}

/* Both halves run the same column tracks, so the continuation's dots land
   under the heads above it. The row counts differ: 1 head + 1 group + 4
   inclusions up top, 2 groups + 10 inclusions below. */
.ledger--head { grid-template-rows: auto; }
.ledger--diff { grid-template-rows: repeat(7, auto); }
.ledger--more { grid-template-rows: repeat(17, auto); }

/* The collapsed view: one group of the rows that differ. It folds away as
   the full list opens, so the heads never sit over two copies of a row. */
.ledger-less {
  display: grid;
  grid-template-rows: 1fr;
  opacity: 1;
  transition: grid-template-rows .6s var(--ease), opacity .35s var(--ease);
}
.ledger-less__inner { overflow: hidden; min-height: 0; }
.is-open > .ledger-less {
  grid-template-rows: 0fr;
  opacity: 0;
  visibility: hidden;
  transition: grid-template-rows .6s var(--ease), opacity .3s var(--ease), visibility 0s linear .6s;
}
.ledger--diff .ledger__col--featured { background: none; }
.ledger-toggle__open { display: none; }
[aria-expanded="true"] .ledger-toggle__closed { display: none; }
[aria-expanded="true"] .ledger-toggle__open { display: inline; }

/* The featured wash belongs to the top of the column, not to a second run
   of rows starting halfway down the table. */
.ledger--more .ledger__col--featured { background: none; }

.ledger-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  border-top: 1px solid var(--rule);
  padding-block: 20px;
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--porcelain-60);
  transition: color .35s var(--ease);
}

.ledger-toggle:hover,
.ledger-toggle:focus-visible { color: var(--porcelain); }

/* A plus that becomes a minus. Two hairlines, no icon font. Shared with the
   steps row so both disclosures speak the same language. */
.disclose-mark {
  position: relative;
  width: 13px;
  height: 13px;
  flex: none;
  color: var(--porcelain-40);
  transition: color .4s var(--ease);
}

.disclose-mark::before,
.disclose-mark::after {
  content: "";
  position: absolute;
  top: 6px;
  left: 0;
  width: 13px;
  height: 1px;
  background: currentColor;
  transition: transform .45s var(--ease);
}

.disclose-mark::after { transform: rotate(90deg); }

[aria-expanded="true"] .disclose-mark::after { transform: rotate(0deg); }

.ledger-toggle .disclose-mark { color: inherit; }

/* Stacked, each continuation column needs to say whose rows these are. */
.ledger__gap-name { display: none; }

@media (max-width: 900px) {
  /* Stacked, the differences grid repeats every package name above a short
     list of rows the heads have already implied — three more blocks to
     scroll past before the page gets anywhere. On a phone the heads stand
     alone and the toggle is the way into the detail; the full grouped list
     it opens still names each package. */
  .ledger-less { display: none; }

  /* Three heads in a row of their own do not need the air a whole stacked
     package column needed. */
  .ledger--head .ledger__col + .ledger__col { margin-top: 18px; }
  .ledger--head .ledger__col { padding-bottom: 0; }
  .ledger--head .ledger__head { padding-top: 16px; padding-bottom: 18px; }
  .ledger--head .ledger__book { margin-top: 14px; }

  .ledger--more .ledger__col + .ledger__col,
  .ledger--diff .ledger__col + .ledger__col { margin-top: 34px; }

  .ledger__gap-name {
    display: block;
    font-family: var(--serif);
    font-size: clamp(20px, 2vw, 26px);
    font-weight: 400;
    line-height: 1.1;
    letter-spacing: -.012em;
    color: var(--porcelain);
    margin-bottom: 14px;
    text-transform: none;
  }
}

/* No JS, no disclosure: the whole ledger simply stands open. */
html:not(.has-js) .ledger-more {
  grid-template-rows: 1fr;
  opacity: 1;
  visibility: visible;
}

html:not(.has-js) .ledger-toggle { display: none; }
html:not(.has-js) .ledger-less { display: none; }


/* 11  MOMENTS ROW
   Four moments on hairlines. Deliberately un-numbered. The title is the
   whole column until you reach for it: hover, focus or tap raises the
   sentence underneath. The row reserves its own height so nothing below
   the section moves while you read across it.

   On the home page this row has no heading of its own — it sits under the
   packages ledger as the rest of the same answer, separated by space.
   ------------------------------------------------------------------------ */
.steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0 clamp(20px, 2.6vw, 44px);
}

.steps--moments { margin-top: clamp(56px, 7vw, 96px); }

.step {
  position: relative;
  border-top: 1px solid var(--rule);
  padding-top: 24px;
}

.step__name {
  font-family: var(--serif);
  font-size: clamp(21px, 1.9vw, 27px);
  font-weight: 400;
  line-height: 1.14;
  letter-spacing: -.012em;
}

/* The title and the plus sit on one line, the mark pinned right so the four
   of them make a column of marks down the row. */
.step__toggle {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  width: 100%;
  text-align: left;
  font: inherit;
  color: var(--porcelain);
  letter-spacing: inherit;
}

.step__toggle .disclose-mark { align-self: center; }

.step:hover .disclose-mark,
.step:focus-within .disclose-mark,
.step.is-open .disclose-mark { color: var(--porcelain); }

.step:hover .disclose-mark::after,
.step:focus-within .disclose-mark::after { transform: rotate(0deg); }

/* The column's own hairline is the affordance — the title stays porcelain
   so it never reads as a link. */
.step { transition: border-color .4s var(--ease); }

.step:hover,
.step:focus-within,
.step.is-open { border-top-color: var(--accent); }

.step__reveal {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  visibility: hidden;
  transition:
    grid-template-rows .55s var(--ease),
    opacity .4s var(--ease),
    visibility 0s linear .55s;
}

.step__reveal > div {
  overflow: hidden;
  min-height: 0;
}

.step__text {
  margin-top: 14px;
  font-size: var(--fs-small);
  line-height: 1.7;
  color: var(--porcelain-60);
  transform: translateY(8px);
  transition: transform .55s var(--ease);
}

.step:hover .step__reveal,
.step:focus-within .step__reveal,
.step.is-open .step__reveal {
  grid-template-rows: 1fr;
  opacity: 1;
  visibility: visible;
  transition:
    grid-template-rows .55s var(--ease),
    opacity .45s var(--ease) .08s,
    visibility 0s;
}

.step:hover .step__text,
.step:focus-within .step__text,
.step.is-open .step__text { transform: none; }

/* No JS: the sentences still answer hover and keyboard focus. */
html:not(.has-js) .step__toggle { cursor: default; }

/* Four titles across: the sentence opens into the section's own bottom
   padding rather than pushing the page around while you read the row. */
@media (min-width: 901px) {
  .step__reveal {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
  }
}

@media (max-width: 900px) {
  .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 36px clamp(20px, 4vw, 40px); }
}

@media (max-width: 560px) {
  .steps { grid-template-columns: 1fr; gap: 30px; }
}


/* 12  RAIL — recent work, in their words
   One sideways run that mixes framed photographs with the reviews, in place
   of the image band, the recent-work grid and the testimonial rotator the
   home page used to carry separately.

   Motion: the run moves on its own, left, at a walking 28px a second, and
   never arrives anywhere — main.js duplicates the tile set and wraps the
   offset, so there is no first tile and no last one. It stands still under
   the hand on a pointer device, and under a held finger on a touch one; a
   drag scrubs it either way and lets go with a little weight behind it.
   All of that is one transform on one element, written by one animation
   frame, and the frame stops entirely when the run is off screen or the
   tab is in the background.

   Geometry: tiles differ in height as well as width — a tall 3:4, a square,
   a low 3:2, and quote tiles shorter still — all standing on one ground
   line, so the run has a skyline instead of reading as a row of boxes.

   Under prefers-reduced-motion none of this runs: §23 turns the viewport
   into a plain horizontal scroller and main.js leaves it alone.
   ------------------------------------------------------------------------ */
.rail-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px 32px;
  border-top: 1px solid var(--rule);
  padding-top: 26px;
  margin-bottom: clamp(34px, 4vw, 54px);
}

.rail-head__title { max-width: none; }

/* Said once. It goes for good the first time the run is actually dragged,
   and main.js remembers that for the rest of the session. */
.rail-hint {
  flex: none;
  padding-bottom: 7px;
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--porcelain-40);
  transition: opacity .7s var(--ease), visibility 0s linear .7s;
}

.rail-hint.is-gone {
  opacity: 0;
  visibility: hidden;
}

/* Nothing to drag without JS, and nothing to say about it. */
html:not(.has-js) .rail-hint { display: none; }

/* --- the run ----------------------------------------------------------- */
.rail {
  --rail-h: clamp(300px, 34vw, 480px);
  --rail-gap: clamp(14px, 1.6vw, 26px);

  /* Only §23 uses this now: the distance from the page edge to the text
     column, struck in per cent of the page rather than vw so a scrollbar
     cannot knock the first tile out of line with the heading above it. */
  --rail-inset: max(var(--gutter), calc((100% - var(--wrap)) / 2 + var(--gutter)));
}

/* The window the run passes behind. It is the full page width, so the strip
   bleeds off both edges at every moment — there is never a tile standing
   flush against a margin. */
.rail__viewport {
  position: relative;
  overflow: hidden;
  touch-action: pan-y;
}

.rail__viewport:focus-visible { outline-offset: -3px; }

@media (hover: hover) and (pointer: fine) {
  .rail__viewport { cursor: grab; }
  .rail__viewport.is-dragging { cursor: grabbing; }
}

.rail__viewport.is-dragging {
  -webkit-user-select: none;
  user-select: none;
}

/* The one element that moves. main.js writes a translate3d here and nothing
   else, and only marks it will-change while the frame is actually running. */
.rail__track {
  display: flex;
  align-items: flex-end;
  gap: var(--rail-gap);
  width: max-content;
}

.rail__set {
  display: flex;
  align-items: flex-end;
  gap: var(--rail-gap);
}

/* The run opens once, the first time it is on screen: a curtain of the
   page's own ink drawn back to the left across the whole strip. One moment
   rather than a wipe per tile — with the strip already moving, nine
   separate wipes would be noise. */
.rail__viewport::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 5;
  background: var(--ink);
  transform-origin: 0 50%;
  transition: transform 1100ms var(--ease);
  pointer-events: none;
}

.rail__viewport.is-in::after { transform: scaleX(0); }

/* No JS, nothing drifts and nothing is duplicated — so the window has to
   become a scroller, or everything past the right edge is unreachable.
   §23 does exactly the same under prefers-reduced-motion. */
html:not(.has-js) .rail__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding-inline-start: var(--rail-inset);
  padding-inline-end: var(--gutter);
  touch-action: auto;
  cursor: auto;
}

html:not(.has-js) .rail__viewport::-webkit-scrollbar { width: 0; height: 0; display: none; }
html:not(.has-js) .rail__viewport::after { content: none; }

/* --- the skyline --------------------------------------------------------- */
/* Every tile stands on the same ground line and reaches a different height.
   Widths follow from the heights, so each photograph keeps its own shape. */
.rail__item {
  position: relative;
  flex: none;
  height: var(--rail-h);
  width: var(--rail-h);
}

.rail__item[data-shape="tall"]   { height: var(--rail-h); width: calc(var(--rail-h) * .75); }
.rail__item[data-shape="square"] { height: calc(var(--rail-h) * .86); width: calc(var(--rail-h) * .86); }
.rail__item[data-shape="wide"]   { height: calc(var(--rail-h) * .62); width: calc(var(--rail-h) * .93); }

/* --- photographs -------------------------------------------------------- */
.rail__item--photo .slot {
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
}

/* --- quotes -------------------------------------------------------------- */
/* A window in the run rather than a card laid over it: the page's own ink,
   one hairline, and nothing else. Shorter than the photographs beside it,
   so the run's top edge keeps breaking.

   The tile, its padding and its type are all struck off --rail-h rather
   than off the viewport. That is what keeps a review filling its tile the
   same way at 1920 as at 700: the box and the words shrink together, so
   the longest of the three never runs out of room at some width in
   between. Only the floor is absolute, for the sake of small phones. */
.rail__item--quote {
  --q-pad: calc(var(--rail-h) * .055);
  height: calc(var(--rail-h) * .78);
  width: calc(var(--rail-h) * 1.1);
  display: flex;
  flex-direction: column;
  border: 1px solid var(--rule);
  background: var(--ink);
  padding: var(--q-pad);
}

.rail__quote {
  font-family: var(--serif);
  font-weight: 400;
  font-size: max(16px, calc(var(--rail-h) * .058));
  line-height: 1.24;
  letter-spacing: -.015em;
  color: var(--porcelain);
}

.rail__cite {
  margin-top: auto;
  padding-top: calc(var(--q-pad) * .8);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--porcelain-40);
}

/* --- the way out --------------------------------------------------------- */
/* The run does not end on another rectangle — it ends on the link out, as
   wide as the words in it and no wider. */
.rail__item--end {
  height: auto;
  width: auto;
  display: flex;
  align-items: flex-end;
  padding: 0 0 6px clamp(10px, 1.4vw, 26px);
}

@media (max-width: 620px) {
  .rail {
    --rail-h: clamp(260px, 70vw, 330px);
    --rail-gap: 12px;
  }

  /* Nothing may be wider than the screen, or half of it is unreadable.
     A phone's quote tile takes the extra width the run can spare — short
     lines of a 200-character review are hard going on a small screen. */
  .rail__item[data-shape="wide"] { width: min(calc(var(--rail-h) * .93), 86vw); }

  .rail-head { display: block; }
  .rail-hint { display: block; margin-top: 22px; padding-bottom: 0; }
}

/* Below about 900px the run has already hit the floor of its height clamp
   while the words in a quote tile are still near their 16px floor, so the
   desktop proportions stop leaving a 200-character review enough room. The
   tile takes the width and the height it needs, and nothing is ever wider
   than the screen. */
@media (max-width: 900px) {
  .rail__item--quote {
    height: calc(var(--rail-h) * .88);
    width: min(calc(var(--rail-h) * 1.32), 86vw);
  }
}


/* 13  QUOTE ROW
   Three reviews side by side. The about page's treatment; the home page
   carries the same three quotes inside the rail instead.
   ------------------------------------------------------------------------ */
.quote-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(28px, 4vw, 56px);
}

.quote-row figure {
  border-top: 1px solid var(--rule);
  padding-top: 26px;
}

.quote-row blockquote {
  font-family: var(--serif);
  font-size: clamp(19px, 1.6vw, 23px);
  line-height: 1.34;
  letter-spacing: -.008em;
}

.quote-row figcaption {
  margin-top: 18px;
  font-size: var(--fs-micro);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--porcelain-40);
}

@media (max-width: 860px) {
  .quote-row { grid-template-columns: 1fr; gap: 30px; }
}


/* 14  BUTTON ROW
   The home page's way out. The run of work above it has already made the
   case, so there is no claim to restate and no heading to carry: two
   buttons on the text column, and then the footer.
   ------------------------------------------------------------------------ */
.cta-row { padding-block: clamp(40px, 4.5vw, 64px); }


/* 15  CLOSING CTA
   A serif line and the two ways in — the inner pages' way out.
   ------------------------------------------------------------------------ */
.closing {
  border-top: 1px solid var(--rule);
  padding-block: clamp(88px, 11vw, 150px);
}

.closing__line {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(34px, 5.2vw, 78px);
  line-height: 1.03;
  letter-spacing: -.02em;
  max-width: 17ch;
}

.closing .btn-row { margin-top: clamp(36px, 4.5vw, 56px); }


/* 16  FOOTER
   ------------------------------------------------------------------------ */
.site-footer {
  border-top: 1px solid var(--rule);
  background: var(--ink-2);
  padding-top: clamp(56px, 7vw, 88px);
  padding-bottom: 34px;
}

.site-footer__top {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(36px, 5vw, 72px);
  align-items: start;
}

.site-footer__mark {
  margin: 0;
  line-height: 0;
}
.site-footer__mark img {
  display: block;
  height: clamp(64px, 7vw, 92px);
  width: auto;
}

.site-footer__tagline {
  margin-top: 18px;
  font-size: var(--fs-small);
  color: var(--porcelain-60);
  max-width: 30ch;
}

.site-footer__colhead {
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--porcelain-40);
  padding-bottom: 18px;
  border-bottom: 1px solid var(--rule);
}

.site-footer__list { margin-top: 18px; }

.site-footer__list li + li { margin-top: 9px; }

.site-footer__list a,
.site-footer__list span {
  font-size: var(--fs-small);
  color: var(--porcelain-60);
  transition: color .35s var(--ease);
}

.site-footer__list a:hover,
.site-footer__list a:focus-visible { color: var(--porcelain); }

.site-footer__bottom {
  margin-top: clamp(48px, 6vw, 76px);
  padding-top: 26px;
  border-top: 1px solid var(--rule);
  display: flex;
  flex-wrap: wrap;
  gap: 12px 26px;
  justify-content: space-between;
  font-size: var(--fs-micro);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--porcelain-40);
}

.site-footer__credit a {
  color: inherit;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 2px;
  transition: color .3s var(--ease), border-color .3s var(--ease);
}
.site-footer__credit a:hover,
.site-footer__credit a:focus-visible { color: var(--porcelain); border-color: var(--porcelain-40); }

@media (max-width: 860px) {
  .site-footer__top { grid-template-columns: 1fr 1fr; }
  .site-footer__mark { grid-column: 1 / -1; }
}

@media (max-width: 520px) {
  .site-footer__top { grid-template-columns: 1fr; }
}


/* 17  PAGE HEAD & SPLIT (inner pages)
   ------------------------------------------------------------------------ */
.page-head {
  padding-top: calc(var(--header-h) + clamp(64px, 9vw, 128px));
  padding-bottom: clamp(56px, 7vw, 96px);
}

.page-head__label { margin-bottom: clamp(26px, 3vw, 38px); }

.page-head__title {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(40px, 5.6vw, 88px);
  line-height: 1.0;
  letter-spacing: -.022em;
  max-width: 18ch;
}

.page-head__note {
  margin-top: clamp(26px, 3vw, 38px);
  padding-top: 24px;
  border-top: 1px solid var(--rule);
  font-size: var(--fs-small);
  line-height: 1.72;
  color: var(--porcelain-60);
  max-width: 56ch;
}

.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 88px);
  align-items: start;
}

.split--wide-left { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); }
.split--wide-right { grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); }

.split__aside.is-sticky {
  position: sticky;
  top: calc(var(--header-h) + 32px);
}

.split__aside.is-sticky > * + * { margin-top: clamp(14px, 1.6vw, 24px); }

@media (max-width: 860px) {
  .split,
  .split--wide-left,
  .split--wide-right {
    grid-template-columns: 1fr;
    gap: 34px;
  }
  .split__aside.is-sticky { position: static; }
}

/* Stacked, hairline-separated blocks (services packages, values rows) */
.rows > * {
  border-top: 1px solid var(--rule);
  padding-block: clamp(38px, 5vw, 64px);
}

.rows > *:last-child { border-bottom: 1px solid var(--rule); }

.row-pair {
  display: grid;
  grid-template-columns: minmax(0, 220px) minmax(0, 1fr);
  gap: clamp(18px, 4vw, 64px);
  align-items: baseline;
}

@media (max-width: 700px) {
  .row-pair { grid-template-columns: 1fr; gap: 12px; }
}

/* Two-column inclusion list used on services.html */
.includes {
  columns: 2;
  column-gap: clamp(24px, 3vw, 48px);
  font-size: var(--fs-small);
  color: var(--porcelain-60);
}

.includes li {
  break-inside: avoid;
  padding-block: 7px;
  border-top: 1px solid var(--rule);
}

@media (max-width: 560px) {
  .includes { columns: 1; }
}


/* 18  COMPARE (before/after slider)
   ------------------------------------------------------------------------ */
/* The mat itself is in 07b so the whole frame treatment reads in one place. */
.compare {
  --pos: 50%;
  position: relative;
  overflow: hidden;
  aspect-ratio: 3 / 2;
  background: var(--ink-2);
  border: 1px solid var(--rule);
  touch-action: pan-y;
}

.compare__layer {
  position: absolute;
  inset: 0;
}

.compare__layer .slot {
  position: absolute;
  inset: 0;
  aspect-ratio: auto;
  height: 100%;
}

.compare__layer--after { clip-path: inset(0 0 0 var(--pos)); }

.compare__handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--pos);
  width: 1px;
  background: var(--porcelain);
  z-index: 4;
  pointer-events: none;
}

.compare__handle::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 46px;
  height: 46px;
  transform: translate(-50%, -50%);
  border: 1px solid var(--porcelain);
  background: rgba(7, 10, 20, .35);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

.compare__range {
  position: absolute;
  inset: 0;
  z-index: 5;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: ew-resize;
  -webkit-appearance: none;
  appearance: none;
  background: none;
}

.compare__range::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 46px;
  height: 200px;
  cursor: ew-resize;
}

.compare__range::-moz-range-thumb {
  width: 46px;
  height: 200px;
  border: 0;
  cursor: ew-resize;
}

.compare__range:focus-visible { outline-offset: -4px; }

.compare__tag {
  position: absolute;
  z-index: 7;
  bottom: calc(var(--mat) + 14px);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--porcelain);
  opacity: .65;
  pointer-events: none;
}

.compare__tag--before { left: calc(var(--mat) + 16px); }
.compare__tag--after { right: calc(var(--mat) + 16px); }

.compare-caption {
  margin-top: 14px;
  font-size: var(--fs-micro);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--porcelain-40);
}


/* 19  THE GUIDE
   One question at a time, on a sheet of porcelain laid over the ink page.

   The inversion is the whole idea. Everywhere else the site is ink with
   porcelain set on it; here the sheet turns over, so the questions read
   like something printed and handed across rather than something typed
   into the dark. It is the same component in both places it appears — the
   contact page and the enquiry modal — and it degrades, without JS, into
   every fieldset standing open in order above one Send button.

   Motion: the cobalt line along the top edge grows a step at a time, and
   each question crossfades up twelve pixels as it arrives. Nothing else on
   the sheet moves.
   ------------------------------------------------------------------------ */
.guide {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--sheet);
  color: var(--sheet-ink);
  padding:
    clamp(32px, 3.6vw, 48px)
    clamp(24px, 3vw, 46px)
    clamp(24px, 2.8vw, 34px);
}

/* --- the line along the top edge ---------------------------------------- */
.guide__progress {
  display: none;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--sheet-rule-soft);
}

.guide__progress span {
  display: block;
  height: 100%;
  background: var(--accent);
  transform: scaleX(var(--p, 0));
  transform-origin: 0 50%;
  transition: transform 520ms var(--ease);
}

.guide.is-guided .guide__progress { display: block; }

/* --- the questions ------------------------------------------------------- */
/* The stage keeps a floor so the sheet does not change height under the
   reader as the questions change. The modal overrides it: there the panel
   is the height and the stage is what scrolls. */
.guide__stage {
  flex: 1 1 auto;
  min-height: 0;
}

.guide.is-guided .guide__stage { min-height: clamp(158px, 19vw, 200px); }

.guide__step {
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}

.guide__step + .guide__step { margin-top: clamp(36px, 4vw, 52px); }

/* Guided, one question stands at a time. Un-guided — no JS — they all do,
   in order, and the review has nothing in it to show. */
.guide.is-guided .guide__step { display: none; margin-top: 0; }
.guide__step--review { display: none; }

.guide.is-guided .guide__step.is-active {
  display: block;
  animation: guide-step 420ms var(--ease) both;
}

@keyframes guide-step {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

.guide__q {
  display: block;
  float: none;
  width: 100%;
  padding: 0;
  margin: 0 0 clamp(22px, 2.6vw, 32px);
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(29px, 3.3vw, 44px);
  line-height: 1.04;
  letter-spacing: -.02em;
  color: var(--sheet-ink);
  max-width: 17ch;
}

/* --- fields -------------------------------------------------------------- */
.guide__field { display: block; min-width: 0; }

.guide__field + .guide__field { margin-top: clamp(20px, 2.4vw, 28px); }

/* The label wraps its control, so the guide can appear twice on one page
   without a single id between the two copies of it. */
.guide__wrap { display: block; }

.guide__label {
  display: block;
  margin-bottom: 6px;
  font-size: 13px;
  line-height: 1.4;
  color: var(--sheet-ink-40);
}

.guide__input {
  width: 100%;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--sheet-rule);
  border-radius: 0;
  padding: 11px 0;
  font-family: var(--sans);
  font-size: clamp(18px, 1.5vw, 21px);
  font-weight: 300;
  line-height: 1.45;
  color: var(--sheet-ink);
  transition: border-color .35s var(--ease), box-shadow .35s var(--ease);
}

textarea.guide__input {
  resize: vertical;
  min-height: 104px;
  font-size: clamp(17px, 1.3vw, 19px);
  line-height: 1.6;
}

/* The date field's picker is drawn by the browser; this is the one line
   that tells it which way round the sheet is. */
.guide__input[type="date"] { color-scheme: light; }

.guide__input::placeholder { color: var(--sheet-ink-40); }

.guide__input:focus {
  outline: none;
  border-bottom-color: var(--accent);
  box-shadow: 0 1px 0 var(--accent);
}

.guide__input:-webkit-autofill {
  -webkit-text-fill-color: var(--sheet-ink);
  -webkit-box-shadow: 0 0 0 1000px var(--sheet) inset;
}

/* A date that has been waved off by the Flexible row stays legible but
   plainly no longer the answer. */
.guide__field.is-off .guide__input { color: var(--sheet-ink-40); }

/* --- what went wrong ----------------------------------------------------- */
/* No new colour: the sheet says it in full-strength navy, which is denser
   than anything else on it, and thickens the field's own rule to match. */
.guide__hint {
  margin-top: 10px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--sheet-ink);
}

.guide__hint[hidden] { display: none; }

.guide__field.is-wrong .guide__input { border-bottom-color: var(--sheet-ink); }

/* --- selectable rows ----------------------------------------------------- */
/* A package, or the Flexible date. Hairline rows with the price out at the
   right margin — no boxes, no tick marks, no circles. */
.guide__choices { margin-top: -4px; }

.choice {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 4px 20px;
  align-items: baseline;
  padding: 15px 10px;
  margin-inline: -10px;
  border-top: 1px solid var(--sheet-rule-soft);
  cursor: pointer;
  transition: background-color .3s var(--ease), border-color .3s var(--ease);
}

.guide__choices .choice:last-child { border-bottom: 1px solid var(--sheet-rule-soft); }

.choice--single { margin-top: clamp(22px, 2.6vw, 30px); }

.choice__input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

.choice__name {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(21px, 2vw, 27px);
  line-height: 1.14;
  letter-spacing: -.014em;
  color: var(--sheet-ink);
  transition: color .3s var(--ease);
}

.choice__price {
  font-size: var(--fs-small);
  color: var(--sheet-ink-40);
  font-variant-numeric: tabular-nums;
  transition: color .3s var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .choice:hover { background-color: var(--sheet-wash); }
}

.choice.is-on {
  background-color: var(--sheet-accent-wash);
  border-top-color: var(--accent);
}

.choice.is-on .choice__name,
.choice.is-on .choice__price { color: var(--accent-deep); }

/* The ring belongs to the keyboard. Focus arrives on this group by script
   as the step opens, and a box drawn around the first row then would read
   as a package already chosen. */
.choice:focus-within {
  outline: 1px solid var(--accent);
  outline-offset: 1px;
}

@supports selector(:has(*)) {
  .choice:focus-within { outline: none; }
  .choice:has(.choice__input:focus-visible) {
    outline: 1px solid var(--accent);
    outline-offset: 1px;
  }
}

/* --- the review ---------------------------------------------------------- */
.guide__review { margin-top: -2px; }

.guide__line {
  display: grid;
  grid-template-columns: minmax(0, 104px) minmax(0, 1fr) auto;
  gap: 4px 18px;
  align-items: baseline;
  padding: 12px 0;
  border-top: 1px solid var(--sheet-rule-soft);
}

.guide__line:last-child { border-bottom: 1px solid var(--sheet-rule-soft); }

.guide__line-key {
  font-size: 13px;
  line-height: 1.4;
  color: var(--sheet-ink-40);
}

.guide__line-val {
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--sheet-ink);
  overflow-wrap: anywhere;
}

.guide__line-val.is-empty { color: var(--sheet-ink-40); }

.guide__change {
  font-size: 13px;
  line-height: 1.4;
  color: var(--accent);
  border-bottom: 1px solid rgba(59, 98, 255, .34);
  padding-bottom: 1px;
  white-space: nowrap;
  transition: color .3s var(--ease), border-color .3s var(--ease);
}

.guide__change:hover,
.guide__change:focus-visible {
  color: var(--accent-deep);
  border-bottom-color: var(--accent-deep);
}

/* --- back, next, send ---------------------------------------------------- */
.guide__foot {
  flex: none;
  margin-top: clamp(26px, 3vw, 36px);
  padding-top: 20px;
  border-top: 1px solid var(--sheet-rule-soft);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px 24px;
}

.guide__back {
  font-family: var(--sans);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--sheet-ink-40);
  padding-block: 6px;
  transition: color .3s var(--ease);
}

.guide__back:hover,
.guide__back:focus-visible { color: var(--sheet-ink); }

/* On the first question Back keeps its place and says nothing. */
.guide__back.is-off { visibility: hidden; }

.guide__go {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 16px 30px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: #fff;
  font-family: var(--sans);
  font-size: var(--fs-micro);
  font-weight: 600;
  line-height: 1;
  letter-spacing: .16em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: background-color .35s var(--ease), border-color .35s var(--ease), color .35s var(--ease);
}

.guide__go:hover,
.guide__go:focus-visible {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
}

.guide__go[disabled] {
  background: transparent;
  border-color: var(--sheet-rule);
  color: var(--sheet-ink-40);
  cursor: default;
}

/* Guided: Back and Next carry the sheet, Send waits for the review.
   Un-guided: there is only ever Send. */
.guide__nav { display: none; }
.guide.is-guided .guide__nav { display: block; }
.guide.is-guided .guide__next { display: inline-flex; }
.guide.is-guided .guide__send { display: none; }
.guide.is-guided.is-review .guide__next { display: none; }
.guide.is-guided.is-review .guide__send { display: inline-flex; }

/* --- sent, and not sent -------------------------------------------------- */
.guide__sent { display: none; }
.guide.is-sent .guide__sent { display: block; }
.guide.is-sent .guide__stage,
.guide.is-sent .guide__foot,
.guide.is-sent .guide__alert { display: none; }

.guide__sent-title {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(28px, 3vw, 40px);
  line-height: 1.06;
  letter-spacing: -.018em;
  max-width: 20ch;
}

.guide__sent-link {
  display: inline-block;
  margin-top: clamp(22px, 2.6vw, 30px);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent);
  border-bottom: 1px solid rgba(59, 98, 255, .34);
  padding-bottom: 5px;
  transition: color .3s var(--ease), border-color .3s var(--ease);
}

.guide__sent-link:hover,
.guide__sent-link:focus-visible {
  color: var(--accent-deep);
  border-bottom-color: var(--accent-deep);
}

.guide__alert {
  margin-top: clamp(22px, 2.6vw, 30px);
  padding-top: 18px;
  border-top: 1px solid var(--sheet-ink);
  font-size: var(--fs-small);
  line-height: 1.7;
  color: var(--sheet-ink);
}

.guide__alert[hidden] { display: none; }

.guide__alert a:not(.social) {
  border-bottom: 1px solid var(--sheet-rule);
  padding-bottom: 1px;
  transition: color .3s var(--ease), border-color .3s var(--ease);
}

.guide__alert a:not(.social):hover,
.guide__alert a:not(.social):focus-visible {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* On the sheet a glyph takes the sheet's own ink. */
.guide .social {
  --glyph: 18px;
  color: var(--sheet-ink-60);
  vertical-align: -4px;
  margin-left: 2px;
}

.guide .social:hover,
.guide .social:focus-visible { color: var(--accent); }

/* Honeypot. Off-screen rather than display:none — bots skip hidden fields. */
.hp {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

@media (max-width: 420px) {
  .guide__line { grid-template-columns: minmax(0, 1fr) auto; }
  .guide__line-key { grid-column: 1 / -1; }
}


/* 19b  THE GUIDE INSIDE THE ENQUIRY MODAL
   The dialog keeps its ink frame and the sheet is laid inside it, held off
   the edges by the same band of ink every photograph on the site is matted
   with. The sheet is as tall as the question it is asking and no taller —
   it grows as the questions get denser — and only once it would outgrow the
   panel do the questions start to scroll inside it, leaving the line along
   its top edge and the Back/Next row in place.
   ------------------------------------------------------------------------ */
.enquiry .guide {
  flex: 0 1 auto;
  min-height: 0;
  max-height: 100%;
}

.enquiry .guide__stage { overflow-y: auto; overscroll-behavior: contain; }

.enquiry .guide.is-guided .guide__stage { min-height: clamp(150px, 18vh, 190px); }

/* The panel is a fixed 560px whatever the window is doing, so the question
   inside it is sized off the sheet rather than off the page. */
.enquiry .guide__q { font-size: clamp(28px, 7vw, 36px); }


/* 20  NOTES STRIP
   ------------------------------------------------------------------------ */
.notes-strip {
  border-top: 1px solid var(--rule);
  padding-top: 30px;
  display: grid;
  grid-template-columns: minmax(0, 200px) minmax(0, 1fr);
  gap: clamp(20px, 4vw, 64px);
  align-items: start;
}

.notes-strip__list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(20px, 3vw, 44px);
  font-size: var(--fs-small);
  line-height: 1.7;
  color: var(--porcelain-60);
}

@media (max-width: 860px) {
  .notes-strip { grid-template-columns: 1fr; gap: 18px; }
  .notes-strip__list { grid-template-columns: 1fr; gap: 16px; }
}


/* 20b  ENQUIRY MODAL
   One dialog, identical on every page, holding the same guide the contact
   page holds. Every "ask a question" link on the site points at /contact and
   opens this instead, so nobody loses the page they were reading.

   Desktop: a full-height panel on the right edge, hairline down its left
   side. Phone: the whole screen. Native <dialog> carries the focus trap,
   the top layer and the escape key; main.js only handles the closing
   animation and the scroll lock.

   The panel itself carries nothing but the close cross and a band of ink
   around the sheet — the guide asks the question, so there is no second
   heading above it saying the same thing in smaller type.
   ------------------------------------------------------------------------ */
.enquiry {
  --enquiry-from: translateX(100%);
  --enquiry-from-opacity: 1;
  position: fixed;
  inset: 0 0 0 auto;
  z-index: 100;
  width: min(560px, 100%);
  max-width: 560px;
  height: 100%;
  max-height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  border-left: 1px solid var(--rule);
  background: var(--ink);
  color: var(--porcelain);
  overflow: hidden;
}

.enquiry::backdrop { background: rgba(3, 5, 11, .74); }

.enquiry__panel {
  height: 100%;
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: 0 clamp(12px, 1.4vw, 18px) clamp(12px, 1.4vw, 18px);
}

.enquiry__head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 10px 0;
}

/* Two hairlines crossed, matching the hamburger's construction. */
.enquiry__close {
  position: relative;
  flex: none;
  width: 44px;
  height: 44px;
  margin: -6px -12px 0 0;
  color: var(--porcelain-60);
  transition: color .35s var(--ease);
}

.enquiry__close:hover,
.enquiry__close:focus-visible { color: var(--porcelain); }

.enquiry__close span {
  position: absolute;
  top: 21px;
  left: 11px;
  width: 22px;
  height: 1px;
  background: currentColor;
}

.enquiry__close span:nth-child(1) { transform: rotate(45deg); }
.enquiry__close span:nth-child(2) { transform: rotate(-45deg); }

.enquiry .enquiry__close { margin: 0 -10px 0 0; }

@keyframes enquiry-in {
  from { transform: var(--enquiry-from); opacity: var(--enquiry-from-opacity); }
  to   { transform: none; opacity: 1; }
}

@keyframes enquiry-veil {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.enquiry[open] { animation: enquiry-in 460ms var(--ease) both; }
.enquiry[open]::backdrop { animation: enquiry-veil 420ms var(--ease) both; }

.enquiry.is-closing { animation: enquiry-in 300ms var(--ease) reverse both; }
.enquiry.is-closing::backdrop { animation: enquiry-veil 300ms var(--ease) reverse both; }

@media (max-width: 620px) {
  .enquiry {
    --enquiry-from: translateY(20px);
    --enquiry-from-opacity: 0;
    inset: 0;
    width: 100%;
    max-width: none;
    border-left: 0;
  }
}


/* 20c  BOOKING PANEL
   The same panel as the enquiry modal — same slide in from the right, same
   close, same escape key, same scroll lock — one step wider, holding the
   Bookedin calendar in a frame. Every "book" link on the site is still an
   ordinary link to bookedin.com: without JS it simply opens there, and with
   JS the panel opens instead so nobody loses the page they were reading.

   The frame carries no src in the HTML. main.js sets it the first time the
   panel opens, so a page that is never booked from never loads it, and
   keeps it afterwards so a second opening is instant.
   ------------------------------------------------------------------------ */
.booking {
  --enquiry-from: translateX(100%);
  --enquiry-from-opacity: 1;
  position: fixed;
  inset: 0 0 0 auto;
  z-index: 100;
  width: min(760px, 100%);
  max-width: 760px;
  height: 100%;
  max-height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  border-left: 1px solid var(--rule);
  background: var(--ink);
  color: var(--porcelain);
  overflow: hidden;
}

.booking::backdrop { background: rgba(3, 5, 11, .74); }

/* A column: a slim bar that keeps its height, and a stage that takes the
   rest of it. */
.booking__panel {
  height: 100%;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.booking__head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 14px clamp(18px, 2.4vw, 28px);
  border-bottom: 1px solid var(--rule);
}

.booking__title {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(24px, 2.4vw, 30px);
  line-height: 1.1;
  letter-spacing: -.018em;
}

.booking__actions {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2vw, 26px);
  flex: none;
}

.booking__out {
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--porcelain-60);
  border-bottom: 1px solid var(--porcelain-20);
  padding-bottom: 4px;
  white-space: nowrap;
  transition: color .35s var(--ease), border-color .35s var(--ease);
}

.booking__out:hover,
.booking__out:focus-visible {
  color: var(--accent-2);
  border-bottom-color: var(--accent-2);
}

.booking .enquiry__close { margin: 0 -10px 0 0; }

/* The frame is matted the way every photograph on the site is: a band of
   ink between the panel's edge and the calendar's own white page, so a
   third party's design arrives inside ours rather than against it. */
.booking__stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  padding: clamp(10px, 1.2vw, 16px);
  background: var(--ink-2);
}

.booking__frame {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

.booking__loading {
  position: absolute;
  z-index: 0;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  text-align: center;
  font-size: var(--fs-small);
  color: var(--porcelain-40);
}

.booking__loading[hidden] { display: none; }

.booking[open] { animation: enquiry-in 460ms var(--ease) both; }
.booking[open]::backdrop { animation: enquiry-veil 420ms var(--ease) both; }

.booking.is-closing { animation: enquiry-in 300ms var(--ease) reverse both; }
.booking.is-closing::backdrop { animation: enquiry-veil 300ms var(--ease) reverse both; }

@media (max-width: 760px) {
  .booking {
    --enquiry-from: translateY(20px);
    --enquiry-from-opacity: 0;
    inset: 0;
    width: 100%;
    max-width: none;
    border-left: 0;
  }
}


/* 21  MOTION & REVEAL
   ------------------------------------------------------------------------ */
[data-reveal] {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity var(--dur) var(--ease),
    transform var(--dur) var(--ease);
  transition-delay: calc(var(--d, 0) * 1ms);
  will-change: opacity, transform;
}

[data-reveal].is-visible {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* Pages without JS must still show their content. */
html:not(.has-js) [data-reveal] {
  opacity: 1;
  transform: none;
}


/* 22  MOTION LAYER
   Six small movements laid over §21's one reveal. Every one of them is a
   property that composites — transform, translate, opacity, background-size
   — so none of it costs the page a layout pass, and every one of them is
   switched off in §23. Nothing here starts on its own except the logo's
   twinkle; the rest answer the scroll, a reveal, a focus or a click.
   ------------------------------------------------------------------------ */

/* --- 22a  Photographs breathe in their frames ---------------------------- */
/* Each matted figure drifts its picture up to 3% of its own height against
   the mat as the page passes it. main.js writes --py once per frame from
   the figure's distance to the middle of the window.

   The drift rides on the `translate` property rather than inside
   `transform`, for one practical reason: the hover zoom lives on transform
   with a 900ms ease, and a drift sharing that transition would trail a
   second behind the scroll. Kept apart, the zoom keeps its ease and the
   drift answers the scroll directly.

   Overscan: at 1.10 the picture hides 5% of itself past each edge of its
   window, comfortably more than the 3% the drift can ever ask for, so the
   mat never shows through from behind — measured, the smallest frame on the
   site still has a couple of pixels in hand at full drift. The hover keeps
   the 3% step up it always had. */
.slot img {
  transform: scale(1.10);
  translate: 0 var(--py, 0%);
}

.slot:hover img,
a:hover .slot img,
a:focus-visible .slot img,
button:hover .slot img,
button:focus-visible .slot img { transform: scale(1.13); }

/* The hero and the rail keep their own transforms (§09, §12). A slider's
   two layers are one frozen comparison — they neither drift nor overscan,
   or the pair stops being the same photograph twice. */
.compare__layer .slot img {
  transform: scale(1.001);
  translate: none;
}


/* --- 22b  Headlines rise line by line ------------------------------------ */
/* The hero does this with .hero__title .line (§09). This is the general
   version: any h1 or h2 inside a [data-reveal] block carries its whole
   inner content in one span, and that span rises out of the heading as the
   block reveals.

   The heading is the mask. It is clipped rather than given overflow:hidden
   so it can keep its exact height — a clip costs no layout at all, and the
   bottom edge is let out .15em so descenders are never shaved off once the
   line has landed. */
.rise { clip-path: inset(-40% -100% -.15em -100%); }

.rise > span {
  display: block;
  transform: translateY(118%);
  transition: transform 800ms var(--ease);
}

.is-visible .rise > span { transform: none; }

html:not(.has-js) .rise > span { transform: none; }


/* --- 22c  Hairlines draw themselves -------------------------------------- */
/* Every rule below keeps the border it always had — the border simply turns
   transparent and a pseudo-element of exactly the same weight is laid over
   the top of it. Nothing is re-boxed, so no rule moves the page by a pixel;
   the line just arrives from the left when its block reveals. */
.section-head,
.section-foot,
.rail-head,
.rows > *,
.quote-row figure,
.step,
.ledger__corner,
.ledger__head,
.ledger__label,
.ledger__cell,
.ledger-toggle,
.site-footer__colhead { position: relative; }

.section-head,
.section-foot,
.rail-head,
.rows > *,
.quote-row figure,
.step,
.ledger__corner,
.ledger__head,
.ledger__label,
.ledger__cell,
.ledger-toggle { border-top-color: transparent; }

/* Two rules that state their colour more specifically than the group. */
.ledger__col--featured .ledger__head { border-top-color: transparent; }
.step:hover,
.step:focus-within,
.step.is-open { border-top-color: transparent; }

.rows > *:last-child { border-bottom-color: transparent; }
.site-footer__colhead { border-bottom-color: transparent; }

.section-head::before,
.section-foot::before,
.rail-head::before,
.rows > *::before,
.quote-row figure::before,
.step::before,
.ledger__corner::before,
.ledger__head::before,
.ledger__label::before,
.ledger__cell::before,
.ledger-toggle::before,
.rows > *:last-child::after,
.site-footer__colhead::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -1px;
  height: 1px;
  background: var(--rule);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 900ms var(--ease), background-color .4s var(--ease);
  pointer-events: none;
}

/* The two that sit under their block rather than over it. */
.rows > *:last-child::after,
.site-footer__colhead::after {
  top: auto;
  bottom: -1px;
}

/* The featured package's rule is the one accent bar in the table. */
.ledger__col--featured .ledger__head::before {
  top: -2px;
  height: 2px;
  background: var(--accent);
}

/* A moment's hairline is its affordance (§11); it still answers the hand. */
.step:hover::before,
.step:focus-within::before,
.step.is-open::before { background: var(--accent); }

:is(.is-visible, .is-visible *):is(
  .section-head, .section-foot, .rail-head, .rows > *, .quote-row figure,
  .step, .ledger__corner, .ledger__head, .ledger__label, .ledger__cell,
  .ledger-toggle)::before,
:is(.is-visible, .is-visible *):is(
  .rows > *:last-child, .site-footer__colhead)::after { transform: none; }

html:not(.has-js) :is(
  .section-head, .section-foot, .rail-head, .rows > *, .quote-row figure,
  .step, .ledger__corner, .ledger__head, .ledger__label, .ledger__cell,
  .ledger-toggle)::before,
html:not(.has-js) :is(
  .rows > *:last-child, .site-footer__colhead)::after { transform: none; }


/* --- 22d  Ledger dots settle --------------------------------------------- */
/* The included dots land a row at a time, 30ms apart down the table; the
   hollow ones have nothing to land, so they simply arrive. main.js numbers
   the rows inside each column, which is what keeps the three packages in
   step with one another. */
.ledger__dot {
  transform: scale(0);
  transition:
    transform 520ms var(--ease) calc(var(--n, 0) * 30ms),
    opacity 420ms var(--ease) calc(var(--n, 0) * 30ms),
    background-color .4s var(--ease),
    box-shadow .4s var(--ease);
}

.ledger__cell.is-out .ledger__dot {
  transform: none;
  opacity: 0;
}

:is(.is-visible, .is-visible *) .ledger__dot { transform: scale(1); }
:is(.is-visible, .is-visible *) .ledger__cell.is-out .ledger__dot { opacity: 1; }

/* The full list is already revealed before anyone opens it, so its dots
   answer the panel rather than the reveal — they land each time it opens. */
.ledger-more .ledger__dot { transform: scale(0); }
.ledger-more .ledger__cell.is-out .ledger__dot { transform: none; opacity: 0; }
.ledger-more.is-open .ledger__dot { transform: scale(1); }
.ledger-more.is-open .ledger__cell.is-out .ledger__dot { opacity: 1; }

html:not(.has-js) .ledger__dot,
html:not(.has-js) .ledger-more .ledger__dot { transform: scale(1); }
html:not(.has-js) .ledger__cell.is-out .ledger__dot,
html:not(.has-js) .ledger-more .ledger__cell.is-out .ledger__dot {
  transform: none;
  opacity: 1;
}


/* --- 22e  The logo twinkles ---------------------------------------------- */
/* The small cobalt shine tucked into the mark's shoulder catches the light
   once every eight seconds, and again whenever the lockup is pointed at.
   transform-box: fill-box is what makes `center` mean the middle of that
   one shape rather than the middle of the whole lockup. */
.logo__spark {
  fill: var(--accent-2);
  transform-box: fill-box;
  transform-origin: center;
  animation: logo-spark 8s var(--ease) infinite;
}

@keyframes logo-spark {
  0%, 91.25%, 100% { transform: scale(1); fill: var(--accent-2); }
  95.6% { transform: scale(1.25); fill: #a9bcff; }
}

@keyframes logo-twinkle {
  0%, 100% { transform: scale(1); fill: var(--accent-2); }
  50% { transform: scale(1.25); fill: #a9bcff; }
}

.wordmark:hover .logo__spark,
.wordmark:focus-visible .logo__spark { animation: logo-twinkle 700ms var(--ease); }


/* --- 22f  Sliders nudge --------------------------------------------------- */
/* Item 7 is entirely main.js: the first time a before/after slider comes
   into view it tweens --pos from 50% to 38% and back, which moves the clip
   and the handle together because both already read that one property
   (§18). It is skipped on any slider the reader has already touched. */


/* 23  REDUCED MOTION
   ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
  }

  .hero__title .line > span { transform: none !important; }
  .hero__actions,
  .hero__facts { opacity: 1 !important; transform: none !important; }
  .hero__media .slot img { transform: scale(1) !important; }

  .slot:hover img,
  a:hover .slot img,
  button:hover .slot img { transform: none !important; }

  .mobile-menu a.mobile-menu__link { opacity: 1; transform: none; }

  /* The rail is still a rail — it just stops moving on its own. main.js
     duplicates nothing and starts no frame here, so the viewport is left
     holding one set of tiles: an ordinary horizontal scroller, with the
     first tile back on the text column where the heading starts. */
  .rail__viewport {
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scroll-behavior: auto !important;
    scrollbar-width: none;
    -ms-overflow-style: none;
    padding-inline-start: var(--rail-inset);
    padding-inline-end: var(--gutter);
    touch-action: auto;
    cursor: auto !important;
  }

  .rail__viewport::-webkit-scrollbar { width: 0; height: 0; display: none; }
  .rail__viewport::after { content: none !important; }
  .rail__track { transform: none !important; }

  /* Nothing is dragged here — it is scrolled, the way everything else is. */
  .rail-hint { display: none !important; }

  /* Disclosures still open and close — they just arrive instead of grow. */
  .step__text { transform: none !important; }
  .enquiry[open],
  .enquiry.is-closing,
  .enquiry[open]::backdrop,
  .enquiry.is-closing::backdrop,
  .booking[open],
  .booking.is-closing,
  .booking[open]::backdrop,
  .booking.is-closing::backdrop { animation: none !important; }

  /* --- §22, switched off ------------------------------------------------ */
  /* Pages stop cross-fading; the navigation itself is untouched. */
  @view-transition { navigation: none; }

  ::view-transition-old(root),
  ::view-transition-new(root) { animation: none !important; }

  /* Photographs sit still in their frames, at their own scale. */
  .slot img {
    transform: none !important;
    translate: none !important;
  }

  /* Headlines are simply there, and nothing is clipped. */
  .rise { clip-path: none !important; }
  .rise > span { transform: none !important; }

  /* Every hairline is already drawn. */
  .section-head::before,
  .section-foot::before,
  .rail-head::before,
  .rows > *::before,
  .quote-row figure::before,
  .step::before,
  .ledger__corner::before,
  .ledger__head::before,
  .ledger__label::before,
  .ledger__cell::before,
  .ledger-toggle::before,
  .rows > *:last-child::after,
  .site-footer__colhead::after { transform: none !important; }

  /* Dots are placed, not landed. */
  .ledger__dot { transform: scale(1) !important; }
  .ledger__cell.is-out .ledger__dot {
    transform: none !important;
    opacity: 1 !important;
  }

  .logo__spark { animation: none !important; }

  /* The guide: a question is simply there when it is its turn, and the
     cobalt line along the sheet's top edge is simply the length it is. */
  .guide__step.is-active { animation: none !important; }
  .guide__progress span { transition: none !important; }
}
