/* ═══════════════════════════════════════════════════════════════════════════
   MARBLELENS · BASE
   Reset, typography, layout primitives, the material language (paper / grain /
   torn edges / mounted depth), shared furniture (header, progress rail,
   buttons, links) and the reveal + reduced-motion systems.
   ═══════════════════════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The torn section covers deliberately overhang both edges by 5% so the rip
     never shows a straight cut at the sides — which makes the DOCUMENT wider
     than the viewport. Clip it here.
     `clip`, not `hidden`: hidden makes this a scroll container and would break
     every position:sticky descendant (the canvas stage, the project board). */
  overflow-x: clip;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--ink-2);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: clip;   /* see html — hidden here would kill sticky too */
}

img, video, canvas, svg { display: block; max-width: 100%; }
img, video { height: auto; }
button { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}
::selection { background: var(--accent-wash); color: var(--ink); }

/* ── PAPER GRAIN ────────────────────────────────────────────────────────────
   A single fixed fibre layer over the whole page ties every sheet to the same
   stock. Kept faint — the site is premium because of restraint (§9). */
body::before {
  content: "";
  position: fixed; inset: 0; z-index: 1; pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23f)' opacity='0.045'/%3E%3C/svg%3E");
}

/* ── TYPOGRAPHY ────────────────────────────────────────────────────────────*/
h1, h2, h3 { margin: 0; font-family: var(--serif); font-weight: 600;
  line-height: var(--lh-head); letter-spacing: var(--ls-head); color: var(--ink);
  text-wrap: balance; }
p { margin: 0; }

.display { font-size: var(--t-hero); line-height: var(--lh-tight); font-weight: 700; }
.h1 { font-size: var(--t-h1); }
.h2 { font-size: var(--t-h2); }
.h3 { font-size: var(--t-h3); }

/* mono kicker — the small technical label above every chapter head. Carries
   the section number and accent; the art-directed "authored" register. */
.kicker {
  display: inline-flex; align-items: center; gap: 0.6em;
  font-family: var(--mono); font-weight: 500;
  font-size: var(--t-label); letter-spacing: var(--ls-label);
  text-transform: uppercase; color: var(--accent-ink);
}
.kicker::before {
  content: ""; width: 1.6em; height: 1px; background: var(--accent); opacity: 0.8;
}
.kicker .idx { color: var(--graphite); }

.lede { font-size: var(--t-lede); line-height: 1.5; color: var(--ink-2);
  max-width: 40ch; text-wrap: pretty; }
.muted { color: var(--graphite); }
.small { font-size: var(--t-small); }
.tag {
  font-family: var(--mono); font-size: var(--t-label);
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--graphite);
}

/* drawn rule — a graphite hairline that "writes" in on reveal */
.rule { display: block; height: 1px; width: clamp(48px, 6vw, 88px);
  background: var(--ink); opacity: 0.5; transform-origin: left center; }
.rule.accent { background: var(--accent); opacity: 0.9; }

/* ── LAYOUT ────────────────────────────────────────────────────────────────*/
.container { width: 100%; max-width: var(--container);
  margin-inline: auto; padding-inline: var(--gutter); }
.container.wide { max-width: var(--container-wide); }
.section { position: relative; padding-block: var(--sec-pad-y); }
.section-head { max-width: 62ch; margin-bottom: var(--sec-gap); }
.section-head .kicker { margin-bottom: var(--s-3); }
.section-head .rule { margin-top: var(--s-4); }
.section-head .lede { margin-top: var(--s-4); }

/* editorial grid helper */
.grid { display: grid; gap: var(--col-gap); }
.cols-2 { grid-template-columns: repeat(2, 1fr); }
.cols-3 { grid-template-columns: repeat(3, 1fr); }
.cols-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 900px) { .cols-3, .cols-4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .cols-2, .cols-3, .cols-4 { grid-template-columns: 1fr; } }

/* ── MATERIAL · a mounted sheet ────────────────────────────────────────────
   The base object of the world: real paper stock, a lifted edge, a true-looking
   shadow. Everything premium on the page is a variation of this. */
.sheet {
  position: relative; background: var(--paper-hi);
  border: 1px solid var(--line);
  box-shadow: var(--sh-mount);
  border-radius: var(--radius-sm);
}
.sheet.raised { box-shadow: var(--sh-lift); }
/* faint printed corner ticks — the registration marks of the studio */
.sheet[data-ticks]::before, .sheet[data-ticks]::after {
  content: ""; position: absolute; width: 10px; height: 10px; opacity: 0.4;
  border: 1px solid var(--graphite); pointer-events: none;
}
.sheet[data-ticks]::before { top: 8px; left: 8px; border-right: 0; border-bottom: 0; }
.sheet[data-ticks]::after  { bottom: 8px; right: 8px; border-left: 0; border-top: 0; }

/* torn top edge — a fresh sheet covering the chapter above it. The mask uses a
   per-section SVG turbulence filter (in index.html) so no two rips match. */
.torn-top {
  position: absolute; left: 0; right: 0; top: -1px; height: 26px;
  background: inherit; z-index: 2;
  -webkit-mask: linear-gradient(#000 0 0); mask: linear-gradient(#000 0 0);
}

/* ── PLATE WINDOW · a paper window a picture is mounted behind ─────────────
   Media (webp/mp4) loads lazily behind the window. Until it arrives, a print
   develops in the tray — never a broken image (§40, ships before footage). */
.plate {
  position: relative; overflow: hidden; background: var(--paper-deep);
  border-radius: var(--radius-sm);
  box-shadow: var(--sh-press);
}
.plate::after {  /* the developing tray — animated until media loads */
  content: ""; position: absolute; inset: 0; z-index: 0;
  background:
    radial-gradient(120% 90% at 30% 0%, rgba(255,255,255,0.25), transparent 60%),
    repeating-linear-gradient(115deg, var(--paper-deep) 0 10px, var(--paper-lo) 10px 20px);
  animation: develop 2.4s var(--ease-out) infinite alternate;
}
@keyframes develop { from { opacity: 0.5; } to { opacity: 0.9; } }
.plate.loaded::after { display: none; }
.plate img, .plate video {
  position: relative; z-index: 1; width: 100%; height: 100%;
  object-fit: cover; opacity: 0; transition: opacity 0.7s var(--ease-out);
}
.plate.loaded img, .plate.loaded video { opacity: 1; }
.plate-cap {
  position: absolute; z-index: 2; left: 10px; bottom: 8px;
  display: flex; align-items: baseline; gap: 0.5em;
  font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--paper-hi);
  text-shadow: 0 1px 3px rgba(0,0,0,0.6);
}
.plate-cap b { color: var(--accent); font-weight: 600; }

/* ── BUTTONS ───────────────────────────────────────────────────────────────*/
.btn {
  --_bg: var(--ink); --_fg: var(--paper-hi);
  display: inline-flex; align-items: center; gap: 0.7em;
  padding: 0.85em 1.4em; border: 0; cursor: pointer;
  font-family: var(--sans); font-weight: 600; font-size: var(--t-small);
  letter-spacing: 0.02em; color: var(--_fg); background: var(--_bg);
  border-radius: var(--radius-sm); box-shadow: var(--sh-pin);
  transition: transform var(--dur-fast) var(--ease-settle),
              box-shadow var(--dur-fast) var(--ease-out), background var(--dur-fast);
}
.btn:hover { transform: translateY(-1px); box-shadow: var(--sh-lift); }
.btn:active { transform: translateY(0) scale(0.985); }
.btn .arr { transition: transform var(--dur-fast) var(--ease-out); }
.btn:hover .arr { transform: translateX(3px); }
.btn.accent { --_bg: var(--accent); --_fg: #fff; }
.btn.ghost {
  --_bg: transparent; --_fg: var(--ink);
  border: 1px solid var(--line); box-shadow: none;
}
.btn.ghost:hover { border-color: var(--accent); box-shadow: var(--sh-mount); }

/* a link with a drawn underline that writes on hover */
.link-u { position: relative; color: var(--ink); font-weight: 500; }
.link-u::after { content: ""; position: absolute; left: 0; right: 0; bottom: -2px;
  height: 1.5px; background: var(--accent); transform: scaleX(0);
  transform-origin: left center; transition: transform var(--dur-fast) var(--ease-out); }
.link-u:hover::after, .link-u:focus-visible::after { transform: scaleX(1); }

/* ── HEADER · restrained, controlled, minimal (§1, §44) ────────────────────*/
.site-header {
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  display: flex; align-items: center; justify-content: space-between;
  padding: clamp(0.9rem, 2vw, 1.4rem) var(--gutter);
  transition: background var(--dur) var(--ease-out), box-shadow var(--dur),
              padding var(--dur);
  /* THE HEADER IS PART OF WHAT THE OPENING ASSEMBLES.
     The approved take paints its own MARBLELENS / AI CONTENT / CREATE / LOG IN
     / SIGN UP bar at t 0.6s and then fades it back out over the last second
     (5.4% ink down to 0.98%). Leaving the real header up through all of that
     would put two navigations on screen for five of the take's six seconds,
     which is the single worst duplicate on the page. It rises instead as the
     generated one finishes leaving — js/opening.js ramps --ui-in only after
     that — so at no point are both visible. The `1` fallback is deliberate:
     if the opening never runs, the header is simply a header. */
  opacity: var(--ui-in, 1);
}
.site-header.docked {
  background: color-mix(in srgb, var(--paper-hi) 86%, transparent);
  backdrop-filter: blur(9px) saturate(1.1);
  box-shadow: 0 1px 0 var(--line), 0 12px 30px -24px rgba(28,20,10,0.5);
  padding-block: clamp(0.7rem, 1.4vw, 1rem);
}
/* ── THE HEADER SITS OVER THE OPENING'S ARTWORK ───────────────────────────
   The take's final composition throws ochre, black splatter and the runner's
   trail through the header band, and the docked backdrop is only 86% opaque —
   so whatever is behind it still shows through at 14%, blurred.

   MEASURED ON THE LIVE PAGE at 1920x1080, u=1.0, glyph core against its own
   local plate: --graphite reads 4.49:1, which is under AA for small text.
   --ink-2 reads 10.56:1. That is the entire fix; nothing else was needed.

   NO HALO, AND THE REASON MATTERS. An earlier pass added a paper text-shadow
   here, because before the opening was scroll-scrubbed it reached its final
   frame at scrollY 0 — the header was UNDOCKED and fully transparent over wet
   pigment, where the background spans luminance 0.106 to 0.832 inside one
   140px label and no ink of any darkness can pass (pure black manages 2.46:1).
   The pin removed that case: app.js docks the bar above scrollY 40, i.e. u
   0.014, and the canvas is still blank ivory that early. The bar therefore has
   its own backdrop for the whole of the scrub, and a halo would only paint
   paper over artwork to solve a problem that no longer exists.

   Hierarchy survives the darkening because it is carried by size, weight,
   family and letter-spacing — 0.66rem mono at 0.16em against a 1.35rem serif
   wordmark — and SIGN UP stays at --ink, so LOG IN is still the quieter of the
   pair. Scoped to .site-header so the menu sheet and the footer keep theirs. */
/* THE CONTINUATION TAKE PAINTS ITS OWN NAVIGATION for its whole span, so the
   real one stands down while that beat owns the ground — otherwise there are
   two MARBLELENS bars on screen at once. The generated pixels are locked; the
   DOM is what yields. It returns the moment the beat releases, and `inert`
   goes with it so the invisible bar cannot take tab focus either. */
.hdr-yield .site-header { opacity: 0; pointer-events: none; }

.site-header .acct-link { color: var(--ink-2); }
/* and SIGN UP has to be restated at the same specificity, or the rule above
   quietly flattens the pair: .acct-primary is one class in auth.css and would
   lose to this two-class selector, leaving LOG IN and SIGN UP identical. */
.site-header .acct-primary { color: var(--ink); }
.wordmark {
  font-family: var(--serif); font-weight: 700;
  font-size: clamp(1.05rem, 1.5vw, 1.35rem); letter-spacing: 0.14em;
  color: var(--ink);
}
.site-nav { position: relative; display: flex; align-items: center; gap: clamp(0.7rem, 1.6vw, 1.4rem); }

/* ── THE MIDDLE SLOT · where you are ──────────────────────────────────────
   Opens on AI CONTENT — the hero capability, and the middle term of the brand
   line — then becomes the live chapter name. One item that earns its place,
   instead of five that compete with the wordmark. */
/* scoped to .site-nav throughout: a bare .nav-chapter loses to `.site-nav a`
   on specificity, and that rule's `padding: 0.3em 0` collapses the side
   padding the dashes are painted into — so they land across the label. */
.site-nav .nav-chapter {
  position: relative;
  font-family: var(--mono);
  font-size: var(--t-label); letter-spacing: 0.2em; text-transform: uppercase;
  /* --ink-2, not --graphite: measured 4.49:1 over the opening's final frame
     through the docked backdrop, which is the wrong side of AA for a 0.66rem
     label. The dashes below stay --graphite-2, so the slot keeps reading as the
     quiet chapter locator — the restraint is in the decoration, not the ink. */
  color: var(--ink-2); font-weight: 500;
  padding: 0.3em clamp(1rem, 1.8vw, 1.5rem);
  /* The em-dashes of MARBLELENS — AI CONTENT — CREATE, painted into the
     padding as backgrounds rather than as pseudo-elements: `.site-nav a::after`
     already owns ::after with left:0;right:0 for the hover underline, and any
     absolutely-positioned override there ends up over-constrained and lays the
     rule straight through the label. A background cannot collide. */
  background-image:
    linear-gradient(var(--graphite-2), var(--graphite-2)),
    linear-gradient(var(--graphite-2), var(--graphite-2));
  background-size: clamp(6px, 1vw, 12px) 1px;
  background-position: left center, right center;
  background-repeat: no-repeat;
}
.site-nav .nav-chapter:hover { color: var(--ink); }
/* the label swaps on a held frame — it is a chapter card being changed, not a
   crossfade */
.site-nav .nav-chapter .lbl { display: inline-block; }
.site-nav .nav-chapter.swap .lbl { animation: chapSwap 0.3s steps(3, end); }
@keyframes chapSwap {
  0%   { opacity: 1; transform: none; }
  40%  { opacity: 0; transform: translateY(-4px); }
  41%  { opacity: 0; transform: translateY(4px); }
  100% { opacity: 1; transform: none; }
}
@media (max-width: 560px) {
  /* on a phone the wordmark plus CREATE plus the trigger is already the whole
     brand line — the chapter slot would wrap it */
  .site-nav .nav-chapter { display: none; }
}
.site-nav a {
  font-family: var(--sans); font-weight: 600; font-size: var(--t-label);
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink);
  position: relative; padding: 0.3em 0;
}
.site-nav a::after { content: ""; position: absolute; left: 0; right: 0; bottom: -3px;
  height: 1px; background: var(--accent); transform: scaleX(0);
  transform-origin: left; transition: transform var(--dur-fast) var(--ease-out); }
.site-nav a:hover::after, .site-nav a.active::after { transform: scaleX(1); }
.site-nav .nav-chapter::after { display: none; }
.site-nav .nav-links { display: flex; gap: clamp(1rem, 2vw, 1.9rem); }
/* scoped to .site-nav: a bare .nav-create loses to `.site-nav a` on
   specificity and the label renders ink-on-ink, i.e. invisible */
.site-nav .nav-create {
  padding: 0.55em 1.05em; background: var(--ink); color: var(--paper-hi);
  border-radius: var(--radius-sm); letter-spacing: 0.14em;
  /* the primary create control on the page: it must read as pressable, and its
     activation should arrive rather than snap. Everything else already does. */
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              letter-spacing var(--dur-fast) var(--ease-out);
}
.site-nav .nav-create::after { display: none; }
.site-nav .nav-create:hover { background: var(--accent); color: #fff; letter-spacing: 0.17em; }
/* ── MOBILE MENU ───────────────────────────────────────────────────────────
   Restrained on purpose (§44): two graphite rules that become a cross, and a
   single paper sheet that drops with the links on it. No full-screen takeover,
   no animated hamburger theatre. */
.nav-toggle {
  display: block; position: relative;
  width: 40px; height: 34px; padding: 0; cursor: pointer;
  background: transparent; border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.nav-toggle span {
  position: absolute; left: 11px; right: 11px; height: 1.5px;
  background: var(--ink); border-radius: 1px;
  transition: transform var(--dur-fast) var(--ease-out), opacity var(--dur-fast);
}
.nav-toggle span:nth-child(1) { top: 13px; }
.nav-toggle span:nth-child(2) { top: 19px; }
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(3px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { transform: translateY(-3px) rotate(-45deg); }

/* ── THE FULL MAP · behind one trigger, at every width ────────────────────
   A sheet of paper dropped under the header, not a mega-menu. Closed it is
   removed from the tab order, not merely invisible. */
.site-nav .nav-links {
  position: absolute; top: calc(100% + 0.7rem); right: 0;
  min-width: 12.5rem; display: grid; gap: 0.15rem;
  padding: var(--s-3);
  background: var(--paper-hi); border: 1px solid var(--line);
  border-radius: var(--radius-sm); box-shadow: var(--sh-lift);
  opacity: 0; visibility: hidden; transform: translateY(-8px);
  transition: opacity var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-settle),
              visibility 0s linear var(--dur-fast);
}
.site-nav .nav-links.open {
  opacity: 1; visibility: visible; transform: none; transition-delay: 0s;
}
.site-nav .nav-links a {
  /* 44px minimum: at label size, padding alone gave a 30px row — fine for a
     mouse, under the guideline for a thumb, and this menu is the only route to
     the account actions on a phone */
  display: flex; align-items: center;
  min-height: 44px; padding: 0 0.6em; border-radius: 4px;
  letter-spacing: 0.12em; color: var(--ink);
}
.site-nav .nav-links a:hover { background: var(--paper-deep); }
.site-nav .nav-links a.active { color: var(--accent-ink); }
.site-nav .nav-links a::after { display: none; }
@media (prefers-reduced-motion: reduce) {
  .nav-toggle span, .site-nav .nav-links { transition: none; }
}

/* ── PROGRESS RAIL · subtle chapter locator, appears after opening (§44) ────*/
.rail {
  position: fixed; right: clamp(0.7rem, 1.4vw, 1.5rem); top: 50%;
  transform: translateY(-50%); z-index: 55;
  display: flex; flex-direction: column; gap: 0.9rem; align-items: flex-end;
  opacity: 0; visibility: hidden; transition: opacity var(--dur) var(--ease-out);
}
.rail.on { opacity: 1; visibility: visible; }
.rail a { display: flex; align-items: center; gap: 0.6rem;
  font-family: var(--mono); font-size: 0.58rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--graphite); }
.rail .dot { width: 6px; height: 6px; border-radius: 50%;
  background: var(--graphite-2); transition: all var(--dur-fast) var(--ease-out); }
.rail .lbl { opacity: 0; transform: translateX(4px);
  transition: all var(--dur-fast) var(--ease-out); }
.rail a:hover .lbl { opacity: 1; transform: translateX(0); }
.rail a.active .dot { background: var(--accent); transform: scale(1.9); }
.rail a.active .lbl { opacity: 1; transform: translateX(0); color: var(--ink); }
@media (max-width: 820px) { .rail { display: none; } }

/* ── REVEAL SYSTEM ─────────────────────────────────────────────────────────
   Elements marked [data-in] start hidden and settle when the orchestrator
   adds .in (IntersectionObserver). Each chapter overrides the *distance and
   character* of its reveal in sections.css — this is only the default. */
[data-in] { opacity: 0; transform: translateY(22px);
  transition: opacity var(--dur-slow) var(--ease-out),
              transform var(--dur-slow) var(--ease-settle);
  transition-delay: var(--d, 0s); will-change: transform, opacity; }
[data-in].in { opacity: 1; transform: none; }

/* stepped "write-on" — clipped left-to-right in discrete frames, the hand
   authoring type into existence (§ opening DRAW/WRITE) */
[data-write] { opacity: 0; clip-path: inset(0 100% -12% 0); }
[data-write].in { animation: writeOn 0.45s steps(9, end) var(--d, 0s) forwards; }
@keyframes writeOn { to { opacity: 1; clip-path: inset(0 0 -12% 0); } }

.rule[data-in] { transform: scaleX(0); }
.rule[data-in].in { transform: scaleX(1);
  transition: transform var(--dur) var(--ease-out) var(--d, 0s); }

/* ── REDUCED MOTION · story preserved, movement removed (§41) ───────────────*/
@media (prefers-reduced-motion: reduce) {
  :root { --grain-opacity: 0.32; }
  [data-in], [data-write], .rule[data-in] {
    opacity: 1 !important; transform: none !important;
    clip-path: none !important; animation: none !important; transition: none !important;
  }
  .plate::after { animation: none; }
  * { scroll-behavior: auto !important; }
}

/* ── .sr-only — IN THE DOCUMENT, NOT ON THE PAGE ───────────────────────────
   For headings a section genuinely has but does not PAINT, because something
   else already does. The AI CONTENT chapter is the case that needed it: the
   approved take opens on its own title card, so a visible DOM copy of the same
   sentence sitting a screen above it was the same words twice, seconds apart.
   The outline and the screen-reader reading order still need the heading.

   clip-path + 1px box rather than display:none or visibility:hidden, both of
   which take the element out of the accessibility tree — which is the one thing
   this class exists to keep. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
