:root {
  color-scheme: dark;
  --ink: #0b0b0c;
  --bg: #111112;
  --panel: #171719;
  --text: #ece9e4;
  --muted: #a9a6a2;
  --faint: #8c8986;
  --rule: #2a2a2c;
  --serif: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  --sans: Karla, "Helvetica Neue", Arial, sans-serif;
  --ease: cubic-bezier(.22, .61, .36, 1);
}

*, *::before, *::after { box-sizing: border-box; }
/* No scroll snapping: it fought iOS momentum scrolling. Spacing between
   statements comes from layout (min-heights) alone. */
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
h1, h2, p { margin: 0; }
h1, h2 { text-wrap: balance; }
a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }
em { font-style: italic; }

h1, h2, .brand-mark, .card-copy, .contact-link {
  font-family: var(--serif);
  font-weight: 400;
}

/* Small tracked utility type */
.brand-line, .micro, .come-in, .eyebrow {
  font-family: var(--sans);
  font-weight: 500;
  font-size: 11px;
  letter-spacing: .24em;
  text-transform: uppercase;
}

/* ---------- Hero ---------- */
.hero {
  position: relative;
  /* Tall enough for the collapsed-toolbar screen on phones (lvh), so the
     next section never peeks in beneath it */
  min-height: 100vh;
  min-height: 100lvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  isolation: isolate;
  background: var(--ink);
}
/* The photo sits on an ordinary layer with the copy stacked above it.
   (A negative z-index plus a transformed text block made iOS Safari drop
   the photo or cut a rectangle out of it.) */
.hero-media {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.masthead, .hero-content, .come-in { position: relative; z-index: 1; }
.hero-img, .hero-plate {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 62%;
}
.hero-img { position: relative; z-index: 2; }

/* ---------- Arrival ----------
   The room is already there in dim ambient light. The doorway light comes up
   (its own glow and floor spill, screened on as light), then the room light
   reaches the near columns and settles. Every pixel of light comes from the
   photograph; nothing is drawn. Once settled, nothing moves. */
@property --reach { syntax: '<percentage>'; inherits: false; initial-value: 0%; }
.hero-plate { display: none; }
.arrival .hero-plate { display: block; position: absolute; inset: 0; z-index: 0; }
/* The glow plate is the ambient room with its doorway light screened on
   ahead of time: iOS Safari draws live blend modes wrongly (in rectangles)
   wherever animated layers overlap them */
.arrival .hero-glow {
  z-index: 1;
  opacity: 0;
  animation: glow-up 2.2s cubic-bezier(.45, 0, .55, 1) .6s forwards;
}
@keyframes glow-up { to { opacity: .8; } }

/* Room light reaches inward from the near columns at the edges, swelling up
   with the doorway light rather than after it, settling at 3.8s */
.arrival .hero-img {
  -webkit-mask-image: radial-gradient(farthest-corner at 50% 62%, transparent var(--reach), #000 calc(var(--reach) + 45%));
  mask-image: radial-gradient(farthest-corner at 50% 62%, transparent var(--reach), #000 calc(var(--reach) + 45%));
  animation: reach-in 2.6s cubic-bezier(.45, 0, .55, 1) 1.2s both;
}
@keyframes reach-in { from { --reach: 100%; } to { --reach: -45%; } }
/* Touch devices: iOS Safari can fail to repaint a mask driven by an animated
   custom property and leave the photo invisible. On a phone the crop is the
   centre of the hall anyway, so the room light simply fades up. */
@media (hover: none), (pointer: coarse) {
  .arrival .hero-img {
    -webkit-mask-image: none;
    mask-image: none;
    animation: room-in 2.6s cubic-bezier(.45, 0, .55, 1) 1.2s both;
  }
}
@keyframes room-in { from { opacity: 0; } to { opacity: 1; } }

/* Selective light: the near foreground and outer edges stay in shadow, so
   only the doorway, its floor and a few edges become fully legible */
.hero-shade {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    radial-gradient(ellipse 62% 72% at 50.8% 63%, rgba(0,0,0,0) 30%, rgba(0,0,0,.45) 100%),
    linear-gradient(to top, rgba(0,0,0,.3), rgba(0,0,0,0) 34%);
}
.arrival .hero-shade { animation: room-shade 2.6s cubic-bezier(.45, 0, .55, 1) 1.2s both; }
@keyframes room-shade { from { opacity: 0; } to { opacity: 1; } }

/* Once the room has settled, after a beat, one near column receives its
   light on its own; the words follow after a short pause (script.js) */
.hero-column { display: none; }
.arrival .hero-column {
  display: block;
  z-index: 2;
  opacity: 0;
  animation: column-on 1.8s cubic-bezier(.45, 0, .55, 1) 4s forwards;
}
@keyframes column-on { to { opacity: 1; } }

.arrived .hero-column { animation: none; opacity: 1; }

/* Crossing the threshold: "Come in" lets the room fall back into darkness,
   the doorway alone still lit, as the page glides on (script.js) */
.hero-dim { display: none; }
[data-hero="hall"] .hero-dim {
  display: block;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 62%;
  z-index: 2;
  opacity: 0;
  pointer-events: none;
  transition: opacity 1.6s cubic-bezier(.4, 0, .2, 1);
}
.dimming .hero-dim { opacity: 1; }
.dimming .hero-content, .dimming .come-in { opacity: .35; transition: opacity 1.6s ease; }
/* the masthead's arrival animation holds its opacity, so dim it by filter */
.masthead { transition: filter 1.6s ease; }
.dimming .masthead { filter: brightness(.35); }

/* The masthead waits until the words have landed (delay set in script.js) */
.arrival .masthead { opacity: 0; animation: masthead-in 2.6s ease 8.6s forwards; }
@keyframes masthead-in { to { opacity: 1; } }

/* Skipped: settle immediately */
.settled .hero-ambient, .settled .hero-glow { display: none; }
.arrived .hero-img { animation: none !important; -webkit-mask-image: none; mask-image: none; opacity: 1; }
.arrived .hero-glow { animation: none; opacity: .8; }
.arrived .hero-shade { animation: none; opacity: 1; }
.arrived .masthead { animation: none; opacity: 1; transition: opacity .4s; }

/* The headline fades in place during the arrival; nothing slides */
.arrival .hero .reveal, .arrival .hero .reveal em { transform: none; }
/* "expected." comes slowly into focus, like the closing line, instead of
   arriving on the quick ease-out every other reveal uses. Longhands, so the
   delays set elsewhere still apply. */
.js .hero h1.reveal em {
  filter: blur(6px);
  transition-property: opacity, filter, transform;
  transition-duration: 1.3s;
  transition-timing-function: cubic-bezier(.45, 0, .25, 1);
}
.js .hero h1.reveal.is-visible em { filter: none; }
/* The lines after "expected." take their time too */
.js .hero .micro.reveal, .js .hero .come-in.reveal { transition-duration: 2.6s; }

.hero-media::after {
  /* keeps the masthead and lower edge legible */
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  background:
    linear-gradient(180deg, rgba(0,0,0,.55) 0%, rgba(0,0,0,0) 22%),
    linear-gradient(0deg, var(--bg) 0%, rgba(17,17,18,.6) 6%, rgba(0,0,0,0) 28%); /* ends in the next section's colour: no seam */
}
.grain {
  position: absolute;
  inset: 0;
  z-index: 4;
  opacity: .07;
  pointer-events: none;
  background-repeat: repeat;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

.masthead {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  padding: 40px 56px 0;
}
.brand { display: flex; flex-direction: column; gap: 12px; }
.brand-mark { font-size: 34px; line-height: 1; }
.brand-line { color: #cfccc7; }

/* Drawn arrow: emoji-style glyphs render inconsistently (iOS especially) */
.arrow {
  display: inline-block;
  width: 1em;
  height: 1em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
  stroke-linecap: square;
  vector-effect: non-scaling-stroke;
}

/* Text sits in the upper half so the lit doorway stays clear below it */
.hero-content {
  text-align: center;
  padding: 0 24px;
  /* set by script.js to line the copy up with the doorway; plain offsets,
     not a transform, so it never becomes its own compositing layer */
  left: var(--nudge-x, 0px);
  top: var(--nudge-y, 0px);
  margin-top: clamp(80px, 17vh, 190px);
}
.hero h1 {
  font-size: clamp(54px, 8.6vw, 136px);
  line-height: 1;
  letter-spacing: -.01em;
}
.micro { color: #b9b6b1; }
.hero h1 + .micro { margin-top: clamp(28px, 4.5vh, 48px); }
.micro + .micro { margin-top: 18px; }

.come-in {
  align-self: center;
  position: relative;
  left: var(--nudge-x, 0px); /* on the doorway's axis, like the copy above */
  margin: auto 0 clamp(40px, 7vh, 72px);
  /* plus the toolbar's height on phones, so it stays visible above it */
  margin: auto 0 calc(clamp(40px, 7vh, 72px) + 100lvh - 100svh);
  padding-bottom: 12px;
  font-size: 12px;
}
/* The line sits exactly under the letters (the right inset drops the
   trailing letter-spacing) and widens a few pixels each side on hover;
   no spring, no custom cursor, nothing on touchscreens */
.come-in::after {
  content: "";
  position: absolute;
  left: 0;
  right: .24em;
  bottom: 0;
  height: 1px;
  background: rgba(236,233,228,.55);
  transition: left .25s ease-out, right .25s ease-out, background-color .25s ease-out;
}
@media (hover: hover) and (pointer: fine) {
  .come-in:hover::after, .come-in:focus-visible::after { left: -5px; right: calc(.24em - 5px); background-color: var(--text); }
}


/* ---------- Opening ----------
   In place of the hall: our details come slowly into focus on the dark,
   centred, with "Come in" low on the same axis. */
.opening {
  min-height: 100vh;
  min-height: 100lvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 0 24px;
}
.opening-content { margin-top: 36vh; }
.opening-mark { font-family: var(--serif); font-size: clamp(56px, 6vw, 88px); line-height: 1; }
.opening .micro { margin-top: 18px; }
.opening-mark + .micro { margin-top: clamp(28px, 4.5vh, 44px); }
.opening .micro + .micro { color: var(--muted); }
/* Each line comes into focus, nothing slides (delays set in script.js) */
.js .opening .reveal {
  transform: none;
  filter: blur(6px);
  transition-property: opacity, filter;
  transition-duration: 1.6s;
  transition-timing-function: cubic-bezier(.45, 0, .25, 1);
}
.js .opening .reveal.is-visible { filter: none; }

/* ---------- Approach ---------- */
/* Each statement gets its own screen; this one sits a little above centre */
.approach {
  min-height: 100vh;
  min-height: 100lvh;
  padding: 0 24px 14vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
}
/* A hierarchy of confidence: the hero is theatrical, the closing line
   definitive; the philosophy and the evidence speak at conversation level */
.approach h2, .work h2 {
  font-size: clamp(34px, 3.7vw, 56px);
  line-height: 1.15;
  letter-spacing: -.005em;
  text-wrap: balance;
}
.approach p {
  max-width: 560px;
  margin-top: 30px;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.8;
  letter-spacing: .02em;
}

/* ---------- Work ---------- */
.work { padding: 0 max(4%, 24px) 160px; } /* same dark as its neighbours: one continuous space */
/* Generous space above the heading so it arrives as its own statement;
   the cards follow at reading distance rather than a screen later */
.work h2 { text-align: center; padding-top: 22vh; margin-bottom: clamp(80px, 13vh, 140px); }
.cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 32px;
  max-width: 1380px;
  margin: 0 auto;
  align-items: start;
}
/* A gentle descending stair: Tonight, Monday, Friday (stacked on phones) */
@media (min-width: 701px) {
  .card:nth-child(2) { margin-top: 60px; }
  .card:nth-child(3) { margin-top: 120px; }
}
.eyebrow { color: var(--faint); font-size: 10px; }
.card-copy {
  margin: 24px 0 40px;
  font-size: clamp(20px, 1.8vw, 28px);
  line-height: 1.45;
}
/* clip, not hidden: hidden makes a scroll container, which stalls the
   view() timelines below */
.card-media { overflow: hidden; overflow: clip; aspect-ratio: 3 / 4; }
.card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1);
}
/* Friday is the world outside the building: the one card in (muted) colour,
   graded in the file itself */
.card:nth-child(3) .card-media img { filter: none; }
/* Images settle once as their card arrives, instead of tracking the scroll */
.js .card-media img { transform: scale(1.08); transition: transform 2.8s var(--ease); }
.js .card.is-visible .card-media img { transform: scale(1); }

/* ---------- Promise ---------- */
/* No image: after three photographs the page goes still and says one thing.
   Set a little below centre, which gives it more weight. */
.promise {
  min-height: 100vh;
  display: grid;
  align-content: center;
  padding: 8vh 24px 0;
  text-align: center;
}
.promise h2 { font-size: clamp(44px, 5.6vw, 88px); line-height: 1.1; }

/* Word by word, slowly coming into focus; nothing moves */
.js .promise h2.reveal, .js .promise h2.reveal em { opacity: 1; transform: none; transition: none; }
.js .promise .w {
  display: inline-block;
  opacity: 0;
  filter: blur(6px);
  transition: opacity 1.6s ease, filter 1.6s ease;
  transition-delay: calc(.3s + var(--i) * .55s);
}
.js .promise .is-visible .w { opacity: 1; filter: none; }

/* ---------- Invitation ---------- */
/* After "Consider it handled", near-total darkness and a quiet invitation,
   low on the left margin like the masthead. No underline, no button. */
/* The page ends here: no footer, just the invitation and the dark */
.invitation {
  min-height: 100vh;
  min-height: 100lvh;
  display: flex;
  align-items: flex-start;
  padding: 55vh 56px 0; /* sits a little past the middle: placed, not stranded */
}
.contact-link {
  font-size: 44px;
  font-style: italic;
  color: rgba(236, 233, 228, .9);
  transition: color .6s var(--ease);
}
.contact-link:hover, .contact-link:focus-visible { color: var(--text); }
/* Close to the words, but clear of the italic f's tail (which also needs
   the link's box to run past it: iOS clips fading text to its box) */
.contact-link .arrow {
  width: 9px;
  height: 9px;
  margin-left: .2em;
  vertical-align: 14px;
  opacity: .7;
  transition: transform .6s var(--ease), opacity .6s;
}
.contact-link:hover .arrow, .contact-link:focus-visible .arrow { opacity: 1; transform: translate(2px, -2px); }


/* ---------- Introduction ----------
   No panel: the introduction happens on the page itself. "Introduce
   yourself" rises to become the heading and the form, already here, is
   revealed beneath it in one movement, in the site's own type. */
.invitation { flex-direction: column; transition: padding-top 1.4s var(--ease); }
.invitation.introducing { padding-top: 12vh; }
.introducing .contact-link { pointer-events: none; color: var(--text); }
.contact-link .period { display: none; }
.introducing .contact-link .period { display: inline; animation: intro-fade 1s ease both; }
.contact-link .arrow { transition: opacity .6s, transform .6s var(--ease); }
.introducing .contact-link .arrow { opacity: 0; }
.introduced .contact-link { display: none; }
@keyframes intro-fade { from { opacity: 0; } }

.intro {
  /* positioned, so the hidden spam-trap field stays inside it rather than
     stretching the page */
  position: relative;
  display: grid;
  grid-template-rows: 0fr;
  width: min(940px, 100%);
  opacity: 0;
  visibility: hidden;
  transition: grid-template-rows 1.4s var(--ease), opacity 1s ease, visibility 0s 1.4s;
}
.introducing .intro { grid-template-rows: 1fr; opacity: 1; visibility: visible; transition-delay: .25s, .6s, 0s; }
.intro-inner { min-height: 0; overflow: hidden; padding: 0 4px 6vh; margin: 0 -4px; }
/* Two columns on wider screens, so it all sits within a laptop's screen:
   name and organisation, email and referrer, then the message in full */
.intro-form { display: grid; grid-template-columns: 1fr 1fr; column-gap: 56px; align-items: end; }
.intro-form[hidden], .intro-thanks[hidden] { display: none; }
.intro-form > .field:nth-of-type(5), .intro-send, .intro-status { grid-column: 1 / -1; justify-self: start; }
.intro-form > .field:nth-of-type(5) { justify-self: stretch; }

.field { display: block; margin-top: 40px; }
.field span { display: block; font-family: var(--sans); font-size: 13px; letter-spacing: .02em; color: var(--muted); }
.field input, .field textarea {
  display: block;
  width: 100%;
  margin-top: 4px;
  padding: 6px 0 10px;
  border: 0;
  border-bottom: 1px solid rgba(236, 233, 228, .14);
  border-radius: 0;
  background: transparent;
  color: var(--text);
  font-family: var(--serif);
  font-style: italic;
  font-size: 27px;
  line-height: 1.25;
  outline: none;
  transition: border-color .4s;
}
/* The message is the field that matters most: room for a few lines */
.field textarea { resize: none; overflow: hidden; min-height: calc(3 * 1.25em + 16px); }
.field input:focus, .field textarea:focus { border-bottom-color: rgba(236, 233, 228, .8); }
.field input[aria-invalid="true"] { border-bottom-color: #b98a84; }
.hp { position: absolute; top: 0; left: -9999px; width: 1px; height: 1px; opacity: 0; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.intro-send {
  margin-top: 40px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text);
  font-family: var(--sans);
  font-weight: 500;
  font-size: 11px;
  letter-spacing: .24em;
  text-transform: uppercase;
  cursor: pointer;
}
.intro-send .arrow { width: 9px; height: 9px; margin-left: 10px; transition: transform .5s var(--ease); }
.intro-send:hover .arrow, .intro-send:focus-visible .arrow { transform: translate(2px, -2px); }
.intro-send[disabled] { opacity: .5; cursor: default; }
.intro-status { margin-top: 16px; min-height: 1.4em; font-size: 13px; color: var(--muted); }
/* After sending, the fields and heading quietly fade, and the thanks takes
   the heading's place (script.js) */
.intro-form { transition: opacity .7s ease; }
.concluding .intro-form { opacity: 0; }
/* (outranks the scroll reveal's opacity) */
.js .invitation.concluding .contact-link.reveal { opacity: 0; transition: opacity .7s ease; }
.intro-thanks { outline: none; animation: intro-fade 1.4s ease both; }
.intro-note { animation: intro-fade 1.4s ease .5s both; }
.intro-title { font-family: var(--serif); font-style: italic; font-size: 44px; line-height: 1.15; }
.intro-note { margin-top: 18px; color: var(--muted); font-size: 15px; }

/* ---------- Motion ---------- */

/* Scroll reveals only apply once JS is running, so content never hides without it */
.js .reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 1.4s var(--ease), transform 1.4s var(--ease);
  transition-delay: var(--delay, 0s);
}
.js .reveal em {
  display: inline-block;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 1.4s var(--ease), transform 1.4s var(--ease);
  transition-delay: calc(var(--delay, 0s) + .45s);
}
.js .reveal.is-visible,
.js .reveal.is-visible em { opacity: 1; transform: none; }

/* Scroll-linked depth driven by the browser itself (smooth on iOS, unlike
   JS scroll handlers). Browsers without support keep the still layout. */
@supports (animation-timeline: view()) {
  /* Mouse and trackpad only: on iOS, scroll-linked animations cost native
     momentum scrolling, which matters far more than the depth effect. */
  @media (prefers-reduced-motion: no-preference) and (hover: hover) and (pointer: fine) {
    .hero-media {
      animation: hero-drift linear both;
      animation-timeline: scroll(root);
      animation-range: 0 100vh;
    }
    .js .card-media img {
      transition: none;
      animation: card-settle linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 60%;
    }
  }
}
@keyframes hero-drift { to { transform: translate3d(0, 30vh, 0); } }
@keyframes card-settle { from { transform: scale(1.14); } to { transform: scale(1); } }

/* ---------- Mobile ---------- */
@media (max-width: 700px) {
  .masthead { padding: 26px 22px 0; }
  .brand-mark { font-size: 28px; }
  .brand-line { font-size: 9px; letter-spacing: .2em; max-width: 190px; line-height: 1.7; }
  .hero-content { margin-top: 16vh; }
  .opening-content { margin-top: 32vh; }
  .hero h1 { font-size: clamp(50px, 14vw, 78px); line-height: 1.02; }
  .micro { font-size: 10px; }
  /* Statements get the whole screen on phones, as on desktop */
  /* lands a little above centre after Come in, as on desktop, even with Safari's toolbar showing */
  .approach { min-height: 100svh; min-height: 100lvh; padding: 96px 28px calc(96px + 14vh); }
  .approach h2 { font-size: clamp(32px, 8.8vw, 40px); line-height: 1.12; }
  .approach p { font-size: 17px; line-height: 1.75; max-width: 320px; margin-top: 36px; }
  .work { padding: 0 22px 120px; }
  .work h2 {
    /* roughly a screen of space, heading centred, cards begin below */
    padding: calc(50svh - 120px) 0 calc(50svh - 160px);
    margin: 0;
    font-size: clamp(32px, 8.8vw, 40px);
    line-height: 1.12;
  }
  .work h2 br { display: none; }
  .cards { grid-template-columns: 1fr; gap: 88px; }
  .card-copy { font-size: 25px; margin-bottom: 28px; }
  .promise { min-height: 100svh; min-height: 100lvh; padding-top: 6vh; }
  .promise h2 { font-size: clamp(36px, 10.4vw, 48px); white-space: nowrap; }
  .invitation { padding: 55vh 22px 0; }
  .contact-link { font-size: 32px; }
  .contact-link .arrow { width: 8px; height: 8px; margin-left: .28em; vertical-align: 9px; }
  .invitation.introducing { padding-top: 14vh; }
  .intro-form { grid-template-columns: 1fr; }
  .field { margin-top: 32px; }
  .field input, .field textarea { font-size: 24px; }
  .intro-title { font-size: 32px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .hero-plate { display: none !important; }
  .invitation, .intro { transition: none !important; }
  .js .reveal, .js .reveal em, .js .promise .w { opacity: 1; transform: none; filter: none; transition: none; }
  .js .card-media img { transform: none; transition: none; }
}
