/* ===========================================================================
   base.css — reset, design tokens, typography, utilities.
   ---------------------------------------------------------------------------
   Everything visual on the site resolves to a token declared here. If you want
   to change the look wholesale, you change this file and nothing else.
   =========================================================================== */

@font-face {
  font-family: 'JetBrains Mono';
  src: url('../fonts/jetbrainsmono-500-latin.woff2') format('woff2');
  font-weight: 500;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'JetBrains Mono';
  src: url('../fonts/jetbrainsmono-500-latin-ext.woff2') format('woff2');
  font-weight: 500;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Inter, variable on the weight axis 100–900. The display face runs at 900
   (Black). Note Inter has no width axis, so `font-stretch` does nothing here
   — the width tuning the previous face carried has been removed, not left dead. */
@font-face {
  font-family: 'Inter';
  src: url('../fonts/inter-latin.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  src: url('../fonts/inter-latin-ext.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Metric-matched stand-in, so the swap from fallback to Inter doesn't shove
   the layout around. Tuned so a line of Arial occupies close to the same box. */
@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial'), local('Helvetica Neue'), local('Liberation Sans');
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

/* ---------------------------------------------------------------- tokens -- */

:root {
  /* -- colour ------------------------------------------------------------
     A near-black ground so footage is the only bright thing on the page.
     `signal` is the single accent and is used sparingly — on a page of
     moving images, one acid colour reads as deliberate; three read as noise. */
  --ink:        #08090B;   /* page ground */
  --ink-2:      #0D0F12;   /* raised surface */
  --ink-3:      #111317;   /* card / frame base */
  --ink-4:      #191C21;   /* hairlines, wells */

  --bone:       #EDEBE6;   /* primary text — warm white, never pure #fff */
  --bone-dim:   #A5A29B;   /* secondary text */
  --bone-faint: #6B6862;   /* tertiary / metadata */

  --signal:     #46B8B0;   /* the accent. Used once per viewport, at most.
                              A muted teal taken from the cold end of the hero
                              gradient — bright enough to find, quiet enough
                              not to fight a deliberately dusty palette. */
  --signal-ink: #05201E;   /* text on top of signal */

  --line:       rgba(237, 235, 230, 0.14);
  --line-soft:  rgba(237, 235, 230, 0.07);

  /* -- type --------------------------------------------------------------
     Two families only. A variable grotesk that can go from fine metadata to
     enormous condensed headline, and a mono for labels, counters and facts.
     The mono is what makes the page read as a technical document about film
     rather than as a template. */
  /* The hero fill, flattened to a single line so it can be reused on type.
     The hero itself needs overlapping radial blooms to avoid banding across a
     whole viewport; over one line of text a linear ramp is enough, and it
     keeps the two readable as the same palette. */
  --chroma: linear-gradient(100deg,
    #1A6C5E 0%, #2A5A70 26%, #30446C 52%, #4E1E68 78%, #681A74 100%);

  --font-display: 'Inter', 'Inter Fallback', 'Helvetica Neue', Arial, sans-serif;
  --font-body:    'Inter', 'Inter Fallback', 'Helvetica Neue', Arial, sans-serif;
  --font-mono:    'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;

  /* Fluid scale. Every step is clamp(min, preferred, max) so nothing ever
     overflows on a 320px phone or turns absurd on a 32" display. */
  --t-mega:  clamp(3.25rem, 13vw, 13rem);      /* hero words */
  --t-h1:    clamp(2.5rem, 8vw, 7rem);         /* section headlines */
  --t-h2:    clamp(1.875rem, 4.5vw, 3.75rem);
  --t-h3:    clamp(1.375rem, 2.4vw, 2rem);
  --t-lead:  clamp(1.0625rem, 1.5vw, 1.375rem);/* intro paragraphs */
  --t-body:  clamp(0.9375rem, 1.05vw, 1.0625rem);
  --t-small: clamp(0.8125rem, 0.9vw, 0.875rem);
  --t-label: clamp(0.6875rem, 0.78vw, 0.75rem);/* mono eyebrows */

  --lh-tight: 0.88;
  --lh-snug:  1.05;
  --lh-body:  1.55;

  --tr-mega: -0.04em;   /* big type needs negative tracking to look drawn */
  --tr-head: -0.025em;
  --tr-body: -0.005em;
  --tr-label: 0.16em;   /* mono labels need air */

  /* -- space --------------------------------------------------------------
     One scale, used everywhere. Section padding is fluid. */
  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-5: 1.5rem;
  --s-6: 2rem;
  --s-7: 3rem;
  --s-8: 4rem;
  --s-9: 6rem;
  --s-10: 8rem;

  --gutter:    clamp(1.25rem, 4vw, 3.5rem);  /* page side padding */
  --section-y: clamp(3.75rem, 9vh, 8rem);    /* vertical section rhythm */
  --maxw:      104rem;

  /* -- motion -------------------------------------------------------------
     `--ease-hk` is the signature curve: a hard, confident out-ease with a
     long tail. Used for anything that reveals. The expo pair is for things
     that travel a long distance. */
  --ease-hk:     cubic-bezier(0.62, 0.05, 0.01, 0.99);
  --ease-out-expo:  cubic-bezier(0.16, 1, 0.30, 1);
  --ease-in-expo:   cubic-bezier(0.70, 0, 0.84, 0);
  --ease-out-quart: cubic-bezier(0.25, 1, 0.50, 1);
  --ease-swift:     cubic-bezier(0.22, 1, 0.36, 1);

  --d-fast: 0.24s;
  --d-mid:  0.5s;
  --d-slow: 0.9s;

  /* -- radii & layers ---------------------------------------------------- */
  --r-sm: 3px;
  --r-md: 6px;
  --r-lg: 12px;

  --z-base:    0;
  --z-raised:  10;
  --z-sticky:  100;
  --z-nav:     200;
  --z-cursor:  9000;
  --z-modal:   9500;
  --z-grain:   9800;  /* grain sits above everything except the cursor probe */

  /* Set from JS so 100vh behaves on iOS where the address bar resizes. */
  --vh: 1vh;
}

/* Breakpoints used throughout (documented here, since @media can't read vars):
     sm   40rem  / 640px
     md   48rem  / 768px   ← below this, pinning and the custom cursor are off
     lg   64rem  / 1024px
     xl   90rem  / 1440px                                                     */

/* ----------------------------------------------------------------- reset -- */

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

html {
  -webkit-text-size-adjust: 100%;
  -moz-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: auto; /* Lenis owns this; native smooth would fight it */
}

body,
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd, ol, ul {
  margin: 0;
  padding: 0;
}

ul[role='list'], ol[role='list'] { list-style: none; }

body {
  min-height: 100svh;
  background: var(--ink);
  color: var(--bone);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

img, picture, video, canvas, svg, iframe {
  display: block;
  max-width: 100%;
}

/* The `hidden` attribute must actually hide. The UA rule for it is only
   `[hidden] { display: none }`, which loses to ANY author `display` — including
   the two rules directly above and every `display: flex` component in
   site.css. Without this, `el.hidden = true` silently does nothing and a
   closed modal keeps sitting over the page swallowing clicks. */
[hidden] {
  display: none !important;
}

img, video { height: auto; }

input, button, textarea, select { font: inherit; color: inherit; }

button {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: inherit;
}

a { color: inherit; text-decoration: none; }

p, li, h1, h2, h3, h4 { overflow-wrap: break-word; }

svg { fill: none; }

/* Focus: invisible for mouse users, unmissable for keyboard users. */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

::selection {
  background: var(--signal);
  color: var(--signal-ink);
}

/* ------------------------------------------------------------ typography -- */

.display {
  font-family: var(--font-display);
  font-weight: 900;     /* Inter Black */
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-mega);
  text-transform: uppercase;

  /* Halation. Film stock bleeds a soft warm-red glow around bright highlights
     — it is the reason 35mm looks like 35mm and digital doesn't, and almost
     nothing on the web reproduces it. Two shadows: a tight warm one for the
     bleed, a wider cold one so it reads as light in a room rather than as a
     drop shadow. Both are far too faint to notice individually, which is the
     point; you should only notice that the type looks lit. */
  text-shadow:
    0 0 0.28em rgba(255, 138, 96, 0.10),
    0 0 1.1em rgba(120, 90, 255, 0.07);
}

.h1 { font-size: var(--t-h1); }
.h2 { font-size: var(--t-h2); }
.h3 {
  font-size: var(--t-h3);
  font-family: var(--font-display);
  font-weight: 700;
  line-height: var(--lh-snug);
  letter-spacing: var(--tr-head);
}

.lead {
  font-size: var(--t-lead);
  line-height: 1.45;
  color: var(--bone-dim);
  max-width: 42ch;
}

/* The mono label. Does an enormous amount of work on this page: it is the
   thing that makes a grid of films read as a considered index. */
.label {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--bone-faint);
  line-height: 1;
}

.label--signal { color: var(--signal); }

/* ------------------------------------------------------------- utilities -- */

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

.section {
  position: relative;
  padding-block: var(--section-y);
}

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

.skip-link {
  position: absolute;
  top: var(--s-4);
  left: var(--s-4);
  z-index: calc(var(--z-modal) + 1);
  padding: var(--s-3) var(--s-5);
  background: var(--signal);
  color: var(--signal-ink);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  border-radius: var(--r-sm);
  transform: translateY(-160%);
  transition: transform var(--d-fast) var(--ease-swift);
}
.skip-link:focus-visible { transform: translateY(0); }

/* Aspect ratio boxes for the mixed 16:9 / 9:16 / 4:5 grid. */
[data-ratio='16x9'] { aspect-ratio: 16 / 9; }
[data-ratio='9x16'] { aspect-ratio: 9 / 16; }
[data-ratio='4x5']  { aspect-ratio: 4 / 5; }
[data-ratio='1x1']  { aspect-ratio: 1 / 1; }

/* ------------------------------------------------- resilience: no-js/js --
   Animated elements must NEVER be left invisible if JavaScript fails, is
   blocked, or the CDN is unreachable. So the rule is inverted: things are
   visible by default, and only hidden once <html> has the `js` class, which
   an inline script in <head> adds before first paint.

   If GSAP never arrives, the page is a clean, static, entirely readable
   portfolio. That is the floor, and it is an acceptable floor.

   The reveals themselves are plain CSS transitions driven by an
   IntersectionObserver — no animation library involved. That is deliberate:
   it costs zero kilobytes, it runs off the main thread, and it means a phone
   never downloads a scroll engine just to fade some text in. GSAP is loaded
   only for the genuinely hard work — scrubbed and pinned sequences — and only
   on desktop.

   Note `visibility`, not just `opacity`. An element at `opacity: 0` is still
   in the tab order, so a keyboard user would otherwise tab into invisible
   content and lose their focus ring entirely. */

html.js [data-anim] {
  opacity: 0;
  visibility: hidden;
  transition:
    opacity var(--d-slow) var(--ease-hk),
    transform var(--d-slow) var(--ease-hk),
    visibility 0s linear;
  transition-delay: var(--anim-delay, 0ms);
}

html.js [data-anim='rise']  { transform: translate3d(0, 2.75rem, 0); }
html.js [data-anim='fade']  { transform: none; }
html.js [data-anim='scale'] { transform: scale(1.07); }
html.js [data-anim='left']  { transform: translate3d(-3rem, 0, 0); }
html.js [data-anim='right'] { transform: translate3d(3rem, 0, 0); }

/* A masked wipe: the line rises out of a window rather than fading, because
   a fade on a big headline reads as cheap.

   The clip is on the PARENT and the movement is on an inner span — never on
   the element itself. That is not a style preference, it is a correctness
   requirement: IntersectionObserver honours clipping when it computes what
   is visible, so an element that clips itself to nothing can never be seen
   by the observer that is supposed to reveal it. It would sit invisible
   forever, waiting for an event that cannot fire. */

html.js [data-anim='wipe'] {
  opacity: 1;
  visibility: visible;
  overflow: hidden;
  /* Room for descenders, which `overflow: hidden` would otherwise shave. */
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
}

html.js [data-anim='wipe'] .wipe__inner {
  display: block;
  transform: translate3d(0, 108%, 0);
  transition: transform var(--d-slow) var(--ease-hk);
  transition-delay: var(--anim-delay, 0ms);
}

html.js [data-anim='wipe'].is-revealed .wipe__inner { transform: none; }

html.js [data-anim].is-revealed {
  opacity: 1;
  visibility: visible;
  transform: none;
}

html.no-gsap [data-anim='wipe'] .wipe__inner,
html [data-anim='wipe']:not([data-anim-bound]) .wipe__inner { transform: none; }

/* Belt and braces: if GSAP fails to load, main.js sets this class and
   everything fades in with plain CSS instead of staying at opacity 0. */
html.no-gsap [data-anim] {
  opacity: 1 !important;
  transform: none !important;
  transition: opacity 0.6s var(--ease-hk);
}

/* ------------------------------------------------------- reduced motion --
   Not a gutting. Reveals still happen, transforms still resolve — they just
   arrive quickly and without travel. Parallax, skew, marquees and the custom
   cursor switch off entirely, because those are the ones that actually cause
   discomfort. */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  html.js [data-anim] {
    opacity: 1 !important;
    visibility: visible !important;
    transform: none !important;
  }

  /* The wipe moves an inner span, not the element carrying [data-anim], so
     the reset above would miss it entirely and leave every section heading
     parked below its own mask — invisible, permanently. */
  html.js [data-anim='wipe'] .wipe__inner {
    transform: none !important;
  }
}
