/* ============================================================
   Base — reset, typography primitives, layout, media, buttons, texture
   ============================================================ */

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

html {
  -webkit-text-size-adjust: 100%;
  /* No scroll-behavior:smooth here — it fights ScrollToPlugin, which
     writes the scroll position every tick. main.js does the smoothing,
     and falls back to native smooth scrolling if GSAP is unavailable. */
}

/* Clears the fixed nav on a plain, script-free anchor jump. */
main [id], footer[id] { scroll-margin-top: calc(var(--nav-h) + 1rem); }

body {
  margin: 0;
  background: var(--paper);
  color: var(--blue);
  font-family: var(--body);
  font-size: var(--t-body);
  line-height: 1.6;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

h1, h2, h3, h4, p, figure, figcaption, blockquote, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
img, svg, canvas, video { display: block; max-width: 100%; }
button, input, textarea, select { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }

/* Inverts whichever ground it lands on, so it is correct on paper and
   on the blue band without a per-section swap. */
::selection { background: var(--fg, var(--blue)); color: var(--bg, var(--paper)); }

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

/* Visually hidden, still announced. Used where a stretched link needs
   a name the design does not print — the service panels, where the
   whole room is the target and the title is already on screen. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- Skip link ---- */
.skip-link {
  position: absolute;
  top: 0; left: 50%;
  transform: translate(-50%, -110%);
  z-index: 9000;
  padding: 0.75rem 1.5rem;
  background: var(--blue);
  color: var(--paper);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: 0 0 4px 4px;
  transition: transform 0.3s var(--ease);
}
.skip-link:focus { transform: translate(-50%, 0); }

/* ============================================================
   Layout primitives
   ============================================================ */

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

.section {
  position: relative;
  background: var(--bg);
  color: var(--fg);
  padding-block: var(--section);
}

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

/* ============================================================
   Type primitives

   Casing rule, and it is the whole system in one line: only micro
   type is capitalised. Labels, eyebrows, tags, form labels and the
   wordmark are uppercase at 0.16em; every headline and every
   sentence is set the way it would be written. Title Case headings
   were doing the job of emphasis with capitals, which is what the
   labels are for — and on a white ground they read as a brochure
   rather than as a drawing.
   ============================================================ */

.label {
  display: inline-block;
  font-family: var(--body);
  font-size: var(--t-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--accent);
}

.display {
  font-family: var(--display);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.025em;
}

/* Italic emphasis inside a headline — the one piece of stress the
   display face is allowed. Montserrat ships a true italic, so this is
   a drawn cut and not a slant. */
h1 em, h2 em, h3 em, .display em { font-style: italic; }

/* ============================================================
   Section head

   Reworked. It used to be a 72px display line under an uppercase
   eyebrow, which meant every section shouted its own name louder
   than the work underneath it — six times down one page.

   Now it is a masthead: a hairline that draws itself across the
   full measure, the section's name set in Title Case at reading
   size, and the heading held to 38px. The name is Title Case
   rather than caps deliberately — capitals are reserved for micro
   type on this site, and "Selected Work" at 13px is a name, not a
   label shouted at the reader.
   ============================================================ */

.section-head {
  position: relative;
  max-width: 52rem;
  padding-top: 1.5rem;
}

/* The rule is the section's opening gesture: it draws left to right
   as the head arrives, and every section starts the same way. */
.section-head::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 1px;
  background: var(--rule-strong);
  transform: scaleX(0);
  transform-origin: left center;
}
.section-head::before { transition: transform 1.1s var(--wipe); }
.section-head.is-in::before,
html:not(.js) .section-head::before { transform: scaleX(1); }

/* The section's name, and the only thing on the line. It used to sit
   opposite an `01 / 07` position counter; that counter was telling
   the reader something the scrollbar already tells them, in the one
   voice a page like this cannot afford — the voice that sounds like
   a slide deck. Title Case, tracked open a touch so it holds the
   line on its own without needing caps. */
.section-head__name {
  display: block;
  margin-bottom: clamp(1.25rem, 2.4vw, 2rem);
  font-family: var(--body);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--accent);
  text-transform: none;
}

.section-head h2 {
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--t-h2);
  line-height: 1.14;
  letter-spacing: -0.02em;
  text-wrap: balance;
  max-width: 34ch;
}
.section-head h2.is-big { font-size: var(--t-h2-big); line-height: 1.04; letter-spacing: -0.03em; }

/* The standfirst — and `:not()` on the name is load-bearing, not
   tidiness. The name is a <p> too, so a bare `.section-head p` is
   (0,1,1) against the name's own (0,1,0) and quietly wins: it was
   handing the section name body size and muted grey instead of the
   small accent line it is meant to be, with nothing to show for it
   but a head that looked slightly wrong and no rule to blame. */
.section-head p:not(.section-head__name) {
  margin-top: 1.1rem;
  font-size: var(--t-body);
  line-height: 1.62;
  color: var(--muted);
  max-width: 54ch;
  text-wrap: pretty;
}

/* Kept for the heads that still carry the old eyebrow. */
.section-head .label { margin-bottom: 1.5rem; }

/* Kept for the few heads that genuinely want the axis — the closing
   CTA on the services page, where there is nothing to align to. */
.section-head--centre {
  text-align: center;
  margin-inline: auto;
}
.section-head--centre p:not(.section-head__name) { margin-inline: auto; }

.lead {
  font-size: var(--t-lead);
  line-height: 1.34;
  color: var(--muted);
  max-width: var(--measure);
}

.numeral {
  font-family: var(--body);
  font-weight: 300;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.05em;
  line-height: 0.85;
}

/* ============================================================
   Media — full colour on a monochrome page.

   The chrome is paper, blue and hairlines; the photography is not.
   That contrast is the point: the interface recedes and the rooms
   carry the colour. A light grade only, to pull a heterogeneous
   stock library onto a common footing.
   ============================================================ */

.media {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--paper-cool);
}

/* Absolutely positioned, not `height: 100%`.

   Every .media box is sized by an `aspect-ratio` and no explicit
   height, which makes the parent's height indefinite — so a
   percentage height on the child resolves to `auto` and the image
   falls back to its own aspect. Where the photograph was shorter than
   its box that left a strip of --paper-cool along the bottom edge of
   the frame. Taking the image out of flow makes the aspect box the
   containing block, and `inset: 0` then means exactly what it says. */
.media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.94) contrast(1.04);
  /* `scale`, not `transform`.

     JD.revealMedia animates these images — the settle on entry and,
     where a figure has a parallax speed, a scrubbed yPercent that is
     live for the whole time the section is on screen. GSAP writes
     both into the inline `transform`, and an inline transform beats
     a class rule outright, so a hover written as `transform: scale()`
     is simply never applied. The independent `scale` property is a
     separate slot the browser composes with `transform` rather than
     replacing it, so the two can coexist. */
  transition: filter 0.8s var(--ease), scale 1.2s var(--ease);
}

@media (hover: hover) {
  .media-hover:hover .media img,
  .media-hover:focus-visible .media img {
    filter: saturate(1.02) contrast(1.06);
    scale: 1.035;
  }
}

/* The wipe that uncovers a photograph. Set to the section's own
   ground so it dissolves into the page rather than off a grey. */
.media__curtain {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: var(--bg);
  transform-origin: bottom center;
}

/* A caption plate under a photograph: name left, discipline right,
   separated from the image by the same hairline as everything else. */
.plate {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  margin-top: 0.9rem;
  padding-top: 0.8rem;
  border-top: 1px solid var(--rule);
}
.plate__name { font-size: var(--t-body); letter-spacing: -0.01em; line-height: 1.2; }
.plate__meta {
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--muted);
  white-space: nowrap;
}

/* ============================================================
   Line-reveal scaffolding (see js/motion.js -> splitLines)
   Montserrat at line-height < 1 clips descenders inside an
   overflow:hidden mask — the padding/margin pair buys the room
   back without shifting layout.
   ============================================================ */

/* Only while JS is running: hold a heading that is about to be split
   until the split has happened, so it cannot flash unsplit first.
   Gated on .js, so a JS failure leaves the text plainly visible. */
.js [data-split-hold]:not(.is-split) { visibility: hidden; }

.line-mask {
  display: block;
  overflow: hidden;
  padding: 0 0.12em 0.18em;
  margin: 0 -0.12em -0.18em;
}
.line { display: block; }
.word { display: inline-block; will-change: transform; }

/* Character split (see js/motion.js -> splitChars). The word wrapper
   is what keeps a per-character reveal from breaking mid-word: the
   characters are inline-block, so without it the browser would
   happily wrap "INTERIORS" after the "T". */
.char-word { display: inline-block; white-space: nowrap; }
.char-mask {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding: 0 0.02em 0.16em;
  margin: 0 -0.02em -0.16em;
}
.char { display: inline-block; will-change: transform, filter; }

/* ============================================================
   Buttons
   ============================================================ */

.btn {
  --btn-pad-y: 0.9rem;
  --btn-pad-x: 1.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: var(--btn-pad-y) var(--btn-pad-x);
  font-family: var(--body);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: 2px;
  cursor: pointer;
  background: none;
  transition:
    background-color 0.45s var(--ease),
    border-color 0.45s var(--ease),
    color 0.45s var(--ease),
    transform 0.45s var(--ease);
}

/* Solid, and the solid flips with the ground: ink-blue on paper,
   paper on the blue band. */
.btn--primary {
  background: var(--btn-bg);
  color: var(--btn-fg);
  border-color: var(--btn-bg);
}

.btn--outline {
  color: var(--fg);
  border-color: var(--rule-strong);
}

.btn--ghost {
  color: var(--accent);
  border-color: transparent;
  padding-inline: 0;
}

/* ============================================================
   The arrow

   One shape, defined once as a <symbol> and referenced by <use>
   everywhere. It replaces what had become four different arrows —
   the "↗" and "&rarr;" text glyphs, which were drawn by the body
   font and so never matched the weight of the label beside them,
   and two hand-written paths at two different stroke widths.

   The gesture: the arrow leaves along its own axis and an identical
   one arrives from the opposite corner, both clipped by a 1em box.
   Because the two are the same shape, the animation has no wrong
   end state — interrupt it anywhere and it still reads as an arrow
   sitting where an arrow belongs.
   ============================================================ */

/* The sprite itself is never rendered; it only holds the symbols. */
.sprite {
  position: absolute;
  width: 0; height: 0;
  overflow: hidden;
}

.arw {
  position: relative;
  display: block;
  flex: none;
  /* Sized in em so it tracks whatever it is set beside — 13px button
     label, 11px micro link — without a single override. */
  width: 1.05em;
  height: 1.05em;
  /* The mask. Both copies live inside it and only one is ever in. */
  overflow: hidden;
}

.arw svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Stroke set here rather than on the symbol's path: stroke
     properties inherit into the <use> shadow tree, so one
     declaration styles every arrow on the site. */
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 0.55s var(--ease);
}

/* The waiting copy sits one box-width off the mask, on the side the
   leaving copy is heading away from. 110% rather than 100% so a
   round cap never shows a sliver at the edge. */
.arw__in { transform: translate(-110%, 0); }
.arw--ne .arw__in { transform: translate(-110%, 110%); }

/* Hover and focus alike — a keyboard user should see the same thing.
   .svc is in the list because a service panel's copy sits under a
   stretched hit area, so the link itself never receives :hover; the
   panel's own open state drives it instead. */
@media (hover: hover) {
  .btn:hover .arw__out,
  .link-arrow:hover .arw__out { transform: translate(110%, 0); }
  .btn:hover .arw--ne .arw__out,
  .link-arrow:hover .arw--ne .arw__out { transform: translate(110%, -110%); }
  .btn:hover .arw__in,
  .link-arrow:hover .arw__in { transform: translate(0, 0); }
}

.btn:focus-visible .arw__out,
.link-arrow:focus-visible .arw__out,
.svc[data-open='true'] .arw__out { transform: translate(110%, 0); }
.btn:focus-visible .arw--ne .arw__out,
.link-arrow:focus-visible .arw--ne .arw__out { transform: translate(110%, -110%); }
.btn:focus-visible .arw__in,
.link-arrow:focus-visible .arw__in,
.svc[data-open='true'] .arw__in { transform: translate(0, 0); }

/* Nothing slides; the arrow is simply there. */
@media (prefers-reduced-motion: reduce) {
  .arw svg { transition: none; }
  .arw__out, .arw--ne .arw__out { transform: none !important; }
  .arw__in, .arw--ne .arw__in { transform: translate(-110%, 0) !important; }
}

@media (hover: hover) {
  .btn--primary:hover { background: var(--accent); border-color: var(--accent); color: var(--btn-fg); }
  .btn--outline:hover { border-color: var(--fg); background: var(--tint); }
  .btn--ghost:hover { color: var(--fg); }
}

/* On the blue band the primary hover would fade paper toward steel and
   lose its own label; hold it at paper and let the border carry the
   change instead. */
[data-ground='blue'] .btn--primary:hover,
[data-ground='deep'] .btn--primary:hover {
  background: var(--blue-soft);
  border-color: var(--blue-soft);
  color: var(--blue);
}

.btn--sm { --btn-pad-y: 0.7rem; --btn-pad-x: 1.25rem; }

/* ============================================================
   Small link with a sliding arrow (SEE DETAILS ->)
   ============================================================ */

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  transition: color 0.4s var(--ease);
}
@media (hover: hover) {
  .link-arrow:hover { color: var(--accent); }
}

/* ============================================================
   ScrollSmoother scaffolding.
   Degrades to native scroll if JS never runs: without
   .smoother-ready the wrapper is a plain static block.
   will-change lives ONLY under .smoother-ready — otherwise it
   makes the wrapper a containing block and breaks the fixed nav.
   ============================================================ */

#smooth-wrapper { position: static; overflow: visible; }
html.smoother-ready #smooth-wrapper { position: fixed; inset: 0; overflow: hidden; }
html.smoother-ready #smooth-content { will-change: transform; }

/* ============================================================
   Film grain — one fixed tile, no JS.

   overlay rather than soft-light now that the page is mostly paper:
   fractalNoise sits at mid-grey, which is overlay's neutral, so the
   noise lightens and darkens by equal amounts whether it lands on a
   white section or a blue band. soft-light was tuned for a site that
   was navy end to end and goes flat on paper.
   ============================================================ */

body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 8200;
  pointer-events: none;
  opacity: var(--grain);
  mix-blend-mode: overlay;
  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='0.86' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

@media (pointer: coarse) { body::after { display: none; } }

/* ============================================================
   Decorative field canvas (drafting marks)
   ============================================================ */

/* Named draft-field, not field: .field is the form-input wrapper. */
.draft-field {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}
.over-field { position: relative; z-index: 1; }

/* ============================================================
   Reduced motion — a real static path, not muted tweens.
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  #smooth-wrapper { position: static !important; overflow: visible !important; }
  .media__curtain { display: none; }
}

/* ---- Screen-reader-only ---- */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
