/*
 * Base: reset, the text-style classes (one per Figma text style), the page grid,
 * and the handful of primitives every block uses (tag, button, media).
 * Block-specific CSS lives in assets/css/blocks/<block>.css, one file per block.
 */

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

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

body {
  margin: 0;
  background: var(--surface-page);
  color: var(--ink-primary);
  font-family: var(--font-body);
  font-size: var(--fs-body-sm);
  line-height: 1.45;
  letter-spacing: -0.02em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-wrap: break-word; /* a word wider than its column (a client name at a large text size) breaks, not clips */
}

img, video, svg { display: block; max-width: 100%; }
/* Safety net: nothing an editor types may create sideways scroll. `clip` (not `hidden`) keeps position: sticky working. */
html, body { overflow-x: clip; }
img, video { height: auto; }
h1, h2, h3, h4, h5, h6, p, blockquote, figure, ul, ol { margin: 0; }
ul, ol { padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
input, textarea, select { font: inherit; color: inherit; }
:focus-visible { outline: 2px solid var(--ink-accent); outline-offset: 3px; }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip-link {
  position: absolute; left: var(--gutter); top: -100px; z-index: 100;
  padding: 10px 20px; border-radius: var(--radius-pill);
  background: var(--action-primary-bg); color: var(--action-primary-ink);
}
.skip-link:focus { top: 16px; }

/* Themed grounds paint themselves, so a .theme-dark section on a light page just works. */
.theme-light, .theme-dark, .theme-subtle { background: var(--surface-page); color: var(--ink-primary); }

/* ---------- Text styles (Figma → CSS, 1:1) ---------- */
.t-display-hero, .t-display-1 { font-family: var(--font-display); font-weight: 300; line-height: 1.03; letter-spacing: -0.05em; }
.t-display-hero { font-size: var(--fs-display-hero); }
.t-display-1 { font-size: var(--fs-display-1); }
.t-display-2 { font-family: var(--font-display); font-weight: 400; font-size: var(--fs-display-2); line-height: 1.08; letter-spacing: -0.05em; }
.t-display-3 { font-family: var(--font-display); font-weight: 400; font-size: var(--fs-display-3); line-height: 1.1; letter-spacing: -0.05em; }
.t-display-4 { font-family: var(--font-display); font-weight: 400; font-size: var(--fs-display-4); line-height: 1.3; letter-spacing: -0.05em; }
.t-display-5 { font-family: var(--font-display); font-weight: 400; font-size: var(--fs-display-5); line-height: 1.15; letter-spacing: -0.04em; }
.t-display-6 { font-family: var(--font-display); font-weight: 400; font-size: var(--fs-display-6); line-height: 1.2; letter-spacing: -0.03em; }

.t-body-xl { font-family: var(--font-body); font-size: var(--fs-body-xl); line-height: 1.2; letter-spacing: -0.03em; }
.t-body-lg { font-family: var(--font-body); font-size: var(--fs-body-lg); line-height: 1.45; letter-spacing: -0.02em; }
.t-body-md { font-family: var(--font-body); font-size: var(--fs-body-md); line-height: 1.45; letter-spacing: -0.02em; }
.t-body-sm { font-family: var(--font-body); font-size: var(--fs-body-sm); line-height: 1.45; letter-spacing: -0.02em; }

.t-label-lg, .t-label-md, .t-label-sm, .t-label-xs { font-family: var(--font-ui); font-weight: 400; letter-spacing: -0.05em; }
.t-label-lg { font-size: var(--fs-label-lg); line-height: 1; }
.t-label-md { font-size: var(--fs-label-md); line-height: 1.25; }
.t-label-sm { font-size: var(--fs-label-sm); line-height: 1; }
.t-label-xs { font-size: var(--fs-label-xs); line-height: 1; }
/* V0.1 uses Ekstra Medium for footer contact lines and the invitation heading. */
.t-medium { font-weight: 500; }

.t-serif-hero, .t-serif-1, .t-serif-2, .t-serif-3 { font-family: var(--font-serif); font-weight: 400; letter-spacing: -0.01em; }
.t-serif-hero { font-size: var(--fs-serif-hero); line-height: 1.08; }
.t-serif-1 { font-size: var(--fs-serif-1); line-height: 1.2; }
.t-serif-2 { font-size: var(--fs-serif-2); line-height: 1.2; }
.t-serif-3 { font-size: var(--fs-serif-3); line-height: 1.45; }

.t-muted { color: var(--ink-muted); }
.t-subtle { color: var(--ink-subtle); }
.t-accent { color: var(--ink-accent); }

/* Writer/textarea output inside body copy */
.prose > * + * { margin-top: 1em; }
/* Links inside running text need a non-colour cue (WCAG link-in-text-block): a quiet underline that firms up on hover. */
.prose a,
:is(.t-body-xl, .t-body-lg, .t-body-md, .t-body-sm, .t-serif-3) a:not(.tag, .btn) {
  color: var(--ink-accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
  transition: text-decoration-color var(--dur-fast) var(--ease-out);
}
:where([data-input="mouse"], .no-js) .prose a:hover,
:where([data-input="mouse"], .no-js) :is(.t-body-xl, .t-body-lg, .t-body-md, .t-body-sm, .t-serif-3) a:not(.tag, .btn):hover { text-decoration-color: currentColor; }
.prose strong { font-weight: 600; }

/* ---------- Page grid ----------
   full-bleed | gutter | content (1184) | gutter
   Inside content, `text` is the recurring x≈450 column (offset 402, width 682). */
.grid {
  display: grid;
  grid-template-columns:
    [full-start] minmax(var(--gutter), 1fr)
    [content-start] var(--col-offset)
    [text-start] minmax(0, var(--col-text))
    [text-end] minmax(0, calc(var(--container) - 2 * var(--gutter) - var(--col-offset) - var(--col-text)))
    [content-end] minmax(var(--gutter), 1fr) [full-end];
}
.grid > * { grid-column: content; min-width: 0; } /* let items shrink below their longest word/pill */
.grid > .col-full { grid-column: full; }
.grid > .col-text { grid-column: text; }
.grid > .col-lead { grid-column: content-start / text-start; }
.grid > .col-text-wide { grid-column: text-start / content-end; }

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

@media (max-width: 1023px) {
  .grid > .col-lead, .grid > .col-text, .grid > .col-text-wide { grid-column: content; }
}

/* ---------- Section rhythm ---------- */
.pad-none { padding-block: 0; }
.pad-sm { padding-block: var(--space-40); }
.pad-md { padding-block: var(--space-80); }
.pad-lg { padding-block: var(--space-120); }
.pad-xl { padding-block: var(--space-160); }
/* Phones: 32px more air between content modules (Nick, 2026-09-26: "+30", rounded onto the
   4px scale). A transparent top border paints in the module's own ground and stacks on
   whatever padding it has, so two coloured bands never get a stripe between them. Not
   after the page hero, whose first screen is sized to the view (Home's strip peek). Not on
   AI Creative Systems (the studio page), where Nick's extra air reads too loose (2026-10-03). */
@media (max-width: 767px) {
  body:not(.tpl-studio) main .block + .block:not(.block--page-hero + .block) { border-top: var(--space-32) solid transparent; }
}

/* ---------- Tag (outlined pill) ---------- */
/* Pill labels are mostly lowercase, and centring Ekstra's line box leaves them reading
   low (worst in Safari). --lift raises the label optically; tags, buttons and the nav
   share it (review 2026-09-24, G5). */
:root { --lift: 0.14em; }
.tag {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 22px; padding: 0 12px var(--lift); /* min: a large text size grows the pill instead of spilling out of it (Nick's phone, 11-05) */
  border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  font-family: var(--font-ui); font-size: var(--fs-label-xs); line-height: 1; letter-spacing: -0.05em;
  white-space: nowrap;
  max-width: 100%; overflow: hidden; /* a too-long label clips at the column instead of pushing the page wide */
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.tag--lg { min-height: 28px; padding: 0 14px var(--lift); font-size: var(--fs-label-sm); }
.tag--solid { background: var(--action-primary-bg); color: var(--action-primary-ink); border-color: var(--action-primary-bg); }
:where([data-input="mouse"], .no-js) a.tag:hover { background: var(--action-primary-bg); color: var(--action-primary-ink); }
/* A linked tag's hit area is 24 tall around the 22px pill (WCAG 2.5.8; the 10-22 audit
   found news-card tags 1px above the title link). Overflow stays visible for the ::after;
   tag labels are a word or two. Phones grow it to 44 (news.css, article.css). */
a.tag { position: relative; overflow: visible; }
a.tag::after { content: ""; position: absolute; inset: -2px 0; } /* 2px: the title link below paints over 1 */
.tags { display: flex; flex-wrap: wrap; gap: 6px; }

/* ---------- Button ---------- */
.btn {
  position: relative; overflow: hidden; isolation: isolate;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 37px; max-width: 100%; padding: 8px 25px calc(8px + var(--lift));
  border-radius: var(--radius-pill); border: 1px solid var(--action-primary-bg);
  text-align: center;
  background: var(--action-primary-bg); color: var(--action-primary-ink);
  font-family: var(--font-ui); font-size: var(--fs-label-sm); line-height: 1.1; letter-spacing: -0.05em;
  transition: color var(--dur-base) var(--ease-out);
}
/* Micro-interaction: an ink fill slides up from the bottom on hover. */
.btn::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: var(--surface-page); transform: translateY(101%);
  transition: transform var(--dur-base) var(--ease-out);
}
:where([data-input="mouse"], .no-js) .btn:hover { color: var(--action-primary-bg); }
:where([data-input="mouse"], .no-js) .btn:hover::before { transform: translateY(0); }
.btn--outline { background: transparent; color: var(--ink-primary); border-color: var(--border-strong); }
.btn--outline::before { background: var(--action-primary-bg); }
:where([data-input="mouse"], .no-js) .btn--outline:hover { color: var(--action-primary-ink); }
/* Phones: every button is a 44px tap target (mobile pass, 2026-09-25). */
@media (max-width: 767px) { .btn { min-height: 44px; } }

/* ---------- Media ---------- */
.media { position: relative; overflow: hidden; background: var(--surface-subtle); }
.media > img, .media > video { width: 100%; height: 100%; object-fit: cover; }
.media--rounded { border-radius: var(--radius-lg); }
.media--rounded-md { border-radius: var(--radius-md); }

/* ---------- Motion primitives (used by many blocks) ----------
   The site has three text animations and no others (review 2026-09-24, G3):
   1. grey → black word reveal scrubbed by scroll (statement.js, text.js)
   2. headline words slide up and go grey → black (.words--fade, below)
   3. a quiet fade (.words--fade's cousin [data-reveal], below)
   All three reverse when you scroll back up past them (G2).
   [data-reveal]        fades up when it enters the viewport (assets/js/modules/reveal.js)
   .words > .w          per-word spans rendered by snippets/words.php */
[data-reveal] { opacity: 0; transform: translateY(24px); transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out); transition-delay: var(--reveal-delay, 0ms); }
[data-reveal].is-in { opacity: 1; transform: none; }
.no-js [data-reveal] { opacity: 1; transform: none; }

.words .w { display: inline-block; text-indent: 0; } /* a hanging indent on the line must not repeat inside every word */
/* A word only breaks when it alone is wider than the line, as at a large text size (Nick's phone, 11-05: "Systems" ran
   off the screen): an inline-block sizes to its whole word, so break-word never fires inside one; anywhere does. */
.words .w { max-width: 100%; overflow-wrap: anywhere; }
/* Headline: words slide up in reading order and settle from grey to full ink, the same
   grey → black the scrubbed copy below them does (review 2026-09-24, G3/H1: no blur).
   Grey is the word's own ink at 22%, so it works on light and dark pages alike.
   The padding gives descenders room: a moving word is its own layer, and WebKit clipped
   the tail of the "g" to the word's line box (G4). */
.words--fade .w {
  padding-block: 0.15em; margin-block: -0.15em;
  opacity: 0; transform: translateY(0.35em);
  color: color-mix(in srgb, currentColor 22%, transparent);
  transition: opacity 450ms var(--ease-out), transform 700ms var(--ease-out), color 900ms var(--ease-out);
  transition-delay: calc(var(--i) * 70ms), calc(var(--i) * 70ms), calc(var(--i) * 70ms + 300ms);
}
.words--fade.is-in .w { opacity: 1; transform: none; color: inherit; }
.no-js .words--fade .w { opacity: 1; transform: none; color: inherit; }
/* r before y/v/w (words.php): 0.04em looser than display tracking; the font lacks the pair. */
:is(.t-display-hero, .t-display-1, .t-display-2, .t-display-3) .k { letter-spacing: -0.01em; }

@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; transition-delay: 0ms !important; }
  [data-reveal], .words--fade .w { opacity: 1 !important; transform: none !important; filter: none !important; }
  .words--fade .w { color: inherit !important; }
}

/* ---------- Lottie (uploaded .json animations, see snippets/media.php) ---------- */
/* Offscreen: assets/js/modules/pause.js adds .is-paused; loops inside stop ticking. */
.is-paused, .is-paused *, .is-paused::before, .is-paused::after, .is-paused *::before, .is-paused *::after { animation-play-state: paused !important; }

.lottie { position: relative; width: 100%; } /* height comes from aspect-ratio (the animation's own w/h) */
.media > .lottie { height: 100%; }
.lottie > svg { position: absolute; inset: 0; }
.lottie__poster { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: opacity var(--dur-base) var(--ease-out); }
.lottie.is-ready .lottie__poster { opacity: 0; }

/* ---------- Phone primitives (the mobile experience, 2026-09-30) ----------
   The two mechanisms the phone site is built on (knowledge/specs/2026-09-30-mobile-experience.md).
   Both apply only at phone widths: the classes are inert above 767px, so a block can carry
   them in its markup at every width. */
@media (max-width: 767px) {
  /* The viewfinder: a full-bleed window that sticks to the top of the screen while the content
     after it scrolls underneath, showing what that content points at (the current client's
     clip, the open problem's picture, the image beside the copy). It paints above what follows
     it and under the header, which slides over it when it comes back. Sticky is position, not
     motion, so reduced motion changes nothing here. Its layers stack; each block decides
     which one shows. */
  .viewfinder {
    position: sticky; top: 0; z-index: 3;
    margin-inline: calc(-1 * var(--gutter));
    aspect-ratio: var(--vf-ratio, 3 / 2);
    overflow: hidden;
    background: var(--surface-subtle);
  }
  .viewfinder > * { position: absolute; inset: 0; margin: 0; }
  .viewfinder .media, .viewfinder img, .viewfinder video { width: 100%; height: 100%; object-fit: cover; border-radius: 0; }

  /* The rail: a row that swipes sideways and snaps, edge to edge, its items starting on the
     content edge. The row is its own scroller, so the page never scrolls sideways. With
     --rail-item under 100% the next item peeks in from the right, which is the affordance.
     A block's own rule for the row may outrank this one: set display there too. */
  .rail {
    display: flex; gap: var(--rail-gap, var(--space-12)); align-items: var(--rail-align, stretch);
    margin-inline: calc(-1 * var(--gutter)); padding-inline: var(--gutter);
    overflow-x: auto; overflow-y: hidden; /* never a vertical scroller: a fading-up item's 24px offset made it one, and a wheel nudged the row */
    overscroll-behavior-x: contain;
    scroll-snap-type: x var(--rail-snap, proximity); scroll-padding-inline: var(--gutter);
    scrollbar-width: none;
  }
  .rail::-webkit-scrollbar { display: none; }
  .rail > * { flex: none; scroll-snap-align: start; width: var(--rail-item, auto); min-width: 0; }
}
