/* ==========================================================================
   Gregory Waite | Photography & Videography
   First draft stylesheet
   Palette: black / white / dark khaki accent. Images always sit on white.
   ========================================================================== */

/* ---------- Fonts ---------------------------------------------------------
   Milker is a licensed font (Los Andes). Drop the webfont files into
   /assets/fonts/ and uncomment the @font-face block below. Until then the
   stack falls through to Big Shoulders Display, which is the closest free
   match in weight, width and geometry.
   -------------------------------------------------------------------------- */

/*
@font-face {
  font-family: 'Milker';
  src: url('../fonts/Milker.woff2') format('woff2'),
       url('../fonts/Milker.woff') format('woff');
  font-weight: 400 900;
  font-display: swap;
}
*/

/* ---------- Tokens -------------------------------------------------------- */

:root {
  /* Core palette */
  --paper:        #FFFFFF;
  --paper-warm:   #F4F3F0;
  --paper-edge:   #DAD6CD;
  --ink:          #0B0B0B;
  --ink-soft:     #56564F;
  --ink-faint:    #8C8C85;

  /* Dark khaki accent */
  --khaki:        #3E442F;
  --khaki-mid:    #59614A;
  --khaki-light:  #8A9174;

  /* Derived */
  --bg:           var(--paper);
  --fg:           var(--ink);
  --fg-soft:      var(--ink-soft);
  --line:         rgba(11, 11, 11, 0.14);
  --line-strong:  rgba(11, 11, 11, 0.3);
  --media-bed:    var(--paper-warm);
  --media-edge:   var(--paper-edge);

  /* Type */
  --display: 'Milker', 'Big Shoulders Display', 'Oswald', 'Haettenschweiler', sans-serif;
  --body:    'EB Garamond', 'Iowan Old Style', Georgia, serif;

  /* Rhythm */
  --gut:      clamp(1.25rem, 3.2vw, 2.75rem);
  --edge:     clamp(1.25rem, 4vw, 4.5rem);
  --section:  clamp(5rem, 12vh, 10rem);
  --header-h: 74px;

  /* The mark, and the dot lifted straight out of it. The circle in the mark is
     r=140 inside a 1705x1105 viewBox, so at any mark height the dot measures
     280/1105 of it. The cursor uses the same figure, so it is always exactly
     the size of the dot on the logo. */
  --mark-h:   clamp(24px, 2.4vw, 34px);
  --cur-size: calc(var(--mark-h) * 280 / 1105);

  /* Motion */
  --ease:     cubic-bezier(0.22, 1, 0.36, 1);
  --ease-io:  cubic-bezier(0.65, 0, 0.35, 1);
  --theme-t:  420ms;
}

/* Dark scroll state. Applied to <body> by the scroll observer. */
body[data-theme='dark'] {
  --bg:          var(--ink);
  --fg:          var(--paper);
  --fg-soft:     rgba(255, 255, 255, 0.6);
  --line:        rgba(255, 255, 255, 0.16);
  --line-strong: rgba(255, 255, 255, 0.34);
  --media-bed:   #26261F;
  --media-edge:  #4E4E40;
}

/* ---------- Reset --------------------------------------------------------- */

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--body);
  font-size: clamp(0.95rem, 0.38vw + 0.87rem, 1.08rem);
  line-height: 1.64;
  font-feature-settings: 'liga' 1, 'kern' 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  transition: color 200ms var(--ease);
}

img, video, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
ul, ol { margin: 0; padding: 0; list-style: none; }
h1, h2, h3, h4, p, figure, blockquote { margin: 0; }

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

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

/* ---------- Type scale ---------------------------------------------------- */

.d1, .d2, .d3, .d4 {
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  line-height: 0.86;
  letter-spacing: -0.015em;
  margin: 0;
}

.d1 { font-size: clamp(3rem, 11.5vw, 12rem); }
.d2 { font-size: clamp(2.4rem, 7.4vw, 7rem); }
.d3 { font-size: clamp(1.9rem, 4.2vw, 3.6rem); line-height: 0.92; }
.d4 { font-size: clamp(1.4rem, 2.2vw, 2.1rem); line-height: 0.98; }

.eyebrow {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: clamp(0.72rem, 0.85vw, 0.86rem);
  letter-spacing: 0.28em;
  line-height: 1;
  color: var(--khaki-mid);
}
body[data-theme='dark'] .eyebrow { color: var(--khaki-light); }

.lede {
  font-size: clamp(1.05rem, 1.15vw, 1.32rem);
  line-height: 1.55;
  color: var(--fg);
  max-width: 46ch;
}

.prose { max-width: 62ch; color: var(--fg-soft); }
.prose p + p { margin-top: 1.1em; }

.idx {
  font-family: var(--display);
  font-weight: 600;
  font-size: 0.8rem;
  letter-spacing: 0.2em;
  color: var(--khaki-mid);
}
body[data-theme='dark'] .idx { color: var(--khaki-light); }

/* ---------- Layout -------------------------------------------------------- */

.shell { padding-inline: var(--edge); }
.section { padding-block: var(--section); }
.section--tight { padding-block: clamp(3rem, 7vh, 5.5rem); }

.rule {
  height: 1px;
  background: var(--line);
  border: 0;
  margin: 0;
  transition: background-color var(--theme-t) var(--ease);
}

/* ---------- Media placeholder boxes --------------------------------------
   Standing in for Greg's photography and video until the album arrives.
   Deliberately designed so the layout reads finished, not broken.
   -------------------------------------------------------------------------- */

.ph {
  position: relative;
  width: 100%;
  background: var(--media-bed);
  border: 1px solid var(--media-edge);
  overflow: hidden;
  display: grid;
  place-items: center;
  transition: background-color var(--theme-t) var(--ease),
              border-color var(--theme-t) var(--ease);
}

/* Fine diagonal hatch so an empty box still has texture */
.ph::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(
    -45deg,
    transparent 0 11px,
    rgba(62, 68, 47, 0.055) 11px 12px
  );
  pointer-events: none;
}
body[data-theme='dark'] .ph::before {
  background-image: repeating-linear-gradient(
    -45deg,
    transparent 0 11px,
    rgba(138, 145, 116, 0.14) 11px 12px
  );
}

/* Khaki corner brackets */
.ph::after {
  content: '';
  position: absolute;
  inset: 14px;
  pointer-events: none;
  background:
    linear-gradient(var(--khaki-light), var(--khaki-light)) left top / 18px 1px no-repeat,
    linear-gradient(var(--khaki-light), var(--khaki-light)) left top / 1px 18px no-repeat,
    linear-gradient(var(--khaki-light), var(--khaki-light)) right bottom / 18px 1px no-repeat,
    linear-gradient(var(--khaki-light), var(--khaki-light)) right bottom / 1px 18px no-repeat;
  opacity: 0.55;
}

.ph__label {
  position: relative;
  z-index: 2;
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: clamp(0.62rem, 0.75vw, 0.74rem);
  letter-spacing: 0.24em;
  color: var(--khaki-mid);
  text-align: center;
  padding: 0.6em 1em;
  line-height: 1.6;
}
body[data-theme='dark'] .ph__label { color: var(--khaki-light); }

/* Ratios */
.ph--21x9  { aspect-ratio: 21 / 9; }
.ph--16x9  { aspect-ratio: 16 / 9; }
.ph--3x2   { aspect-ratio: 3 / 2; }
.ph--4x3   { aspect-ratio: 4 / 3; }
.ph--1x1   { aspect-ratio: 1 / 1; }
.ph--4x5   { aspect-ratio: 4 / 5; }
.ph--2x3   { aspect-ratio: 2 / 3; }
.ph--3x4   { aspect-ratio: 3 / 4; }
.ph--9x16  { aspect-ratio: 9 / 16; }
.ph--fill  { height: 100%; }

/* Video variant gets a play mark */
.ph--video .ph__play {
  position: relative;
  z-index: 2;
  width: clamp(52px, 5vw, 76px);
  height: clamp(52px, 5vw, 76px);
  display: grid;
  place-items: center;
  margin-bottom: 0.9rem;
  /* Chamfered, not circular */
  clip-path: polygon(22% 0, 100% 0, 100% 78%, 78% 100%, 0 100%, 0 22%);
  background: var(--khaki);
  color: #fff;
  transition: transform 500ms var(--ease), background-color 400ms var(--ease);
}
.ph--video .ph__play svg { width: 32%; }
.ph--video:hover .ph__play { transform: scale(1.08); background: var(--ink); }
body[data-theme='dark'] .ph--video .ph__play { background: var(--khaki-light); color: var(--ink); }
body[data-theme='dark'] .ph--video:hover .ph__play { background: var(--paper); color: var(--ink); }

/* Wired video slots: the real clip, muted and looping like a living
   photograph. No button, no click needed. */
.ph__vid {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #000;
}

.ph__inner {
  position: relative;
  z-index: 2;
  display: grid;
  place-items: center;
}

/* ---------- Quirky buttons ------------------------------------------------
   Brief: no standard pills or circles. Signature shape is a chamfered slab
   with a diagonal wipe on hover and an arrow that steps forward.
   -------------------------------------------------------------------------- */

.btn {
  --chamfer: 13px;
  --btn-line: var(--fg);
  --btn-face: var(--bg);
  --btn-fg: var(--fg);
  --btn-fill: var(--khaki);
  --btn-fill-fg: #fff;

  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 1.1em;
  padding: 1.05em 1.9em;
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: clamp(0.74rem, 0.9vw, 0.88rem);
  letter-spacing: 0.2em;
  line-height: 1;
  color: var(--btn-fg);
  isolation: isolate;
  cursor: pointer;

  /* The outline is a filled chamfered slab with a smaller slab punched out of
     it, so the cut corners are stroked properly. A plain border cannot follow
     a clip-path diagonal. */
  background: var(--btn-line);
  clip-path: polygon(
    var(--chamfer) 0, 100% 0,
    100% calc(100% - var(--chamfer)), calc(100% - var(--chamfer)) 100%,
    0 100%, 0 var(--chamfer));
  transition: color 420ms var(--ease), background-color 420ms var(--ease),
              transform 420ms var(--ease);
}

/* Inner face */
.btn::before {
  content: '';
  position: absolute;
  inset: 1.5px;
  z-index: -2;
  background: var(--btn-face);
  clip-path: polygon(
    calc(var(--chamfer) - 1px) 0, 100% 0,
    100% calc(100% - var(--chamfer) + 1px), calc(100% - var(--chamfer) + 1px) 100%,
    0 100%, 0 calc(var(--chamfer) - 1px));
  transition: background-color 420ms var(--ease);
}

/* Diagonal wipe.
   A skewed rectangle can only reach both ends of the button if it is wider
   than the button, and widening it swallows the outline. So the box stays
   exactly on the inner face and the diagonal comes from a hard stop gradient
   sweeping across it. The fill reaches both edges and the outline survives. */
.btn::after {
  content: '';
  position: absolute;
  inset: 1.5px;
  z-index: -1;
  background-image: linear-gradient(106deg, var(--btn-fill) 0 50%, rgba(0, 0, 0, 0) 50%);
  background-size: 300% 100%;
  background-repeat: no-repeat;
  background-position: 100% 0;
  transition: background-position 560ms var(--ease);
}

.btn:hover, .btn:focus-visible { color: var(--btn-fill-fg); }
/* Lifts on hover, the same move the portfolio tiles make. The chamfer is a
   clip-path rather than a border, and it scales with the element, so the cut
   corners keep their proportions instead of thickening. */
.btn:hover, .btn:focus-visible { transform: scale(1.04); }

/* The same move for a finger. A touch screen has no cursor, so :hover either
   never fires there or sticks on after the tap - the press is the only moment
   a phone can answer. Quicker going in, because a tap on a link is usually
   followed by the page leaving, and 420ms of it would never be seen. */
.btn:active {
  transform: scale(1.04);
  transition-duration: 120ms;
}
.btn:active .btn__arrow { transform: translateX(6px); }

@media (prefers-reduced-motion: reduce) {
  .btn:hover, .btn:focus-visible, .btn:active { transform: none; }
}
.btn:hover::after, .btn:focus-visible::after { background-position: 0% 0; }

.btn__arrow {
  display: inline-block;
  width: 22px;
  height: 8px;
  flex: none;
  transition: transform 480ms var(--ease);
}
.btn:hover .btn__arrow { transform: translateX(6px); }

.btn--solid {
  --btn-line: var(--khaki);
  --btn-face: var(--khaki);
  --btn-fg: #fff;
  --btn-fill: var(--ink);
}
body[data-theme='dark'] .btn--solid { --btn-fill: var(--paper); --btn-fill-fg: var(--ink); }

.btn--lg {
  --chamfer: 20px;
  padding: 1.4em 2.6em;
  font-size: clamp(0.82rem, 1.05vw, 1rem);
}

/* Bracket link: the quieter secondary action */
.blink {
  display: inline-flex;
  align-items: baseline;
  gap: 0.55em;
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: clamp(0.74rem, 0.9vw, 0.86rem);
  letter-spacing: 0.2em;
  color: var(--fg);
  position: relative;
}
.blink::before, .blink::after {
  content: '[';
  color: var(--khaki-mid);
  transition: transform 400ms var(--ease), opacity 400ms var(--ease);
  opacity: 0.55;
}
.blink::after { content: ']'; }
.blink:hover::before { transform: translateX(-5px); opacity: 1; }
.blink:hover::after  { transform: translateX(5px);  opacity: 1; }
body[data-theme='dark'] .blink::before,
body[data-theme='dark'] .blink::after { color: var(--khaki-light); }

/* ---------- Header --------------------------------------------------------
   Solid dark khaki at rest. On scroll it becomes a black gradient so the
   photography can run right up underneath it.
   -------------------------------------------------------------------------- */

.hdr {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 90;
  height: var(--header-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: var(--edge);
  color: #fff;
  transition: height 500ms var(--ease);
}

.hdr::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--khaki);
  transition: opacity 600ms var(--ease);
}
.hdr::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -2;
  background: var(--ink);
  opacity: 0;
  transition: opacity 600ms var(--ease);
}

.hdr.is-scrolled::before { opacity: 0; }
.hdr.is-scrolled::after  { opacity: 1; }
.hdr.is-scrolled { height: 62px; }

/* Over the pinned hero on a phone the header carries no panel at all, so the
   picture runs right to the top of the screen. Phones only: it is the pinned
   full-bleed hero that makes this work, and there is no such thing on
   desktop, where the header still needs its ground. */
@media (max-width: 859px) {
  .hdr.is-over-hero::before,
  .hdr.is-over-hero::after { opacity: 0; }
  /* What sits behind changes from bright sand to the film as the shutter
     opens, so the mark and the toggle hold themselves against both rather
     than trusting either. */
  .hdr.is-over-hero .hdr__mark,
  .hdr.is-over-hero .hdr__toggle {
    filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.45)) drop-shadow(0 4px 16px rgba(0, 0, 0, 0.3));
  }
}

.hdr__brand {
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(1.1rem, 1.7vw, 1.5rem);
  letter-spacing: 0.05em;
  line-height: 1;
  display: flex;
  align-items: center;
  gap: 0.62em;
}
.hdr__brand b { font-weight: inherit; }
.hdr__brand span {
  font-family: var(--body);
  text-transform: uppercase;
  letter-spacing: 0.22em;
  font-size: 0.5em;
  font-style: normal;
  opacity: 0.72;
}

.hdr__nav { display: flex; align-items: center; gap: clamp(1rem, 2.2vw, 2.6rem); }

@media (max-width: 1023px) { .hdr__brand span { display: none; } }

/* Below tablet the three primary links move into the overlay menu, so the
   brand has the bar to itself and nothing collides. */
@media (max-width: 780px) {
  .hdr__brand { font-size: 1rem; white-space: nowrap; }
  .hdr__nav .hdr__link { display: none; }
}

.hdr__link {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.8rem;
  letter-spacing: 0.2em;
  position: relative;
  padding-block: 0.4em;
}
.hdr__link::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 480ms var(--ease);
}
.hdr__link:hover::after,
.hdr__link[aria-current='page']::after { transform: scaleX(1); transform-origin: left; }
.hdr__link[aria-current='page'] { color: var(--khaki-light); }
.hdr.is-scrolled .hdr__link[aria-current='page'] { color: var(--khaki-light); }

/* Menu toggle: two offset bars in a chamfered slab. Reads as a menu, which
   the hook mark did not. Bars are absolutely placed so the close state crosses
   exactly. */
.hdr__toggle {
  position: relative;
  width: 46px;
  height: 34px;
  flex: none;
  clip-path: polygon(9px 0, 100% 0, 100% calc(100% - 9px), calc(100% - 9px) 100%, 0 100%, 0 9px);
  border: 1px solid rgba(255, 255, 255, 0.4);
  transition: background-color 400ms var(--ease), border-color 400ms var(--ease);
}
.hdr__toggle:hover { background: rgba(255, 255, 255, 0.12); border-color: #fff; }

.hdr__toggle i {
  position: absolute;
  left: 50%;
  top: 50%;
  display: block;
  height: 1px;
  background: #fff;
  transform-origin: center;
  transition: transform 450ms var(--ease), width 450ms var(--ease);
}
.hdr__toggle i:nth-child(1) { width: 20px; transform: translate(-50%, -50%) translateY(-3px); }
.hdr__toggle i:nth-child(2) { width: 12px; transform: translate(-50%, -50%) translateY(3px) translateX(4px); }
.hdr__toggle:hover i:nth-child(2) { width: 20px; transform: translate(-50%, -50%) translateY(3px); }

body.menu-open .hdr__toggle i:nth-child(1) { width: 20px; transform: translate(-50%, -50%) rotate(45deg); }
body.menu-open .hdr__toggle i:nth-child(2) { width: 20px; transform: translate(-50%, -50%) rotate(-45deg); }

/* ---------- Services overlay menu ----------------------------------------- */

.menu {
  position: fixed;
  inset: 0;
  z-index: 85;
  background: var(--ink);
  color: #fff;
  padding: calc(var(--header-h) + 4vh) var(--edge) 4vh;
  display: grid;
  align-content: center;
  grid-template-columns: minmax(0, 1fr);
  clip-path: inset(0 0 100% 0);
  transition: clip-path 780ms var(--ease);
  pointer-events: none;
  overflow-y: auto;
}
body.menu-open .menu { clip-path: inset(0 0 0 0); pointer-events: auto; }

.menu__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
  width: 100%;
  min-width: 0;
}
@media (min-width: 900px) {
  .menu__grid { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); }
}
.menu__grid > * { min-width: 0; }
/* .menu__primary is defined once, further down with the phone rules. */

.menu__list li { border-top: 1px solid rgba(255, 255, 255, 0.16); }
.menu__list li:last-child { border-bottom: 1px solid rgba(255, 255, 255, 0.16); }

.menu__item {
  display: flex;
  align-items: baseline;
  gap: 1.1rem;
  padding: clamp(0.6rem, 1.4vh, 1.1rem) 0;
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(1.35rem, 3.6vw, 3rem);
  line-height: 1;
  opacity: 0;
  transform: translateY(18px);
  transition: color 400ms var(--ease), opacity 600ms var(--ease), transform 600ms var(--ease);
}
body.menu-open .menu__item { opacity: 1; transform: none; }
.menu__item:hover { color: var(--khaki-light); }
@media (max-width: 480px) {
  .menu__item { font-size: 1.25rem; gap: 0.8rem; }
}

.menu__item em {
  font-family: var(--display);
  font-style: normal;
  font-size: 0.32em;
  letter-spacing: 0.2em;
  color: var(--khaki-light);
}

/* ---------- Hero: drone film left, still right, tagline across the seam ----
   The tagline is knocked through both images with mix-blend-mode: difference,
   so it always holds contrast whatever photograph sits behind it, and the
   photography stays the thing you look at first.
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  padding-top: calc(var(--header-h) + clamp(0.75rem, 2vh, 1.5rem));
  padding-bottom: clamp(1.5rem, 4vh, 3rem);
}

.hero__stage {
  position: relative;
  display: grid;
  grid-template-areas: 'stage';
  min-height: clamp(460px, calc(100svh - var(--header-h) - 11rem), 780px);
}
.hero__stage > * { grid-area: stage; }

.hero__split {
  display: grid;
  grid-template-columns: 1fr;
  grid-auto-rows: 1fr;
  gap: clamp(0.75rem, 1.5vw, 1.25rem);
}
@media (min-width: 860px) {
  /* Photograph left, film right - placed rather than reordered in the markup,
     because mobile stacks these in DOM order and builds a different picture
     out of them there: the film as a tall frame with the still stepped in
     over its bottom corner (see the max-width rules near the end of this
     file), which only composes in the order they are written.

     The columns swap with them, so each frame keeps the width it had and only
     changes sides - the film stays the wider of the two.

     The row is named on both as well as the column. Given only a column, grid
     auto-placement fills forwards and will not step backwards for the second
     item, so the still - written after the film but asking for the column
     before it - would get pushed onto a row of its own. */
  .hero__split { grid-template-columns: 1fr 1.08fr; }
  .hero__still { grid-column: 1; grid-row: 1; }
  .hero__drone { grid-column: 2; grid-row: 1; }
}

.hero__media { position: relative; min-height: clamp(220px, 34vh, 420px); }
.hero__media .ph { height: 100%; }

/* This frame is a good deal taller than the photograph's own shape at every
   size - a tall column on desktop, a square on mobile - so object-fit always
   crops the sides, and cropped from the centre it cuts the surfer off. The
   22% anchor holds her, and the board she is stood over, in shot. Per cent
   rather than pixels because it pins the same point of the photograph to the
   same point of the frame whatever the crop, so she survives every window
   size rather than only the one this was checked at. Out here rather than in
   the desktop query below because mobile's square crop cuts her off just as
   readily as the tall one does. */
.hero__still .ph__img {
  object-position: 22% center;
}

@media (min-width: 860px) {
  .hero__media { min-height: 0; }
}

/* Tagline layer */
/* Frames are positioned elements, so the title has to be positioned too or the
   pictures paint straight over it. Both layers carry an explicit z-index rather
   than relying on auto, so the order cannot drift. */
.hero__split { position: relative; z-index: 1; }
.hero__tag {
  position: relative;
  z-index: 3;
  display: grid;
  place-items: center;
  pointer-events: none;
  padding-inline: 0.5rem;
}
.hero__tag h1 {
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(2.4rem, 7vw, 6.6rem);
  line-height: 0.92;
  letter-spacing: 0.015em;
  text-align: center;
  color: var(--fg);
  margin: 0;
}
.hero__tag h1 em {
  display: block;
  font-family: var(--body);
  font-style: normal;
  font-weight: 400;
  text-transform: uppercase;
  font-size: 0.165em;
  letter-spacing: 0.26em;
  line-height: 1.2;
  padding-top: 1.15em;
}

@media (min-width: 860px) {
  /* Dark type over photographs that range from bright sand to near-black
     water, so it carries its own ground rather than trusting the picture
     behind it: a soft white ellipse centred on the words.

     What makes a scrim like this read as a panel stuck over the photograph is
     a visible edge, not its strength - so the falloff runs long and lands at
     zero well inside the frame, with the extra padding purely to give it room
     to fade out in. Kept weak enough to disappear into the sand on one side
     while still lifting the type off the dark water on the other. */
  .hero__tag h1 {
    padding: clamp(2rem, 7vh, 4.5rem) clamp(2.5rem, 9vw, 7.5rem);
    background: radial-gradient(
      ellipse at center,
      rgba(255, 255, 255, 0.4) 0%,
      rgba(255, 255, 255, 0.3) 30%,
      rgba(255, 255, 255, 0.15) 52%,
      rgba(255, 255, 255, 0.05) 70%,
      rgba(255, 255, 255, 0) 86%
    );
  }
}

@media (max-width: 859px) {
  /* The title stays over the pictures on a phone rather than stacking above
     them: the stage keeps its overlay grid (it is not switched to a column
     here) so .hero__tag lands on the film, the way it reads on desktop.

     White on a soft dark scrim rather than desktop's ink on white, because
     the frame underneath is not the same one. On desktop the title straddles
     the seam and sits mostly over bright sand; here it has only the film,
     which is dark water and trees. */
  /* No shadow and no wash: the title only ever sits on the surf still now
     (it rides the shutter and is gone before the film is uncovered), and that
     photograph is bright and quiet enough behind white letters to carry them
     unaided. The strap below is the one that still needs help, because it
     lies on the film - see .hero__meta. */
  .hero__tag h1 {
    color: var(--ink);
    text-align: center;
    font-size: clamp(2.3rem, 11vw, 4rem);
    padding: 0;
    background: none;
  }
  .hero__tag h1 em { font-size: 0.24em; letter-spacing: 0.22em; padding-top: 1.9em; }
  .hero__media { min-height: 0; }
  .hero__still { height: auto; }
  .hero__media .ph { align-items: center; }
  .hero__media .ph__label, .hero__media .ph__inner { margin-bottom: 0; }
}

@media (max-width: 620px) {
  .eyebrow { letter-spacing: 0.18em; }
  .hero__sig { gap: 0.35rem; }
}



.hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1rem, 3vw, 3rem);
  align-items: flex-end;
  justify-content: space-between;
  margin-top: clamp(1.2rem, 2.6vh, 2rem);
  padding-top: 1.3rem;
  border-top: 1px solid var(--line);
}

/* The strap reads as a standfirst sitting across the foot of the pictures,
   so it runs two lines wide rather than four lines down a narrow column.
   .lede's own 46ch is a reading measure for body copy set in a column, which
   is not this job. balance splits the two lines evenly instead of leaving a
   full line above a short one. */
.hero__meta .lede {
  max-width: 84ch;
  text-wrap: balance;
}

/* Name sits above the stage, small, the way a photographer signs a print */
.hero__sig {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: clamp(0.75rem, 1.8vh, 1.25rem);
}

/* Scroll cue */
.cue {
  display: inline-flex;
  align-items: center;
  gap: 0.8em;
  font-family: var(--display);
  font-weight: 600;
  font-size: 0.72rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--fg-soft);
  white-space: nowrap;
}
.cue i {
  display: block;
  width: 1px;
  height: 30px;
  background: var(--khaki-mid);
  transform-origin: top;
  animation: cue 2.4s var(--ease-io) infinite;
}
@keyframes cue {
  0%   { transform: scaleY(0); }
  45%  { transform: scaleY(1); }
  100% { transform: scaleY(0); transform-origin: bottom; }
}

/* ---------- Hero shutter (phones only) -------------------------------------
   The hero opens rather than just being there: the surf still covers it,
   split down the middle, and the two halves draw back as you scroll to
   uncover the film underneath.

   Inert by default. Everything that makes it work is inside the phone query
   at the end of this file, so on a desktop the wrapper is a plain div, the
   hero is an ordinary section, and the cover is not rendered at all.
   -------------------------------------------------------------------------- */

.shutterwrap { position: relative; }
.shutter { display: none; }

/* ---------- Showreel ------------------------------------------------------ */

/* Runs the full width of the section, lining up with the heading above it */
.reel__frame { width: 100%; }

.reel__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--gut);
  margin-bottom: clamp(1.6rem, 3.5vh, 2.8rem);
}

/* ---------- Marquee ticker ------------------------------------------------ */

.marquee {
  overflow: hidden;
  border-block: 1px solid var(--line);
  padding-block: clamp(0.9rem, 1.8vh, 1.4rem);
  white-space: nowrap;
}
.marquee__track {
  display: inline-flex;
  gap: 2.4rem;
  align-items: center;
  animation: slide 42s linear infinite;
  will-change: transform;
}
.marquee:hover .marquee__track { animation-play-state: paused; }
.marquee span {
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(1.1rem, 2.4vw, 2.1rem);
  letter-spacing: 0.02em;
  line-height: 1;
}
.marquee b { color: var(--khaki-mid); font-weight: 400; }
body[data-theme='dark'] .marquee b { color: var(--khaki-light); }
@keyframes slide { to { transform: translateX(-50%); } }

/* ---------- Pinned tagline with images crossing it (ref 1.2) -------------
   The tagline sits behind, stuck to the middle of the screen. Photographs
   scatter across the full width at different drift speeds and pass over it,
   so the images stay the thing you look at and the line is the refrain.
   -------------------------------------------------------------------------- */

.pinned { position: relative; }

.pinned__wrap {
  display: grid;
  grid-template-areas: 'pin';
}
.pinned__wrap > * { grid-area: pin; }

.pinned__centre {
  position: sticky;
  top: 0;
  height: 100vh;
  align-self: start;
  display: grid;
  place-items: center;
  pointer-events: none;
}

.pinned__tag {
  margin: 0;
  text-align: center;
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(2.6rem, 9vw, 8.5rem);
  line-height: 0.86;
  letter-spacing: -0.01em;
  color: var(--tag-col, var(--khaki));
  transition: color 200ms var(--ease);
}
.pinned__tag span { display: block; }

/* The words are marked up one per span so a phone can stack all four (see
   home() in build.js). Desktop wants three lines, not four, so "dictates"
   and "the" go back inline here - the newline between them in the markup is
   what supplies the space. */
@media (min-width: 860px) {
  .pinned__tag span:nth-child(2),
  .pinned__tag span:nth-child(3) { display: inline; }
}

/* On a phone this is the one full-screen typographic moment in the page, so
   it stops being a small centred block and becomes the page: ranged left in
   the gutter, one word a line, set as large as four lines of it can go. */
@media (max-width: 859px) {
  /* No padding of its own - .pinned__wrap is a .shell and already holds the
     page gutter, so adding it again here insets the words twice and pushes
     the longest of them ("Environment") back out through the other side. */
  .pinned__centre { place-items: center start; }
  /* As large as the longest word will allow, which is what caps this: set it
     by eye and "Environment" runs straight out of the gutter. 11vw is the
     measured ceiling for this face from 320px up; Playfair needs its own
     number and overrides this in playfair.css. */
  .pinned__tag {
    text-align: left;
    font-size: clamp(2.2rem, 11vw, 4.5rem);
    line-height: 0.9;
    letter-spacing: -0.02em;
  }
}


.pinned__scatter {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: clamp(0.75rem, 1.6vw, 1.5rem);
  /* Generous room between rows: the frames drift as you scroll, and without
     this the one below rises into the one above. */
  row-gap: clamp(3rem, 15vh, 11rem);
}
.pinned__scatter .sc { margin: 0; }
.pinned__scatter .cap { justify-content: flex-start; }

/* Stacked on small screens, scattered from tablet up.
   Columns 6 and 7 are kept clear on every row so the pinned tagline always
   has a readable corridor. The photographs crop its ends rather than bury it. */
.sc { grid-column: 1 / -1; margin-bottom: clamp(2rem, 6vh, 4rem); }

@media (min-width: 760px) {
  .sc { margin-bottom: 0; }
  .sc--1 { grid-column: 1 / span 5;  margin-top: 2vh; }
  .sc--2 { grid-column: 7 / span 6;  margin-top: 26vh; }
  .sc--3 { grid-column: 1 / span 6;  margin-top: 20vh; }
  .sc--4 { grid-column: 9 / span 4;  margin-top: 2vh; }
  .sc--5 { grid-column: 2 / span 4;  margin-top: 6vh; }
  .sc--6 { grid-column: 8 / span 5;  margin-top: 22vh; }
}
@media (min-width: 1200px) {
  .sc--1 { grid-column: 1 / span 5;  margin-top: 2vh; }
  /* The drone frame. It was the narrowest of the six at four columns, which
     on a wide landscape aerial left it reading as a strip and losing the
     scale the shot is about. */
  .sc--2 { grid-column: 7 / span 6;  margin-top: 30vh; }
  /* Outdoors runs wide - it is a landscape shot of a headland, and the space
     around the shelter is the point of it. Weddings is a square detail and
     reads better small, so it gives the columns back. */
  .sc--3 { grid-column: 1 / span 6;  margin-top: 24vh; }
  .sc--4 { grid-column: 9 / span 4;  margin-top: 2vh; }
  .sc--5 { grid-column: 1 / span 4;  margin-top: 8vh; }
  .sc--6 { grid-column: 8 / span 5;  margin-top: 26vh; }
}

/* The frames sit flat against the page: no shadow, no outline */
.pinned__scatter .ph { box-shadow: none; border-color: transparent; }

/* ---------- Services index ------------------------------------------------
   Six rows. Hovering a row floats a preview that tracks the cursor, so six
   services fit without the site turning into a long stack of panels.
   -------------------------------------------------------------------------- */

.svc { position: relative; }
.svc__list { border-top: 1px solid var(--line); }

.svc__row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: clamp(1rem, 3vw, 2.6rem);
  align-items: center;
  padding: clamp(1.5rem, 3.4vh, 2.6rem) 0;
  border-bottom: 1px solid var(--line);
  position: relative;
  isolation: isolate;
  transition: padding-inline 620ms var(--ease), color 500ms var(--ease);
}
.svc__row::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--khaki);
  transform: scaleY(0);
  transform-origin: bottom;
  transition: transform 620ms var(--ease);
}
.svc__row:hover { padding-inline: clamp(0.8rem, 2.2vw, 2rem); color: #fff; }
.svc__row:hover::before { transform: scaleY(1); transform-origin: top; }
.svc__row:hover .idx, .svc__row:hover .svc__note { color: rgba(255, 255, 255, 0.72); }

.svc__name {
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(1.9rem, 5.4vw, 4.4rem);
  line-height: 0.9;
  letter-spacing: -0.015em;
}
.svc__note {
  font-size: 0.98rem;
  color: var(--fg-soft);
  max-width: 34ch;
  display: none;
  transition: color 500ms var(--ease);
}
@media (min-width: 1100px) { .svc__note { display: block; } }

.svc__arrow { width: 30px; flex: none; transition: transform 520ms var(--ease); }
.svc__row:hover .svc__arrow { transform: translateX(10px); }

.svc__peek {
  position: fixed;
  top: 0; left: 0;
  width: clamp(220px, 20vw, 330px);
  z-index: 60;
  pointer-events: none;
  opacity: 0;
  transform: translate3d(-50%, -50%, 0) scale(0.9);
  transition: opacity 420ms var(--ease), transform 620ms var(--ease);
}
.svc__peek.is-on { opacity: 1; transform: translate3d(-50%, -50%, 0) scale(1); }

/* ---------- Editorial grids ----------------------------------------------- */

.grid {
  display: grid;
  gap: var(--gut);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}
.grid--2 { grid-template-columns: 1fr; }
.grid--3 { grid-template-columns: 1fr; }
@media (min-width: 720px) { .grid--2 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 900px) { .grid--3 { grid-template-columns: repeat(3, 1fr); } }

/* Staggered editorial layout used on portfolio and service pages */
.stack { display: grid; gap: clamp(2rem, 5vh, 4.5rem); }
.stack__row {
  display: grid;
  gap: var(--gut);
  grid-template-columns: 1fr;
  align-items: end;
}
@media (min-width: 860px) {
  .stack__row--a { grid-template-columns: 7fr 5fr; }
  .stack__row--b { grid-template-columns: 4fr 8fr; align-items: start; }
  .stack__row--c { grid-template-columns: 5fr 3fr 4fr; }
  .stack__offset { transform: translateY(clamp(2rem, 7vh, 6rem)); }
}

.cap {
  margin-top: 0.85rem;
  display: flex;
  gap: 0.9em;
  align-items: baseline;
  font-size: 0.9rem;
  color: var(--fg-soft);
}
.cap b {
  font-family: var(--display);
  font-weight: 600;
  font-size: 0.72rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--khaki-mid);
}
body[data-theme='dark'] .cap b { color: var(--khaki-light); }

/* ---------- Split intro block --------------------------------------------- */

.split {
  display: grid;
  gap: clamp(1.6rem, 4vw, 4rem);
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 900px) { .split { grid-template-columns: 1fr 1.25fr; } }
.split--even { grid-template-columns: 1fr; }
@media (min-width: 900px) { .split--even { grid-template-columns: 1fr 1fr; } }

/* ---------- Testimonial ---------------------------------------------------- */

.quotes { position: relative; }
.quote {
  display: grid;
  gap: clamp(1.4rem, 3vw, 3rem);
  grid-template-columns: 1fr;
  align-items: start;
}
/* One variable drives the mark column and the nav offset, so the arrows line
   up with the right edge of the quote rather than the edge of the page. */
:root { --qmark: clamp(3.4rem, 7vw, 6.5rem); --qgap: clamp(1.4rem, 3vw, 3rem); }
@media (min-width: 900px) { .quote { grid-template-columns: var(--qmark) 1fr; gap: var(--qgap); } }

.quote__mark {
  font-family: var(--display);
  font-weight: 700;
  width: var(--qmark);
  font-size: clamp(4.4rem, 10vw, 9rem);
  line-height: 0.62;
  color: var(--khaki);
}
body[data-theme='dark'] .quote__mark { color: var(--khaki-light); }

.quote__text {
  font-family: var(--body);
  font-size: clamp(1.25rem, 1.9vw, 1.95rem);
  line-height: 1.4;
  font-style: italic;
  max-width: 42ch;
}
.quote__by {
  margin-top: clamp(1.4rem, 3vh, 2.2rem);
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.76rem;
  letter-spacing: 0.22em;
  color: var(--fg-soft);
}

.quotes__nav {
  display: flex;
  gap: 0.8rem;
  justify-content: flex-end;
  margin-top: clamp(1.4rem, 3vh, 2.2rem);
  max-width: 42ch;
}
@media (min-width: 900px) {
  .quotes__nav { margin-left: calc(var(--qmark) + var(--qgap)); }
}
.qbtn {
  width: 54px; height: 44px;
  display: grid; place-items: center;
  border: 1px solid var(--line-strong);
  clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
  transition: background-color 400ms var(--ease), color 400ms var(--ease);
}
.qbtn:hover { background: var(--khaki); color: #fff; }
.qbtn svg { width: 20px; }

.quote[hidden] { display: none; }

/* ---------- Price note ----------------------------------------------------- */

.price {
  display: grid;
  gap: clamp(1.2rem, 3vw, 2.6rem);
  grid-template-columns: 1fr;
  align-items: center;
  padding: clamp(1.8rem, 4vh, 3rem);
  border: 1px solid var(--line);
  clip-path: polygon(26px 0, 100% 0, 100% calc(100% - 26px), calc(100% - 26px) 100%, 0 100%, 0 26px);
}
@media (min-width: 860px) { .price { grid-template-columns: 1fr auto; } }

/* ---------- Collection CTA ------------------------------------------------- */

.collect { border-top: 1px solid var(--line); }
.collect__row {
  display: grid;
  grid-template-columns: auto auto 1fr auto;
  gap: clamp(1rem, 3vw, 2.4rem);
  align-items: center;
  padding: clamp(1.5rem, 3.2vh, 2.4rem) 0;
  border-bottom: 1px solid var(--line);
  transition: padding-inline 600ms var(--ease);
}
.collect__row:hover { padding-inline: clamp(0.6rem, 1.8vw, 1.6rem); }
.collect__row:hover .collect__name { color: var(--khaki-mid); }
body[data-theme='dark'] .collect__row:hover .collect__name { color: var(--khaki-light); }
.collect__phrase {
  font-size: 0.95rem;
  color: var(--fg-soft);
  display: none;
  text-align: right;
}
@media (min-width: 900px) { .collect__phrase { display: block; } }

.collect__name {
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(1.15rem, 2.4vw, 2rem);
  line-height: 1;
  transition: color 460ms var(--ease);
}

/* ---------- Contact form --------------------------------------------------- */

.form { display: grid; gap: clamp(1.6rem, 3.4vh, 2.6rem); align-content: start; }
.field { display: grid; gap: 0.55rem; align-content: start; }
.field label {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.72rem;
  letter-spacing: 0.22em;
  color: var(--khaki-mid);
}
body[data-theme='dark'] .field label { color: var(--khaki-light); }
.field input,
.field select,
.field textarea {
  font-family: var(--body);
  font-size: 0.98rem;
  color: var(--fg);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line-strong);
  padding: 0.7rem 0;
  border-radius: 0;
  transition: border-color 400ms var(--ease);
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-bottom-color: var(--khaki);
}
.field textarea { resize: vertical; min-height: 130px; }
.field select { appearance: none; cursor: pointer; }

/* ---------- Footer --------------------------------------------------------- */

.ftr {
  background: var(--ink);
  color: var(--paper);
  padding-top: clamp(3.5rem, 8vh, 6.5rem);
}

.ftr__cta {
  display: grid;
  gap: clamp(1.6rem, 4vw, 3rem);
  grid-template-columns: 1fr;
  align-items: end;
  padding-bottom: clamp(3rem, 7vh, 5rem);
  border-bottom: 1px solid rgba(255, 255, 255, 0.16);
}
@media (min-width: 900px) { .ftr__cta { grid-template-columns: 1.4fr auto; } }
.ftr__cta a.mail {
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(1.4rem, 3.6vw, 3.2rem);
  line-height: 1;
  letter-spacing: -0.01em;
  display: inline-block;
  position: relative;
}
.ftr__cta a.mail::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -6px;
  height: 2px;
  background: var(--khaki-light);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 560ms var(--ease);
}
.ftr__cta a.mail:hover::after { transform: scaleX(1); transform-origin: left; }

/* Instagram strip */
.ig { padding-block: clamp(2.6rem, 6vh, 4.5rem); }
.ig__head {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: clamp(1.2rem, 2.6vh, 2rem);
}
.ig__strip {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}
@media (min-width: 760px) { .ig__strip { grid-template-columns: repeat(6, 1fr); } }
.ig__tile {
  position: relative;
  overflow: hidden;
  clip-path: polygon(12px 0, 100% 0, 100% calc(100% - 12px), calc(100% - 12px) 100%, 0 100%, 0 12px);
  transition: transform 620ms var(--ease);
}
.ig__tile:hover { transform: translateY(-8px); }
.ig__tile .ph { background: #26261F; border-color: #4E4E40; }
.ig__tile .ph::before {
  background-image: repeating-linear-gradient(-45deg, transparent 0 11px, rgba(138,145,116,.14) 11px 12px);
}
.ig__tile .ph__label { color: var(--khaki-light); }

.ftr__grid {
  display: grid;
  gap: clamp(2rem, 4vw, 3.5rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
  padding-block: clamp(2.4rem, 5vh, 4rem);
  border-top: 1px solid rgba(255, 255, 255, 0.16);
}
.ftr__col h3 {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.72rem;
  letter-spacing: 0.24em;
  color: var(--khaki-light);
  margin-bottom: 1.2rem;
}
.ftr__col li + li { margin-top: 0.5rem; }
.ftr__col a { color: rgba(255, 255, 255, 0.72); transition: color 320ms var(--ease); }
.ftr__col a:hover { color: #fff; }

.ftr__base {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2rem;
  justify-content: space-between;
  align-items: center;
  padding-block: 1.6rem;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
  font-size: 0.85rem;
  color: rgba(255, 255, 255, 0.5);
}

/* Big wordmark sign off */
.ftr__mark {
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(2.4rem, 12.4vw, 11.5rem);
  line-height: 0.82;
  letter-spacing: 0;
  color: #1E1E1B;
  padding-bottom: 0.1em;
  user-select: none;
  overflow: hidden;
  white-space: nowrap;
}

/* ---------- Page intro (inner pages) --------------------------------------- */

.pintro {
  padding-top: calc(var(--header-h) + clamp(3rem, 9vh, 7rem));
  padding-bottom: clamp(2rem, 5vh, 4rem);
}
.pintro__head {
  display: grid;
  gap: clamp(1.4rem, 4vw, 3.4rem);
  grid-template-columns: 1fr;
  align-items: end;
}
@media (min-width: 900px) { .pintro__head { grid-template-columns: 1.25fr 1fr; } }

/* ---------- Reveal animation ----------------------------------------------- */

/* Hidden states only apply once the script has run and set rv-ready, so a
   script failure can never leave the page with invisible image slots. */
.rv-ready [data-rv] {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 900ms var(--ease), transform 1100ms var(--ease);
  transition-delay: var(--rv-d, 0ms);
}
.rv-ready [data-rv].is-in { opacity: 1; transform: none; }

/* Mask wipe for media */
.rv-ready [data-rv='mask'] {
  opacity: 1;
  transform: none;
  clip-path: inset(0 0 100% 0);
  transition: clip-path 1200ms var(--ease);
  transition-delay: var(--rv-d, 0ms);
}
.rv-ready [data-rv='mask'].is-in { clip-path: inset(0 0 0 0); }

/* Line-by-line rise for display type */
.rv-line {
  display: block;
  overflow: hidden;
  padding-block: 0.06em 0.2em;
  margin-block: -0.06em -0.2em;
}
.rv-line > span { display: block; }
.rv-ready .rv-line > span {
  transform: translateY(140%);
  transition: transform 1100ms var(--ease);
  transition-delay: var(--rv-d, 0ms);
}
.rv-ready .is-in .rv-line > span,
.rv-ready .rv-line.is-in > span { transform: none; }

/* ---------- Custom cursor: the dot from the logo ----------------------------
   Just the circle lifted out of the mark. Ink on light sections, paper on dark
   ones, so it stays visible the whole way down the page.
   -------------------------------------------------------------------------- */

.cur {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 200;
  width: var(--cur-size);
  height: var(--cur-size);
  border-radius: 50%;
  background: var(--ink);
  pointer-events: none;
  opacity: 0;
  transition: opacity 300ms var(--ease),
              width 380ms var(--ease),
              height 380ms var(--ease),
              background-color var(--theme-t) var(--ease);
}

body[data-theme='dark'] .cur { background: var(--paper); }

body.cur-live .cur { opacity: 1; }
body.cur-hot .cur {
  width: calc(var(--cur-size) * 1.82);
  height: calc(var(--cur-size) * 1.82);
}

/* Native pointer is only hidden once the mark is actually tracking, so the
   page never ends up with no cursor at all if the script does not run. */
body.cur-live,
body.cur-live * { cursor: none; }

@media (hover: none), (pointer: coarse) { .cur { display: none; } }

/* ---------- Intro curtain --------------------------------------------------- */

.curtain {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: var(--khaki);
  display: grid;
  place-items: center;
  color: #fff;
  transition: clip-path 1000ms var(--ease) 300ms;
  clip-path: inset(0 0 0 0);
}
body.loaded .curtain { clip-path: inset(0 0 100% 0); pointer-events: none; }
/* The opener carries the full horizontal wordmark from the brand pack */
.curtain__logo {
  width: min(64vw, 680px);
  height: auto;
  display: block;
  color: #fff;
  animation: pulse 1.6s var(--ease-io) infinite;
}
@media (max-width: 620px) { .curtain__logo { width: 76vw; } }
@keyframes pulse { 0%, 100% { opacity: 0.45; } 50% { opacity: 1; } }

/* ---------- Utilities -------------------------------------------------------- */

.u-max { max-width: 1680px; margin-inline: auto; }
.u-hide { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.u-khaki { color: var(--khaki-mid); }
body[data-theme='dark'] .u-khaki { color: var(--khaki-light); }
.u-right { text-align: right; }
.u-mt { margin-top: clamp(1.6rem, 3.5vh, 2.6rem); }
.u-mt-lg { margin-top: clamp(2.4rem, 6vh, 4.4rem); }

/* ---------- Reduced motion ---------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  [data-rv] { opacity: 1 !important; transform: none !important; clip-path: none !important; }
  .rv-line > span { transform: none !important; }
  .curtain { display: none; }
}

/* ==========================================================================
   Service splitter (ref 1.1, adapted from four panels to six)
   Full-height vertical panels. Hovering one opens it and squeezes the rest,
   so six services fit the split treatment Greg liked without shrinking to
   unreadable slivers.
   ========================================================================== */

.splitter {
  display: grid;
  grid-template-columns: 1fr;
  background: var(--paper);
  gap: 2px;
}
@media (min-width: 900px) {
  .splitter { height: clamp(560px, 82vh, 860px); }
}

.panel {
  position: relative;
  overflow: hidden;
  background: var(--paper-warm);
  min-height: 180px;
  display: grid;
  align-items: end;
  text-align: center;
  padding: 0;
  transition: flex-grow 800ms var(--ease);
}
@media (min-width: 900px) {
  .splitter { display: flex; }
  .panel { flex: 1 1 0; min-width: 0; }
  .splitter:hover .panel { flex-grow: 0.8; }
  .splitter .panel:hover { flex-grow: 2.4; }
}

.panel__bed { position: absolute; inset: 0; z-index: 0; }
.panel__bed .ph { height: 100%; border: 0; background: var(--paper-warm); }
.panel__bed .ph::after { inset: 22px; }
.panel__bed .ph__label { display: none; }

/* The photograph pushes in behind the words on hover, the move the portfolio
   tiles and the scattered frames both make. The card already clips, so it
   crops rather than spilling. Repeated for :active because a touch screen has
   no cursor to hover with - the press is the only moment a phone can answer. */
.panel__bed .ph { transition: transform 900ms var(--ease); }
.panel:hover .panel__bed .ph,
.panel:active .panel__bed .ph { transform: scale(1.04); }

/* Veil sits on the lower half only, so the photograph itself stays open */
.panel__veil {
  position: absolute;
  inset: 42% 0 0 0;
  z-index: 1;
  background: linear-gradient(to bottom,
    rgba(11, 11, 11, 0) 0%,
    rgba(11, 11, 11, 0.18) 38%,
    rgba(11, 11, 11, 0.88) 100%);
  transition: opacity 700ms var(--ease), background 700ms var(--ease);
}
.panel:hover .panel__veil {
  background: linear-gradient(to bottom,
    rgba(62, 68, 47, 0) 0%,
    rgba(62, 68, 47, 0.5) 36%,
    rgba(28, 32, 20, 0.95) 100%);
}

.panel__body {
  position: relative;
  z-index: 2;
  color: #fff;
  display: grid;
  gap: 0.4rem;
  justify-items: center;
  align-content: end;
  width: 100%;
  padding: clamp(1.2rem, 3vh, 2.2rem) 0.7rem clamp(1.4rem, 3.4vh, 2.4rem);
}
.panel__num {
  font-family: var(--display);
  font-weight: 600;
  font-size: 0.68rem;
  letter-spacing: 0.24em;
  color: var(--khaki-light);
}
.panel__title {
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(1.4rem, 2.1vw, 2.1rem);
  line-height: 0.94;
  letter-spacing: -0.005em;
  white-space: nowrap;
}
.panel__kind {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.58rem;
  letter-spacing: 0.14em;
  color: rgba(255, 255, 255, 0.7);
  line-height: 1.5;
}
.panel__more {
  overflow: hidden;
  max-width: 30ch;
  opacity: 0;
  max-height: 0;
  transform: translateY(10px);
  transition: opacity 600ms var(--ease), max-height 700ms var(--ease), transform 700ms var(--ease);
  font-size: 0.95rem;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.86);
}
@media (min-width: 900px) {
  .panel:hover .panel__more { opacity: 1; max-height: 9rem; transform: none; margin-top: 0.5rem; }
}
@media (max-width: 899px) {
  .panel { min-height: 300px; }
  .panel__more { opacity: 1; max-height: none; transform: none; margin-top: 0.4rem; }
  .panel__title { white-space: normal; }
  .panel__veil {
    inset: 0;
    background: linear-gradient(to bottom,
      rgba(11, 11, 11, 0.34) 0%,
      rgba(11, 11, 11, 0.5) 45%,
      rgba(11, 11, 11, 0.82) 100%);
  }
}

/* ---------- Full bleed helper ---------------------------------------------- */

.bleed { margin-inline: calc(var(--edge) * -1); }

/* The closing thank you sits centred over its photograph, narrow enough to
   read as a note rather than another passage of the story. */
.thanks {
  /* A shade larger than a story passage, since it closes the page rather than
     carrying it, and balanced so the centred lines break evenly. */
  font-size: clamp(1.25rem, 2vw, 1.95rem);
  line-height: 1.4;
  max-width: 34ch;
  margin-inline: auto;
  text-align: center;
  text-wrap: balance;
  color: var(--fg);
}

/* ---------- Statement block (ref 1.1 lower half) ---------------------------- */

.stmt__text {
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(1.8rem, 4.4vw, 4.2rem);
  line-height: 0.96;
  letter-spacing: 0.005em;
  max-width: 23ch;
}
.stmt__text i {
  font-family: var(--body);
  font-style: italic;
  font-weight: 400;
  text-transform: none;
  letter-spacing: -0.005em;
  color: var(--khaki-light);
}
.stmt__list {
  margin-top: clamp(2.4rem, 6vh, 4.5rem);
  display: grid;
  gap: 0.6rem;
  max-width: 34ch;
}
.stmt__list a {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1.2rem;
  padding-block: 0.55rem;
  border-bottom: 1px solid var(--line);
  transition: color 380ms var(--ease), padding-left 500ms var(--ease);
}
.stmt__list a:hover { color: var(--khaki-light); padding-left: 0.5rem; }

/* ---------- Spec block (ref 2.1 info panel) --------------------------------- */

.spec {
  background: var(--paper-warm);
  padding: clamp(1.8rem, 4.5vh, 3.4rem);
  display: grid;
  gap: clamp(1.6rem, 4vw, 3.4rem);
  grid-template-columns: 1fr;
  clip-path: polygon(30px 0, 100% 0, 100% calc(100% - 30px), calc(100% - 30px) 100%, 0 100%, 0 30px);
  transition: background-color var(--theme-t) var(--ease);
}
body[data-theme='dark'] .spec { background: #171714; }
@media (min-width: 900px) { .spec { grid-template-columns: 1fr 1.15fr; } }

.spec__list { display: grid; gap: 0.75rem; grid-template-columns: 1fr; }
@media (min-width: 520px) { .spec__list { grid-template-columns: 1fr 1fr; } }
.spec__list li {
  display: flex;
  gap: 0.7em;
  align-items: baseline;
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.74rem;
  letter-spacing: 0.16em;
  line-height: 1.5;
  color: var(--fg-soft);
}
.spec__list li::before {
  content: '';
  flex: none;
  width: 9px; height: 9px;
  background: var(--khaki-mid);
  transform: translateY(-1px);
  clip-path: polygon(0 0, 100% 0, 100% 66%, 66% 100%, 0 100%);
}

/* ---------- Collections grid (ref 2.1 discover) ----------------------------- */

.disc {
  display: grid;
  gap: clamp(1.4rem, 3vw, 2.6rem);
  grid-template-columns: 1fr;
  align-items: stretch;
}
@media (min-width: 960px) { .disc { grid-template-columns: 0.85fr 1.15fr; } }

.disc__panel {
  align-content: start;
  background: var(--khaki);
  color: #fff;
  padding: clamp(1.8rem, 4.5vh, 3.4rem);
  display: grid;
  align-content: space-between;
  gap: clamp(1.6rem, 4vh, 3rem);
  clip-path: polygon(30px 0, 100% 0, 100% calc(100% - 30px), calc(100% - 30px) 100%, 0 100%, 0 30px);
}
.disc__panel .eyebrow { color: var(--khaki-light); }
.disc__panel .prose { color: rgba(255, 255, 255, 0.78); }

.disc__tiles {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 620px) { .disc__tiles { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1100px) { .disc__tiles { grid-template-columns: repeat(4, 1fr); } }

.disc__tile { position: relative; display: block; overflow: hidden; }
.disc__tile .ph { transition: transform 900ms var(--ease); }
.disc__tile:hover .ph { transform: scale(1.04); }
.disc__tile figcaption {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 3;
  padding: 1.6rem 0.9rem 0.75rem;
  background: linear-gradient(to top,
    rgba(11, 11, 11, 0.92) 0%,
    rgba(11, 11, 11, 0.62) 45%,
    rgba(11, 11, 11, 0) 100%);
  color: #fff;
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.68rem;
  letter-spacing: 0.2em;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.6em;
}
.disc__tile figcaption em { font-style: normal; color: var(--khaki-light); flex: none; }

/* ---------- Instagram strip: full bleed (ref footer) ------------------------ */

.ig--bleed { padding-inline: 0; }
.ig--bleed .ig__strip { gap: 2px; }
.ig--bleed .ig__tile { clip-path: none; }
.ig--bleed .ig__tile:hover { transform: none; }
.ig--bleed .ig__tile .ph,
.ig--bleed .ig__tile img { transition: transform 900ms var(--ease), filter 700ms var(--ease); }
.ig--bleed .ig__tile:hover .ph,
.ig--bleed .ig__tile:hover img { transform: scale(1.035); }

/* Real frames rather than placeholders. Instagram's grid is 4:5 now, and the
   supplied files keep their white print border, so each tile reads as a print
   laid on the black footer. */
.ig__tile img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: contain;
  background: #fff;
}
/* Hover is a slight zoom and nothing else: no wash, no label. */
.ig__tile { overflow: hidden; }

/* ---------- Portrait feature (angling page) --------------------------------- */

.portrait {
  display: grid;
  gap: clamp(1.6rem, 4vw, 3.4rem);
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 960px) { .portrait { grid-template-columns: 0.72fr 1fr; } }
@media (min-width: 960px) {
  .portrait__sticky { position: sticky; top: calc(var(--header-h) + 1.5rem); }
  /* Keep the 9:16 film inside one screen so it actually pins */
  .portrait__sticky .ph--9x16 { max-width: calc(72vh * 9 / 16); margin-inline: auto; }
}
.portrait__side { display: grid; gap: clamp(1.6rem, 4vh, 3rem); }

/* Hero placeholders: label sits low so the tagline crossing the seam stays clean */
.hero__media .ph { align-items: end; }
.hero__media .ph__label,
.hero__media .ph__inner { margin-bottom: clamp(1rem, 3vh, 2rem); }

/* ==========================================================================
   Round 2 additions
   ========================================================================== */

/* ---------- About statement ------------------------------------------------ */

.stmt__sub {
  margin-top: clamp(1rem, 2.4vh, 1.8rem);
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(1.1rem, 2.4vw, 2.2rem);
  line-height: 1;
  letter-spacing: 0.01em;
  color: var(--khaki-light);
}
.stmt__close {
  margin-top: clamp(1.2rem, 2.6vh, 2rem);
  font-size: clamp(1.15rem, 1.5vw, 1.5rem);
  color: var(--fg-soft);
}

/* ---------- Portfolio mosaic ----------------------------------------------
   The six areas lead the page. Sizes deliberately uneven and the chamfer
   alternates corner, so it reads as a composition rather than a grid of
   equal boxes.
   -------------------------------------------------------------------------- */

.mosaic {
  display: grid;
  gap: clamp(0.75rem, 1.4vw, 1.4rem);
}

/* Rows are flex, not grid tracks, so hovering one card can genuinely widen it
   and squeeze its neighbours, the way the splitter does on the home page.
   A grid cannot do that without animating the track list. */
.mos-row {
  display: grid;
  gap: clamp(0.75rem, 1.4vw, 1.4rem);
  grid-template-columns: 1fr;
}

.mos {
  position: relative;
  grid-column: 1 / -1;
  overflow: hidden;
  display: block;
  isolation: isolate;
  border-radius: 2px;
  transition: flex-grow 800ms var(--ease), opacity 600ms var(--ease);
}


.mos__bed { position: absolute; inset: 0; z-index: 0; }
.mos__bed .ph { height: 100%; border: 0; }
.mos__bed .ph__label { display: none; }
.mos__bed .ph { transition: transform 900ms var(--ease); }
.mos:hover .mos__bed .ph,
.mos:active .mos__bed .ph { transform: scale(1.04); }

/* These tiles are wide and shallow, so a landscape photograph is cropped to a
   band across its middle - fine for a view, wrong for a photograph with a
   subject standing up out of it. The outdoors frame carries the board above
   the rider's head, and centred it lost the top of it. Taking the band higher
   up the picture brings the whole figure in.

   Keyed off the link, not the tile's position, since the six get reordered.
   If this starts being wanted for more than one of them it belongs on the
   photograph as a focal point in the CMS, rather than a rule per picture. */

.mos__veil {
  position: absolute;
  inset: 46% 0 0 0;
  z-index: 1;
  background: linear-gradient(to bottom,
    rgba(11, 11, 11, 0) 0%,
    rgba(11, 11, 11, 0.12) 42%,
    rgba(11, 11, 11, 0.82) 100%);
  transition: background 700ms var(--ease);
}
.mos:hover .mos__veil {
  background: linear-gradient(to bottom,
    rgba(62, 68, 47, 0) 0%,
    rgba(62, 68, 47, 0.5) 34%,
    rgba(28, 32, 20, 0.94) 100%);
}

.mos__body {
  position: relative;
  z-index: 2;
  display: grid;
  gap: 0.3rem;
  align-content: end;
  min-height: clamp(230px, 34vh, 460px);
  padding: clamp(1.4rem, 3vh, 2.2rem);
  color: #fff;
}
.mos__num {
  font-family: var(--display);
  font-weight: 600;
  font-size: 0.7rem;
  letter-spacing: 0.24em;
  color: var(--khaki-light);
}
.mos__title {
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(1.5rem, 3vw, 2.8rem);
  line-height: 0.94;
  letter-spacing: 0.005em;
}
.mos__kind {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.66rem;
  letter-spacing: 0.24em;
  color: rgba(255, 255, 255, 0.66);
}
.mos__go {
  position: absolute;
  right: clamp(1.4rem, 3vh, 2.2rem);
  bottom: clamp(1.6rem, 3.2vh, 2.4rem);
  z-index: 2;
  color: #fff;
  opacity: 0;
  transform: translateX(-10px);
  transition: opacity 500ms var(--ease), transform 600ms var(--ease);
}
.mos:hover .mos__go { opacity: 1; transform: none; }

@media (min-width: 760px) {
  .mos-row { display: flex; }
  .mos { flex: var(--f, 4) 1 0; min-width: 0; }
}

/* ---------- Gallery with load more ----------------------------------------- */

/* Three to a row, each row justified to its own height. Rows of uprights come
   out tall and rows of landscapes short, which keeps the stepped feel of the
   original masonry while every row still finishes flush. */
.gal__grid {
  display: grid;
  gap: clamp(1.2rem, 2.2vw, 2.2rem);
}
.gal__row {
  display: grid;
  gap: clamp(1rem, 1.8vw, 1.75rem);
  grid-template-columns: 1fr;
}
@media (min-width: 700px) {
  .gal__row { display: flex; align-items: flex-start; }
  .gal__row > * { flex: var(--ar, 1) 1 0; min-width: 0; }
}
.gal__row--more { display: none; }
.gal.is-open .gal__row--more { display: grid; }
@media (min-width: 700px) { .gal.is-open .gal__row--more { display: flex; } }





.gal__more {
  display: flex;
  justify-content: center;
  margin-top: clamp(2rem, 5vh, 3.5rem);
}
.gal.is-open .gal__more { display: none; }

/* ---------- Softer spec block ----------------------------------------------
   Round 2: these panels read too military. Warmer ground, softer corner,
   and the list set in the body face rather than condensed uppercase.
   -------------------------------------------------------------------------- */

.spec {
  clip-path: none;
  border-radius: 3px;
  padding: clamp(2rem, 5vh, 3.8rem);
  gap: clamp(2rem, 4.5vw, 4rem);
}

.spec__list { gap: 0.5rem 2rem; align-content: center; }
.spec__list li {
  font-family: var(--body);
  font-weight: 400;
  text-transform: none;
  font-size: 1.02rem;
  letter-spacing: 0;
  line-height: 1.45;
  color: var(--fg-soft);
  gap: 0.7em;
}
.spec__list li::before {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  clip-path: none;
  background: var(--khaki-mid);
  transform: translateY(-0.15em);
}
body[data-theme='dark'] .spec__list li::before { background: var(--khaki-light); }

/* ---------- Softer collections panel ---------------------------------------- */

.disc__panel {
  clip-path: none;
  border-radius: 3px;
  padding: clamp(2rem, 5vh, 3.8rem);
}
.disc__tile figcaption {
  padding: 1.5rem 1rem 0.8rem;
  background: linear-gradient(to top,
    rgba(11, 11, 11, 0.88) 0%,
    rgba(11, 11, 11, 0.45) 55%,
    rgba(11, 11, 11, 0) 100%);
}

/* ---------- Footer -----------------------------------------------------------*/

.ftr__mark {
  font-size: clamp(2.2rem, 10.6vw, 9.5rem);
  line-height: 1;
  padding-top: 0.06em;
  overflow: visible;
  letter-spacing: 0;
  color: #1E1E1B;
}

.ftr__credit a {
  color: rgba(255, 255, 255, 0.72);
  border-bottom: 1px solid rgba(255, 255, 255, 0.3);
  transition: color 320ms var(--ease), border-color 320ms var(--ease);
}
.ftr__credit a:hover { color: #fff; border-color: #fff; }

/* ==========================================================================
   Social media block on the home page
   A package rather than a shooting discipline, so it reads differently from
   the splitter: a khaki panel of what is included, next to a reel and a grid.
   ========================================================================== */

.sos {
  display: grid;
  gap: clamp(1.4rem, 3vw, 2.6rem);
  grid-template-columns: 1fr;
  align-items: stretch;
}
@media (min-width: 960px) { .sos { grid-template-columns: 1fr 1fr; } }

.sos__panel {
  background: var(--khaki);
  color: #fff;
  border-radius: 3px;
  padding: clamp(2rem, 5vh, 3.8rem);
  display: grid;
  align-content: space-between;
  gap: clamp(1.8rem, 4vh, 3rem);
}
.sos__panel .eyebrow { color: var(--khaki-light); }
.sos__panel .prose { color: rgba(255, 255, 255, 0.8); }

.sos__list {
  margin-top: clamp(1.4rem, 3vh, 2.2rem);
  display: grid;
  gap: 0.8rem;
}
.sos__list li {
  display: flex;
  gap: 0.85em;
  align-items: baseline;
  font-size: 1rem;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.86);
}
.sos__list li::before {
  content: '';
  flex: none;
  width: 14px;
  height: 1px;
  background: var(--khaki-light);
  transform: translateY(-0.4em);
}

/* Reel beside a small grid, so the block reads as social at a glance */
/* A rail, not a grid. The reel leads and the stills follow it at the same
   size, one to a screen, scrolled through sideways - which is how the work
   itself is watched, and lets each still be seen at the size it was made for
   rather than as a quarter tile.

   Snapping is per item so it always comes to rest on a picture. Each panel
   stops a little short of the full width so the next one shows at the edge:
   a rail with everything flush gives no sign there is anything to the right
   of it, and this is the only part of the page that scrolls that way. */
.sos__media {
  display: flex;
  gap: clamp(0.75rem, 1.4vw, 1.2rem);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.sos__media::-webkit-scrollbar { display: none; }

/* Holds the cue still while the panels move under it. min-width:0 because the
   rail is the grid item now: a grid item sizes to its content by default, and
   the scroller used to escape that only because overflow-x on the item itself
   resolves its min-width to zero. Without this the rail grows to the full
   width of the panels and nothing scrolls at all. */
.sos__rail { position: relative; min-width: 0; }

.sos__cue {
  position: absolute;
  right: clamp(0.5rem, 1.4vw, 1rem);
  top: 50%;
  translate: 0 -50%;
  display: grid;
  place-items: center;
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 50%;
  background: rgba(11, 11, 11, 0.55);
  color: #fff;
  border: 0;
  cursor: pointer;
  opacity: 1;
  transition: opacity 400ms var(--ease), background 400ms var(--ease);
}
.sos__cue:hover, .sos__cue:focus-visible { background: rgba(62, 68, 47, 0.9); }
/* Out of the way once there is nothing left, and not focusable while hidden. */
.sos__rail.is-end .sos__cue--next { opacity: 0; pointer-events: none; }
.sos__cue .btn__arrow { width: 1.15rem; animation: sos-nudge 1.9s var(--ease) infinite; }

/* The pair that drive the rail: same button on either edge, the back one with
   its arrow turned round, each hiding itself at the end it belongs to so
   neither offers a move that does nothing.

   These must sit after the rule above, not before it. Both are two classes
   deep, so at equal specificity the later one wins, and declared first the
   nudge was switched back on and its keyframes overwrote the flip. */
.sos__cue--back { right: auto; left: clamp(0.5rem, 1.4vw, 1rem); }
.sos__cue--back .btn__arrow { animation: none; transform: scaleX(-1); }
.sos__rail.is-start .sos__cue--back { opacity: 0; pointer-events: none; }

@keyframes sos-nudge {
  0%, 60%, 100% { transform: translateX(0); }
  30%           { transform: translateX(4px); }
}
@media (prefers-reduced-motion: reduce) {
  .sos__cue .btn__arrow { animation: none; }
}

.sos__shot {
  flex: 0 0 93%;
  scroll-snap-align: start;
}
.sos__shot .ph { height: 100%; }

/* Once the section splits into two columns the rail is wide enough that a
   full-width upright panel stands 1060px tall - taller than most screens, so
   no single picture could be seen whole. Past that point the panels take
   their size from the height of the screen instead and the width follows the
   9:16, which keeps the stills framed exactly as the reel is while leaving
   the next one showing at the edge. */
@media (min-width: 960px) {
  .sos__shot { flex: 0 0 auto; }
  .sos__shot .ph {
    height: min(76svh, 820px);
    width: auto;
    aspect-ratio: 9 / 16;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sos__media { scroll-snap-type: none; }
}

/* Zone markers: let one section span two background states, so the page can
   fade from white to black partway through it rather than at its edge. */
.zone {
  position: absolute;
  left: 0;
  right: 0;
  pointer-events: none;
  z-index: -1;
}


/* ---------- Portfolio subheading -------------------------------------------- */

.pintro__sub {
  margin-top: clamp(0.9rem, 2vh, 1.4rem);
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: clamp(0.9rem, 1.5vw, 1.35rem);
  letter-spacing: 0.08em;
  line-height: 1.15;
  color: var(--khaki-mid);
}
body[data-theme='dark'] .pintro__sub { color: var(--khaki-light); }

/* ---------- Collections listed rather than tiled ---------------------------- */

.disc__list {
  border-top: 1px solid var(--line);
  align-self: start;
}
.disc__list .collect__row:last-child { border-bottom: 1px solid var(--line); }

/* ---------- Button surfaces in context --------------------------------------
   The button's inner face defaults to the page background, which is wrong
   whenever it sits on a panel. And the overlay menu is always black whatever
   the page theme is, so its solid button has to keep the light hover fill or
   it turns black on black and vanishes.
   -------------------------------------------------------------------------- */

.menu .btn--solid {
  --btn-fill: var(--paper);
  --btn-fill-fg: var(--ink);
}

.spec .btn {
  --btn-face: var(--paper-warm);
}
body[data-theme='dark'] .spec .btn {
  --btn-face: #171714;
}

/* The green panel beside this list already says "explore the entire
   collection", so repeating it on every row is redundant, and the long
   service names push it into a four line wrap. Name and arrow only. */
.disc__list .collect__phrase { display: none; }
.disc__list .collect__row { grid-template-columns: auto 1fr auto; }


/* ==========================================================================
   Service page: approach and film blocks
   The old layout pinned a tall portrait film beside a stack of four stills,
   which left most of a screen empty while you scrolled past it. Both blocks
   are now balanced and each sits within roughly one screen.
   ========================================================================== */

.approach {
  display: grid;
  /* Gutter taken from .gal__row, not chosen here. This section and the gallery
     sit one under the other on the same page, both grids of photographs, so
     any difference between them reads as a mistake rather than a distinction.
     It was 3.4rem against the gallery's 1.75rem. */
  gap: clamp(1rem, 1.8vw, 1.75rem);
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 900px) { .approach { grid-template-columns: 1fr 1fr; } }

/* One landscape beside the copy, then two landscapes, then a landscape with an
   upright alongside it. */
.approach__media { display: block; }

/* The writing sits centred against the photograph beside it rather than
   hanging from the top of the row. Only the copy moves: align-self here, not
   align-items on the grid, which would lift the picture off the top edge too.
   Horizontal alignment is untouched - the text still ranges left.

   Below 900px the section is one column and each row is only as tall as the
   thing in it, so this has nothing to centre against and no effect. */
.approach__copy { align-self: center; }

.approach__pair,
.approach__mix {
  display: grid;
  gap: clamp(1rem, 1.8vw, 1.75rem);
  grid-template-columns: 1fr;
  align-items: start;
  margin-top: clamp(1.2rem, 2.2vw, 2.2rem);
}
/* The rhythm belongs to every block in the section, not just the two that
   used to follow. .approach carried no top margin because it was always
   first; once the writing can come second it needs one too, or it sits flush
   against the pair above it. Whichever block opens the section drops it. */
.approach {
  margin-top: clamp(1.2rem, 2.2vw, 2.2rem);
}
/* Only the margin. These carried grid-template-columns and align-items for a
   while by accident, which beat the two-column rule above on specificity and
   collapsed .approach to one column on every page whose copy comes first. */
.approach:first-child,
.approach__pair:first-child,
.approach__mix:first-child {
  margin-top: 0;
}
@media (min-width: 760px) {
  /* Weighted by each frame's own shape, so the landscape and the upright beside
     it finish at exactly the same height. */
  .approach__pair,
  .approach__mix { display: flex; align-items: flex-start; }
  .approach__pair > *,
  .approach__mix > * { flex: var(--ar, 1) 1 0; min-width: 0; }
}

/* Portrait film beside a compact block of stills, capped by height so the
   whole thing stays on one screen. */
.film {
  display: grid;
  gap: clamp(0.85rem, 1.6vw, 1.4rem);
  grid-template-columns: 1fr;
}
/* Either three portrait edits in a row, or a single landscape one */
.film { grid-template-columns: 1fr; }

.film__three {
  display: grid;
  gap: clamp(0.85rem, 1.6vw, 1.5rem);
  grid-template-columns: 1fr;
}
@media (min-width: 700px) {
  .film__three { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 900px) {
  .film__three { grid-template-columns: repeat(4, 1fr); }
}

/* A wired film slot can carry a label; it sits in the grid exactly where the
   bare frame would, so a captioned film and an uncaptioned one still line up. */
.reelcap { display: flex; flex-direction: column; gap: 0.7rem; margin: 0; }
.reelcap figcaption {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: clamp(0.62rem, 0.72vw, 0.74rem);
  letter-spacing: 0.22em;
  line-height: 1.3;
  color: var(--khaki-mid);
}
body[data-theme='dark'] .reelcap figcaption { color: var(--khaki-light); }

.film__stills {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(0.85rem, 1.6vw, 1.4rem);
  align-content: start;
}

/* ==========================================================================
   Waite Media mark
   ========================================================================== */

/* Header, top left. The mark stands alone now, so it carries the brand on
   its own and is sized accordingly. */
.hdr__mark {
  width: auto;
  height: var(--mark-h);
  flex: none;
  display: block;
  color: #fff;
  transition: transform 500ms var(--ease);
}
.hdr__brand:hover .hdr__mark { transform: scale(1.06); }


/* Intro curtain: mark sits above the name */
.curtain {
  gap: clamp(1.1rem, 3vh, 2.2rem);
  align-content: center;
  justify-items: center;
}
.curtain__mark {
  width: auto;
  height: clamp(46px, 8vh, 84px);
  color: #fff;
  animation: pulse 1.6s var(--ease-io) infinite;
}

/* Footer: the mark replaces the wordmark */
.ftr__mark {
  display: flex;
  justify-content: center;
  padding-block: clamp(2.5rem, 7vh, 5rem) clamp(2rem, 5vh, 3.5rem);
  overflow: visible;
  white-space: normal;
}
.ftr__logo {
  width: auto;
  height: clamp(90px, 20vw, 260px);
  color: rgba(255, 255, 255, 0.12);
}

/* ---------- Page watermark ---------------------------------------------------
   One mark per page, held behind the content as a very faint texture so large
   areas of white are not completely empty. Colour is interpolated by the same
   scroll value that drives the background, so it shifts as the page moves from
   white to black.
   -------------------------------------------------------------------------- */

.wm {
  position: fixed;
  z-index: -1;
  top: 50%;
  right: -6vw;
  transform: translateY(-50%);
  width: min(62vw, 78vh);
  pointer-events: none;
  user-select: none;
}
.wm__mark {
  width: 100%;
  height: auto;
  display: block;
  color: var(--wm-col, var(--khaki));
  opacity: 0.055;
}
body[data-theme='dark'] .wm__mark { opacity: 0.075; }

@media (max-width: 760px) {
  .wm { right: -20vw; width: 110vw; }
}
@media (prefers-reduced-motion: reduce) {
  .curtain__mark { animation: none; }
}

/* ==========================================================================
   Round 10: mark placements
   ========================================================================== */

/* Green opening screen. The colour arriving and leaving is the event, so the
   type stays small and low: just the name of the page you are arriving at,
   set like a title card rather than a splash screen. */
.curtain {
  align-content: start;
  justify-items: center;
  padding: clamp(2.2rem, 8vh, 5rem) var(--edge) 0;
}

/* Footer: mark sits behind the link columns, hard left */
.ftr__body { position: relative; }
.ftr__body .ftr__grid { position: relative; z-index: 1; }

.ftr__mark {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 0;
  display: block;
  padding: 0;
  pointer-events: none;
}
.ftr__logo {
  width: auto;
  height: clamp(140px, 26vw, 330px);
  color: rgba(255, 255, 255, 0.07);
}

/* Overlay menu: mark behind the content */
.menu { position: fixed; }
.menu__wm {
  position: absolute;
  left: -5vw;
  top: 50%;
  transform: translateY(-52%);
  z-index: 0;
  width: min(70vw, 78vh);
  pointer-events: none;
}
.menu__wm-mark {
  width: 100%;
  height: auto;
  display: block;
  color: rgba(255, 255, 255, 0.06);
}
.menu__grid { position: relative; z-index: 1; }

.menu__label {
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(1.6rem, 3.4vw, 2.8rem);
  letter-spacing: 0.01em;
  line-height: 1;
  color: var(--khaki-light);
  margin: 0;
}

/* ==========================================================================
   Project pages
   A service page sells a discipline and leads with the work. A project page
   tells what happened, so the written lead runs first, wider and larger than
   a normal intro, before any imagery.
   ========================================================================== */

.proj__lede {
  margin-top: clamp(1.4rem, 3.4vh, 2.4rem);
  max-width: 52ch;
  font-size: clamp(1rem, 1.1vw, 1.16rem);
  line-height: 1.6;
  color: var(--fg);
}

/* ---------- Project story beats -----------------------------------------------
   A project should read as something that happened, not as a contact sheet.
   Each beat is one short phrase sitting beside the pictures it belongs to, the
   side alternating as you go down, with the occasional wordless full width
   frame as a breath between passages.
   -------------------------------------------------------------------------- */

.beats {
  display: grid;
  gap: clamp(3rem, 9vh, 7.5rem);
}

.beat {
  display: grid;
  gap: clamp(1.2rem, 3vw, 3rem);
  grid-template-columns: 1fr;
  align-items: center;
}
@media (min-width: 900px) {
  .beat { grid-template-columns: minmax(0, 0.72fr) minmax(0, 1.6fr); }
  .beat--flip { grid-template-columns: minmax(0, 1.6fr) minmax(0, 0.72fr); }
  .beat--flip .beat__text { order: 2; }
}

.beat--full { grid-template-columns: 1fr; }
@media (min-width: 900px) { .beat--full { grid-template-columns: 1fr; } }

/* Words standing on their own between two sets of photographs. Wider than the
   26ch a line gets when a picture sits beside it, since nothing does. */
.beat--words { grid-template-columns: 1fr; }
.beat--words .beat__text { max-width: 44ch; }
@media (min-width: 900px) { .beat--words { grid-template-columns: 1fr; } }

.beat__text {
  font-size: clamp(1.08rem, 1.5vw, 1.55rem);
  line-height: 1.45;
  color: var(--fg);
  max-width: 26ch;
  margin: 0;
}
.beat--flip .beat__text { justify-self: end; }

.beat__media {
  display: grid;
  gap: clamp(0.85rem, 1.6vw, 1.4rem);
  align-items: start;
}
@media (min-width: 620px) {
  .beat__media--2,
  .beat__media--3 { display: flex; align-items: flex-start; }
  .beat__media--2 > *,
  .beat__media--3 > * { flex: var(--ar, 1) 1 0; min-width: 0; }

  /* Uprights need a ceiling on width, or they tower. A pair in a text column
     lands about 645px tall, which is the height the whole page is built
     around; the two cases below are the ones that would break that.

     A wordless beat runs the full width, so a pair there would be 973px tall.
     Cap the frames and centre what is left, rather than capping the row: a
     max-width with auto margins makes the row size to its content, and its
     content is flex-basis:0, so the whole row collapses to the gap.

     Only where the row is uprights. A row of landscapes is already short, and
     capping those to a shared width breaks the matched heights the rows are
     built on, which is what it did to the pair on the Village Maid page. */
  .beat--full .beat__media--2:not(:has(.ph--3x2)) { justify-content: center; }
  .beat--full .beat__media--2:not(:has(.ph--3x2)) > * { max-width: 440px; }

  /* And a lone upright would take the whole column: 1319px, taller than the
     window. Capped, it finishes at about the same height as everything else. */
  .beat__media--1 .ph--2x3 { max-width: 52%; }
}



/* A held film's start button. It sits over the poster and steps aside once the
   film is running, so a playing frame is just the picture. */
.ph__start {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: grid;
  place-items: center;
  border: 0;
  padding: 0;
  background: rgba(11, 11, 11, 0.22);
  color: #fff;
  cursor: pointer;
  transition: background 400ms var(--ease), opacity 300ms var(--ease);
}
.ph__start::before {
  content: '';
  position: absolute;
  width: 3.6rem;
  height: 3.6rem;
  border-radius: 50%;
  background: rgba(11, 11, 11, 0.55);
  transition: background 400ms var(--ease), scale 400ms var(--ease);
}
.ph__start svg { position: relative; width: 1.15rem; }
.ph__start:hover::before,
.ph__start:focus-visible::before { background: rgba(62, 68, 47, 0.9); scale: 1.06; }
.ph--video-live.is-running .ph__start { opacity: 0; pointer-events: none; }

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

/* ---------- Projects index: tiles ---------------------------------------------
   The portfolio page's visual language reused here, so the two sit together:
   the photograph is a bed under a veil, the words sit over it, and hovering
   pushes the picture up and deepens the veil. What differs is the shape of the
   block - one upright standing the height of two landscapes beside it - and
   the blurb, which rises out from under the title rather than always showing.
   -------------------------------------------------------------------------- */

.pgrid {
  display: grid;
  gap: clamp(0.75rem, 1.4vw, 1.4rem);
  grid-template-columns: 1fr;
}
/* Below this the three simply stack. */
.pstack { display: grid; gap: clamp(0.75rem, 1.4vw, 1.4rem); }

/* Past it the row is [upright | column of two], both flex, so hovering either
   side widens it and squeezes the other - grid tracks cannot do that without
   animating the track list. Nesting is what lets the two landscapes sit beside
   the upright and still grow on their own. */
@media (min-width: 860px) {
  .pgrid { display: flex; align-items: stretch; }
  /* Longhands, not the flex shorthand. A var() inside the shorthand was being
     dropped here and every grow fell back to 0, so both sides collapsed onto
     their minimum width. */
  .pgrid > * {
    flex-grow: var(--side, 1);
    flex-shrink: 1;
    flex-basis: 0;
    /* A floor on the width: squeezed while the other side is hovered, the
       upright fell to 158px and clipped "Village Maid Cheese". */
    min-width: clamp(160px, 16vw, 232px);
  }
  .pcard--tall { --side: 0.93; }
  .pstack { --side: 1; display: flex; flex-direction: column; }
  .pstack > .pcard { flex-grow: 1; flex-shrink: 1; flex-basis: 0; min-height: 0; }

  /* The upright alone sets the row's height; the stack divides it between its
     two, so the three always finish level. */
  .pcard--tall .pcard__body { min-height: clamp(680px, 96vh, 1000px); }
  .pstack > .pcard .pcard__body { min-height: 0; }

  .pgrid > *, .pstack > .pcard { transition: flex-grow 800ms var(--ease), opacity 600ms var(--ease); }
}

.pcard {
  position: relative;
  overflow: hidden;
  /* Grid, so the body stretches to whatever height the card is given. The tall
     one takes its height from the two rows it spans, and its words still need
     to sit on the bottom edge of it. */
  display: grid;
  isolation: isolate;
  border-radius: 2px;
  text-decoration: none;
  color: inherit;
  transition: flex-grow 800ms var(--ease), opacity 600ms var(--ease);
}
.pcard__bed { position: absolute; inset: 0; z-index: 0; }
.pcard__bed .ph { height: 100%; border: 0; }
/* A tile playing its film fills the bed exactly as the photograph does. */
.pcard__bed .ph__vid { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.pcard__bed .ph__label { display: none; }
.pcard__bed .ph { transition: transform 900ms var(--ease); }
.pcard:hover .pcard__bed .ph,
.pcard:active .pcard__bed .ph { transform: scale(1.04); }

.pcard__veil {
  position: absolute;
  inset: 40% 0 0 0;
  z-index: 1;
  background: linear-gradient(to bottom,
    rgba(11, 11, 11, 0) 0%,
    rgba(11, 11, 11, 0.12) 42%,
    rgba(11, 11, 11, 0.82) 100%);
  transition: background 700ms var(--ease);
}
.pcard:hover .pcard__veil,
.pcard:active .pcard__veil {
  background: linear-gradient(to bottom,
    rgba(62, 68, 47, 0) 0%,
    rgba(62, 68, 47, 0.5) 34%,
    rgba(28, 32, 20, 0.94) 100%);
}

.pcard__body {
  position: relative;
  z-index: 2;
  display: grid;
  gap: 0.35rem;
  align-content: end;
  min-height: clamp(240px, 36vh, 430px);
  padding: clamp(1.4rem, 3vh, 2.2rem);
  /* Set once, on the upright: it is what gives the row its height, and the
     stack beside it divides that between its two. */
  color: #fff;
}

.pcard__num {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.7rem;
  letter-spacing: 0.24em;
  /* White, not khaki. Sitting directly above a white title on a dark veil the
     green read as muddy and was the hardest thing on the tile to make out. */
  color: rgba(255, 255, 255, 0.82);
}
.pcard__title {
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(1.5rem, 2.6vw, 2.5rem);
  line-height: 0.94;
}
.pcard__blurb { max-width: 40ch; color: rgba(255, 255, 255, 0.86); }
.pcard__go {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.7rem;
  letter-spacing: 0.22em;
}
.pcard:hover .pcard__go .svc__arrow { transform: translateX(10px); }
.pcard__go .svc__arrow { transition: transform 600ms var(--ease); }

/* Hovering widens what is under the pointer and settles the rest back, on the
   portfolio's figures so the two pages behave the same. It works at both
   levels: across the row for the two sides, and down the stack for its pair,
   since hovering a landscape hovers the column holding it. */
@media (hover: hover) and (min-width: 860px) {
  .pgrid:hover > * { flex-grow: calc(var(--side, 1) * 0.68); }
  .pgrid > *:hover { flex-grow: calc(var(--side, 1) * 1.85); }
  /* Gentler than the row's figures. These two are already wide and shallow, so
     the row's 0.68 left the one not under the pointer a 6:1 sliver. */
  .pstack:hover > .pcard { flex-grow: 0.9; }
  .pstack > .pcard:hover { flex-grow: 1.2; }
  /* Dimming is per card, so the one under the pointer stays lit whichever
     side of the row it is on. */
  .pgrid:hover .pcard { opacity: 0.58; }
  .pgrid .pcard:hover { opacity: 1; }
  /* The upright sets the row height, so opening the row downwards is done
     there whichever card is hovered. */
  .pcard--tall .pcard__body { transition: min-height 800ms var(--ease); }
  .pgrid:hover .pcard--tall .pcard__body { min-height: clamp(740px, 104vh, 1080px); }
}

/* The blurb and the prompt rise out from under the title on hover. Gated on
   hover being available: with no pointer to hover there is nothing to reveal
   them, so on touch they simply stay put. */
@media (hover: hover) and (min-width: 860px) {
  .pcard__blurb,
  .pcard__go {
    opacity: 0;
    translate: 0 0.7rem;
    transition: opacity 500ms var(--ease), translate 500ms var(--ease);
  }
  .pcard:hover .pcard__blurb,
  .pcard:hover .pcard__go,
  .pcard:focus-visible .pcard__blurb,
  .pcard:focus-visible .pcard__go { opacity: 1; translate: 0 0; }
}
@media (prefers-reduced-motion: reduce) {
  .pcard__bed .ph,
  .pcard__blurb,
  .pcard__go { transition: none; }
  .pcard__blurb,
  .pcard__go { opacity: 1; translate: 0 0; }
}

/* Project rows carry a longer descriptor than the service rows, so the name
   gets a fixed share rather than shrinking to fit around it. */
@media (min-width: 900px) {
  .collect__row { grid-template-columns: auto minmax(0, 1.1fr) minmax(0, 1fr) auto; }
  .collect__name { white-space: nowrap; }
}

/* ---------- Section label ----------------------------------------------------
   Bigger than an eyebrow, quieter than a heading. Used where a section needs a
   name rather than a title, such as "My approach". */

.sec__label {
  margin: 0;
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(1.15rem, 2vw, 1.9rem);
  letter-spacing: 0.01em;
  line-height: 1.1;
  color: var(--fg);
}

/* ---------- Social panel button ------------------------------------------------
   Grid items stretch by default, which was pulling the button to full panel
   width and pinning it to the bottom. It sits under the list at its own size. */

.sos__panel { align-content: start; }
.sos__btn {
  justify-self: start;
  margin-top: clamp(1.4rem, 3.5vh, 2.4rem);
}



/* Collections panel button behaves like the social one: its own width, sitting
   under the copy rather than stretched and pinned to the bottom. */
.disc__btn {
  justify-self: start;
  margin-top: clamp(1.4rem, 3.5vh, 2.4rem);
}

/* ==========================================================================
   Projects landing: chapters
   Each project gets a band rather than a card, so the page reads as a shelf of
   stories instead of a grid of tiles.
   ========================================================================== */

.chapters {
  display: grid;
  gap: clamp(3rem, 9vh, 7rem);
}

.chapter {
  display: grid;
  gap: clamp(1.4rem, 4vw, 3.5rem);
  grid-template-columns: 1fr;
  align-items: center;
}
@media (min-width: 860px) {
  .chapter { grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); }
  .chapter--flip .chapter__media { order: 2; }
}

.chapter__media { overflow: hidden; }
.chapter__media .ph { transition: transform 900ms var(--ease); }
.chapter:hover .chapter__media .ph { transform: scale(1.03); }

.chapter__body { display: grid; gap: 0.9rem; align-content: center; }

.chapter__num {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.72rem;
  letter-spacing: 0.2em;
  color: var(--khaki-mid);
  margin: 0;
}
body[data-theme='dark'] .chapter__num { color: var(--khaki-light); }

.chapter__title {
  margin: 0;
  font-family: var(--display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(1.7rem, 3.6vw, 3.2rem);
  line-height: 1.04;
  letter-spacing: 0;
  transition: color 420ms var(--ease);
}
.chapter:hover .chapter__title { color: var(--khaki-mid); }
body[data-theme='dark'] .chapter:hover .chapter__title { color: var(--khaki-light); }

.chapter__lede {
  margin: 0;
  max-width: 46ch;
  color: var(--fg-soft);
}

.chapter__go {
  display: inline-flex;
  align-items: center;
  gap: 0.9em;
  margin-top: 0.4rem;
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.78rem;
  letter-spacing: 0.18em;
}
.chapter__go .svc__arrow { transition: transform 520ms var(--ease); }
.chapter:hover .chapter__go .svc__arrow { transform: translateX(10px); }

/* ---------- Required and optional markers on form labels --------------------- */

.field label em {
  font-family: var(--body);
  font-style: normal;
  text-transform: none;
  letter-spacing: 0;
  font-size: 0.86em;
  color: var(--khaki-mid);
}
.field label em::before { content: '\00a0\2217\00a0'; }
.field label em.opt {
  color: var(--fg-soft);
  opacity: 0.75;
}
.field label em.opt::before { content: '\00a0'; }
body[data-theme='dark'] .field label em { color: var(--khaki-light); }

/* ==========================================================================
   Portfolio mosaic: hovering one card lifts it and settles the rest
   The splitter on the home page does this with flex-grow, which reflows the
   layout. The mosaic is a fixed grid and the design is signed off, so the same
   effect comes from scale and opacity instead: the geometry does not move.
   ========================================================================== */

/* Hovering a card widens it, settles the rest of its row back, and opens the
   row downwards. The hovered card sets the row height and the siblings stretch
   to match, so the card grows in both directions rather than just sideways. */
@media (hover: hover) and (min-width: 760px) {
  .mos-row:hover .mos { flex-grow: calc(var(--f, 4) * 0.68); opacity: 0.58; }
  .mos-row .mos:hover { flex-grow: calc(var(--f, 4) * 1.85); opacity: 1; }
  .mos__body { transition: min-height 800ms var(--ease); }
  .mos-row .mos:hover .mos__body { min-height: clamp(320px, 46vh, 620px); }
}

/* ---------- Slots carrying a real photograph ---------------------------------
   The frame keeps its ratio and the photograph fills it. The hatch, brackets
   and label are placeholder furniture, so they come off once there is an image.
   -------------------------------------------------------------------------- */

.ph--photo { background: var(--media-bed); border: 0; }
.ph--photo::before,
.ph--photo::after { display: none; }

.ph__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* A photograph in a video slot is the poster, so the play mark needs a scrim */
.ph--video.ph--photo .ph__inner { position: relative; z-index: 2; }
.ph--video.ph--photo::before {
  display: block;
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: radial-gradient(closest-side, rgba(11,11,11,.42), rgba(11,11,11,0) 78%);
  background-image: radial-gradient(closest-side, rgba(11,11,11,.42), rgba(11,11,11,0) 78%);
}

/* Panels and mosaic beds size their own image */
.panel__bed .ph--photo,
.mos__bed .ph--photo { height: 100%; }

/* ---------- Photographs at their own proportions ------------------------------
   A slot with `natural` keeps the picture's own shape instead of cropping it to
   the placeholder ratio. Portrait stays portrait, landscape stays landscape,
   and nothing gets its top and bottom cut off to fit a square.
   -------------------------------------------------------------------------- */

.ph--natural {
  aspect-ratio: auto;
  height: auto;
  display: block;
}
.ph--natural .ph__img {
  position: static;
  width: 100%;
  height: auto;
}

/* The film block no longer stretches its stills to match the reel, since they
   now carry their own proportions. */
@media (min-width: 860px) {
  .film { align-items: start; }
  .film__stills .ph--natural { height: auto; }
}

/* Scatter frames are links now */
.pinned__scatter .sc { display: block; text-decoration: none; color: inherit; }
.pinned__scatter .sc .ph { transition: transform 800ms var(--ease); }
/* Matched to the panels and the portfolio tiles - it was doing half as much
   as either of them, which read as a wobble rather than a move. */
.pinned__scatter .sc:hover .ph,
.pinned__scatter .sc:active .ph { transform: scale(1.04); }
.pinned__scatter .sc:hover .cap b { color: var(--khaki-mid); }

/* ---------- No borders on media, hover or otherwise --------------------------- */

.ph--photo,
.ph--photo:hover,
.ig__tile img,
.mos__bed .ph,
.panel__bed .ph {
  border: 0;
  outline: 0;
}
.ph--natural { border: 0; }

/* ---------- Cursor over dark ground -------------------------------------------
   Dark sections already flip the whole palette, so the dot turns white with
   them. These surfaces stay dark whatever the page theme is, so the dot needs
   telling separately.
   -------------------------------------------------------------------------- */

body.cur-light .cur { background: var(--paper); }

/* ---------- Wordmark in the hero ----------------------------------------------- */

.hero__logo {
  width: min(54vw, 640px);
  height: auto;
  display: block;
  margin-inline: auto;
  color: inherit;
}

/* The two-line setting is the phone's; the strip is everywhere else. Only one
   of the pair is ever displayed, so the hidden one stays out of the
   accessibility tree and the name is not announced twice. */
.hero__logo--stack { display: none; }
@media (max-width: 859px) {
  /* Two lines on a phone, which roughly doubles the letters for the same
     width and turns the name into a statement rather than a strip. */
  .hero__logo { display: none; }
  .hero__logo--stack { display: block; }
  .hero__logo--stack svg {
    display: block;
    width: 88%;
    height: auto;
    margin-inline: auto;
    color: inherit;
  }
  /* The viewBoxes are cropped close to the letters, so the two lines need a
     gap put back between them or they read as one block. */
  .hero__logo--stack svg + svg { margin-top: 0.14em; }
}

/* ---------- Scrim behind the pinned tagline -----------------------------------
   A soft dark halo under the words rather than a band across the frame. It is
   centred on the tagline, which is pinned to the middle of the screen, so the
   scroll handler writes that point in the frame's own coordinates.

   It lives on the picture itself, not on the link around it: the link also
   contains the caption underneath, and a scrim covering that spilled onto the
   white below the photograph.
   -------------------------------------------------------------------------- */

/* One shade, pinned behind the words, instead of one on every photograph.

   Painting it onto each frame meant it had to be chased: positioned per frame
   from the scroll handler, faded in as the tagline approached, faded out
   again after. It still read as something arriving rather than something
   simply there, because it was - it belonged to the pictures, which move.

   Here it belongs to the tagline, which is stuck to the middle of the screen,
   so it does not move at all. No scroll handler, nothing to time, and it is
   in exactly the same place relative to the type at every point in the
   scroll. It sits above the photographs and below the words: .pinned__centre
   paints after .pinned__scatter, and .pinned__tag is lifted over it below. */

/* The shade goes on the photographs, not over the whole frame.

   Painted on the pinned tagline layer it covered everything behind the words
   - including the white page showing between the pictures, which came out as
   a grey cloud sitting on nothing. An element cannot be masked to whatever
   photographs happen to be under it, so the only way to keep it to them is to
   let each one carry its own: inside the frame, it can only ever darken the
   frame.

   It still reads as pinned. The gradient is placed from the middle of the
   screen rather than from the picture, written per frame by the scroll
   handler in site.js, so it holds still against the type while the pictures
   drift underneath it. It cannot be done with a fixed background attachment:
   the frames carry a transform, which makes anything fixed inside them
   behave as though it were scrolling after all. */

.pinned__scatter .ph--photo::after {
  display: block;
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: radial-gradient(
    26vw 20vh at var(--scrim-x, 50%) var(--scrim-y, 50%),
    rgba(11, 11, 11, 0.40) 0%,
    rgba(11, 11, 11, 0.33) 38%,
    rgba(11, 11, 11, 0.2) 62%,
    rgba(11, 11, 11, 0.07) 82%,
    rgba(11, 11, 11, 0) 96%
  );
}

/* The tagline is set far larger relative to the screen on a phone - four
   words stacked down the gutter - so the shade is wider and shallower there
   to sit around them rather than inside them. */
@media (max-width: 859px) {
  .pinned__scatter .ph--photo::after {
    background: radial-gradient(
      52vw 15vh at var(--scrim-x, 50%) var(--scrim-y, 50%),
      rgba(11, 11, 11, 0.40) 0%,
      rgba(11, 11, 11, 0.33) 38%,
      rgba(11, 11, 11, 0.2) 62%,
      rgba(11, 11, 11, 0.07) 82%,
      rgba(11, 11, 11, 0) 96%
    );
  }
}
.pinned__scatter .sc { position: relative; }


/* ==========================================================================
   Phones
   The home page was a stack of full width bands, which read as much flatter
   than the desktop layout. These rules give it some structure back.
   ========================================================================== */

/* The small run of links under the heading and button. It holds the
   disciplines now rather than the pages, but the treatment is unchanged: a
   wrapped row, each link underlined, no dividers and no numbering. */
.menu__primary {
  display: flex;
  flex-wrap: wrap;
  gap: 1.1rem 1.6rem;
  margin-top: clamp(2rem, 5vh, 3.5rem);
}
.menu__primary a {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.82rem;
  letter-spacing: 0.2em;
  padding-bottom: 0.3em;
  border-bottom: 1px solid rgba(255, 255, 255, 0.3);
  transition: border-color 320ms var(--ease), color 320ms var(--ease);
}
.menu__primary a:hover { color: var(--khaki-light); border-color: var(--khaki-light); }

@media (max-width: 859px) {
  /* The two lines above the wordmark crowded the top of the screen */
  .hero__sig { display: none; }

  /* Inside the pin the hero *is* the screen. The film fills it edge to edge
     and top to bottom - the identical frame the covering photograph occupies,
     including running up under the header - so opening the shutter swaps one
     picture for the other with nothing changing size or shifting. */
  .hero { padding-top: 0; padding-bottom: 0; }

  /* The film runs the full width of the screen, out through the page gutter,
     and stands tall enough to hold the top of the phone on its own. Only the
     pictures break out - the strap and scroll cue below keep the page's
     margins, so the type still lines up with everything under it. */
  /* Without this the stage keeps its desktop min-height, the grid rows take a
     definite height from it, and aspect-ratio then drives each frame's *width*
     from that height, which pushed them off the side of the screen. */
  .hero__stage {
    min-height: 0;
    height: 100vh;
    height: 100svh;
    margin-inline: calc(var(--edge) * -1);
  }
  .hero__split { grid-auto-rows: auto; gap: 0; height: 100%; }

  /* No ratio: the film takes the frame's shape rather than the frame taking
     the film's, which is what makes it match the photograph over it. */
  .hero__media { min-height: 0; height: 100%; }
  .hero__drone .ph { aspect-ratio: auto; height: 100%; }

  /* One frame on a phone. The stepped-in still made a composition out of two
     pictures at a size that has room for one, and it is the film that should
     hold the screen here. */
  .hero__still { display: none; }

  /* The strap and the scroll cue lie on the film, at the foot of the screen,
     since the hero no longer has a column of page below it to sit in. Under
     the shutter, so they arrive with the film rather than sitting over the
     photograph. */
  .hero__meta {
    position: absolute;
    left: var(--edge);
    right: var(--edge);
    bottom: clamp(1.1rem, 3.5vh, 2.2rem);
    z-index: 5;
    margin: 0;
    padding: 0;
    border-top: 0;
    color: var(--paper);
  }
  .hero__meta .lede,
  .hero__meta .cue { color: var(--paper); }
  .hero__meta .cue i { background: rgba(255, 255, 255, 0.6); }
  .hero__meta .lede { font-size: clamp(0.95rem, 3.7vw, 1.1rem); line-height: 1.45; }
  .hero__meta > * {
    filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.5)) drop-shadow(0 6px 22px rgba(0, 0, 0, 0.4));
  }

  /* Above the shutter, not under it: the wordmark sits on the photograph and
     goes with it, rather than waiting behind it to be uncovered. */
  .hero__tag { z-index: 9; }

  /* --shutter-open is written by site.js as the halves draw back. The title
     is gone by the time they are two thirds open, so it clears the screen
     well before the film is fully uncovered rather than lingering over it. */
  .hero__tag h1 {
    opacity: calc(1 - var(--shutter-open, 0) * 1.55);
    transform: translateY(calc(var(--shutter-open, 0) * -4vh));
  }

  /* ---- The shutter (see the note beside .shutterwrap above) ----
     Twice the screen: the hero is pinned across the first screen's worth of
     scrolling, which is what the halves draw back over, and the rest is the
     beat it holds open for before the page moves on. svh, not vh, so the
     pinned hero is measured against the screen as it actually is on a phone
     rather than as it would be with the browser bars hidden. */
  .shutterwrap { height: 200vh; height: 200svh; }
  .shutterwrap__pin {
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100svh;
    overflow: hidden;
  }

  .shutter {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 8; /* over the film and the strap, under the wordmark */
    pointer-events: none;
  }
  .shutter__half {
    position: absolute;
    top: 0;
    height: 100%;
    width: 50.2%; /* the fraction over half closes the hairline down the seam */
    overflow: hidden;
    will-change: transform;
  }
  .shutter__half--l { left: 0; }
  .shutter__half--r { right: 0; }

  /* Each half is a window onto a full-width copy of the photograph, held to
     that half's outside edge - so the left window shows the left of the
     picture and the right window the right, and the two meet as one image. */
  /* Taller than the screen on purpose. At its natural cover size the
     photograph fills the height exactly, which leaves no vertical crop to
     play with - object-position can only move it sideways, and the surfer
     lands mid-frame, directly behind the title. Oversizing it and hanging it
     from the top clips the foot of the picture instead, which carries her
     down into the lower corner and out from under the words. */
  .shutter__img {
    position: absolute;
    top: 0;
    height: 138%;
    width: 100vw;
    display: block;
  }
  .shutter__half--l .shutter__img { left: 0; }
  .shutter__half--r .shutter__img { right: 0; }
  .shutter__img img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 14% center;
    display: block;
  }
  .hero__tag h1 { font-size: clamp(2.1rem, 12.5vw, 3.4rem); }

  .marquee span { font-size: 1.05rem; }

  /* Six full width bands was a long, samey scroll. Two columns of taller
     cards covers the same ground in a third of the distance.

     The shape has to go on the card itself. .panel__bed is positioned
     absolutely so that the photograph can sit behind the words, which takes
     it out of flow - it cannot give the card any height, so a ratio set on it
     did nothing and the cards collapsed onto their text alone, about 100px
     tall. The card holds the ratio; the bed still fills it. */
  .splitter { display: grid; grid-template-columns: 1fr 1fr; gap: 2px; }
  .panel { min-height: 0; aspect-ratio: 3 / 4; }
  .panel__bed .ph { aspect-ratio: auto; height: 100%; }
  .panel__body { padding: clamp(0.9rem, 2.4vh, 1.4rem) 0.6rem clamp(1rem, 2.6vh, 1.6rem); }
  .panel__title { font-size: clamp(1rem, 4.4vw, 1.3rem); }
  .panel__kind { font-size: 0.54rem; letter-spacing: 0.1em; }
  .panel__more { display: none; }
}

/* Native date field, kept in the same underlined style as the rest */
.field input[type='date'] {
  appearance: none;
  min-height: 2.6em;
  color: var(--fg);
}
.field input[type='date']::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: 0.55;
  transition: opacity 300ms var(--ease);
}
.field input[type='date']:hover::-webkit-calendar-picker-indicator { opacity: 1; }
body[data-theme='dark'] .field input[type='date']::-webkit-calendar-picker-indicator { filter: invert(1); }

/* ==========================================================================
   Social media page
   Built around the service rather than the pictures: the reels lead, the
   accounts and what they did follow.
   ========================================================================== */

/* Six vertical edits in a row, scrolling sideways on narrow screens rather
   than stacking into an enormous column */
.reels {
  display: grid;
  gap: clamp(0.7rem, 1.4vw, 1.1rem);
  grid-auto-flow: column;
  grid-auto-columns: minmax(150px, 1fr);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding-bottom: 0.5rem;
  scrollbar-width: thin;
}
.reels > * { scroll-snap-align: start; }
@media (min-width: 900px) {
  .reels { grid-auto-columns: 1fr; overflow-x: visible; }
}

.credit {
  margin: clamp(1rem, 2.4vh, 1.6rem) 0 0;
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.68rem;
  letter-spacing: 0.18em;
  color: var(--khaki-mid);
}
body[data-theme='dark'] .credit,
.credit--dark { color: var(--khaki-light); }

/* ---------- Accounts ---------------------------------------------------- */

.accounts {
  display: grid;
  gap: clamp(0.85rem, 1.8vw, 1.5rem);
  grid-template-columns: 1fr;
}
@media (min-width: 760px) { .accounts { grid-template-columns: repeat(3, 1fr); } }

.account {
  display: grid;
  align-content: start;
  gap: 0.3rem;
  padding: clamp(1.3rem, 3vh, 2rem);
  border: 1px solid var(--line);
  border-radius: 3px;
  transition: background-color 500ms var(--ease), border-color 500ms var(--ease), transform 600ms var(--ease);
}
.account:hover {
  background: rgba(255, 255, 255, 0.04);
  border-color: var(--khaki-light);
  transform: translateY(-4px);
}

.account__sector {
  margin: 0;
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.66rem;
  letter-spacing: 0.22em;
  color: var(--khaki-light);
}
.account__handle {
  margin: 0.35rem 0 0;
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(1.25rem, 2.2vw, 1.7rem);
  line-height: 1.1;
}
.account__what { margin: 0; font-size: 0.92rem; color: var(--fg-soft); }

.account__growth {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  margin-top: clamp(1.2rem, 3vh, 1.8rem);
  padding-top: clamp(0.9rem, 2vh, 1.3rem);
  border-top: 1px solid var(--line);
}
.account__growth > div { display: grid; gap: 0.2rem; }
.account__n {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(1.3rem, 2.4vw, 1.9rem);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.account__n--to { color: var(--khaki-light); }
.account__l {
  font-family: var(--body);
  font-size: 0.76rem;
  line-height: 1.3;
  color: var(--fg-soft);
}

/* ---------- Grid ---------------------------------------------------------- */

.feed {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(0.4rem, 1vw, 0.75rem);
}

@media (prefers-reduced-motion: reduce) {
  .panel:hover .panel__bed .ph, .panel:active .panel__bed .ph,
  .pinned__scatter .sc:hover .ph, .pinned__scatter .sc:active .ph,
  .mos:hover .mos__bed .ph, .mos:active .mos__bed .ph { transform: none; }
}


/* The lead line above the display word: number, rule, then the h1 itself. The
   h1 is set at eyebrow size on purpose - see the note in build.js. */
.pintro__lead {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0 0.55em;
  margin-bottom: clamp(0.9rem, 1.8vw, 1.5rem);
}
.pintro__num,
.pintro__sep {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  font-size: clamp(0.72rem, 0.85vw, 0.86rem);
  letter-spacing: 0.28em;
  line-height: 1;
  color: var(--khaki-mid);
}
.pintro__sep { opacity: 0.5; letter-spacing: 0; }
.pintro__kw { line-height: 1.45; }
/* One line on anything desktop sized. Below that it wraps, which is right -
   the alternative is a heading running off the side of a phone. */
@media (min-width: 900px) { .pintro__lead { flex-wrap: nowrap; white-space: nowrap; } }
body[data-theme='dark'] .pintro__num,
body[data-theme='dark'] .pintro__sep { color: var(--khaki-light); }


/* ---------- The social offer on a portfolio page ---------------------------- */
/* A photograph and a khaki panel, half the page each, so it reads as its own
   thing rather than a second Work with me. */
.social { display: grid; grid-template-columns: 1fr; }
@media (min-width: 900px) { .social { grid-template-columns: 1fr 1fr; } }

/* On desktop the frame drops its own ratio and takes the panel's height, so the
   two halves always finish level whatever the copy runs to. On one column it
   keeps 4:5, or height:100% would collapse it to nothing. */
@media (min-width: 900px) {
  .social__media { height: 100%; }
  .social__media .ph { height: 100%; aspect-ratio: auto; border-right: 0; }
}

.social__copy {
  background: var(--khaki);
  color: #fff;
  padding: clamp(1.8rem, 4vw, 3.2rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.social__copy .eyebrow { color: var(--khaki-light); }
.social__copy .d3 { color: #fff; text-wrap: balance; }
.social__copy .prose { color: rgba(255, 255, 255, 0.8); }
.social__lead {
  margin-top: 0.55rem;
  font-size: clamp(1rem, 1.25vw, 1.12rem);
  line-height: 1.4;
  color: var(--khaki-light);
  max-width: 34ch;
}

.social__list {
  margin: clamp(1.2rem, 2.6vh, 1.9rem) 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.7rem;
}
.social__list li {
  display: flex;
  gap: 0.85em;
  align-items: baseline;
  font-size: 0.96rem;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.86);
}
.social__list li::before {
  content: '';
  flex: none;
  width: 14px;
  height: 1px;
  background: var(--khaki-light);
  transform: translateY(-0.4em);
}
.social__copy .btn { align-self: flex-start; }

/* Instagram grid in place of the single photo. Three across with a hairline
   gap, which is what a feed looks like; squares, because that is the shape a
   grid thumbnail is whatever was uploaded. Nine of them square off to very
   near the panel's own height, so the two halves still finish level. */
.social__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2px;
  align-content: start;
}
.social__grid .ph { border: 0; }

/* A hero frame and the first frame below it are two stacked pictures, so they
   take the page's own rhythm for that - the same clamp .approach__pair and
   .approach__mix use - instead of the pintro's padding-bottom plus the next
   section's padding-top, which together came to 108px against 32 everywhere
   else. Only pages that actually carry a hero are touched; the ones with
   heroMedia false have no .pintro__hero to match. */
.pintro:has(.pintro__hero) { padding-bottom: 0; }
.pintro:has(.pintro__hero) + .section { padding-top: clamp(1.2rem, 2.2vw, 2.2rem); }

/* A square or upright approach photograph, held back from the full column
   width so it does not dwarf the paragraph it sits beside. Flush right, so the
   page's own margin still lines up. */
@media (min-width: 900px) {
  .approach__media--tall .ph { max-width: 84%; margin-left: auto; }
}

/* Off screen until it is focused, which is the only time it is wanted. */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 0.75rem 1.25rem;
  background: var(--ink, #14140F);
  color: #fff;
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  text-decoration: none;
}
.skip:focus { left: 0; }

/* The measurement notice. Bottom of the screen, quiet, and out of the way of
   the work - it is a legal necessity, not a piece of the design. */
.consent {
  position: fixed;
  left: 50%;
  bottom: 1rem;
  transform: translateX(-50%);
  z-index: 90;
  width: min(46rem, calc(100vw - 2rem));
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.5rem;
  padding: 1.1rem 1.35rem;
  background: #14140F;
  color: rgba(255, 255, 255, 0.82);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.35);
}
.consent__text {
  flex: 1 1 18rem;
  margin: 0;
  font-size: 0.82rem;
  line-height: 1.55;
}
.consent__text a { color: #fff; text-underline-offset: 3px; }
.consent__acts { display: flex; gap: 0.6rem; margin-left: auto; }
.consent__btn {
  border: 1px solid rgba(255, 255, 255, 0.35);
  background: transparent;
  color: #fff;
  padding: 0.55rem 1.1rem;
  font: inherit;
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 320ms var(--ease), border-color 320ms var(--ease);
}
.consent__btn--yes { background: #fff; color: #14140F; border-color: #fff; }
.consent__btn:hover { background: rgba(255, 255, 255, 0.15); }
.consent__btn--yes:hover { background: rgba(255, 255, 255, 0.85); }
@media (max-width: 620px) {
  .consent { flex-direction: column; align-items: stretch; }
  .consent__acts { margin-left: 0; }
  .consent__btn { flex: 1; }
}

/* The honeypot. Off screen rather than display:none - some bots skip anything
   hidden that way, and the point is that they do fill it in. */
.hp {
  position: absolute;
  left: -5000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* A failed send has to look different from a successful one, or the two read
   the same at a glance and someone walks away thinking it went. */
.is-bad { color: #8C3A2B; }

/* The confirmation that replaces the enquiry form. Deliberately the size of
   the thing it replaced - a sent message is the most important thing on the
   page at that moment, and small print under a button read as nothing at all. */
.sent {
  border: 1px solid var(--line, rgba(20, 20, 15, 0.14));
  background: rgba(62, 68, 47, 0.05);
  padding: clamp(1.75rem, 4vw, 2.75rem);
}
.sent__tick {
  display: grid;
  place-items: center;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: 50%;
  background: #3E442F;
  color: #fff;
}
.sent__tick svg { width: 1.5rem; height: 1.5rem; }

/* .form sets display:grid, which beats the hidden attribute's display:none
   from the browser's own stylesheet - so hiding the form in script left it on
   screen. Both of these are toggled by hidden, so both need to mean it. */
.form[hidden],
.sent[hidden] { display: none; }
