/* ──────────────────────────────────────────────────────────────────────────
   JT Marlon — page shell
   ──────────────────────────────────────────────────────────────────────────
   The chrome every page of the main site is built on: the lit ground, the
   type scale, the glass components, the safe-area padding, and the light-mode
   half of all of it. Five pages carried a byte-identical copy of these 36
   rules — studio, bio, features, onboarding and reviews — which is the same
   count, and the same rule of three, that moved their theme toggle into
   assets/shell.js. This is that file's stylesheet half.

   Not on this sheet: portal-preview.html. Deliberately excluded, not drift —
   see that file's own header comment. It previews the real client portal
   behind a login, so it is built to the same constraints: no Tailwind CDN (a
   third-party script-src exception nothing behind auth should carry), no
   Google Fonts, single theme only (a light rendering would misrepresent the
   product). It already reuses this sheet's token VALUES — its --gold is this
   sheet's lime, #D9B45B; --night and --panel match too — reimplemented in
   plain CSS rather than linked, because none of this sheet's classes are
   usable without the Tailwind utilities they are meant to pair with.

   index.html and founders.html moved onto this sheet 2026-09-03;
   founders.html keeps its own jblue/torange/mgreen per-founder accent
   colours and its letterbox/grain/scene-scrub machinery — those are that
   page's content, not chrome, and are unrelated to the shared system.

   ── Load order is the whole contract ──────────────────────────────────────
   Link this BEFORE the page's own <style>, in the <head>:

     <link rel="stylesheet" href="assets/shell.css" />
     <style> … the page's own rules … </style>

   Everything here is base layer. A page's inline block comes after it and so
   wins every tie, which is what lets a page restyle .ios-glass or add its own
   html.light overrides without touching this file. Reverse the two and pages
   start losing races they are written to win.

   The rules are ordered tokens → ground → type → interaction → glass →
   keyframes → layout → light overrides, and the light block MUST stay last:
   it overrides the base rules above it at equal specificity and does its work
   by source order alone.

   ── The reveal and the transform lane ─────────────────────────────────────
   .rv / .rv.in live here too, but only after the collision that used to keep
   them off a shared sheet was fixed rather than worked around: the reveal now
   offsets with `translate` instead of `transform`, and the two components that
   also transition get an explicit merged rule. Both are documented at the
   rules themselves. The short version: nothing in this file wins by being
   further down a page's stylesheet any more.
   ────────────────────────────────────────────────────────────────────────── */

:root {
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
}

html { background: #0A0A0A; }

body { font-family: 'Lato', sans-serif; color: #EDEDEF; overflow-x: hidden; }

/* Key-light geometry lives in a variable because both themes draw the same
   pool and a phone needs a different one: at desktop proportions the ellipse
   falls off above where the first card lands on a narrow screen — studio.html's
   builder is the tallest case — so that card would sit below its own light. */
:root { --key: 72rem 46rem at 50% -14%; }

@media (max-width: 640px) { :root { --key: 150% 105% at 50% -2%; } }

/* ── site background — a lit stage, not a texture ──
   The marble swirl this replaces stayed legible through its veil, and the
   ribbons crossed straight behind the builder card: glass panels blur what
   is behind them, so a busy layer reads as smudge exactly where the UI is.
   Two soft lights over a flat ground instead — a warm key pooling down from
   above centre so the page's first panel sits in the brightest part of the
   frame, and a
   dim cool fill low-right so the foot of the fold never goes dead flat.
   Grain rides above them in ::after: radials this broad and this dark band
   visibly on 8-bit panels, and the noise is what dithers them smooth.
   Still a fixed layer rather than `background-attachment: fixed`: iOS Safari
   doesn't honour that property (it sizes the image to the whole document
   instead), and where it is honoured it repaints on every scroll frame. */
body::before {
  content: ''; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background-color: #0A0A0A;
  background-image:
    radial-gradient(125% 105% at 50% 38%, rgba(0,0,0,0) 55%, rgba(0,0,0,.34) 100%),
    radial-gradient(var(--key), rgba(217,180,91,.14), rgba(217,180,91,0) 64%),
    radial-gradient(56rem 44rem at 92% 112%, rgba(150,168,208,.06), rgba(150,168,208,0) 62%),
    linear-gradient(180deg, #111013 0%, #0A0A0A 44%, #070707 100%);
  background-repeat: no-repeat;
}

/* Grain gets its own layer so each theme can dial it with a single opacity. */
body::after {
  content: ''; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  opacity: .1;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)' opacity='.3'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}

::selection { background: #D9B45B; color: #0A0A0A; }

p { font-weight: 700; }

.h-display { font-weight: 900; letter-spacing: -0.03em; text-transform: uppercase; }

.script { font-weight: 900; font-style: italic; text-transform: none; letter-spacing: -0.02em; display: inline-block; padding-right: 0.14em; margin-right: -0.05em; }

.h-label { font-weight: 800; letter-spacing: -0.01em; text-transform: uppercase; }

/* ── the serif display pair ──
   Marcellus for eyebrows and section headings, Bodoni Moda for the one hero
   headline under them. The pairing is the point: Marcellus is a low-contrast
   inscriptional capital, Bodoni an extreme-contrast didone — two faces from
   the same corner of history would read as an accident, these read as a
   choice.

   Here rather than in a page, because studio.html and reviews.html carried
   byte-identical copies and onboarding.html would have been a third. Same
   argument as the 36 rules already in this file.

   Both are self-contained rather than modifiers on .h-display, so there is no
   same-specificity race to lose. .h-display's 900 turns a didone from elegant
   to shouting and flattens the thick/thin contrast that is the whole reason
   to use one; its -0.03em is drawn for Lato's wide caps, and didone caps are
   narrower and want the opposite.

   The font files are NOT loaded here — a stylesheet cannot do that. Any page
   using these must add Marcellus and Bodoni Moda to its own fonts.googleapis
   link, or it silently gets the Times New Roman fallback in the stack. */
/* The small caps line above a headline — "THE WEB CREW", "REVIEWS", "START A
   PROJECT". Marcellus is a Roman inscriptional capital, so caps are the form
   it was drawn for rather than a transform applied to it.

   This REPLACES .script on those elements rather than layering over it.
   .script is drawn for a lowercase italic Lato word ("builds", "theirs."),
   and every one of its declarations is wrong here: the italic would be a
   synthesised slant, since Marcellus ships none; 900 would be a synthetic
   bold, since Marcellus ships one weight; -0.02em tightens caps that want
   air; and the 0.14em/-0.05em pair compensates for an italic overhang that an
   upright glyph does not have, which on a centred line pushes it 0.09em off
   true. display:inline-block is the one thing worth keeping, so it is kept —
   that is what makes the parent's text-align centre it. */
.h-eyebrow {
  font-family: Marcellus, 'Times New Roman', serif;
  font-weight: 400; font-style: normal;
  text-transform: uppercase; letter-spacing: 0.10em;
  display: inline-block;
}

.h-serif {
  font-family: 'Bodoni Moda', 'Didot', 'Times New Roman', serif;
  font-weight: 600; letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* The gold accent set inside an .h-serif headline. A self-contained
   replacement for .script, not a modifier on it, so the two never race on
   source order — and so .script keeps working unchanged for "builds" and
   "hand over keys." on studio.html.

   It differs from .script in exactly two places. text-transform, obviously.
   And the tracking: .script's -0.02em pulls lowercase italic together, but
   Lato 900 is a heavy face and its capitals need separating rather than
   tightening, so +0.04em — wider than .h-serif's +0.02em beside it, because
   the weight is doing more to close the counters.

   The 0.14em/-0.05em overhang pair is KEPT: it compensates for the italic's
   overshoot on the final glyph, and this accent is still italic. */
.script-caps {
  font-weight: 900; font-style: italic;
  text-transform: uppercase; letter-spacing: 0.04em;
  display: inline-block;
  padding-right: 0.14em; margin-right: -0.05em;
}

/* pressable feedback - everything clickable responds */
.press { transition: transform 160ms var(--ease-out); }

.press:active { transform: scale(0.97); }

/* iOS liquid-glass cards */
.ios-glass {
  background: linear-gradient(135deg, rgba(255,255,255,0.10) 0%, rgba(255,255,255,0.035) 60%, rgba(255,255,255,0.07) 100%);
  backdrop-filter: blur(24px) saturate(170%);
  -webkit-backdrop-filter: blur(24px) saturate(170%);
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.20), inset 0 -1px 0 rgba(255,255,255,0.05), 0 16px 48px rgba(0,0,0,0.5);
  border-radius: 1.75rem;
}

.ios-glass-strong {
  background: linear-gradient(135deg, rgba(217,180,91,0.16) 0%, rgba(255,255,255,0.05) 55%, rgba(217,180,91,0.10) 100%);
  backdrop-filter: blur(28px) saturate(180%);
  -webkit-backdrop-filter: blur(28px) saturate(180%);
  border: 1px solid rgba(233,205,130,0.55);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.28), 0 0 44px rgba(217,180,91,0.16), 0 16px 48px rgba(0,0,0,0.5);
  border-radius: 1.75rem;
}

.glass-btn {
  background: rgba(255,255,255,0.08);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255,255,255,0.16);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.18);
  border-radius: 9999px;
  transition: background 200ms ease, transform 160ms var(--ease-out);
}

.glass-btn:hover { background: rgba(255,255,255,0.14); }

.glass-btn:active { transform: scale(0.97); }

.glass-gold {
  background: linear-gradient(150deg, #F7E6A8 0%, #DDBA5F 28%, #A8842F 52%, #EBD188 76%, #C9A54A 100%);
  background-size: 200% 200%;
  background-position: 0% 0%;
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255,246,214,0.55);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.65), inset 0 -1px 1px rgba(0,0,0,0.3), 0 6px 22px rgba(217,180,91,0.35);
  border-radius: 9999px;
  transition: background-position 500ms var(--ease-out), transform 160ms var(--ease-out);
}

.glass-gold:hover { background-position: 90% 90%; }

.glass-gold:active { transform: scale(0.97); }

/* Sweeps between 12% and 62%, then rests off-canvas: the jump back to the
   start happens while the band is outside the text, so the loop is invisible. */
@keyframes sheen {
  0%, 12%   { background-position: -60% 0, 0 0; }
  62%, 100% { background-position: 175% 0, 0 0; }
}

/* ── entrance reveal ──
   Elements marked .rv start faded and offset; assets/shell.js adds .in as they
   cross into view and the transition below plays them in.

   The offset is `translate`, not `transform`, and that is the point of this
   rule's shape. It used to be `transform: translateY(14px)`, which left
   .rv.in pinning `transform: translateY(0)` on every revealed element — and at
   equal specificity that silently beat .tile:hover's `translateY(-3px)` on the
   ten cards marked `class="tile rv …"`, so those cards never lifted on hover.
   Same for .press:active and .glass-btn:active where they reveal. `translate`
   is its own property, so the reveal now owns a lane nothing else is in and
   every transform-driven interaction on a revealed element works again.

   `transition` is still shared ground: it is a single property, so the later
   rule replaces the list wholesale rather than adding to it. Where a component
   with its own transition also reveals, the lists have to be merged by hand,
   in a rule specific enough to win outright — below for the shared components,
   and .tile.rv on studio.html and bio.html for the page-local one. */
.rv { opacity: 0; translate: 0 14px; transition: opacity 350ms var(--ease-out), translate 350ms var(--ease-out); }
.rv.in { opacity: 1; translate: none; }

/* The merge, for the two shared components that carry a transition of their
   own. At (0,2,0) this beats both .rv and the component rule, so it holds
   wherever the sheets land relative to each other — unlike the collision it
   replaces, it does not depend on source order at all. */
.press.rv, .glass-btn.rv {
  transition: opacity 350ms var(--ease-out), translate 350ms var(--ease-out),
              transform 160ms var(--ease-out), background 200ms ease;
}

/* ─────────── responsive / adaptive ─────────── */

.safe-x { padding-left: max(1rem, env(safe-area-inset-left)); padding-right: max(1rem, env(safe-area-inset-right)); }

@media (min-width: 768px) {
  .safe-x { padding-left: max(2rem, env(safe-area-inset-left)); padding-right: max(2rem, env(safe-area-inset-right)); }
}

/* ── light mode ── */
html, body { transition: background-color .4s ease, color .4s ease; }

html.light { background: #F2F0EA; }

html.light body { color: #1A1A1C; }

/* the same two lights, re-lit for the cream ground: the key warms the top of
   the frame instead of lifting it, and the vignette closes the corners in
   brown rather than black so it never reads as a grey smudge */
html.light body::before {
  background-color: #F2F0EA;
  background-image:
    radial-gradient(125% 105% at 50% 38%, rgba(122,104,68,0) 55%, rgba(122,104,68,.10) 100%),
    radial-gradient(var(--key), rgba(217,180,91,.17), rgba(217,180,91,0) 64%),
    radial-gradient(56rem 44rem at 92% 112%, rgba(118,138,180,.07), rgba(118,138,180,0) 62%),
    linear-gradient(180deg, #F7F5EF 0%, #F2F0EA 46%, #EAE7DE 100%);
}

/* dark grain reads heavier on a light ground, so it comes down to match */
html.light body::after { opacity: .07; }

html.light .text-fog { color: #6B6860 !important; }

html.light .border-line { border-color: rgba(0,0,0,.1) !important; }

html.light .bg-white\/10 { background-color: rgba(0,0,0,.08) !important; }

html.light .hover\:text-white:hover { color: #141414 !important; }

html.light .glass-btn:hover { background: rgba(0,0,0,0.09); }

html.light .h-display, html.light .h-label {
  background: linear-gradient(165deg, #55555B 0%, #1A1A1E 30%, #060608 55%, #3E3E44 78%, #121214 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
