/* ==========================================================================
   London Headshot
   1. Tokens
   2. Reset and base
   3. Layout shells and bands
   4. Masthead and footer
   5. Portrait wall
   6. Components
   7. Forms
   8. Editorial prose
   9. Responsive
   ========================================================================== */

/* 1. Tokens ------------------------------------------------------------- */

:root {
  --paper:        #f1f1ef;
  --paper-sunk:   #e3e3e0;
  --paper-deep:   #d8d8d4;
  --rule:         #d2d2ce;
  --rule-soft:    #dededb;   /* card borders — 30% paler than --rule */
  /* Accent washed into the paper ground. Used where a block needs to read as
     related to the accent without shouting — the border is a measured step
     darker than the fill, not a guess. */
  --accent-wash:       #dee5e4;
  --accent-wash-line:  #c6d6d7;
  --accent-wash-hover: #d1dddd;
  --accent-wash-hover-line: #b1c9cb;
  --graphite:     #26282b;
  --graphite-lift:#34373b;
  --ink:          #16181a;
  --ink-soft:     #5c6064;
  --ink-invert:   #f4f4f2;
  --ink-invert-soft: #a9aeb2;
  --accent:       #1b6c77;
  --accent-deep:  #14525b;
  --accent-lift:  #2fa0ae;

  --shell:     76rem;
  /* The single source of truth for the rail's left edge. Both the heading
     and the card track are full-width children of the same section and both
     take their leading inset from this one value, so they cannot disagree —
     whatever it resolves to, it resolves to the same number for both. The
     percentage is of the section's own box, which excludes the scrollbar,
     matching how .shell centres everything else on the page. */
  --rail-inset: max(var(--gutter), calc((100% - var(--shell)) / 2 + var(--gutter)));
  --shell-narrow: 44rem;
  --gutter:    clamp(1.25rem, 4vw, 3rem);
  --band-y:    clamp(3.5rem, 8vw, 7rem);
  --wall-gap:  0.375rem;

  --radius:    2px;
  --radius-card: 5px;
  --ease:      cubic-bezier(0.2, 0.6, 0.2, 1);
  --ease-soft: cubic-bezier(0.16, 1, 0.3, 1);   /* long tail, for the menu */

  --step--1: 0.875rem;
  --step-0:  1.0625rem;
  --step-1:  1.2rem;
  --step-2:  clamp(1.35rem, 0.5vw + 1.22rem, 1.55rem);
  --step-3:  clamp(1.75rem, 1.4vw + 1.35rem, 2.4rem);
  --step-4:  clamp(2.2rem, 3vw + 1.4rem, 3.4rem);
  --step-5:  clamp(2.8rem, 5vw + 1.4rem, 5rem);
}

/* 2. Reset and base ----------------------------------------------------- */

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

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: "Schibsted Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: var(--step-0);
  line-height: 1.6;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
}

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

h1, h2, h3 {
  margin: 0 0 0.45em;
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 0.98;
  text-wrap: balance;
}

h1 { font-size: var(--step-5); }
h2 { font-size: var(--step-4); }
h3 { font-size: var(--step-2); letter-spacing: -0.022em; line-height: 1.15; }

p { margin: 0 0 1.1em; max-width: 62ch; }

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
@media (hover: hover) { a:hover { color: var(--accent-deep); } }

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

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

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--graphite);
  color: var(--ink-invert);
  padding: 0.75rem 1rem;
  z-index: 100;
}
.skip:focus { left: 0; }

/* 3. Layout shells and bands -------------------------------------------- */

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

.shell--narrow { max-width: var(--shell-narrow); }

/* Articles run wider than the 44rem used for forms and short pages. */
.shell--article { max-width: 54rem; }

.shell--split {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}

.band { padding-block: var(--band-y); }

.band--sunk { background: var(--paper-sunk); }

/* Two plain bands in a row, separated by a hairline instead of a colour change. */
.band--ruled { border-top: 1px solid var(--rule); }

.band--dark {
  background: var(--graphite);
  color: var(--ink-invert);
}
/* Prose links only. A bare .band--dark a scores higher than .button--accent,
   so it repainted button labels teal on a teal fill. */
.band--dark a:not([class]) { color: var(--accent-lift); }
@media (hover: hover) { .band--dark a:not([class]):hover { color: #5cc0cc; } }
.band--dark .textlink { color: var(--accent-lift); }
@media (hover: hover) { .band--dark .textlink:hover { color: #5cc0cc; } }
.band--dark p { color: var(--ink-invert-soft); }
.band--dark h2, .band--dark h3 { color: var(--ink-invert); }

.band--intro { padding-block: clamp(2.5rem, 5vw, 4rem); }

/* One-line note under the packages on /services. */
.event-note {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.75rem 1.5rem;
}
.event-note p { margin: 0; font-size: var(--step-1); }

.band--cta {
  background: var(--paper-sunk);
  text-align: left;
  border-top: 1px solid var(--rule);
}

.page-head {
  padding-block: clamp(3rem, 7vw, 5.5rem) clamp(1.5rem, 3vw, 2.5rem);
  border-bottom: 1px solid var(--rule);
}
.page-head--tall { padding-block: clamp(5rem, 12vw, 9rem); }

.lede {
  font-size: var(--step-3);
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.018em;
  color: var(--ink);
  max-width: 48ch;
}

/* No rule of its own — the block below carries a top border, and two
   hairlines a few rem apart read as a mistake. */
.section-head {
  font-size: var(--step-4);
  margin-bottom: clamp(1.5rem, 2.5vw, 2.25rem);
}

/* 4. Masthead and footer ------------------------------------------------ */

.masthead {
  position: sticky;
  top: 0.6rem;
  z-index: 40;
  margin-top: 0.6rem;
  padding-inline: var(--gutter);
}

/* Transitions are withheld until the script has set the opening state, so a
   reload part-way down a page does not animate the header away from a
   position it was never really in. */
.masthead.is-ready {
  transition: transform 300ms var(--ease), opacity 200ms var(--ease);
}

/* No will-change. Left on permanently it holds a compositor layer for the
   life of the page, which on a phone costs memory for no gain — a sticky
   element is promoted anyway, and the retract is a single 300ms transform. */

/* Retracts once the wall is behind you. Restored at the top of the page, or
   as soon as anything inside it takes keyboard focus. */
/* Two selectors, one appearance. .is-retracted is set by the script once it
   runs; .pre-retracted is set on <html> by the inline head script before the
   first paint, from a remembered value — see base.php. Without the second
   one, a reload part-way down a page paints the header visible for a frame
   before the script can hide it. */
.masthead.is-retracted,
.pre-retracted .masthead {
  transform: translateY(calc(-100% - 1.2rem));
  opacity: 0;
  pointer-events: none;
}

/* A capsule that floats over the portrait wall rather than a bar ruled off
   from it. Glass so the faces behind stay part of the composition. */
.masthead__inner {
  max-width: var(--shell);
  margin-inline: auto;
  padding: 0.4rem 0.4rem 0.4rem 1.4rem;
  display: grid;
  /* auto | minmax(0,1fr) | auto — not 1fr auto 1fr. A 1fr track floors at
     min-content, so once the three zones exceed the capsule the grid grows
     past it and the button escapes the rounded box. Here the wordmark and
     the button keep their natural width, and the nav takes what is left and
     scrolls if it has to. */
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  position: relative;   /* containing block for the mega panel */
  gap: clamp(0.75rem, 2vw, 1.5rem);
  border-radius: 999px;
  border: 1px solid rgba(22, 24, 26, 0.07);
  background: rgba(244, 244, 242, 0.72);
  backdrop-filter: blur(18px) saturate(170%);
  -webkit-backdrop-filter: blur(18px) saturate(170%);
  box-shadow:
    0 1px 1px rgba(22, 24, 26, 0.03),
    0 10px 30px -14px rgba(22, 24, 26, 0.35);
}

/* The name is drawn, not typeset: the brand wordmark rather than the site
   typeface pretending to be it. Both marks take currentColor. */
.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--ink);
  text-decoration: none;
}

/* Both marks are single-colour SVG with fill="currentColor", so one file
   serves the pale masthead and the graphite footer. Height is set and the
   width follows the viewBox. */
.logo { display: block; fill: currentColor; }

/* Monogram matched to the full height of the two-line wordmark beside it,
   which is how the supplied lockup relates them. */
.logo--monogram {
  height: 1.5rem;
  width: auto;
  flex: none;
}

.logo--wordmark {
  height: 1.5rem;
  width: auto;
  flex: none;
}

.logo--lockup {
  height: auto;
  width: 5rem;       /* the wordmark sets the width; the frame mark sits above it */
}

.masthead__action {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* Shown only where the link row is hidden — see the 64rem block. */
.nav-toggle {
  display: none;
  align-items: center;
  gap: 0.5rem;
  padding: 0.55rem 0.9rem 0.55rem 0.75rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: -0.015em;
  cursor: pointer;
  transition: background-color 160ms var(--ease), border-color 160ms var(--ease);
}
@media (hover: hover) { .nav-toggle:hover { background: rgba(22, 24, 26, 0.055); border-color: var(--ink-soft); } }

.nav-toggle__bars {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 1rem;
}
.nav-toggle__bars span {
  display: block;
  height: 1.5px;
  background: currentColor;
  border-radius: 2px;
}
.masthead__inner > * { min-width: 0; }
.wordmark { white-space: nowrap; }

.nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.15rem;
  min-width: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* 1.3 then 20% back off it */
  font-size: calc(var(--step--1) * 1.04);
  font-weight: 500;
}

.nav a {
  color: var(--ink-soft);
  text-decoration: none;
  padding: 0.5rem 0.95rem;
  border-radius: 999px;
  letter-spacing: -0.015em;
  transition: background-color 160ms var(--ease), color 160ms var(--ease);
}

.nav::-webkit-scrollbar { display: none; }
.nav a { white-space: nowrap; }

@media (hover: hover) { .nav a:hover { color: var(--ink); background: rgba(22, 24, 26, 0.055); } }

.nav__trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}

.nav__caret {
  width: 0.75em;
  height: 0.75em;
  flex: none;
  opacity: 0.7;
  transition: transform 200ms var(--ease), opacity 160ms var(--ease);
}

@media (hover: hover) { .nav__trigger:hover .nav__caret { opacity: 1; } }
.nav__trigger[aria-expanded="true"] .nav__caret { transform: rotate(180deg); opacity: 1; }

.nav a[aria-current="page"] {
  color: var(--ink-invert);
  background: var(--graphite);
}

/* The action now sits outside .nav in its own masthead zone, so these are
   scoped to the class itself rather than to a .nav descendant. */
.nav__action {
  display: inline-block;
  padding: 0.6rem 1.2rem;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: -0.015em;
  text-decoration: none;
  white-space: nowrap;
  box-shadow: 0 1px 2px rgba(20, 82, 91, 0.25);
  transition: background-color 160ms var(--ease), box-shadow 160ms var(--ease);
}

.nav__action:focus-visible {
  background: var(--accent-deep);
  color: #fff;
  box-shadow: 0 2px 10px -2px rgba(20, 82, 91, 0.45);
}
@media (hover: hover) { .nav__action:hover {
  background: var(--accent-deep);
  color: #fff;
  box-shadow: 0 2px 10px -2px rgba(20, 82, 91, 0.45);
} }

.nav__action:active {
  background: #0f424a;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);
}

.nav__action[aria-current="page"] {
  background: var(--accent-deep);
  color: #fff;
}

.footer {
  background: var(--graphite);
  color: var(--ink-invert-soft);
  padding-block: clamp(3rem, 6vw, 4.5rem) 0;
  font-size: var(--step--1);
}

.footer__inner {
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: grid;
  /* The brand column carries a sentence and a button, so it gets more room
     than the three link lists. */
  grid-template-columns: minmax(0, 1.6fr) repeat(3, minmax(0, 1fr));
  gap: clamp(2rem, 4vw, 3.5rem);
  align-items: start;
}

.footer__name {
  color: var(--ink-invert);
  margin-bottom: 1.1rem;
}

.footer__line {
  margin-bottom: 0;
  max-width: 34ch;
  line-height: 1.6;
}

.footer__heading {
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink-invert);
  margin: 0 0 0.9rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--graphite-lift);
}

.footer__col {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
}

.footer__col a {
  color: var(--ink-invert-soft);
  text-decoration: none;
  line-height: 1.35;
  transition: color 160ms var(--ease);
}
@media (hover: hover) { .footer__col a:hover { color: var(--accent-lift); } }

/* The scale figure under a service name, so the link says who it is for. */
.footer__hint {
  display: block;
  font-size: 0.75rem;
  color: #7c8186;
}

.footer__hours {
  margin: 0.4rem 0 0;
  line-height: 1.6;
  color: #7c8186;
}

.footer__base {
  max-width: var(--shell);
  margin: clamp(2.5rem, 5vw, 3.5rem) auto 0;
  padding: 1.4rem var(--gutter) 2rem;
  border-top: 1px solid var(--graphite-lift);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.4rem 1.5rem;
  color: #7c8186;
}
.footer__base p { margin: 0; max-width: none; }

.footer__copy { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem 1.25rem; }
.footer__policies { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; }
.footer__policies a { color: inherit; text-decoration: none; }
@media (hover: hover) { .footer__policies a:hover { color: var(--ink-invert); text-decoration: underline; text-underline-offset: 0.2em; } }

/* The statutory line runs longer than the copyright notice beside it, so it
   takes the full width on its own row rather than squeezing. */
.footer__legal {
  flex: 1 1 100%;
  order: 2;
  max-width: 70ch;
  line-height: 1.55;
}

/* Two columns all the way down. Services and Site hold short labels and sit
   happily at half width even on a 320px screen, which keeps the footer from
   running to four stacked blocks. The brand needs the full width for its
   sentence, and Get in touch for the address and hours — both span. */
@media (max-width: 60rem) {
  .footer__inner {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(1.75rem, 5vw, 2.5rem) clamp(1.25rem, 4vw, 2rem);
  }
  .footer__brand { grid-column: 1 / -1; }

  /* Get in touch — the last block in the row. */
  .footer__col:last-child { grid-column: 1 / -1; }
}

/* 5. Portrait wall ------------------------------------------------------ */

/* No outer frame. The grid bleeds to all four edges, so there is no border of
   page background that can end up thicker on one side than another. */
.wall {
  background: var(--paper);
  overflow: hidden;
  position: relative;
}

.wall__grid {
  display: grid;
  grid-template-columns: repeat(var(--wall-cols, 5), minmax(0, 1fr));
  grid-template-rows: repeat(var(--wall-rows, 4), 1fr);
  gap: var(--wall-gap);
  /* The grid gets a definite height from its own width, so its rows are true
     equal fractions of it: no child's content can size a row, and the
     geometry is identical at every viewport width. Tiles inherit their cell
     rather than claiming a ratio.

     Derived from the column and row counts rather than hard-coded, so
     changing gallery.columns or gallery.rows in config is enough — the ratio
     follows. Columns of 2:3 tiles give (cols x 2) / (rows x 3). */
  aspect-ratio: calc(var(--wall-cols, 5) * 2) / calc(var(--wall-rows, 4) * 3);
}

.wall__grid > * { min-width: 0; min-height: 0; }

/* The headline is a tile in the contact sheet, not a banner over it. */
.wall__statement {
  grid-column: 2 / span 2;
  grid-row: 2 / span 2;
  background: var(--graphite);
  color: var(--ink-invert);
  padding: clamp(1.5rem, 2.8vw, 2.75rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.5rem;
  overflow: hidden;
}

.wall__statement h1 {
  font-size: clamp(1.9rem, 3.4vw, 3.2rem);
  letter-spacing: -0.04em;
  line-height: 0.96;
  margin: 0;
  color: var(--ink-invert);
}

.wall__statement p {
  margin: 0;
  color: var(--ink-invert-soft);
  font-size: var(--step-0);
  line-height: 1.45;
  max-width: 32ch;
}

/* The three tiers, stated early. Set as information rather than buttons —
   the accent button below is the only thing asking to be clicked. */
.wall__tiers {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem clamp(1rem, 2.2vw, 1.9rem);
  margin: 1.1rem 0 0;
  padding: 0.95rem 0 0;   /* the 0 inline-start matters: <ul> defaults to 40px */
  border-top: 1px solid rgba(244, 244, 242, 0.16);
  list-style: none;
}

.wall__tiers a {
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
  text-decoration: none;
  color: var(--ink-invert);
}

.wall__tier-name {
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: -0.015em;
  border-bottom: 1px solid transparent;
  transition: border-color 160ms var(--ease), color 160ms var(--ease);
}

.wall__tier-scale {
  font-size: 0.78rem;
  color: var(--ink-invert-soft);
  white-space: nowrap;
}

@media (hover: hover) { .wall__tiers a:hover .wall__tier-name {
  color: var(--accent-lift);
  border-bottom-color: currentColor;
} }

.wall__statement .button { margin-top: 1.3rem; align-self: flex-start; }

/* The one piece of page-load motion besides the tiles resolving: headline,
   line, button, in that order. */
.wall__statement h1,
.wall__statement p,
.wall__statement .wall__tiers,
.wall__statement .button {
  opacity: 0;
  transform: translateY(12px);
}

.wall__statement.is-visible h1 { animation: statement-rise 640ms var(--ease) 60ms forwards; }
.wall__statement.is-visible p { animation: statement-rise 640ms var(--ease) 200ms forwards; }
.wall__statement.is-visible .wall__tiers { animation: statement-rise 640ms var(--ease) 300ms forwards; }
.wall__statement.is-visible .button { animation: statement-rise 640ms var(--ease) 400ms forwards; }

@keyframes statement-rise {
  to { opacity: 1; transform: translateY(0); }
}

.tile {
  margin: 0;
  background: var(--paper-sunk);
  overflow: hidden;
  position: relative;
}

.tile img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transform: scale(1.015);
  transition: opacity 900ms var(--ease), transform 1400ms var(--ease);
  transition-delay: calc(var(--i, 0) * 55ms);
}

/* Per-tile, on entry. The grid is taller than the viewport, so a single
   page-load sequence would finish off-screen before you ever saw it. */
.tile.is-visible img { opacity: 1; transform: scale(1); }

/* Tiles below the first screen fade faster: they are revealed as you scroll
   towards them, and a long fade left them half-drawn as they arrived. */
.tile:not([data-initial]) img { transition-duration: 550ms, 900ms; }

/* A replacement image starts hidden, then crossfades over the one below it. */
.tile.is-visible img.is-entering {
  opacity: 0;
  transform: scale(1.02);
  transition-delay: 0ms;
}

.tile.is-visible img.is-leaving {
  opacity: 0;
  transition: opacity 1100ms var(--ease);
  transition-delay: 0ms;
}

.tile--empty::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    135deg,
    var(--paper-sunk) 0 10px,
    var(--paper-deep) 10px 20px
  );
}

.wall__note {
  max-width: none;
  text-align: center;
  padding: 1.25rem;
  color: var(--ink-soft);
  font-size: var(--step--1);
}
.wall__note code {
  background: var(--paper-sunk);
  padding: 0.15em 0.4em;
  border-radius: var(--radius);
}

/* 6. Components --------------------------------------------------------- */

/* One shape for every call to action on the site, matching the capsule
   button in the masthead. */
.button {
  display: inline-block;
  padding: 0.6rem 1.2rem;
  border: 1px solid var(--ink);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: -0.015em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 160ms var(--ease), color 160ms var(--ease),
              border-color 160ms var(--ease), box-shadow 160ms var(--ease);
}
@media (hover: hover) { .button:hover { background: var(--ink); color: var(--paper); } }

.button--accent {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  box-shadow: 0 1px 2px rgba(20, 82, 91, 0.25);
}

.button.button--accent:focus-visible {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  color: #fff;
  box-shadow: 0 2px 10px -2px rgba(20, 82, 91, 0.45);
}
@media (hover: hover) { .button.button--accent:hover {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  color: #fff;
  box-shadow: 0 2px 10px -2px rgba(20, 82, 91, 0.45);
} }

.button.button--accent:active {
  background: #0f424a;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);
}

.button--wide { width: 100%; text-align: center; padding-block: 0.8rem; }

.textlink {
  font-weight: 600;
  font-size: var(--step--1);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  padding-bottom: 1px;
}

.jump {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.25rem;
  margin-top: 1.5rem;
  font-size: var(--step--1);
  font-weight: 500;
}

.packages {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0;
  background: var(--paper);
  border-block: 1px solid var(--rule);
}

.package {
  background: var(--paper);
  padding: clamp(1.5rem, 2.5vw, 2.25rem);
  display: flex;
  flex-direction: column;
}

/* The divider is a border on the column rather than a grey container
   showing through a 1px gap. */
.package + .package { border-left: 1px solid var(--rule); }

.package__scale,
.service__scale {
  font-size: clamp(1.7rem, 1.5vw + 1.1rem, 2.5rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  color: var(--accent);
  margin-bottom: 0.5rem;
  line-height: 1.05;
}

@media (min-width: 40rem) {
  /* Three columns side by side: one line each, or the rows below stop
     agreeing with one another. Below this the columns stack full width and
     wrapping costs nothing. */
  .package__scale { white-space: nowrap; }
}

.package h3 {
  margin-bottom: 0.75rem;
  min-height: calc(2 * 1.15em);
}
.package h3 a { color: var(--ink); text-decoration: none; }
@media (hover: hover) { .package h3 a:hover { color: var(--accent); } }

.package__summary {
  font-size: var(--step--1);
  color: var(--ink-soft);
  /* margin-top:auto on the meta only creates space when a column is short.
     In the tallest column it resolves to zero, leaving the rule sitting on
     the last line of text. This is the floor. */
  margin-bottom: 2rem;
}

/* The duration line sits on the floor of every column, so the rules above
   them read as one horizontal band rather than three stray marks. */
.package__summary + .package__meta { margin-top: auto; }

.package__meta {
  margin: 0;
  font-size: var(--step--1);
  font-weight: 500;
  color: var(--ink);
  /* Matches the column's own bottom padding, so the gap above this line and
     the gap below it are the same. A fixed value here did not track the
     clamped padding and ran short by up to 0.85rem. */
  padding-top: clamp(1.5rem, 2.5vw, 2.25rem);
  border-top: 1px solid var(--rule);
}

.runsheet {
  display: grid;
  gap: 2rem;
}
.runsheet__item h3 { color: var(--ink-invert); margin-bottom: 0.35rem; }
.runsheet__item p { margin: 0; }

.service__duration {
  font-size: var(--step--1);
  font-weight: 500;
  color: var(--ink-soft);
  margin: 0;
}

.service__summary { font-size: var(--step-1); line-height: 1.55; }

.service__detail {
  margin: 0 0 1.5rem;
  padding: 0;
  list-style: none;
  max-width: 60ch;
}
.service__detail li {
  padding: 0.7rem 0 0.7rem 1.5rem;
  border-top: 1px solid var(--rule);
  position: relative;
}
.service__detail li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1.35rem;
  width: 0.5rem;
  height: 1px;
  background: var(--accent);
}

.service__for {
  font-size: var(--step--1);
  color: var(--ink-soft);
}

.news-list { display: grid; gap: clamp(1.5rem, 3vw, 2.5rem); }
.news-list--full { gap: 0; }
.news-list--full .news-item {
  padding-block: clamp(1.5rem, 3vw, 2.25rem);
  border-top: 1px solid var(--rule);
}
.news-list--full .news-item:last-child { border-bottom: 1px solid var(--rule); }

.news-item__date {
  font-size: var(--step--1);
  color: var(--ink-soft);
  margin-bottom: 0.3rem;
}

.news-item h2, .news-item h3 { margin-bottom: 0.4rem; }
.news-item h2 a, .news-item h3 a { color: var(--ink); text-decoration: none; }
@media (hover: hover) { .news-item h2 a:hover,
.news-item h3 a:hover { color: var(--accent); } }

.sidenote__figure { margin: 0; }
.sidenote__figure img { width: 100%; height: auto; border-radius: var(--radius); background: var(--paper-sunk); }
.sidenote__figure figcaption {
  font-size: var(--step--1);
  color: var(--ink-soft);
  margin-top: 0.6rem;
}

.contact-aside h2 { font-size: var(--step-2); }
.contact-aside p { margin-bottom: 0.4rem; }
.contact-aside__note {
  font-size: var(--step--1);
  color: var(--ink-soft);
  margin-top: 1rem;
}

.notice {
  padding: 1.25rem 1.5rem;
  border-radius: var(--radius);
  border-left: 3px solid var(--accent);
  background: var(--paper-sunk);
}
.notice h2 { font-size: var(--step-2); }
.notice p:last-child { margin-bottom: 0; }
.notice--bad { border-left-color: #a8332f; }

.empty { color: var(--ink-soft); }

/* 7. Forms -------------------------------------------------------------- */

.form { max-width: 38rem; }

/* Two to a line, always — including inside the drawer. They only stop
   pairing when the container is genuinely too narrow for two inputs. */
.form__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

@media (max-width: 26rem) {
  .form__row { grid-template-columns: 1fr; gap: 0; }
}

.field { position: relative; margin-bottom: 1.35rem; }

.field input,
.field select,
.field textarea {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: #fbfbfa;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1.15rem 0.9rem 0.4rem;
  transition: border-color 140ms var(--ease), box-shadow 140ms var(--ease);
  appearance: none;
}

.field textarea { resize: vertical; min-height: 6.5rem; line-height: 1.5; }

.field label {
  position: absolute;
  left: 0.95rem;
  top: 0.82rem;
  font-size: var(--step-0);
  color: var(--ink-soft);
  pointer-events: none;
  transform-origin: left top;
  transition: transform 150ms var(--ease), color 150ms var(--ease);
}

/* Float the label when the control has content or focus. */
.field input:focus + label,
.field input:not(:placeholder-shown) + label,
.field textarea:focus + label,
.field textarea:not(:placeholder-shown) + label,
.field select:focus + label,
.field select:valid + label {
  transform: translateY(-0.55rem) scale(0.75);
  color: var(--accent);
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(27, 108, 119, 0.14);
  outline: none;
}

.field--select::after {
  content: "";
  position: absolute;
  right: 1.1rem;
  top: 1.3rem;
  width: 0.5rem;
  height: 0.5rem;
  border-right: 1.5px solid var(--ink-soft);
  border-bottom: 1.5px solid var(--ink-soft);
  transform: rotate(45deg);
  pointer-events: none;
}

.field--select select { padding-right: 2.5rem; }

/* A select with no chosen value must not float its label. */
.field.field--select select:invalid + label { transform: none; color: var(--ink-soft); }
.field.field--select select:invalid:focus + label { transform: translateY(-0.55rem) scale(0.75); color: var(--accent); }

.field--area label { top: 0.82rem; }

/* A date input cannot carry a placeholder, so the usual float trigger never
   fires. The label simply stays floated and the native dd/mm/yyyy hint sits
   below it — no JS, and no reliance on ::-webkit-datetime-edit, which
   Firefox does not implement. */
.field--date label {
  transform: translateY(-0.55rem) scale(0.75);
  color: var(--ink-soft);
}
.field--date:focus-within label { color: var(--accent); }

.field--date input { color: var(--ink-soft); }
.field--date input:focus,
.field--date input:valid { color: var(--ink); }

.field--date input::-webkit-calendar-picker-indicator {
  opacity: 0.45;
  cursor: pointer;
}
@media (hover: hover) { .field--date input:hover::-webkit-calendar-picker-indicator { opacity: 1; } }

.field--error input,
.field--error select,
.field--error textarea { border-color: #a8332f; }

.field__error {
  margin: 0.35rem 0 0;
  font-size: var(--step--1);
  color: #a8332f;
}

.honey {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form__legal {
  font-size: var(--step--1);
  color: var(--ink-soft);
  margin-top: 1rem;
}

/* Cloudflare renders a 300x65 widget. Reserving it stops the submit button
   jumping down when the script finishes loading. */
.cf-turnstile {
  min-height: 65px;
  margin-bottom: 1.25rem;
}

/* Area page ------------------------------------------------------------- */

/* Three columns, so six zones land as two even rows. auto-fit resolved to
   five at this width and left a single orphan on the second row.

   Dividers are borders on the cells rather than a coloured container showing
   through 1px gaps — same reasoning as the package columns. */
.area-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-auto-rows: auto;
  gap: 0;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
}

.area-zone {
  background: var(--paper);
  padding: clamp(1.4rem, 2.4vw, 2rem);
  border-top: 1px solid var(--rule);
}

/* Subgrid: the heading, the note and the place list each get their own row,
   shared across every cell in the row of the outer grid. A two-line heading
   in one cell therefore sets the heading row for all of them, and the notes
   and lists still start level. Unlike a fixed min-height this holds however
   long the content gets. */
@supports (grid-template-rows: subgrid) {
  .area-zone {
    display: grid;
    grid-row: span 3;
    grid-template-rows: subgrid;
    align-content: start;
  }
}

/* Older browsers: reserve two lines for the heading and the note, which
   covers the content actually on the page. */
@supports not (grid-template-rows: subgrid) {
  .area-zone h3 { min-height: calc(2 * 1.15em); }
  .area-zone .area-zone__note { min-height: calc(2 * 1.6em); }
}

/* Vertical rules between columns, none on the leading edge of a row. */
.area-zone:not(:nth-child(3n + 1)) { border-left: 1px solid var(--rule); }

.area-zone h3 {
  font-size: var(--step-2);
  margin-bottom: 0.2rem;
}

.area-zone__note {
  font-size: var(--step--1);
  color: var(--ink-soft);
  margin-bottom: 1rem;
}

.area-zone ul {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--step--1);
  columns: 2;
  column-gap: 1.25rem;
}

.area-zone li {
  padding: 0.28rem 0;
  break-inside: avoid;
}

@media (max-width: 62rem) {
  .area-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .area-zone:not(:nth-child(3n + 1)) { border-left: 0; }
  .area-zone:not(:nth-child(2n + 1)) { border-left: 1px solid var(--rule); }
}

@media (max-width: 40rem) {
  .area-grid { grid-template-columns: 1fr; }
  .area-zone { border-left: 0 !important; }
  .area-zone ul { columns: 2; }
}

.area-note {
  margin-top: clamp(2rem, 4vw, 3rem);
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule);
}

/* 8. Editorial prose ---------------------------------------------------- */

.prose { max-width: 64ch; }

/* The 64ch cap, not the shell, is what actually sets the line length — so
   widening the container alone would have changed nothing. 72ch at this size
   is roughly 80 characters a line, near the top of what stays comfortable to
   read without the eye losing its place on the return sweep.

   The summary card is deliberately not capped this way: it lines up with the
   hero image at the full width of the shell. */
.band--article .prose { max-width: 72ch; }
.prose h2 { font-size: var(--step-3); margin-top: 2rem; }
.prose h3 { margin-top: 1.5rem; }
.prose p { font-size: var(--step-1); line-height: 1.65; }
.prose ul, .prose ol { font-size: var(--step-1); line-height: 1.65; padding-left: 1.2rem; }
.prose li { margin-bottom: 0.4rem; }
.prose blockquote {
  margin: 1.75rem 0;
  padding-left: 1.25rem;
  border-left: 3px solid var(--accent);
  font-size: var(--step-2);
  line-height: 1.45;
}
.prose img { border-radius: var(--radius); margin-block: 1.5rem; }

.band--article { padding-top: clamp(2.5rem, 6vw, 4.5rem); }

/* Summary block ---------------------------------------------------------- */

/* Takes its cues from the mega panel: near-white on the paper ground, the
   same 10px radius, and separation from a soft two-layer shadow rather than
   a coloured edge. No hover state — it is not clickable. */
.tldr {
  margin: 0 0 clamp(2rem, 4vw, 2.75rem);
  padding: clamp(1.35rem, 2.6vw, 1.9rem);
  background: #fbfbfa;
  border: 1px solid var(--rule-soft);
  border-radius: 10px;
  box-shadow:
    0 1px 2px rgba(22, 24, 26, 0.03),
    0 14px 30px -18px rgba(22, 24, 26, 0.28);
}

.tldr h2 {
  font-size: var(--step-1);
  letter-spacing: -0.02em;
  margin-bottom: 0.9rem;
}

.tldr ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.tldr li {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 0.32rem 0;
  font-size: var(--step-0);
  line-height: 1.5;
  color: var(--ink);
}

.tldr__icon {
  width: 1.05em;
  height: 1.05em;
  flex: none;
  margin-top: 0.22em;
  color: var(--accent);
}

.article__hero { margin: 1.75rem 0; }
.article__hero img { border-radius: var(--radius); }

/* Post gallery ----------------------------------------------------------- */

/* Justified rows without layout script. Each tile's basis is its aspect
   ratio times the target row height, and it grows in that same proportion,
   so every tile in a row stretches by the same factor and the row stays one
   height with nothing cropped. The trailing pseudo-element soaks up the
   spare space on the last row so a lone portrait isn't blown up to full
   width. */
/* Full site width rather than the article column, so the target row height
   can be taller: about two to three landscape frames to a row on a laptop. */
.post-gallery {
  --row-h: clamp(10rem, 22vw, 19rem);
  margin: 3rem 0 2.5rem;
}

.post-gallery__rows {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--wall-gap);
}
.post-gallery__rows::after {
  content: '';
  flex-grow: 999999;
}

.post-gallery__item {
  flex-grow: calc(var(--r) * 100);
  flex-basis: calc(var(--r) * var(--row-h));
  min-width: 0;
  aspect-ratio: var(--r);
  background: var(--paper-sunk);
  border-radius: var(--radius);
  overflow: hidden;
}

.post-gallery__item a {
  display: block;
  width: 100%;
  height: 100%;
  cursor: zoom-in;
}
.post-gallery__item a:focus-visible { outline-offset: -3px; }

.post-gallery__item img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;   /* only ever trims sub-pixel rounding */
}

/* A lower target height on phones keeps two or three tiles to a row rather
   than one, so a gallery doesn't become a long single-file scroll. */
@media (max-width: 30rem) {
  .post-gallery { --row-h: 8.5rem; }
}

/* Lightbox ---------------------------------------------------------------- */

.lightbox-open { overflow: hidden; }

.lightbox {
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ink-invert);
  overflow: hidden;
}
.lightbox::backdrop { background: rgba(22, 24, 26, 0.94); }

.lightbox__frame {
  margin: 0;
  width: 100%;
  height: 100%;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  place-items: center;
  padding: clamp(1rem, 4vw, 3rem) clamp(3.5rem, 7vw, 6rem) 1rem;
  box-sizing: border-box;
}

.lightbox__img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  transition: opacity 200ms linear;
}
.lightbox--loading .lightbox__img { opacity: 0.25; }

.lightbox__count {
  font-size: var(--step--1);
  color: var(--ink-invert-soft);
  font-variant-numeric: tabular-nums;
  min-height: 1.5em;
  padding-top: 0.5rem;
}

.lightbox__btn {
  position: absolute;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border: 0;
  border-radius: 50%;
  background: rgba(244, 244, 242, 0.08);
  color: var(--ink-invert);
  cursor: pointer;
  transition: background-color 160ms var(--ease);
}
.lightbox__btn:focus-visible { background: rgba(244, 244, 242, 0.18); }
@media (hover: hover) { .lightbox__btn:hover { background: rgba(244, 244, 242, 0.18); } }
.lightbox__btn svg { width: 1.4rem; height: 1.4rem; }

.lightbox__btn--prev { left: clamp(0.5rem, 2vw, 1.5rem); top: 50%; transform: translateY(-50%); }
.lightbox__btn--next { right: clamp(0.5rem, 2vw, 1.5rem); top: 50%; transform: translateY(-50%); }
.lightbox__btn--close { right: clamp(0.5rem, 2vw, 1.5rem); top: clamp(0.5rem, 2vw, 1.5rem); }

/* Phones: swipe does the stepping, so pull the arrows down out of the way
   of the picture. */
@media (max-width: 40rem) {
  .lightbox__frame { padding-inline: 0.75rem; padding-bottom: 4.5rem; }
  .lightbox__btn--prev,
  .lightbox__btn--next { top: auto; bottom: 1rem; transform: none; }
  .lightbox__btn--prev { left: calc(50% - 3.25rem); }
  .lightbox__btn--next { right: calc(50% - 3.25rem); }
}

@media (prefers-reduced-motion: reduce) {
  .lightbox__img,
  .lightbox__btn { transition: none; }
}


/* Horizontal rail ------------------------------------------------------- */

/* Runs off the right edge of the viewport so it reads as "there is more"
   without a row of dots. Native scroll, so it works with no JS at all. */
.rail-head {
  padding-inline-start: var(--rail-inset);
  padding-inline-end: var(--gutter);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.5rem;
  margin-bottom: clamp(1.5rem, 3vw, 2.25rem);
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--rule);
}

.rail-head h2 { margin: 0; font-size: var(--step-4); }

.rail-controls { display: flex; gap: 0.5rem; flex: none; }

.rail-controls[hidden] { display: none; }

.rail-btn {
  width: 2.75rem;
  height: 2.75rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--ink);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  transition: background-color 160ms var(--ease), color 160ms var(--ease), opacity 160ms var(--ease);
}
@media (hover: hover) { .rail-btn:hover { background: var(--ink); color: var(--paper); } }
.rail-btn:disabled { opacity: 0.28; cursor: default; }
@media (hover: hover) { .rail-btn:disabled:hover { background: transparent; color: var(--ink); } }
.rail-btn svg { width: 1.1rem; height: 1.1rem; }

.rail {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  /* Without this, snapping aligns a card to the scrollport edge — the
     viewport — and the browser scrolls straight past the track's leading
     padding at rest, pulling the first card flush left. scroll-padding moves
     the snap line to where the content actually starts, so the first card
     holds its alignment with the heading. */
  scroll-padding-inline-start: var(--rail-inset);
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.rail::-webkit-scrollbar { display: none; }

.rail__track {
  display: flex;
  gap: 1rem;
  margin: 0;
  padding-block: 0.5rem 1.75rem;
  padding-inline-start: var(--rail-inset);
  padding-inline-end: var(--gutter);
  list-style: none;
}

.rail-card {
  flex: 0 0 auto;
  /* Wide enough that the next card is plainly cut by the viewport edge
     rather than peeking as a sliver — roughly two and a half on screen. */
  width: clamp(17rem, 36vw, 31rem);
  scroll-snap-align: start;
  position: relative;
  display: flex;
  flex-direction: column;
  background: #fbfbfa;
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius-card);
  overflow: hidden;
  transition:
    transform 260ms var(--ease),
    box-shadow 260ms var(--ease),
    border-color 260ms var(--ease);
}

/* At rest the card sits flat on the page; the lift is the hover signal, so
   it reads as something you can act on rather than decoration. */
@media (hover: hover) { .rail-card:hover {
  transform: translateY(-3px);
  border-color: #d3d3cf;
  box-shadow:
    0 2px 4px rgba(22, 24, 26, 0.04),
    0 16px 32px -12px rgba(22, 24, 26, 0.26);
} }

.rail-card:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(27, 108, 119, 0.16);
}

/* The whole card is the hit area — the title carries the link. */
.rail-card__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* The photograph gives the card a hard edge. A pale panel with a hairline
   border reads as no edge at all, which made the rail look unaligned with
   the heading even when it was not. */
.rail-card__image {
  margin: 0;
  aspect-ratio: 3 / 2;
  background: var(--paper-sunk);
  overflow: hidden;
}
/* No zoom on hover. The card lift is the signal; moving the photograph as
   well was two things saying the same thing. */
.rail-card__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.rail-card__body {
  padding: clamp(1.1rem, 2vw, 1.5rem);
  display: flex;
  flex-direction: column;
  flex: 1;
}

.rail-card__date {
  font-size: var(--step--1);
  color: var(--ink-soft);
  margin: 0 0 0.4rem;
}

.rail-card h3 { margin-bottom: 0.5rem; }
.rail-card h3 a { color: var(--ink); text-decoration: none; }
@media (hover: hover) { .rail-card h3 a:hover { color: var(--accent); } }

.rail-card__excerpt {
  font-size: var(--step--1);
  color: var(--ink-soft);
  margin-bottom: 1.25rem;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.rail-card .textlink {
  margin-top: auto;
  align-self: flex-start;
  color: var(--accent);
}
@media (hover: hover) { .rail-card:hover .textlink { color: var(--accent-deep); } }

@media (prefers-reduced-motion: reduce) {
  .rail { scroll-snap-type: none; }
  .rail-card:hover { transform: none; }
}


/* Services mega menu ---------------------------------------------------- */

/* Deliberately not position:relative. The panel anchors to .masthead__inner
   instead, so the nav's horizontal scrolling cannot clip it. */
.nav__group { display: flex; }

/* Opacity and a 4px lift only — no height or scale, so nothing reflows and
   the panel never appears to jump. */
.mega {
  position: absolute;
  top: calc(100% + 0.7rem);
  /* Positioned against .masthead__inner so the nav's overflow cannot clip
     it, but placed under the trigger — JS measures the offset and sets
     --mega-left, since the trigger sits inside a scrolling track. */
  left: var(--mega-left, 1.4rem);
  z-index: 50;
  min-width: 20rem;
  padding: 0.75rem;
  background: var(--paper);
  border: 1px solid var(--rule-soft);
  border-radius: 10px;
  box-shadow:
    0 2px 6px rgba(22, 24, 26, 0.05),
    0 22px 44px -16px rgba(22, 24, 26, 0.3);
  opacity: 0;
  transform: translateY(-6px);
  visibility: hidden;
  transition:
    opacity 340ms var(--ease-soft),
    transform 340ms var(--ease-soft),
    visibility 0s linear 340ms;
}

.mega.is-open {
  opacity: 1;
  transform: translateY(0);
  visibility: visible;
  transition:
    opacity 340ms var(--ease-soft),
    transform 340ms var(--ease-soft),
    visibility 0s;
}

/* Bridges the gap between the capsule and the panel so a diagonal mouse
   path does not fall through the crack and close it. */
.mega::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -1.1rem;
  height: 1.1rem;
}

.mega__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Scoped through .mega so these beat .nav a, which is (0,1,1) and otherwise
   reimposes the capsule's pill radius and padding on everything in here. */
.mega .mega__list a {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.7rem 0.85rem;
  border-radius: 6px;
  text-decoration: none;
  color: var(--ink);
  transition: background-color 150ms var(--ease);
}
@media (hover: hover) { .mega .mega__list a:hover { background: rgba(22, 24, 26, 0.05); } }

.mega__name { font-weight: 600; font-size: var(--step-0); letter-spacing: -0.015em; }
.mega__scale { font-size: var(--step--1); color: var(--ink-soft); }

.mega a.mega__promo {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  margin-top: 0.5rem;
  /* Same radius as a list item so it sits on the panel's grid, and 1px less
     padding to absorb the border — otherwise this one box is 2px taller. */
  padding: calc(0.7rem - 1px) calc(0.85rem - 1px);
  border-radius: 6px;
  /* A green cast rather than the neutral grey the list items use on hover, so
     this reads as a different kind of thing rather than a stuck hover state. */
  background: var(--accent-wash);
  border: 1px solid var(--accent-wash-line);
  text-decoration: none;
  transition: background-color 150ms var(--ease), border-color 150ms var(--ease);
}

@media (hover: hover) { .mega a.mega__promo:hover {
  background: var(--accent-wash-hover);
  border-color: var(--accent-wash-hover-line);
} }

.mega__promo-label { font-size: var(--step--1); color: var(--ink-soft); }
.mega__promo-action { font-weight: 600; color: var(--accent); letter-spacing: -0.015em; }

/* Narrow screens have no hover and the capsule stacks — Services is just a
   link there. */
/* Tighten the row before it runs out of space, rather than after. With five
   items the single row needs about 846px, so it stacks at 64rem where the
   capsule still offers 942px. */
@media (max-width: 80rem) {
  .nav { gap: 0; }
  .nav a { padding-inline: 0.7rem; }
  .nav__action { padding: 0.6rem 1rem; }
}

@media (max-width: 64rem) {
  /* No scrolling row of links — the whole thing moves into a menu panel. */
  .masthead__inner {
    grid-template-columns: auto 1fr;
    padding: 0.4rem 0.4rem 0.4rem 1.1rem;
  }
  .nav { display: none; }
  .nav-toggle { display: inline-flex; }
  .mega { display: none; }
}

/* Below this the wordmark, the CTA and the menu button no longer fit on one
   line. The CTA goes — it is the first thing in the menu panel, and the
   homepage statement block carries one too. */
@media (max-width: 36rem) {
  .nav__action { display: none; }
}

@media (max-width: 26rem) {
  /* The frame mark is narrow enough that mark and name both fit beside the
     menu button even on a small phone. Keep the name: the corners alone say
     nothing about who this is. */
  .logo--monogram,
  .logo--wordmark { height: 1.3rem; }
}

@media (prefers-reduced-motion: reduce) {
  .mega { transform: none; transition: opacity 1ms, visibility 0s; }
  .mega.is-open { transform: none; }
}

/* Site menu (narrow screens) -------------------------------------------- */

/* Same native <dialog> mechanics as the enquiry drawer: focus trapping,
   Esc and an inert page behind, all for free. */
.menu {
  border: 0;
  padding: 0;
  margin: 0 0 0 auto;
  max-width: none;
  max-height: none;
  width: min(24rem, 100%);
  height: 100%;
  height: 100dvh;
  background: var(--paper);
  color: var(--ink);
  overflow: hidden;
  transform: translateX(100%);
  transition: transform 340ms var(--ease-soft);
}
.menu.is-open { transform: translateX(0); }

/* On a phone the 24rem panel left a sliver of page down the left edge, which
   read as a mistake rather than a peek. Phones get the full width; tablets
   keep the side panel with the page dimmed behind it. */
@media (max-width: 30rem) {
  .menu { width: 100%; }
}

.menu::backdrop {
  background: rgba(22, 24, 26, 0);
  transition: background-color 340ms var(--ease-soft);
}
.menu.is-open::backdrop { background: rgba(22, 24, 26, 0.42); }

.menu__panel {
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: clamp(1.25rem, 4vw, 1.75rem);
}

.menu__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1.25rem;
}
.menu__head h2 { font-size: var(--step-2); margin: 0; }

.menu__nav { display: flex; flex-direction: column; }

.menu__link {
  display: block;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--rule);
  font-size: var(--step-2);
  font-weight: 600;
  letter-spacing: -0.025em;
  color: var(--ink);
  text-decoration: none;
}
.menu__link[aria-current="page"] { color: var(--accent); }

.menu__sub {
  list-style: none;
  margin: 0;
  padding: 0.5rem 0 0.9rem;
  border-bottom: 1px solid var(--rule);
}
/* Package rows: name on the left, size and an arrow on the right, the usual
   sign on a phone that a row opens something. No hover state to get stuck
   after a tap; instead the row flashes teal while it is being pressed. */
.menu__sub a {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  padding: 0.55rem 0;
  font-size: var(--step-0);
  color: var(--ink);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: color 120ms var(--ease);
}
.menu__sub a > :first-child { margin-right: auto; }
.menu__sub a::after {
  content: "";
  flex: none;
  align-self: center;
  width: 0.45rem;
  height: 0.45rem;
  margin-left: -0.25rem;
  border-top: 1.5px solid currentColor;
  border-right: 1.5px solid currentColor;
  transform: rotate(45deg);
  opacity: 0.45;
}
.menu__sub a:active,
.menu__sub a:active .menu__sub-scale { color: var(--accent); }
@media (hover: hover) { .menu__sub a:hover { color: var(--accent); } }
.menu__sub-scale { font-size: var(--step--1); color: var(--ink-soft); }

.menu__foot { margin-top: auto; padding-top: 1.75rem; }

.menu__contact {
  margin: 1rem 0 0;
  font-size: var(--step--1);
  color: var(--ink-soft);
  line-height: 1.7;
}
.menu__contact a { color: var(--ink-soft); text-decoration: none; }
@media (hover: hover) { .menu__contact a:hover { color: var(--accent); } }

@media (prefers-reduced-motion: reduce) {
  .menu { transition: none; }
}

/* Enquiry drawer -------------------------------------------------------- */

/* Native <dialog>, so focus trapping, Esc and inert background come free.
   The slide is driven by a class rather than [open], because display goes
   none -> block and there is nothing to transition from. */
.drawer {
  border: 0;
  padding: 0;
  margin: 0 0 0 auto;
  max-width: none;
  max-height: none;
  width: min(30rem, 100%);
  height: 100%;
  height: 100dvh;
  background: var(--paper);
  color: var(--ink);
  overflow: hidden;
  transform: translateX(100%);
  transition: transform 380ms var(--ease);
}

.drawer.is-open { transform: translateX(0); }
/* site.js focuses the dialog itself on open; it needs no ring of its own. */
.drawer:focus, .menu:focus { outline: none; }

.drawer::backdrop {
  background: rgba(22, 24, 26, 0);
  transition: background-color 380ms var(--ease);
}
.drawer.is-open::backdrop { background: rgba(22, 24, 26, 0.42); }

.drawer__panel {
  height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: clamp(1.5rem, 4vw, 2.25rem);
  -webkit-overflow-scrolling: touch;
}

.drawer__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.5rem;
}
.drawer__head h2 { font-size: var(--step-3); margin: 0; }

.drawer__close {
  flex: none;
  width: 2.5rem;
  height: 2.5rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  transition: background-color 160ms var(--ease), color 160ms var(--ease);
}
@media (hover: hover) { .drawer__close:hover { background: var(--ink); color: var(--paper); } }
.drawer__close svg { width: 1.1rem; height: 1.1rem; }

.drawer__lede {
  font-size: var(--step--1);
  color: var(--ink-soft);
  margin-bottom: 1.75rem;
}

.drawer .form { max-width: none; }

/* Submit states --------------------------------------------------------- */

.submit {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  min-height: 2.9rem;
}

.submit__spinner {
  display: none;
  width: 1.05rem;
  height: 1.05rem;
  border: 2px solid rgba(255, 255, 255, 0.35);
  border-top-color: #fff;
  border-radius: 999px;
  animation: submit-spin 700ms linear infinite;
}

.submit.is-busy { cursor: progress; }
.submit.is-busy .submit__spinner { display: block; }

@keyframes submit-spin { to { transform: rotate(360deg); } }

/* The form leaves, the confirmation arrives in its place. */
.form.is-leaving {
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity 260ms var(--ease), transform 260ms var(--ease);
  pointer-events: none;
}

.sent {
  color: var(--accent);
  padding: 1rem 0 2rem;
}

.sent__tick { width: 3.5rem; height: 3.5rem; display: block; margin-bottom: 1.25rem; }

.sent__tick circle,
.sent__tick path {
  stroke-dasharray: 160;
  stroke-dashoffset: 160;
}

.sent.is-shown .sent__tick circle { animation: tick-draw 620ms var(--ease) forwards; }
.sent.is-shown .sent__tick path { animation: tick-draw 420ms var(--ease) 420ms forwards; }

@keyframes tick-draw { to { stroke-dashoffset: 0; } }

.sent h3 {
  color: var(--ink);
  font-size: var(--step-3);
  opacity: 0;
}
.sent p {
  color: var(--ink-soft);
  opacity: 0;
}
.sent.is-shown h3 { animation: sent-rise 420ms var(--ease) 520ms forwards; }
.sent.is-shown p { animation: sent-rise 420ms var(--ease) 640ms forwards; }

@keyframes sent-rise {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

.form__alert {
  padding: 0.9rem 1.1rem;
  margin-bottom: 1.25rem;
  border-left: 3px solid #a8332f;
  background: rgba(168, 51, 47, 0.07);
}
.form__alert p { margin: 0; font-size: var(--step--1); color: #8a2a26; }

@media (prefers-reduced-motion: reduce) {
  .drawer { transition: none; }
  .sent__tick circle, .sent__tick path { stroke-dashoffset: 0; }
  .sent h3, .sent p { opacity: 1; animation: none; }
}

/* 9. Responsive --------------------------------------------------------- */

@media (max-width: 60rem) {
  .shell--split { grid-template-columns: 1fr; }

  /* Three columns, statement on its own full-width row, 15 tiles below.
     
     The container drops its aspect-ratio here and the rows size themselves.
     A fixed-ratio grid gives every row the same height, which is fine for
     photographs but not for the statement: its heading, supporting line, tier
     list and button need more room than one row provides at this width, and
     the surplus was being clipped — the button disappeared behind the first
     row of faces.
     
     With auto rows the statement takes the height it needs, and the tiles
     hold their shape by claiming 2:3 individually instead of inheriting it
     from the container. */
  .wall__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-rows: none;
    grid-auto-rows: auto;
    aspect-ratio: auto;
  }
  .tile { aspect-ratio: 2 / 3; }
  .tile:nth-child(n + 17) { display: none; }

  .wall__statement {
    grid-column: 1 / -1;
    grid-row: auto;
    overflow: visible;
    gap: 0.75rem;
    padding-block: clamp(2rem, 7vw, 3rem);
  }

  /* Carry the statement's graphite up behind the masthead to the top of the
     page. The masthead is sticky rather than fixed, so it takes space in the
     flow and the paper ground showed above the wall as a pale band.

     Painted by a pseudo-element rather than by moving anything: a negative
     margin would need to match the capsule's height, which changes with the
     logo and the font. This just fills whatever is up there. The masthead
     sits at z-index 40 and so stays above it. */
  .wall { overflow: visible; }

  .wall::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 100%;
    height: 50vh;
    background: var(--graphite);
    pointer-events: none;
  }

  /* The capsule is translucent glass tuned for the paper ground, where 0.72
     resolves to #F3F3F1. Over the graphite above it resolves to #BABBBA — a
     dull mid-grey. Raising the alpha on this page only brings it back to
     #E4E4E2 while the blur still does its work. Scoped to the homepage
     because every other page keeps the pale ground it was designed for. */
  .page-home .masthead__inner {
    background: rgba(244, 244, 242, 0.96);
    border-color: rgba(22, 24, 26, 0.04);
    box-shadow:
      0 1px 1px rgba(22, 24, 26, 0.10),
      0 12px 32px -14px rgba(0, 0, 0, 0.55);

    /* No backdrop blur here. It asks the browser to blur whatever is behind
       the capsule on every frame, and on this page that is a grid of
       photographs moving under it — the most expensive case there is. At
       this opacity only 4% of the backdrop shows through, so the blur was
       costing frames to produce an effect nobody can see. Other pages keep
       it: there the capsule is more transparent and the ground is flat. */
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

@media (max-width: 40rem) {
  /* Same auto-row arrangement as above, with 12 tiles rather than 15 — four
     rows of three under the statement. */
  .tile:nth-child(n + 14) { display: none; }

  .wall__statement { padding: 1.5rem 1.25rem; }
  .packages { grid-template-columns: 1fr; }
  .package + .package {
    border-left: 0;
    border-top: 1px solid var(--rule);
  }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  .tile img { opacity: 1; transform: none; }

  .wall__statement h1,
  .wall__statement p,
  .wall__statement .wall__tiers,
  .wall__statement .button {
    opacity: 1;
    transform: none;
    animation: none;
  }
  .tile img {
    opacity: 1;
    transform: none;
    transition: none;
    transition-delay: 0ms !important;
  }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* Holding page ----------------------------------------------------------- */

.holding {
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  place-items: center;
  background: var(--graphite);
  color: var(--ink-invert);
  padding: var(--gutter);
}

.holding__inner {
  width: min(42rem, 100%);
}

.holding__mark {
  margin: 0 0 clamp(2rem, 6vw, 3.5rem);
  color: var(--ink-invert);
}
.holding__mark .logo--lockup { width: 7.5rem; }

.holding h1 {
  font-size: var(--step-5);
  color: var(--ink-invert);
  margin-bottom: 0.5em;
}

.holding__message {
  font-size: var(--step-2);
  line-height: 1.45;
  color: var(--ink-invert-soft);
  max-width: 46ch;
}

.holding__tiers {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem clamp(1.25rem, 3vw, 2.5rem);
  margin: clamp(2rem, 5vw, 3rem) 0 0;
  padding: 1.1rem 0 0;
  border-top: 1px solid rgba(244, 244, 242, 0.16);
  list-style: none;
}

.holding__tiers li { display: flex; flex-direction: column; gap: 0.05rem; }
.holding__tier-name { font-size: var(--step-0); font-weight: 600; letter-spacing: -0.015em; }
.holding__tier-scale { font-size: 0.78rem; color: var(--ink-invert-soft); }

.holding__contact {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.75rem;
  margin: clamp(2rem, 5vw, 3rem) 0 0;
  font-size: var(--step--1);
}
.holding__contact a { color: var(--accent-lift); text-decoration: none; }
@media (hover: hover) { .holding__contact a:hover { color: #5cc0cc; } }

/* Holding-page ident: focus lock. The two corners of the frame mark close
   in from wide like autofocus brackets, give one confirm flick, and settle;
   then the name and the page follow.

   CSS only, so it runs from first paint with no script; `both` holds each
   piece hidden until its turn. One timeline, expressed as delays:
     0–1.30s     corners converge (held faint for the first 0.2s)
     1.35–1.61s  confirm flick
     1.55s on    wordmark, then the copy in sequence */
.holding__mark svg { overflow: visible; }

.holding__mark .logo__ell,
.holding__mark .logo__upper,
.holding__mark .logo__words {
  transform-box: view-box;
}

.holding__mark .logo__ell   { animation: lh-lock-ell 1650ms both; }
.holding__mark .logo__upper { animation: lh-lock-upper 1650ms both; }
.holding__mark .logo__words { animation: lh-fade 700ms linear 1550ms both; }

.holding h1,
.holding__message,
.holding__tiers,
.holding__contact {
  animation: lh-rise 800ms var(--ease-soft) both;
}
.holding h1           { animation-delay: 1900ms; }
.holding__message     { animation-delay: 2050ms; }
.holding__tiers       { animation-delay: 2200ms; }
.holding__contact     { animation-delay: 2350ms; }

@keyframes lh-lock-ell {
  0%     { opacity: 0;    transform: translate(-48px, 48px); animation-timing-function: linear; }
  11.8%  { opacity: 0.35; transform: translate(-48px, 48px); animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
  78.8%  { opacity: 1;    transform: translate(0, 0); }
  81.8%  { transform: translate(0, 0); animation-timing-function: ease-in-out; }
  89.7%  { transform: translate(3px, -3px); animation-timing-function: ease-in-out; }
  97.6%, 100% { opacity: 1; transform: translate(0, 0); }
}

@keyframes lh-lock-upper {
  0%     { opacity: 0;    transform: translate(48px, -48px); animation-timing-function: linear; }
  11.8%  { opacity: 0.35; transform: translate(48px, -48px); animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
  78.8%  { opacity: 1;    transform: translate(0, 0); }
  81.8%  { transform: translate(0, 0); animation-timing-function: ease-in-out; }
  89.7%  { transform: translate(-3px, 3px); animation-timing-function: ease-in-out; }
  97.6%, 100% { opacity: 1; transform: translate(0, 0); }
}

@keyframes lh-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes lh-rise {
  from { opacity: 0; transform: translateY(0.75rem); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .holding__mark .logo__ell,
  .holding__mark .logo__upper,
  .holding__mark .logo__words,
  .holding h1,
  .holding__message,
  .holding__tiers,
  .holding__contact {
    animation: none;
  }
}

/* Hidden-site flag ------------------------------------------------------- */

/* Fixed strip at the bottom of the screen holding the cookie banner and the
   admin's coming-soon flag, so the two stack instead of overlapping. */
.bottom-stack {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
}

/* Cookie banner. Both answers share one button style: under UK guidance,
   refusing must be as easy and as prominent as accepting. */
.consent {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.75rem 1.5rem;
  padding: 1rem var(--gutter);
  background: var(--graphite);
  color: var(--ink-invert);
  font-size: var(--step--1);
  box-shadow: 0 -6px 20px -10px rgba(22, 24, 26, 0.5);
}
.consent[hidden] { display: none; }
.consent__text { margin: 0; max-width: 60ch; }
.consent__text a { color: inherit; font-weight: 600; text-underline-offset: 0.2em; }
.consent__actions { display: flex; gap: 0.5rem; }
.consent .button { border-color: var(--ink-invert); color: var(--ink-invert); }
.consent .button:focus-visible { background: var(--ink-invert); color: var(--graphite); }
@media (hover: hover) { .consent .button:hover { background: var(--ink-invert); color: var(--graphite); } }

/* The change-your-mind controls on /cookies. */
.consent-settings { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1rem; }

/* Shown on the public site to a signed-in admin while coming-soon mode is on.
   Lives in .bottom-stack so it cannot scroll out of sight and be forgotten. */
.hidden-flag {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.5rem 1rem;
  padding: 0.65rem var(--gutter);
  background: #8a2a26;
  color: #fff;
  font-size: var(--step--1);
  font-weight: 500;
  text-align: center;
  box-shadow: 0 -6px 20px -10px rgba(22, 24, 26, 0.5);
}

.hidden-flag a {
  color: #fff;
  font-weight: 600;
  text-underline-offset: 0.2em;
}

.hidden-flag__dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 999px;
  background: #fff;
  flex: none;
  animation: hidden-flag-pulse 2.4s ease-in-out infinite;
}

@keyframes hidden-flag-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

@media (prefers-reduced-motion: reduce) {
  .hidden-flag__dot { animation: none; }
}

/* News list reveal ------------------------------------------------------- */

/* Opacity only, nothing moving. The hidden state is behind a class the script
   adds to <html>, so with JS off the list is never left invisible. */
.js-news-reveal .news-list--full .news-item { opacity: 0; }

.js-news-reveal .news-list--full .news-item.is-in {
  animation: news-fade 900ms linear forwards;
  animation-delay: calc(var(--i, 0) * 110ms);
}

@keyframes news-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .js-news-reveal .news-list--full .news-item { opacity: 1; animation: none; }
}

/* News list thumbnails --------------------------------------------------- */

.news-item--illustrated {
  display: grid;
  grid-template-columns: 12rem minmax(0, 1fr);
  gap: clamp(1rem, 2.5vw, 2rem);
  align-items: start;
}

.news-item__thumb {
  display: block;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius-card);
  background: var(--paper-sunk);
}

.news-item__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 600ms var(--ease);
}
@media (hover: hover) { .news-item:hover .news-item__thumb img { transform: scale(1.03); } }

.news-item__body > :last-child { margin-bottom: 0; }

@media (max-width: 40rem) {
  /* Stack rather than shrink — a 6rem thumb is too small to read. */
  .news-item--illustrated { grid-template-columns: 1fr; }
  .news-item__thumb { max-width: 16rem; }
}

@media (prefers-reduced-motion: reduce) {
  .news-item:hover .news-item__thumb img { transform: none; }
}

/* Article images --------------------------------------------------------- */

/* 1000ms linear. Linear because an eased fade does most of its work in the
   first third and the tail is wasted, so it feels faster than the number
   suggests — which is why this reads slower than the wall's 900ms eased.

   The wall also runs a slow scale; omitted here because an article image is
   far larger and the movement reads as a wobble rather than a settle. The
   hidden state is behind a class the script adds to <html>, so nothing is
   left invisible without JS. */
.js-img-reveal .band--article .article__hero img,
.js-img-reveal .band--article .prose img,
.js-img-reveal .band--article .post-gallery img {
  opacity: 0;
  transition: opacity 1000ms linear;
  transition-delay: calc(var(--i, 0) * 80ms);
}

.js-img-reveal .band--article .article__hero img.is-in,
.js-img-reveal .band--article .prose img.is-in,
.js-img-reveal .band--article .post-gallery img.is-in {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .js-img-reveal .band--article .article__hero img,
  .js-img-reveal .band--article .prose img,
  .js-img-reveal .band--article .post-gallery img {
    opacity: 1;
    transition: none;
  }
}

/* FAQ -------------------------------------------------------------------- */

.band--faq { padding-block: clamp(2.5rem, 5vw, 4rem); }

/* The group name sits in the left column of the split shell and stays put
   while the questions run down beside it. */
.faq__group {
  font-size: var(--step-3);
  margin: 0;
}

.faq { display: grid; }

.faq__item {
  padding: clamp(1.1rem, 2vw, 1.5rem) 0;
  border-top: 1px solid var(--rule);
}
.faq__item:last-child { border-bottom: 1px solid var(--rule); }

.faq__item h3 {
  font-size: var(--step-2);
  margin-bottom: 0.45rem;
}

.faq__item p {
  margin: 0;
  max-width: 62ch;
  font-size: var(--step-0);
  line-height: 1.6;
  color: var(--ink-soft);
}

@media (min-width: 60rem) {
  .band--faq .faq__group { position: sticky; top: 6rem; }
}

/* Jump to the form ------------------------------------------------------- */

.jump-down {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 1.5rem;
  padding: 0.6rem 1.1rem 0.6rem 1.2rem;
  border-radius: 999px;
  background: var(--accent-wash);
  border: 1px solid var(--accent-wash-line);
  color: var(--accent-deep);
  font-size: var(--step--1);
  font-weight: 600;
  text-decoration: none;
  transition: background-color 160ms var(--ease), border-color 160ms var(--ease);
}

@media (hover: hover) { .jump-down:hover {
  background: var(--accent-wash-hover);
  border-color: var(--accent-wash-hover-line);
  color: var(--accent-deep);
} }

.jump-down svg {
  width: 1rem;
  height: 1rem;
  flex: none;
  /* A slow nudge rather than a bounce. The point is to catch the eye of
     someone who has not realised there is more page, not to demand it. */
  animation: jump-down-nudge 2.6s ease-in-out infinite;
}

@keyframes jump-down-nudge {
  0%, 60%, 100% { transform: translateY(0); }
  75%           { transform: translateY(0.2rem); }
  88%           { transform: translateY(0); }
}

@media (hover: hover) { .jump-down:hover svg { animation: none; transform: translateY(0.15rem); } }

/* Land clear of the floating capsule rather than under it. */
#enquiry-form { scroll-margin-top: 6rem; }

@media (prefers-reduced-motion: reduce) {
  .jump-down svg { animation: none; }
}

/* Area page ------------------------------------------------------------- */

/* Full-width skyline under the page heading, letterboxed. */
.area-banner { margin: 0; }
.area-banner img {
  width: 100%;
  height: clamp(13rem, 34vw, 30rem);
  object-fit: cover;
  object-position: center 72%;
  background: var(--paper-sunk);
}

/* Office photo beside the room requirements; the text column is the
   narrower one so the picture carries the section. */
.area-room {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}
.area-room__figure { margin: 0; }
.area-room__figure img { width: 100%; height: auto; border-radius: var(--radius); background: var(--paper-sunk); }
@media (max-width: 60rem) {
  .area-room { grid-template-columns: 1fr; }
}

/* Services: sticky package tabs ----------------------------------------- */

/* A small glass capsule, like the masthead, that sticks while the packages
   are on screen. It sits below the masthead while that is showing and moves
   up into its place once it retracts, so the two never overlap. */
.pkg-tabs {
  position: sticky;
  top: 4.6rem;
  z-index: 30;
  display: flex;
  justify-content: center;
  padding: 0.75rem var(--gutter) 0;
  margin-bottom: -3.4rem;   /* overlap the first band rather than push it down */
  pointer-events: none;     /* only the capsule itself takes clicks */
  transition: top 300ms var(--ease);
}
html:has(.masthead.is-retracted) .pkg-tabs,
.pre-retracted .pkg-tabs { top: 0.6rem; }

.pkg-tabs__inner {
  display: flex;
  gap: 0.15rem;
  padding: 0.3rem;
  border-radius: 999px;
  border: 1px solid rgba(22, 24, 26, 0.07);
  background: rgba(244, 244, 242, 0.9)  /* more solid than the masthead: it sits over body text, not photographs */;
  backdrop-filter: blur(18px) saturate(170%);
  -webkit-backdrop-filter: blur(18px) saturate(170%);
  box-shadow: 0 1px 1px rgba(22, 24, 26, 0.03), 0 10px 30px -14px rgba(22, 24, 26, 0.35);
  pointer-events: auto;
  font-size: var(--step--1);
  font-weight: 500;
}
.pkg-tabs a {
  padding: 0.45rem 0.95rem;
  border-radius: 999px;
  color: var(--ink-soft);
  text-decoration: none;
  white-space: nowrap;
  letter-spacing: -0.015em;
  transition: background-color 160ms var(--ease), color 160ms var(--ease);
}
@media (hover: hover) { .pkg-tabs a:hover { color: var(--ink); background: rgba(22, 24, 26, 0.055); } }
.pkg-tabs a[aria-current="true"] { color: var(--ink-invert); background: var(--graphite); }

/* Land each package clear of the tabs (and the masthead) when jumped to. */
.band--service { scroll-margin-top: 5.5rem; }

@media (prefers-reduced-motion: reduce) {
  .pkg-tabs { transition: none; }
}


/* Contact page ---------------------------------------------------------- */

/* Desktop: intro and next steps on the left, the form in a card on the
   right, beside them rather than below. Phone: heading, form, then the rest. */
.contact { padding-block: clamp(2.5rem, 6vw, 5rem) var(--band-y); }
.contact__grid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  grid-template-areas:
    "head card"
    "more card";
  grid-template-rows: auto 1fr;
  gap: 0 clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}
.contact__head { grid-area: head; }
.contact__card { grid-area: card; }
.contact__more { grid-area: more; }

.contact__eyebrow {
  margin: 0 0 0.6rem;
  color: var(--accent);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.contact__head h1 { font-size: var(--step-4); line-height: 1.02; letter-spacing: -0.035em; }
.contact__lede { font-size: var(--step-1); line-height: 1.5; color: var(--ink-soft); max-width: 34ch; margin: 0; }

.contact__card {
  padding: clamp(1.5rem, 3vw, 2.25rem);
  background: #fafaf8;
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius-card);
  box-shadow: 0 1px 2px rgba(22, 24, 26, 0.04), 0 18px 40px -28px rgba(22, 24, 26, 0.35);
  scroll-margin-top: 6rem;
}
/* The card sets the width; the form fills it. */
.contact__card .form { max-width: none; }

.contact__more { margin-top: clamp(2rem, 4vw, 3rem); }
.contact__more h2 { font-size: var(--step-1); letter-spacing: -0.015em; margin-bottom: 0.9rem; }

.contact__steps {
  list-style: none;
  counter-reset: step;
  margin: 0 0 2.25rem;
  padding: 0;
}
.contact__steps li {
  counter-increment: step;
  position: relative;
  padding: 0 0 1rem 2.6rem;
  line-height: 1.5;
  color: var(--ink-soft);
}
.contact__steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: -0.05rem;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background: var(--accent-wash);
  border: 1px solid var(--accent-wash-line);
  color: var(--accent-deep);
  font-size: 0.85rem;
  font-weight: 600;
}
.contact__steps strong { color: var(--ink); }

.contact__direct { padding-top: 1.5rem; border-top: 1px solid var(--rule); }
.contact__direct p { margin: 0 0 0.35rem; }
.contact__direct a { font-weight: 600; }
.contact__note { margin-top: 0.9rem !important; font-size: var(--step--1); color: var(--ink-soft); }

@media (max-width: 60rem) {
  .contact__grid {
    grid-template-columns: 1fr;
    grid-template-areas: "head" "card" "more";
    grid-template-rows: auto;
  }
  .contact__head { margin-bottom: 1.75rem; }
  .contact__card { max-width: 42rem; }
}

/* Homepage intro: a large statement beside a normal-sized explanation. */
.intro-split {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: 1rem clamp(2rem, 6vw, 5.5rem);
  align-items: center;
}
.intro-split__statement {
  margin: 0;
  font-size: var(--step-3);
  line-height: 1.04;
  letter-spacing: -0.035em;
}
/* Sits under the photo wall with the same gap as between its tiles. */
.band--intro.band--dark { margin-top: var(--wall-gap); }
/* On the dark band both parts are white, like the panel in the photo wall. */
.band--dark .intro-split__body { color: var(--ink-invert); }
/* One sentence to a line, so it never breaks mid-phrase. */
.intro-split__statement span { display: block; }
.intro-split__body {
  margin: 0;
  font-size: var(--step-1);
  line-height: 1.6;
  color: var(--ink-soft);
  max-width: 46ch;
}
@media (max-width: 60rem) {
  .intro-split { grid-template-columns: 1fr; }
}

/* News list paging: newer on the left, older on the right, like the order
   of the list itself. */
.pager {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: baseline;
  gap: 1rem;
  margin-top: clamp(2rem, 4vw, 3rem);
}
.pager > * { justify-self: start; }
.pager > :last-child { justify-self: end; }
.pager__where { font-size: var(--step--1); color: var(--ink-soft); }

