/* ==========================================================================
   site.css — everything shared by every page: base, layout, the reveal
   mechanics, SiteNav, the mobile menu, the theme toggle, the eyebrow, the
   ContactFooter and the LiquidImage component.

   Loaded second, after tokens.css and before the page's own stylesheet. It
   declares no html font-size — tokens.css owns the rem cascade.

   Every duration and easing here comes from the motion table in tokens.css.
   Do not write a raw ms value in this file.
   ========================================================================== */

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

body {
  margin: 0;
  background: var(--background);
  color: var(--foreground);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

main { position: relative; width: 100%; overflow-x: hidden; }

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

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

/* --- Lenis ---------------------------------------------------------------- */
/* Lenis' own required styles. lenis-stopped is what holds the page still
   while the preloader counts, and it keeps working if the module never
   loads, because then nothing adds the class in the first place. */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }
.lenis.lenis-smooth iframe { pointer-events: none; }

/* --- Layout --------------------------------------------------------------- */
.shell {
  width: 100%;
  max-width: 120rem;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { padding-block: var(--section); }
.section--tight { padding-block: calc(var(--section) * .55); }

.section__head { display: flex; flex-direction: column; gap: 1.5rem; }

/* Section furniture. Three page stylesheets had restated these verbatim
   because they started life in home.css; they are site chrome, so they live
   here and every page gets them from one place. */
.section__title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  letter-spacing: var(--track-heading);
  text-transform: uppercase;
  --rv-dur: var(--t-heading);
}

@media (min-width: 1024px) {
  .section__title { font-size: var(--fs-display); line-height: var(--lh-display); }
}

.section__link { margin-top: 3.5rem; }

/* The skip link, which every page's skeleton carries. It was in home.css,
   which only `/` loads, so /404.html and every page that did not copy the
   rule rendered a plain visible link above the navigation. Reported
   independently by the Work and Contact sessions; this is the move they
   asked for. It sits beside .sr-only because it is the same kind of thing:
   present for a keyboard, out of the way for everyone else. */
.skip {
  position: absolute;
  left: var(--gutter);
  top: 0;
  z-index: 80;
  transform: translateY(-120%);
  padding: .75rem 1rem;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  font-size: var(--fs-eyebrow);
  text-transform: uppercase;
  letter-spacing: var(--track-eyebrow-tight);
}
/* :focus, not :focus-visible. A skip link exists to be focused, so if it has
   focus at all it has to be on screen — otherwise a keyboard visitor tabs to
   something they cannot see. */
.skip:focus { transform: none; }


.u-display {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: var(--track-heading);
  text-transform: uppercase;
}

.u-lead { font-size: var(--fs-lead); line-height: var(--lh-lead); color: var(--muted); }
.u-muted { color: var(--muted); }

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

/* --- Eyebrow -------------------------------------------------------------- */
.eyebrow {
  display: flex;
  align-items: center;
  gap: .75rem;
  font-size: var(--fs-eyebrow);
  line-height: var(--lh-eyebrow);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--track-eyebrow);
  color: var(--muted);
}

.eyebrow__dot {
  flex: none;
  width: .5rem; height: .5rem;
  border-radius: 9999px;
  background: var(--accent);
}

/* --- Links ---------------------------------------------------------------- */
.link-accent { transition: color var(--t-hover) var(--ease-out); }
.link-accent:hover { color: var(--accent); }

.link-on {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-size: var(--fs-eyebrow);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--track-eyebrow-tight);
  color: var(--muted);
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: .4rem;
  transition: color var(--t-hover) var(--ease-out), border-color var(--t-hover) var(--ease-out);
}
.link-on:hover { color: var(--accent); border-color: var(--accent); }

/* --- The reveal mechanics ------------------------------------------------- */
/* The contract, used by every page and by the generated templates:
 *
 *   [data-reveal]            a reveal root. reveal.mjs adds .is-revealed when
 *                            it first enters the viewport, then stops watching
 *                            it, so scrolling back finds the final state.
 *                            data-reveal="manual" is left to the page's own
 *                            script (the hero sequence).
 *   .rv-line > span          a line that translates up inside a clipped parent.
 *   .rv-l > span             the same, per letter.
 *   .rv-word > span          the same, per word.
 *   .rv                      a block that fades and rises 18px.
 *   .rv-scale                a block that fades and scales in from .94.
 *   --i                      the element's index; drives the stagger.
 *   --rv-dur / --rv-stagger  per-group overrides from the motion table.
 *
 * The start states apply only under html.js, so a visitor whose scripts never
 * run reads the finished page (00-shared.md AC-25 to AC-27).
 *
 * Source whitespace between .rv-l spans renders as a word space, so letter
 * spans are written with none.
 */
.rv-line, .rv-word, .rv-l { display: inline-block; overflow: hidden; vertical-align: bottom; }
/* Words are the one unit that does not clip. They rise 18px rather than a
   whole line height, and uppercase display glyphs sit outside the content box
   at that line height, so a clipped word box cuts the tops off the letters. */
.rv-word { overflow: visible; }
.rv-line { display: block; }
.rv-line > span, .rv-word > span, .rv-l > span { display: inline-block; }
.rv-line > span { display: block; }

html.js [data-reveal] .rv-line > span,
html.js [data-reveal] .rv-l > span,
html.js [data-reveal] .rv-word > span { transform: translateY(110%); opacity: 0; }

html.js [data-reveal] .rv { opacity: 0; transform: translateY(18px); }
html.js [data-reveal] .rv-scale { opacity: 0; transform: scale(.94); }
html.js [data-reveal] .rv-scale { --rv-dur: var(--t-portrait); }

html.js [data-reveal].is-revealed .rv-line > span,
html.js [data-reveal].is-revealed .rv-l > span,
html.js [data-reveal].is-revealed .rv-word > span,
html.js [data-reveal].is-revealed .rv-scale,
html.js [data-reveal].is-revealed .rv {
  transform: none;
  opacity: 1;
  transition:
    transform var(--rv-dur, var(--t-body)) var(--rv-ease, var(--ease-expo)) var(--rv-delay, 0ms),
    opacity var(--rv-dur, var(--t-body)) var(--rv-ease, var(--ease-expo)) var(--rv-delay, 0ms);
}

html.js [data-reveal].is-revealed .rv-line > span,
html.js [data-reveal].is-revealed .rv-l > span,
html.js [data-reveal].is-revealed .rv-word > span,
html.js [data-reveal].is-revealed .rv-scale,
html.js [data-reveal].is-revealed .rv {
  --rv-delay: calc(var(--line-delay, 0ms) + var(--i, 0) * var(--rv-stagger, var(--stagger-line)));
}

/* --- SiteNav -------------------------------------------------------------- */
.nav {
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  height: var(--nav-h);
  padding-inline: var(--gutter);
  background: color-mix(in srgb, var(--background) 78%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}

.nav__mark {
  font-family: var(--font-display);
  font-size: 1.5rem;
  letter-spacing: .04em;
  line-height: 1;
  transition: color var(--t-hover) var(--ease-out);
}
.nav__mark:hover { color: var(--accent); }

.nav__list { display: flex; align-items: center; gap: 2rem; }

.nav__link {
  position: relative;
  font-size: var(--fs-eyebrow);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--track-eyebrow-tight);
  color: var(--muted);
  padding-block: .35rem;
  transition: color var(--t-hover) var(--ease-out);
}
.nav__link:hover { color: var(--foreground); }

/* The current page is marked twice over — an underline and the full-strength
   text colour — so it does not rely on colour alone (00-shared.md AC-2). */
.nav__link[aria-current="page"], .nav__mark[aria-current="page"] { color: var(--foreground); }
.nav__link[aria-current="page"]::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 2px;
  background: var(--accent);
}

.nav__tools { display: flex; align-items: center; gap: 1rem; }

/* --- Theme toggle --------------------------------------------------------- */
/* Hidden without scripts, because without them it cannot do anything. */
.theme-toggle { display: none; }
html.js .theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem; height: 2.25rem;
  border: 1px solid var(--line-strong);
  border-radius: 9999px;
  color: var(--muted);
  transition: color var(--t-hover) var(--ease-out), border-color var(--t-hover) var(--ease-out);
}
html.js .theme-toggle:hover { color: var(--accent); border-color: var(--accent); }

.theme-toggle__icon { width: 1.125rem; height: 1.125rem; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }
.theme-toggle__moon { display: none; }
/* aria-pressed is the state; the icon follows it. */
.theme-toggle[aria-pressed="true"] .theme-toggle__sun,
.theme-toggle[aria-pressed="true"] .theme-toggle__rays { display: none; }
.theme-toggle[aria-pressed="true"] .theme-toggle__moon { display: block; }

/* --- Burger and mobile menu ----------------------------------------------- */
.nav__burger { display: none; }

.nav__burger-bars, .nav__burger-bars::before, .nav__burger-bars::after {
  display: block;
  width: 1.25rem; height: 1px;
  background: currentColor;
}
.nav__burger-bars { position: relative; }
.nav__burger-bars::before, .nav__burger-bars::after { content: ""; position: absolute; inset-inline: 0; }
.nav__burger-bars::before { top: -.375rem; }
.nav__burger-bars::after { top: .375rem; }

.menu {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2rem;
  padding: var(--gutter);
  background: var(--background);
  opacity: 0;
  transition: opacity var(--t-menu) var(--ease-out);
}
.menu[hidden] { display: none; }
.menu.is-open { opacity: 1; }

.menu__close {
  position: absolute;
  top: calc(var(--nav-h) / 2);
  right: var(--gutter);
  transform: translateY(-50%);
  font-size: var(--fs-eyebrow);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--track-eyebrow-tight);
  color: var(--muted);
}
.menu__close:hover { color: var(--accent); }

.menu__list { display: flex; flex-direction: column; gap: .25rem; }

.menu__link {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(2rem, 9vw, 3.5rem);
  line-height: 1.1;
  text-transform: uppercase;
  letter-spacing: var(--track-heading);
  padding-block: .15rem;
  transition: color var(--t-hover) var(--ease-out);
}
.menu__link:hover { color: var(--accent); }

/* The link trail: each item rises as the menu fades in. */
html.js .menu .menu__list li { opacity: 0; transform: translateY(.75rem); }
html.js .menu.is-open .menu__list li {
  opacity: 1;
  transform: none;
  transition:
    opacity var(--t-menu-link) var(--ease-out) calc(var(--i, 0) * 40ms),
    transform var(--t-menu-link) var(--ease-out) calc(var(--i, 0) * 40ms);
}

@media (max-width: 768px) {
  /* With scripts, the inline list collapses behind one control. Without them
     the control would do nothing, so the list stays visible instead. */
  html.js .nav__primary { display: none; }
  html.js .nav__burger { display: inline-flex; align-items: center; justify-content: center; width: 2.25rem; height: 2.25rem; color: var(--muted); }
  .nav__list { gap: 1rem; flex-wrap: wrap; justify-content: flex-end; }
}

/* --- ContactFooter -------------------------------------------------------- */
.footer {
  border-top: 1px solid var(--line);
  padding-block: var(--section) calc(var(--section) * .35);
  background: var(--background);
}

.footer__title {
  font-family: var(--font-display);
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--track-heading);
  text-transform: uppercase;
  margin-block: 2rem 4rem;
  --rv-dur: var(--t-heading);
}

.footer__details {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 2.5rem;
  border-top: 1px solid var(--line);
  padding-top: 2.5rem;
  --rv-dur: var(--t-list);
  --rv-stagger: 110ms;
}

.detail dt {
  font-size: var(--fs-eyebrow);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--track-stat);
  color: var(--muted);
  margin-bottom: .75rem;
}
.detail dd { font-size: var(--fs-lead); line-height: var(--lh-lead); }
/* An email address is one unbreakable word, and the column it sits in is a
   fraction of the viewport. Without this it pushes past its cell and takes
   the page with it at some widths — it did, at 768px. */
.detail dd, .footer__details { min-width: 0; }
.detail dd a { overflow-wrap: anywhere; }

.footer__legal {
  margin-top: 4rem;
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--track-eyebrow-tight);
  text-transform: uppercase;
  color: var(--muted);
}

/* --- Not found ------------------------------------------------------------ */
.notfound {
  min-height: 72vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2rem;
  padding-top: calc(var(--nav-h) + 4rem);
  padding-bottom: var(--section);
}

.notfound__title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-h1);
  line-height: var(--lh-h1);
  letter-spacing: var(--track-heading);
  text-transform: uppercase;
  --rv-dur: var(--t-heading);
}
@media (min-width: 1024px) { .notfound__title { font-size: var(--fs-display); line-height: var(--lh-display); } }

.notfound__body { max-width: 36rem; font-size: var(--fs-lead); line-height: var(--lh-lead); color: var(--muted); }

/* --- LiquidImage ---------------------------------------------------------- */
/* The contract, so every page builds the same component:
 *
 *   <figure class="liquid" role="img" aria-label="…" data-liquid
 *           data-liquid-scale="28" data-filter="liquid-UNIQUE">
 *     <svg class="liquid__defs" …>  feTurbulence → feDisplacementMap, id="liquid-UNIQUE"
 *     <img class="liquid__img" style="filter:url(#liquid-UNIQUE)" width height alt="">
 *     <span class="liquid__veil">  <span class="liquid__sweep">  <span class="liquid__vignette">
 *   </figure>
 *
 *   .liquid--bare    strips every overlay (the hero portrait).
 *   .liquid--empty   placeholder mode: a branded radial card with a label and
 *                    no <img> at all, so there is no request and no broken
 *                    image marker. Mark the whole figure aria-hidden="true"
 *                    and let the heading beside it carry the meaning.
 *
 * liquid.mjs animates the filter attributes imperatively. Each instance needs
 * its own filter id: a shared filter would mean hovering one card distorts
 * every image on the page.
 */
.liquid {
  position: relative;
  overflow: hidden;
  margin: 0;
  background: var(--surface);
  isolation: isolate;
}

.liquid__defs { position: absolute; width: 0; height: 0; overflow: hidden; }

.liquid__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.liquid__veil, .liquid__sweep, .liquid__vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* Grayscale → colour. A neutral grey under mix-blend-mode:color desaturates
   whatever is beneath it, so this layer is deliberately not a theme token. */
.liquid__veil {
  background: #7f7f7f;
  mix-blend-mode: color;
  opacity: .88;
  transition: opacity var(--t-veil) var(--ease-out);
}

.liquid__sweep {
  opacity: 0;
  background: linear-gradient(120deg, transparent 30%, color-mix(in srgb, var(--accent) 35%, transparent) 50%, transparent 70%);
  transition: opacity var(--t-veil) var(--ease-out);
}

.liquid__vignette {
  background: radial-gradient(120% 120% at 50% 120%, color-mix(in srgb, var(--shade) 70%, transparent), transparent 60%);
}

.liquid--empty {
  background: radial-gradient(130% 130% at 30% 15%, var(--surface-2), var(--background));
  border: 1px solid var(--line);
}

.liquid__label {
  position: absolute;
  inset-inline: 1.5rem;
  bottom: 1.5rem;
  font-size: var(--fs-eyebrow);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--track-eyebrow-tight);
  color: var(--muted);
}

.liquid--bare { background: none; }
.liquid--bare .liquid__veil,
.liquid--bare .liquid__sweep,
.liquid--bare .liquid__vignette { display: none; }

/* An image that fails to load takes its own slot away rather than leaving a
   frame or a broken-image marker. liquid.mjs sets this. */
.liquid[data-state="missing"] { display: none; }

/* Hover is the only thing hover does: it is disabled at 768px and below and
   on touch input, so nothing may be readable only through it
   (00-shared.md AC-18). */
@media (min-width: 769px) and (hover: hover) and (pointer: fine) {
  .liquid:hover .liquid__veil { opacity: 0; }
  .liquid:hover .liquid__sweep { opacity: 1; }
}

/* --- Reduced motion ------------------------------------------------------- */
/* Everything arrives in its final state, nothing animates, nothing is
   withheld, and the preloader is skipped entirely (00-shared.md AC-21). */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
  }

  html.js [data-reveal] .rv,
  html.js [data-reveal] .rv-scale,
  html.js [data-reveal] .rv-line > span,
  html.js [data-reveal] .rv-l > span,
  html.js [data-reveal] .rv-word > span {
    opacity: 1 !important;
    transform: none !important;
  }

  html.js .menu .menu__list li { opacity: 1; transform: none; }
}
