/* ============================================================
   HYPERLAB — base.css
   Design tokens, reset, typography, layout, utilities
   Palette and rules per approved design system v2 (2026-07-24)
   ============================================================ */

/* ---------- Fonts — self-hosted, no third-party request ----------

   Variable woff2, taken unmodified from Google's own build. Not
   re-subset here on purpose: subsetting would make these Modified
   Versions under the OFL, and Playfair Display carries a Reserved
   Font Name. Serving Google's files as they are keeps the name valid.

   Licences sit beside the fonts — OFL-Archivo.txt, OFL-Jost.txt,
   OFL-PlayfairDisplay.txt. The OFL requires the copyright notice and
   licence to travel WITH the files, and these are publicly served.

   Two subsets per family. A visitor loads latin only; latin-ext is
   fetched just when a glyph needs it. No page currently uses one —
   checked 03.08.2026 — but the range stays so a Slovenian or Czech
   name in a credit list cannot go missing.

   Ranges are the real axis limits: Archivo and Jost carry 100–900,
   Playfair 400–900. That is why .num can ask for Jost 350 and get an
   actual 350; against the old static 300/400 pair it silently
   rendered as 300.

   woff2 only. A woff fallback would serve IE11 and Safari 11 alone,
   both long dead. */

@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/archivo-latin.woff2') format('woff2');
  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: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/archivo-latin-ext.woff2') format('woff2');
  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;
}
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/jost-latin.woff2') format('woff2');
  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: 'Jost';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/jost-latin-ext.woff2') format('woff2');
  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;
}
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('../fonts/playfair-display-latin.woff2') format('woff2');
  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: 'Playfair Display';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('../fonts/playfair-display-latin-ext.woff2') format('woff2');
  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;
}

/* ---------- Tokens ---------- */

:root {
  /* Surfaces — light comes toward you */
  --bg:            #1A140F;
  --bg-2:          #282A2F;
  --surface:       #31343A;
  --surface-hover: #383B42;

  /* Text — final site-wide typography pass (review round 4):
     confident on charcoal, hierarchy preserved, surfaces untouched */
  --text:        #F2F2F0;
  --text-bright: #FCFCFA;   /* every serif display voice & key values */
  --text-2:      #CDD1D8;   /* body prose, navigation, labels, metadata */
  --text-3:      #9CA3AE;   /* muted floor: notes, footer small print */

  /* Lines */
  --line:      rgba(255, 255, 255, 0.08);
  --line-soft: rgba(255, 255, 255, 0.05);

  /* Champagne gold — candlelight, not lighting */
  --gold:        #C9A876;
  --gold-bright: #D9C08F;
  --gold-soft:   rgba(201, 168, 118, 0.35);
  --gold-faint:  rgba(201, 168, 118, 0.14);

  /* Sampled from the HYPERLAB logo typography (not a generic gold).
     Flat gold carries the small tracked labels — the logo does the same with
     its own "CINEMATIC VISUAL PRODUCTION STUDIO" line, where the metallic
     material is reserved for the large lettering. */
  --gold-editorial:        #D4A857;
  --gold-editorial-bright: #FDD38D;   /* brightest champagne band of the logo face */

  /* True gold material extracted from the HYPERLAB logo artwork itself:
     per-row color profile sampled from the actual letterform pixels (bright
     champagne upper band → warm amber → dark base bevel, soft specular streak),
     baked to assets/img/gold-type-material.png. Not a CSS approximation. */
  --gold-metallic: url("../img/gold-type-material.png");

  /* Anisotropic gloss sweep layered over the material (screen-blended),
     the diagonal light-catch of polished metal. */
  --gold-sheen: linear-gradient(
    112deg,
    rgba(255, 251, 235, 0) 40%,
    rgba(255, 251, 235, 0.30) 50%,
    rgba(255, 251, 235, 0) 60%
  );

  /* Dimensional depth for the glossy metallic type: bright top light-catch,
     dark bottom bevel shadow, soft ambient drop shadow for lift. */
  --gold-depth-filter:
    drop-shadow(0 1px 0 rgba(255, 250, 225, 0.55))
    drop-shadow(0 -1.5px 1px rgba(0, 0, 0, 0.5))
    drop-shadow(0 2px 4px rgba(0, 0, 0, 0.35));

  /* Type */
  --font-display: "Playfair Display", "Didot", Georgia, serif;
  --font-sans:    "Archivo", "Helvetica Neue", Arial, sans-serif;
  --font-mark:    "Jost", "Futura", sans-serif;

  /* Layout */
  --container: 1440px;
  --gutter: clamp(20px, 4vw, 64px);
  --header-h: 76px;

  /* Shape — media stays sharp, UI is gently rounded */
  --r-ui: 8px;
  --r-pill: 999px;

  /* Motion — slow and few */
  --ease: cubic-bezier(0.22, 0.61, 0.21, 1);
  --dur: 650ms;
  --dur-fast: 300ms;
}

/* ---------- Reset ---------- */

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

html {
  color-scheme: dark;
  background: var(--bg);
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: clamp(15px, 0.4vw + 14px, 17px);
  font-weight: 400;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

main { flex: 1 0 auto; }

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

::selection { background: rgba(201, 168, 118, 0.3); color: var(--text); }

:focus { outline: none; }
:focus-visible {
  outline: 1px solid var(--gold);
  outline-offset: 4px;
  border-radius: 2px;
}

/* Subtle scrollbar (non-overlay platforms) */
::-webkit-scrollbar { width: 10px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.12); border-radius: 5px; }
::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.2); }

/* ---------- Typography ---------- */

/* Didone hairlines sink into dark grounds — weights sit one step
   heavier than print convention so strokes hold on charcoal. */
/* All large display headings carry the logo's metallic gold material */
.display-xl,
.display-l,
.display-m {
  background: var(--gold-sheen), var(--gold-metallic);
  background-size: 100% 100%, 100% 100%;
  background-repeat: no-repeat, no-repeat;
  background-blend-mode: screen, normal;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  filter: var(--gold-depth-filter);
}

.display-xl {
  font-family: var(--font-display);
  font-weight: 525;
  font-size: clamp(2.75rem, 6.5vw, 6.5rem);
  line-height: 1.03;
  letter-spacing: 0.004em;
  text-wrap: balance;
}

.display-l {
  font-family: var(--font-display);
  font-weight: 525;
  font-size: clamp(2.1rem, 4.2vw, 4.25rem);
  line-height: 1.08;
  text-wrap: balance;
}

.display-m {
  font-family: var(--font-display);
  font-weight: 525;
  font-size: clamp(1.55rem, 2.5vw, 2.5rem);
  line-height: 1.18;
  text-wrap: balance;
}

.serif-lead {
  font-family: var(--font-display);
  font-weight: 470;
  font-size: clamp(1.2rem, 1.7vw, 1.55rem);
  line-height: 1.5;
}

/* Tracked caps — the Saint Laurent voice */
.label {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-2);
}

.label--gold  { color: var(--gold); }
.label--muted { color: var(--text-3); }

/* Edition numerals — 01–09 */
.num {
  font-family: var(--font-mark);
  font-weight: 350;
  font-size: 12px;
  letter-spacing: 0.18em;
  color: var(--gold-editorial);
}

.prose {
  max-width: 62ch;
  color: var(--text-2);
}
.prose strong { color: var(--text); font-weight: 500; }

/* ---------- Layout ---------- */

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

.section        { padding-block: clamp(88px, 11vw, 152px); }
.section--tight { padding-block: clamp(56px, 7vw, 96px); }
.section--alt   { background: var(--bg); }

/* Section header pattern: numeral + label, then headline */
.section-head {
  display: flex;
  align-items: baseline;
  gap: 20px;
  margin-bottom: clamp(36px, 5vw, 64px);
}

/* Section labels join the numerals in gold (.num is already gold globally) */
.section-head .label { color: var(--gold-editorial); }

/* Hairline dividers — gold is the soft 35% tone */
.divider       { border: 0; height: 1px; margin: 0; width: 100%; background: var(--gold-soft); }
.divider--line { background: var(--line); }

/* Vertical light bars from the badge — ambient grid lines */
.grid-lines { position: relative; }
.grid-lines::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.035));
  background-size: 1px 100%;
  background-repeat: no-repeat;
  background-position: 25% 0;
  box-shadow: none;
}
.grid-lines::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.035)),
    linear-gradient(rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.035));
  background-size: 1px 100%, 1px 100%;
  background-repeat: no-repeat;
  background-position: 50% 0, 75% 0;
}
@media (max-width: 899px) {
  .grid-lines::before, .grid-lines::after { display: none; }
}

/* ---------- Media frames — film stays rectangular ---------- */

.frame {
  position: relative;
  background: var(--surface);
  overflow: hidden;
  box-shadow: inset 0 0 0 1px var(--line-soft);
}

.frame--169 { aspect-ratio: 16 / 9; }
.frame--239 { aspect-ratio: 2.39 / 1; }  /* letterbox */
.frame--45  { aspect-ratio: 4 / 5; }
.frame--11  { aspect-ratio: 1 / 1; }
/* ⚠️ Moved here from a page-local <style> in social-campaigns.html on
   04.08.2026, unchanged. It was never a new idea — that page had been using
   9:16 for its Shorts feed all along; it simply lived in one file.
   ⚠️ portrait still maps to frame--45. Do NOT repoint it here: 40 frames
   across 18 files use 4:5, including every impossible-sets poster and eleven
   on noah-priest. A 9:16 source belongs to a kind that asks for 9:16. */
.frame--916 { aspect-ratio: 9 / 16; }

.frame > img,
.frame > video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Placeholder state — until real footage lands */
.frame-note {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  text-align: center;
  padding: 24px;
}

/* Film grain — barely there */
.grain::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.05;
  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.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

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

/* ⚠️ VERSTECKT NUR, WENN JS WIRKLICH LÄUFT — html.js setzt ein Inline-
   Skript im Kopf jeder Seite. Bis 05.08.2026 versteckte .reveal
   bedingungslos; Inhalt hing damit am Gelingen von main.js. Regel seit dem
   Kachel-Ausfall auf social-campaigns: Inhalt darf NIE vom Abschluss eines
   Skripts abhängen. Ohne JS gibt es kein html.js, nichts wird versteckt —
   die <noscript>-Blöcke in den Seiten sind seither Redundanz, kein Träger. */
html.js .reveal {
  opacity: 0;
  transform: translateY(26px);
  transition:
    opacity var(--dur) var(--ease),
    transform var(--dur) var(--ease);
  transition-delay: var(--d, 0ms);
  will-change: opacity, transform;
}

/* ⚠️ html.js im Selektor ist Spezifität, nicht Deko: die Versteck-Regel oben
   wiegt 0-2-1, ein nacktes .reveal.in nur 0-2-0 und würde VERLIEREN. */
html.js .reveal.in {
  opacity: 1;
  transform: none;
}

/* ⚠️ WACHHUND — dieselbe Kopfzeile setzt nach 2500 ms html.reveal-fallback.
   Gleiche Spezifität wie die Versteck-Regel (0-2-1); diese Regel steht
   SPÄTER und gewinnt deshalb. Wer sie über die Versteck-Regel verschiebt,
   schaltet die Ausfallsicherung aus. Preis: nach 2,5 s offenbart sich alles
   noch nicht Enthüllte ohne Animation — Sichtbarkeit schlägt Choreografie. */
html.reveal-fallback .reveal {
  opacity: 1;
  transform: none;
}

/* Scroll cue — a falling thread of light */
.scroll-cue {
  position: relative;
  width: 1px;
  height: 56px;
  overflow: hidden;
  background: var(--line);
}
.scroll-cue::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 1px;
  height: 40%;
  background: var(--gold);
  animation: cue-fall 2.6s var(--ease) infinite;
}
@keyframes cue-fall {
  0%   { transform: translateY(-100%); }
  60%  { transform: translateY(260%); }
  100% { transform: translateY(260%); }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  /* ⚠️ html.js-Präfix wegen Spezifität: das nackte .reveal (0-1-0) verlöre
     seit dem 05.08.2026 gegen html.js .reveal (0-2-1) — Nutzer mit
     reduzierter Bewegung sähen versteckten Inhalt, bis der Observer feuert. */
  html.js .reveal { opacity: 1; transform: none; transition: none; }
  .scroll-cue::after { animation: none; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------- Utilities ---------- */

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

.text-center { text-align: center; }

/* ============================================================
   EDITORIAL LANGUAGE — WHITE-FIRST (28.07.2026, rule number one).
   Everything outside the landing page hero is WHITE. Deliberately
   no gold tier here: gold returns selectively in a joint design
   review afterwards. The landing page hero (and its header) keeps
   the approved metallic system untouched. Colour only — fonts,
   sizes, weights, spacing, layout untouched. Any future colour
   change goes through this block.
   ============================================================ */
body:not([data-page="home"]),
body:not([data-page="home"]) * {
  color: var(--text-bright) !important;
  -webkit-text-fill-color: var(--text-bright) !important;
}

/* Homepage BELOW the hero: the same white-first rule.
   Hero, header and menu overlay stay exactly as approved. */
body[data-page="home"] main section:not(.hero),
body[data-page="home"] main section:not(.hero) *,
body[data-page="home"] .site-footer,
body[data-page="home"] .site-footer * {
  color: var(--text-bright) !important;
  -webkit-text-fill-color: var(--text-bright) !important;
}

/* The metallic material is hero-only now: strip it from the few
   material classes that appear below the hero so they render as
   plain white type instead of white fill over gold shadows. */
body[data-page="home"] main section:not(.hero) .tile-title,
body[data-page="home"] main section:not(.hero) .link-serif,
body[data-page="home"] .site-footer .link-serif {
  background: none !important;
  filter: none !important;
}

/* Gold decision #1 (joint review, 28.07.2026): the Work page title is
   the gold anchor of the portfolio section — on the Work archive AND
   the homepage Selected Work section (same shared component). The
   white editorial statement beneath it remains the visual hero. */
body:not([data-page="home"]) .work-title,
body[data-page="home"] main section:not(.hero) .work-title {
  color: var(--gold-editorial) !important;
  -webkit-text-fill-color: var(--gold-editorial) !important;
}

/* Gold decision #2 (joint review, 28.07.2026): inside the Services
   hero statement, the first word "One" speaks gold — a single accent
   word; the statement itself stays the shared white component. */
body:not([data-page="home"]) .statement-accent {
  color: var(--gold-editorial) !important;
  -webkit-text-fill-color: var(--gold-editorial) !important;
}

/* Warm-white decision (28.07.2026): the Selected Work statement on the
   homepage reads slightly cold in neutral --text-bright against the warm
   world around it, so it takes a warm ivory instead. Flat colour only —
   no gradient, no material: the distance to the gold title above is what
   carries the hierarchy, and that distance has to stay obvious.
   Scoped to the homepage section on purpose. .work-statement is a shared
   component (Work archive, the seven category pages, the Services hero)
   and those keep the neutral white. The white-first block above sets the
   colour with !important, so this override needs the id to outrank it. */
body[data-page="home"] #selected-work .work-statement {
  color: #F0E7D8 !important;
  -webkit-text-fill-color: #F0E7D8 !important;
}

/* Gold decision #3 (28.07.2026): the Work title carries the logo's own
   metal now — the same material, sheen and depth as the wordmark and the
   landing hero headlines (see .work-title in components.css).
   Decision #1 above forces a flat fill with !important, which would paint
   straight over the clipped material, so the fill has to be released here.
   It is released ONLY where background-clip: text actually works: without
   that guard a browser without the clip would render the title as nothing
   at all. Where the guard fails, decision #1's solid gold stays as the
   fallback. Applies to every .work-title instance — the Work archive, all
   seven category pages and the homepage Selected Work head. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  body:not([data-page="home"]) .work-title,
  body[data-page="home"] main section:not(.hero) .work-title {
    color: transparent !important;
    -webkit-text-fill-color: transparent !important;
  }
}
