/* ==========================================================================
   Jannelle Orilla — Personal Portfolio
   Global design system: tokens, base, layout, components, responsive.

   Palette: porcelain / wine / champagne, warmed from the previous
   ivory / ink / terracotta system. Wine is the brand (logo, nameplate,
   primary CTA, focus ring); terracotta stays the interaction colour so the
   two case-study pages keep working unchanged; champagne is decorative only.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. SELF-HOSTED FONTS
   Latin subsets, served locally so there is no render-blocking round trip to
   a third-party origin. See assets/fonts/LICENSE.txt for provenance.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: "Archivo Black";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/archivo-black-latin-normal.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: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/inter-latin-normal.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: "Fraunces";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("fonts/fraunces-latin-normal.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: "Fraunces";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/fraunces-latin-italic.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;
}

/* --------------------------------------------------------------------------
   1. TOKENS

   Every ratio noted below was computed from these exact hex values, not copied
   from a spec sheet. See the note inside :root for what that pass changed.
   -------------------------------------------------------------------------- */
:root {
  /* ---- header logo ---------------------------------------------------------
     One size for the JO mark in every page header: 28px on phones (≤640px),
     easing to 32px from 1280px up — the sizes the inner-page headers already
     used. Width follows the SVG's own 1.364:1 proportions. Defined identically
     in main.css and styles.css, which never load together; keep the two in
     sync. Footer marks are sized separately and do not use this. */
  --header-logo-h: clamp(1.75rem, 1.5rem + .625vw, 2rem);   /* also in main.css */

  /* Repointed to the brief's palette on 15 Aug 2026, so the case-study pages
     share one system with the rebuilt homepage. Every ratio below was
     recomputed against the new porcelain (#F8F2E9, relative luminance .8935),
     not carried over. Two colours changed role as a result:

       - coral #C95E51 measures 3.61:1 — it clears the 3:1 non-text bar and is
         safe as display type, but fails AA for body-size text. --terracotta
         (the interaction colour, used at small sizes throughout) is therefore
         the darker #B24A3A, at 4.80:1.
       - gold #D4A24C measures 2.08:1 on porcelain and 5.61:1 on wine. It is
         decorative on light grounds and text-safe only on burgundy. */

  /* ---- Surfaces ---- */
  --porcelain:      #F8F2E9;   /* warm porcelain — page ground */
  --porcelain-warm: #FBF6EF;
  --shell:          #F3D7D3;   /* pale blush */
  --shell-deep:     #EDC3BD;
  --paper:          #FDF9F3;   /* cards, contrast blocks */

  /* ---- Ink ---- */
  --espresso:       #28131D;   /* near-black plum · 15.60:1 on porcelain - AAA */
  --espresso-soft:  #4A2B35;   /* 10.28:1 - AAA */
  --ink-muted:      #6B5158;   /*  6.41:1 - AA  */

  /* ---- Brand ---- */
  --wine:           #5A1830;   /* deep wine burgundy · 11.68:1 on porcelain */
  --wine-deep:      #431122;   /* hover / pressed only */
  --wine-tint:      rgba(90, 24, 48, .08);

  /* ---- Accents ---- */
  --terracotta:     #B24A3A;   /* 4.80:1 on porcelain - AA. See note above. */
  --terracotta-deep:#9C3C2D;   /* 6.02:1 - hover / pressed */
  --rose:           #C95E51;   /* 3.61:1 - large display type and fills only */
  --champagne:      #D4A24C;   /* 2.08:1 - DECORATIVE on light; text-safe on wine */
  --champagne-lite: #ECD3A4;   /* 1.33:1 - DECORATIVE ONLY, never text */

  /* ---- Dark grounds ---- */
  --dark:           #3A1A26;   /* porcelain on this = 13.94:1 - AAA */
  --dark-deep:      #1C0D14;   /* porcelain on this = 17.11:1 - AAA */

  /* ---- Type ---- */
  --font-display: "Archivo Black", "Anton", Impact, system-ui, sans-serif;
  --font-body:    "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-accent:  "Fraunces", "Iowan Old Style", Georgia, serif;

  /* ---- Fluid type scale ---- */
  --step--1:  clamp(.83rem, .79rem + .18vw, .94rem);
  --step-0:   clamp(1rem, .95rem + .24vw, 1.19rem);
  --step-1:   clamp(1.2rem, 1.11rem + .42vw, 1.5rem);
  --step-2:   clamp(1.44rem, 1.29rem + .71vw, 1.9rem);
  --step-3:   clamp(1.73rem, 1.49rem + 1.14vw, 2.4rem);
  --step-4:   clamp(2.07rem, 1.7rem + 1.77vw, 3.03rem);
  /* Measured, not estimated: "JANNELLE" in Archivo Black at -.035em tracking
     renders at 0.698em per character, so 8 caps = 5.584em. To span ~94vw the
     font-size must be 94 / 5.584 = 16.8vw. The first guess of 19vw put the
     nameplate at 106% of the viewport and clipped the final E. */
  --headline:  clamp(1.8rem, .9rem + 2.6vw, 3.4rem);
  --nameplate: clamp(2.4rem, 16.8vw, 20rem);

  /* The four-point sparkle from the JO monogram's counter, redrawn as a mask so
     it can be tinted. The logo file itself is never touched. */
  --sparkle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 0c1.1 6.6 5.3 10.9 12 12-6.7 1.1-10.9 5.4-12 12-1.1-6.6-5.3-10.9-12-12C6.7 10.9 10.9 6.6 12 0z' fill='%23000'/%3E%3C/svg%3E");

  /* ---- Space ---- */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px; --sp-5: 24px;
  --sp-6: 32px; --sp-7: 48px; --sp-8: 64px; --sp-9: 96px; --sp-10: 128px;

  /* ---- Shape ---- */
  --radius-sm:   4px;
  --radius:      4px;   /* kept at the built value; case-study cards depend on it */
  --radius-md:   10px;
  --radius-lg:   10px;
  --radius-xl:   18px;
  --radius-pill: 999px;
  --border-hair:     1px solid rgba(90, 24, 48, .14);
  --border-champagne:1px solid rgba(212, 162, 76, .38);

  /* ---- Elevation ---- */
  --shadow-card: 0 1px 2px rgba(40, 19, 29, .04), 0 8px 24px rgba(40, 19, 29, .07);
  --shadow-cta:  0 2px 6px rgba(90, 24, 48, .18), 0 12px 28px rgba(90, 24, 48, .14);
  --glass-bg:    rgba(255, 253, 250, .42);
  --glass-blur:  blur(14px) saturate(1.15);

  /* ---- Layout ---- */
  --container:        1240px;
  --container-wide:   1440px;
  --container-narrow: 780px;
  --gutter:    clamp(20px, 5vw, 56px);
  --section-y: clamp(72px, 9vw, 140px);

  /* ---- Motion ---- */
  --dur-fast: 160ms;
  --dur:      .55s;                          /* legacy name, used by .reveal */
  --dur-mid:  320ms;
  --dur-slow: 560ms;
  --ease:     cubic-bezier(.22, 1, .36, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);

  /* ---- Z-index ---- */
  --z-nameplate: 1;  --z-portrait: 2;  --z-cards: 3;   --z-copy: 4;
  --z-header:   900; --z-stickybar: 60; --z-modal: 1500;

  /* ------------------------------------------------------------------------
     COMPATIBILITY LAYER
     The two case-study pages and project-page.css reference the original token
     names throughout. Re-pointing the names rather than renaming usages moves
     the whole site onto the new palette without touching ~70 KB of markup that
     is already verified, and keeps 177 checked internal references intact.
     ---------------------------------------------------------------------- */
  --ivory:       var(--porcelain);
  --ivory-warm:  var(--shell);
  --ink:         var(--espresso);
  --ink-soft:    var(--espresso-soft);
  --accent:      var(--terracotta);
  --accent-deep: var(--terracotta-deep);
  --accent-tint: rgba(168, 74, 41, .09);
  --dark-soft:   #241618;

  /* Hairlines are wine at low alpha so they warm with the ground instead of
     reading as the old grey stone. */
  --stone-100: rgba(90, 24, 48, .12);
  --stone-200: rgba(90, 24, 48, .22);
  --stone-400: rgba(90, 24, 48, .42);

  --bg:          var(--porcelain);
  --text:        var(--espresso);
  --muted:       var(--ink-muted);
  --line:        var(--stone-100);
  --line-strong: var(--stone-200);
}

/* Windows High Contrast / forced colours: let the OS substitute. The champagne
   thread is decorative only, so losing its colour costs no information. */
@media (forced-colors: active) {
  :root {
    --champagne: CanvasText;
    --champagne-lite: CanvasText;
  }
}

/* --------------------------------------------------------------------------
   2. RESET & BASE
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  scroll-padding-top: 92px;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.65;
  font-feature-settings: "kern" 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-wrap: break-word;
}

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

h1, h2, h3, h4 {
  font-family: var(--font-accent);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.02em;
  margin: 0 0 .5em;
  text-wrap: balance;
}

h1 { font-size: clamp(2.6rem, 6.2vw, 4.6rem); }
h2 { font-size: clamp(1.9rem, 3.9vw, 3rem); }
h3 { font-size: clamp(1.25rem, 2.1vw, 1.6rem); }
h4 { font-size: 1.08rem; letter-spacing: -0.01em; }

p { margin: 0 0 1.1em; }
p:last-child { margin-bottom: 0; }

a { color: inherit; text-decoration-color: var(--stone-200); text-underline-offset: 3px; }
a:hover { text-decoration-color: var(--accent); }

ul, ol { margin: 0 0 1.2em; padding-left: 1.15em; }
li { margin-bottom: .45em; }

::selection { background: var(--accent); color: var(--paper); }

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

.skip-link {
  position: fixed; top: 12px; left: 12px; z-index: 2000;
  background: var(--ink); color: var(--paper);
  padding: 12px 20px; border-radius: var(--radius);
  font-size: 15px; font-weight: 600; text-decoration: none;
  transform: translateY(-160%); transition: transform .2s var(--ease);
}
.skip-link:focus-visible { transform: translateY(0); }

/* Global visible focus — every interactive element.
   2px wine at 2px offset: wine is the brand colour and clears 9.97:1 on the
   porcelain ground, so the ring is unambiguous wherever it lands. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--wine);
  outline-offset: 2px;
  border-radius: 2px;
}
:focus:not(:focus-visible) { outline: none; }

/* On dark grounds wine would disappear (1.55:1 on --dark-deep). */
.block-dark :where(a, button, [tabindex]):focus-visible,
.site-footer :where(a, button, [tabindex]):focus-visible,
.modal :where(a, button, [tabindex]):focus-visible {
  outline-color: var(--porcelain);
}

/* --------------------------------------------------------------------------
   4. LAYOUT
   -------------------------------------------------------------------------- */
.container { width: 100%; max-width: var(--container); margin-inline: auto; padding-inline: var(--gutter); }
.container-narrow { max-width: var(--container-narrow); }
.section { padding-block: var(--section-y); }
.section-tight { padding-block: clamp(52px, 6vw, 88px); }

.section-head { max-width: 62ch; margin-bottom: clamp(38px, 5vw, 66px); }
.section-head.wide { max-width: 100%; }

.eyebrow {
  font-family: var(--font-body);
  font-size: 12px; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--accent); margin: 0 0 16px;
  display: flex; align-items: center; gap: 10px;
}
.eyebrow::after { content: ""; flex: 1; height: 1px; background: var(--line-strong); max-width: 90px; }
.eyebrow.no-rule::after { display: none; }

.lede { font-size: clamp(1.05rem, 1.6vw, 1.22rem); color: var(--ink-soft); line-height: 1.6; }

/* --------------------------------------------------------------------------
   5. HEADER / NAV
   -------------------------------------------------------------------------- */
/* A floating glass pill rather than a full-width bar. The <header> itself is
   an invisible sticky strip; the .nav-wrap inside it carries the rounded,
   frosted surface, inset from the viewport edges so the page reads around it.
   The strip ignores the pointer so the sliver of page beside the pill stays
   clickable. .scrolled (past 80px, script.js) only firms the glass up a little
   and deepens the shadow — with JS off it simply keeps its resting state. */
.site-header {
  --pill-top: 14px;
  position: sticky; top: 0; z-index: var(--z-header);
  padding-top: var(--pill-top);
  pointer-events: none;
}
.site-header .nav-wrap {
  pointer-events: auto;
  width: calc(100% - 2 * var(--gutter));
  max-width: calc(var(--container) - 2 * var(--gutter) + 32px);
  padding-inline: 22px 10px;
  min-height: 64px;
  border-radius: var(--radius-pill);
  position: relative; isolation: isolate;
}
/* The glass lives on a pseudo-element, not on .nav-wrap itself: a
   backdrop-filter makes its element the containing block for position:fixed
   descendants, which pinned the mobile drawer inside the pill and stopped it
   blurring the page behind it. */
.site-header .nav-wrap::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  border-radius: inherit;
  background: rgba(253, 249, 243, .62);
  -webkit-backdrop-filter: saturate(170%) blur(18px);
  backdrop-filter: saturate(170%) blur(18px);
  border: 1px solid rgba(90, 24, 48, .09);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .55),
              0 1px 2px rgba(40, 19, 29, .04), 0 10px 30px rgba(40, 19, 29, .07);
  transition: background .35s var(--ease), box-shadow .35s var(--ease);
}
.site-header.scrolled .nav-wrap::before {
  background: rgba(253, 249, 243, .74);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .55),
              0 1px 2px rgba(40, 19, 29, .05), 0 14px 38px rgba(40, 19, 29, .11);
}
/* Without backdrop-filter the glass would just be see-through; go near-solid. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header .nav-wrap::before, .site-header.scrolled .nav-wrap::before { background: rgba(253, 249, 243, .96); }
}

.nav-wrap { display: flex; align-items: center; justify-content: space-between; gap: 24px; min-height: 72px; }

/* --- Brand lockup: official JO monogram + wordmark ---------------------- */
.brand {
  display: inline-flex; align-items: center; gap: 11px;
  text-decoration: none; color: inherit;
  /* The mark is 32px tall but the target must be 44x44. Negative margin keeps
     the enlarged hit area from pushing the header taller. */
  min-height: 44px; min-width: 44px;
  padding-block: 6px; margin-block: -6px;
}
.brand-logo {
  display: block; flex-shrink: 0;
  width: 44px; height: 32px;      /* viewBox aspect 1.3638 */
}
/* Header marks follow the shared token; footer marks keep the fixed size above.
   Two-class specificity also outranks the mobile .brand-logo override. */
.site-header .brand-logo { width: auto; height: var(--header-logo-h); }
.brand-name { font-size: 15px; font-weight: 600; letter-spacing: -.01em; line-height: 1.25; }
.brand-name .brand-role { display: block; font-size: 11.5px; font-weight: 400; color: var(--muted); letter-spacing: .02em; }

.site-nav { display: flex; align-items: center; gap: clamp(16px, 2.4vw, 34px); }
.site-nav a {
  font-size: 14.5px; font-weight: 500; text-decoration: none;
  color: var(--ink-soft); transition: color .2s;
  position: relative;
}
.site-nav a:not(.nav-cta) {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px;          /* padding alone landed at 43.98px */
  min-width: 44px;           /* "Work" is only 37px of text */
}
.site-nav a:hover { color: var(--ink); }

/* Active section marker — the Strategy Thread surfacing in the nav.
   Champagne is decorative here: the accessible state is carried by
   aria-current, which script.js sets, so colour is never the only signal. */
.site-nav a:not(.nav-cta)[aria-current="true"] { color: var(--wine); font-weight: 600; }
.site-nav a:not(.nav-cta)[aria-current="true"]::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 2px;
  height: 2px; background: var(--champagne); border-radius: 2px;
}

.nav-cta {
  background: var(--wine); color: var(--porcelain) !important;
  padding: 11px 20px; border-radius: var(--radius-pill); font-weight: 600 !important;
  min-height: 44px; display: inline-flex; align-items: center; gap: 7px;
  transition: background .25s var(--ease);
}
.nav-cta:hover { background: var(--wine-deep); }
.nav-cta span { font-size: .9em; transition: transform var(--dur-mid) var(--ease); }
.nav-cta:hover span { transform: translate(2px, -2px); }

.menu-toggle {
  display: none; width: 44px; height: 44px; padding: 0;
  background: rgba(90, 24, 48, .06); border: 0; border-radius: 50%;
  cursor: pointer; position: relative;
  transition: background .25s var(--ease);
}
.menu-toggle:hover, .menu-toggle[aria-expanded="true"] { background: rgba(90, 24, 48, .11); }
.menu-toggle span {
  display: block; width: 18px; height: 1.5px; background: var(--ink);
  margin: 4px auto; transition: transform .3s var(--ease), opacity .2s;
}
.menu-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
.menu-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.menu-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }

/* --------------------------------------------------------------------------
   6. BUTTONS
   -------------------------------------------------------------------------- */
/* line-height is set here, not inherited. Left to inherit, the same button
   came out 57px tall in a <div> (body's 1.65) and 48px in a case-study <p>,
   with the label sitting off-centre in the shorter one. Now every .button is
   50px, centred by flex, whatever it sits in. The border is always present
   (transparent on filled buttons) so filled and outlined versions match. */
.button {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  box-sizing: border-box;
  padding: 12px 28px; border-radius: var(--radius-pill);
  min-height: 50px;
  font-family: var(--font-body); font-size: 15px; font-weight: 600; line-height: 1.2;
  text-align: center;
  text-decoration: none; cursor: pointer; border: 1px solid transparent;
  transition: background .25s var(--ease), color .25s var(--ease),
              border-color .25s var(--ease), transform .25s var(--ease);
}
.button:hover { transform: translateY(-2px); }
/* The arrow glyph carries the font's full line box; at line-height 1 it
   centres on the label instead of riding high. */
.button > span[aria-hidden="true"] { line-height: 1; }

/* Wine fill / porcelain label = 9.97:1. */
.button-primary { background: var(--wine); color: var(--porcelain); box-shadow: var(--shadow-cta); }
.button-primary:hover { background: var(--wine-deep); }

/* Hairline wine border rather than the old grey stone. */
.button-secondary { background: transparent; color: var(--wine); border-color: rgba(90, 24, 48, .34); }
.button-secondary:hover { border-color: var(--wine); background: var(--paper); }

.button-facebook, .button-instagram { background: var(--wine); color: var(--porcelain); }
.button-facebook:hover, .button-instagram:hover { background: var(--wine-deep); }

/* min-height 44px: these are standalone calls to action, not links inside a
   sentence, so the WCAG 2.5.8 inline exception does not cover them. The text
   sits centred, so the extra height is hit area rather than visible bulk. */
.text-link {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 44px;
  font-size: 14.5px; font-weight: 600; color: var(--accent);
  text-decoration: none; border-bottom: 1px solid transparent;
  transition: border-color .25s var(--ease), gap .25s var(--ease);
}
.text-link:hover { border-bottom-color: var(--accent); gap: 11px; }

/* --------------------------------------------------------------------------
   7. HERO — three depth planes

   Plane 1 (z1) nameplate  ORILLA, bleeding off the left edge only
   Plane 2 (z2) portrait   free-standing cutout, overlapping the nameplate
   Plane 3 (z3/z4) copy, CTAs and the evidence index

   Built with Grid + clamp() only. Nothing is positioned with fixed pixel
   coordinates, so the composition interpolates rather than snapping.
   -------------------------------------------------------------------------- */
.hero {
  /* Top padding clears the 72px sticky header, which is transparent over the
     hero — without it the nameplate would collide with the nav. */
  padding-block: clamp(74px, 5vw, 94px) clamp(24px, 3vw, 46px);
  /* Clips the nameplate's bleed. In LTR, overflow past the left edge is clipped
     without ever creating a scrollable area, so this cannot produce horizontal
     scroll — verified at all eight widths. */
  overflow: hidden;
  position: relative;
}

/* Soft pink bloom behind the composition. Two radial gradients rather than an
   image, so it costs nothing and scales to any viewport. */
.hero-bloom {
  position: absolute; inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(58% 50% at 50% 42%, var(--shell) 0%, rgba(248, 220, 207, 0) 72%),
    radial-gradient(42% 38% at 82% 16%, var(--shell-deep) 0%, rgba(241, 200, 192, 0) 68%),
    radial-gradient(38% 34% at 12% 78%, var(--porcelain-warm) 0%, rgba(250, 226, 215, 0) 70%);
}

/* Twelve columns so the flanking cards, the centred portrait and the bottom
   copy can all be placed on shared lines instead of pixel offsets. */
/* Three rows, not four: the identity block shares the cards' row (it sits to
   their left) rather than occupying one of its own. That overlap is what keeps
   the whole composition inside one viewport, as the reference does. */
.hero-inner {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-template-rows: auto auto auto;
  column-gap: clamp(8px, 1.2vw, 20px);
  align-items: start;
}

/* --- Plane 1: nameplate ------------------------------------------------- */
.hero-nameplate {
  grid-column: 1 / -1;
  grid-row: 1;
  z-index: var(--z-nameplate);
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--nameplate);
  line-height: .8;
  letter-spacing: -.035em;
  color: var(--wine);
  opacity: .92;
  text-align: center;
  white-space: nowrap;
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
}

/* The nameplate is wider than the container's content box, and an overflowing
   nowrap line does not centre — it starts at the content edge and runs off the
   right (measured: -17px at 1440, -163px at 1920, both clipped by the hero's
   overflow). Giving the element the whole viewport as its box means the glyph
   run fits inside it and text-align actually centres. */
@media (min-width: 768px) {
  .hero-nameplate { width: 100vw; justify-self: center; }
}

/* --- Identity block ----------------------------------------------------- */
/* In normal flow inside .hero-copy, immediately above the headline. */
.hero-identity { margin-bottom: clamp(14px, 1.6vw, 24px); }
.hero-name {
  font-size: clamp(1rem, .9rem + .3vw, 1.2rem);
  font-weight: 700; color: var(--espresso); margin: 0 0 4px; letter-spacing: -.01em;
}
.hero-role {
  font-size: clamp(.78rem, .72rem + .18vw, .9rem);
  font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
  color: var(--terracotta); margin: 0 0 12px;
}
/* Champagne rule — decorative, and the only thing it carries is emphasis. */
.hero-identity::after {
  content: ""; display: block; width: 64px; height: 2px;
  background: var(--champagne); border-radius: 2px;
}

/* --- Plane 4: headline + CTAs ------------------------------------------- */
.hero-copy { grid-column: 1 / 6; grid-row: 3; z-index: var(--z-copy); }

.hero h1 {
  font-family: var(--font-display);
  font-size: var(--headline);
  line-height: .94;
  letter-spacing: -.02em;
  font-weight: 400;             /* Archivo Black has one weight; 400 is it */
  text-transform: uppercase;
  color: var(--espresso);
  margin: 0 0 clamp(20px, 2.4vw, 34px);
}
.hero h1 em { font-style: normal; color: var(--terracotta); }

.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; }
.hero-actions .button span { font-size: .9em; }

/* --- Supporting copy, bottom right -------------------------------------- */
.hero-support {
  grid-column: 9 / 13; grid-row: 3; z-index: var(--z-copy);
  align-self: end;
  border-inline-start: 2px solid var(--champagne);
  padding-inline-start: clamp(12px, 1.4vw, 20px);
}
.hero-support p { font-size: var(--step-0); color: var(--espresso-soft); margin: 0; line-height: 1.55; }
.hero-support em { font-style: normal; color: var(--terracotta); font-weight: 600; }

/* --- Plane 3: the two glass cards --------------------------------------- */
.hero-card {
  position: relative;
  z-index: var(--z-cards);
  padding: clamp(13px, 1.3vw, 19px) clamp(15px, 1.5vw, 22px);
  border-radius: var(--radius-xl);
  border: var(--border-champagne);
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-card);
}
/* Required opaque fallback — without backdrop-filter these labels would sit on
   whatever happens to be behind them, including the portrait. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .hero-card { background: var(--paper); }
}

/* Columns 1-4 and 9-12. The cards sit ABOVE the portrait in the stacking order,
   so unlike the nameplate and the bloom they really can cover her. At 1440 the
   first attempt (cols 3-6) ran to x=711 with her jaw starting near x=660, and
   the second (cols 2-5) still clipped the left edge of her hair. These lines
   leave roughly 60px of clearance at 1280, 1440 and 1920, and have the side
   benefit of aligning the card with the headline below it. */
.hero-card-traits   { grid-column: 1 / 5;  grid-row: 2; align-self: start; margin-top: clamp(10px, 1.4vw, 22px); }
.hero-card-evidence { grid-column: 9 / 13; grid-row: 2; align-self: start; margin-top: clamp(26px, 4vw, 62px); }

/* The disc carries the four-point sparkle from the JO monogram's counter —
   the signature element, drawn in CSS. The logo file is never modified. */
.hero-card-node {
  position: absolute; top: -18px; inset-inline-start: -18px;
  width: 44px; height: 44px; border-radius: 50%;
  background: linear-gradient(140deg, var(--champagne-lite), var(--champagne));
  box-shadow: 0 4px 14px rgba(90, 24, 48, .18);
  display: grid; place-items: center;
}
.hero-card-node::after {
  content: "";
  width: 20px; height: 20px;
  background: var(--paper);
  -webkit-mask: var(--sparkle) center / contain no-repeat;
  mask: var(--sparkle) center / contain no-repeat;
}

.hero-card-traits ul { list-style: none; margin: 0; padding: 0; }
.hero-card-traits li {
  margin: 0;
  padding-block: clamp(5px, .6vw, 9px);
  font-size: clamp(.8rem, .74rem + .2vw, .95rem);
  font-weight: 600; letter-spacing: .13em; text-transform: uppercase;
  /* Espresso, not champagne: champagne is 2.45:1 here and fails AA for text. */
  color: var(--espresso);
}
.hero-card-traits li + li { border-top: 1px solid rgba(90, 24, 48, .14); }

.evidence-index { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.evidence-index li { margin: 0; }
.evidence-index li + li { border-top: 1px solid rgba(90, 24, 48, .14); }
.evidence-index a {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: baseline;
  column-gap: clamp(8px, 1vw, 14px);
  padding-block: 10px;
  min-height: 44px;
  text-decoration: none; color: inherit;
  border-radius: var(--radius-md);
  transition: background .25s var(--ease);
}
.evidence-index a:hover { background: var(--wine-tint); }
.evidence-num {
  font-family: var(--font-accent); font-size: 12.5px; font-weight: 600;
  color: var(--wine); letter-spacing: .06em; font-variant-numeric: tabular-nums;
}
.evidence-name {
  font-size: clamp(.82rem, .76rem + .2vw, .96rem); font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--espresso);
}
.evidence-role { grid-column: 2; font-size: 12.5px; color: var(--ink-muted); margin-top: 3px; }

/* --- Plane 2: portrait -------------------------------------------------- */
.hero-portrait {
  grid-column: 5 / 9;
  grid-row: 1 / -1;
  z-index: var(--z-portrait);
  align-self: end;
  justify-self: center;
  position: relative;
  margin: 0;
  min-width: 0;
}
/* The cutout is 669x1431 — roughly 1:2.14. Sizing it by WIDTH the way a 4:5
   portrait would be sized produces something over 1200px tall, so it is sized
   by HEIGHT instead, which also keeps it inside the resolution the 669px
   original can actually serve. */
.hero-portrait img {
  width: auto;
  height: auto;
  max-height: min(74vh, 760px);
  max-width: 100%;
  margin-inline: auto;
}
/* Bottom fade as a gradient overlay, never baked into the image: a baked fade
   cannot adapt to viewport height and would destroy the alpha channel. */
.hero-portrait::after {
  content: "";
  position: absolute; inset-inline: 0; bottom: 0;
  height: 20%;
  background: linear-gradient(to bottom, rgba(249, 233, 222, 0) 0%, rgba(249, 233, 222, .92) 88%);
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   8. CREDIBILITY STRIP
   -------------------------------------------------------------------------- */
.strip { border-block: 1px solid var(--line); background: var(--paper); }
.strip-inner {
  display: flex; flex-wrap: wrap; align-items: center; gap: 12px clamp(20px, 4vw, 52px);
  padding-block: 26px;
}
.strip-item {
  font-family: var(--font-accent); font-size: clamp(1rem, 1.5vw, 1.22rem);
  font-weight: 500; letter-spacing: -.01em; color: var(--espresso-soft);
  white-space: nowrap;
}
/* Champagne diamond separators — decorative only, and aria-hidden in the markup,
   so their 2.45:1 ratio never withholds information. */
.strip-sep {
  display: inline-block; width: 6px; height: 6px;
  background: var(--champagne); transform: rotate(45deg);
  font-size: 0; line-height: 0; flex-shrink: 0;
}

/* --------------------------------------------------------------------------
   9. WORK / PROJECT CARDS
   -------------------------------------------------------------------------- */
.work-list { display: grid; gap: clamp(28px, 4vw, 56px); }

.work-card {
  display: grid; gap: clamp(22px, 3.4vw, 52px);
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  align-items: center;
  padding-bottom: clamp(28px, 4vw, 56px);
  border-bottom: 1px solid var(--line);
}
.work-card:last-child { border-bottom: 0; padding-bottom: 0; }
.work-card.reverse { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); }
.work-card.reverse .work-media { order: 2; }
.work-card.reverse .work-body { order: 1; }

.work-media {
  display: block; position: relative; overflow: hidden;
  border-radius: var(--radius-lg); border: 1px solid var(--line);
  background: var(--paper);
}
.work-media img {
  width: 100%; aspect-ratio: 16 / 10; object-fit: cover; object-position: top center;
  transition: transform .7s var(--ease);
}
.work-media:hover img { transform: scale(1.028); }

.work-index {
  font-family: var(--font-accent); font-size: 13px; font-weight: 600;
  color: var(--accent); letter-spacing: .04em; display: block; margin-bottom: 12px;
}
.work-body h3 { font-size: clamp(1.6rem, 2.7vw, 2.2rem); margin-bottom: 8px; }
.work-role { font-size: 13.5px; color: var(--muted); margin-bottom: 16px; }
.work-body > p { color: var(--ink-soft); max-width: 50ch; }

.work-proof { list-style: none; padding: 0; margin: 18px 0 22px; display: grid; gap: 7px; }
.work-proof li { position: relative; padding-left: 20px; font-size: 14.5px; color: var(--ink-soft); margin: 0; }
.work-proof li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: 8px; height: 1px; background: var(--accent);
}

.tag-row { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 20px; }
.tag {
  font-size: 12px; font-weight: 500; letter-spacing: .02em;
  padding: 5px 11px; border-radius: 100px;
  border: 1px solid var(--line-strong); color: var(--muted);
}

/* --------------------------------------------------------------------------
   10. CAPABILITIES
   -------------------------------------------------------------------------- */
.cap-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(248px, 1fr)); gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden; }
.cap {
  background: var(--bg); padding: clamp(26px, 3vw, 38px);
  transition: background .3s var(--ease);
}
.cap:hover { background: var(--paper); }
.cap-num { font-family: var(--font-accent); font-size: 12.5px; color: var(--accent); font-weight: 600; letter-spacing: .06em; }
.cap h3 { margin: 14px 0 10px; font-size: 1.28rem; }
.cap p { font-size: 14.8px; color: var(--ink-soft); margin-bottom: 14px; }
.cap ul { list-style: none; padding: 0; margin: 0; display: grid; gap: 5px; }
.cap li { font-size: 13.5px; color: var(--muted); margin: 0; }

/* --------------------------------------------------------------------------
   11. APPROACH
   -------------------------------------------------------------------------- */
.approach-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: clamp(24px, 3vw, 44px); }
.approach-step { border-top: 2px solid var(--ink); padding-top: 20px; }
.approach-step .step-n { font-family: var(--font-accent); font-size: 12.5px; color: var(--accent); font-weight: 600; letter-spacing: .08em; }
.approach-step h3 { font-size: 1.16rem; margin: 10px 0 8px; }
.approach-step p { font-size: 14.5px; color: var(--ink-soft); margin: 0; }

/* --------------------------------------------------------------------------
   12. DARK BLOCK (visual storytelling)
   -------------------------------------------------------------------------- */
.block-dark { background: var(--dark); color: var(--porcelain); }
.block-dark h2, .block-dark h3 { color: var(--paper); }
.block-dark p { color: rgba(249, 233, 222, .82); }
/* champagne-lite on --dark measures 9.47:1 — safe here, unlike on light grounds */
.block-dark .eyebrow { color: var(--champagne-lite); }
.block-dark .eyebrow::after { background: rgba(249, 233, 222, .28); }
.block-dark .text-link { color: var(--champagne-lite); }
.block-dark .text-link:hover { border-bottom-color: var(--champagne-lite); }
.block-dark .tag { border-color: rgba(249, 233, 222, .26); color: rgba(249, 233, 222, .82); }

.still-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(258px, 1fr)); gap: 16px; }
.still-grid figure { margin: 0; }
.still-grid img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-radius: var(--radius); }
.still-grid figcaption { margin-top: 10px; font-size: 12.5px; color: rgba(249, 233, 222, .72); }

/* --------------------------------------------------------------------------
   13. ABOUT
   -------------------------------------------------------------------------- */
.about-grid { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, .95fr); gap: clamp(34px, 5vw, 76px); align-items: start; }
.about-body p { color: var(--ink-soft); }
.about-body p:first-of-type {
  font-family: var(--font-accent); font-size: clamp(1.2rem, 2vw, 1.5rem);
  line-height: 1.42; color: var(--ink); letter-spacing: -.015em;
}

.toolset { border: 1px solid var(--line); border-radius: var(--radius-lg); padding: clamp(24px, 3vw, 34px); background: var(--paper); }
.toolset h3 { font-size: 13px; font-family: var(--font-body); font-weight: 600; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); margin-bottom: 16px; }
.toolset + .toolset { margin-top: 16px; }
.tool-cloud { display: flex; flex-wrap: wrap; gap: 7px; }

/* --------------------------------------------------------------------------
   14. CONTACT
   -------------------------------------------------------------------------- */
.contact { background: var(--paper); border-top: 1px solid var(--line); }
.contact-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.08fr); gap: clamp(36px, 5vw, 80px); align-items: start; }
.contact-body h2 { margin-bottom: 18px; }
.contact-body p { color: var(--ink-soft); }
.contact-points { list-style: none; padding: 0; margin: 26px 0 0; display: grid; gap: 11px; }
.contact-points li { position: relative; padding-left: 22px; font-size: 15px; color: var(--ink-soft); margin: 0; }
.contact-points li::before { content: ""; position: absolute; left: 0; top: .62em; width: 9px; height: 1px; background: var(--accent); }
/* Direct contact — the form is no longer the only route to a real person. */
.contact-direct { margin-top: 30px; padding-top: 24px; border-top: 1px solid var(--line); }
.contact-direct h3 {
  font-family: var(--font-body); font-size: 12px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
  margin: 0 0 12px;
}
.contact-email { margin: 0 0 14px; font-size: var(--step-0); }
.contact-email a {
  color: var(--wine); font-weight: 600; text-decoration: none;
  border-bottom: 1px solid rgba(90, 24, 48, .3);
  display: inline-flex; align-items: center; min-height: 44px;
}
.contact-email a:hover { border-bottom-color: var(--wine); }
.contact-social { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px 22px; }
.contact-social li { margin: 0; }
.contact-social a {
  display: inline-flex; align-items: center; gap: 6px; min-height: 44px;
  font-size: 14.5px; font-weight: 600; color: var(--terracotta); text-decoration: none;
  border-bottom: 1px solid transparent;
}
.contact-social a:hover { border-bottom-color: var(--terracotta); }

.contact-location { margin-top: 28px; padding-top: 22px; border-top: 1px solid var(--line); font-size: 14px; color: var(--muted); }

.form { display: grid; gap: 17px; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 17px; }
.form label { display: grid; gap: 7px; font-size: 13px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.form input, .form select, .form textarea {
  font-family: var(--font-body); font-size: 16px; color: var(--ink);
  background: var(--bg); border: 1px solid var(--line-strong);
  border-radius: var(--radius); padding: 13px 15px; width: 100%;
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.form input:hover, .form select:hover, .form textarea:hover { border-color: var(--stone-400); }
.form input:focus, .form select:focus, .form textarea:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-tint);
}
.form textarea { resize: vertical; min-height: 132px; }
.form .button { width: 100%; margin-top: 4px; }
.form-note { font-size: 13px; color: var(--muted); text-align: center; margin: 0; }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* --------------------------------------------------------------------------
   15. FOOTER
   -------------------------------------------------------------------------- */
.site-footer { background: var(--dark); color: rgba(249, 233, 222, .82); padding-block: clamp(48px, 6vw, 76px) 34px; }
.footer-grid { display: grid; grid-template-columns: minmax(0, 1.5fr) repeat(3, minmax(0, 1fr)); gap: clamp(24px, 3vw, 44px); }
.site-footer .brand { align-items: center; }
.footer-col a { overflow-wrap: anywhere; }
.site-footer .brand-name { color: var(--paper); }
.site-footer .brand-role { color: rgba(249, 233, 222, .62); }
.footer-tag { margin-top: 16px; font-size: 14.5px; max-width: 34ch; }
.footer-col h3 { font-family: var(--font-body); font-size: 12px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase; color: rgba(249, 233, 222, .68); margin-bottom: 14px; }
/* padding rather than margin, so the 44px minimum is hit area, not dead space */
.footer-col a {
  display: flex; align-items: center; min-height: 44px;
  font-size: 14.5px; text-decoration: none;
  color: rgba(249, 233, 222, .84); transition: color .2s;
}
/* champagne-lite measures 8.1:1 on --dark, so it is safe as a link colour here
   even though it is decorative-only on light grounds. */
.footer-col a:hover { color: var(--champagne-lite); }
.footer-base {
  margin-top: clamp(36px, 5vw, 60px); padding-top: 22px;
  border-top: 1px solid rgba(249, 233, 222, .16);
  display: flex; flex-wrap: wrap; gap: 10px 22px; justify-content: space-between;
  font-size: 13px; color: rgba(249, 233, 222, .66);
}
.footer-base p { margin: 0; }

/* --------------------------------------------------------------------------
   16. MODALS — lightbox & video
   -------------------------------------------------------------------------- */
.modal {
  position: fixed; inset: 0; z-index: 1500;
  display: flex; align-items: center; justify-content: center;
  padding: clamp(16px, 4vw, 56px);
  background: rgba(23, 21, 15, .93);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
}
.modal[hidden] { display: none; }
.modal-body { position: relative; max-width: 100%; max-height: 100%; display: flex; align-items: center; justify-content: center; }
.modal img { max-width: 100%; max-height: 86vh; width: auto; height: auto; object-fit: contain; border-radius: var(--radius); }
.modal-frame { width: min(1040px, 92vw); aspect-ratio: 16 / 9; background: #000; border-radius: var(--radius); overflow: hidden; }
.modal-frame iframe { width: 100%; height: 100%; border: 0; display: block; }
.modal-close {
  position: absolute; top: clamp(12px, 2.4vw, 26px); right: clamp(12px, 2.4vw, 26px);
  width: 46px; height: 46px; border-radius: 50%; cursor: pointer;
  background: rgba(247, 244, 239, .1); border: 1px solid rgba(247, 244, 239, .28);
  color: var(--paper); font-size: 22px; line-height: 1;
  display: grid; place-items: center; transition: background .2s, border-color .2s;
}
.modal-close:hover { background: var(--accent); border-color: var(--accent); }
.modal-caption {
  position: absolute; left: 50%; bottom: clamp(-38px, -4vw, -30px); transform: translateX(-50%);
  font-size: 13px; color: rgba(247, 244, 239, .66); text-align: center;
  width: max-content; max-width: 90vw;
}
body.modal-open { overflow: hidden; }

/* --------------------------------------------------------------------------
   17. VIDEO CARD
   -------------------------------------------------------------------------- */
.video-card {
  position: relative; display: block; width: 100%; padding: 0; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden;
  background: var(--ivory-warm); aspect-ratio: 16 / 9;
  transition: border-color .25s var(--ease);
}
.video-card:hover { border-color: var(--stone-400); }
.video-card img { width: 100%; height: 100%; object-fit: cover; }
.play-button { position: absolute; inset: 0; display: grid; place-items: center; }
.play-button span {
  width: 62px; height: 62px; border-radius: 50%;
  background: rgba(23, 21, 15, .8); border: 1px solid rgba(247, 244, 239, .3);
  display: grid; place-items: center; transition: background .25s var(--ease), transform .25s var(--ease);
}
.play-button span::after {
  content: ""; border-style: solid; border-width: 9px 0 9px 15px;
  border-color: transparent transparent transparent var(--paper); margin-left: 4px;
}
.video-card:hover .play-button span { background: var(--accent); border-color: transparent; transform: scale(1.06); }

/* --------------------------------------------------------------------------
   17b. THE JO STRATEGY THREAD

   A champagne line seeded by the sparkle already sitting in the monogram's
   counter. Purely decorative: it is aria-hidden, it never carries information
   that exists nowhere else, and it is drawn in CSS — the logo file is never
   touched. Because champagne measures 2.45:1 on porcelain, losing it entirely
   (forced colours, no CSS) costs the visitor nothing.

     >= 1280   sticky vertical spine, 32px inside the left gutter
     768-1279  section-header nodes only, no spine
     <= 767    hidden — a spine on a 360px screen competes for scarce width
   -------------------------------------------------------------------------- */
.thread { display: none; }

@media (min-width: 1280px) {
  .thread {
    display: block;
    position: sticky;
    top: 0;
    height: 0;            /* occupies no space in flow */
    z-index: 0;
    pointer-events: none;
  }
  .thread-line {
    position: absolute;
    top: 0;
    left: calc((100vw - var(--container)) / 2 + 32px);
    width: 1px;
    height: 100vh;
    background: linear-gradient(
      to bottom,
      transparent 0%,
      var(--champagne) 10%,
      var(--champagne) 88%,
      transparent 100%
    );
    opacity: .5;
  }
}

/* Nodes marking section starts. The eyebrow is already a flex row with a
   trailing rule, so the node slots in ahead of the label as ◆ LABEL ———— */
@media (min-width: 768px) {
  .section-head .eyebrow::before {
    content: "";
    width: 6px; height: 6px;
    background: var(--champagne);
    transform: rotate(45deg);
    flex-shrink: 0;
    opacity: 1;                                  /* fully drawn without JS */
    transition: opacity var(--dur-mid) var(--ease);
  }
  /* Only dim-then-brighten when JS is actually driving the observer. */
  .js .section-head.reveal:not(.visible) .eyebrow::before { opacity: .35; }
}

/* --------------------------------------------------------------------------
   17c. HERO MOTION

   Three layers, in order of how much they can fail without hurting anyone:

     1. Entrance   — CSS @keyframes, gated on .js so a no-JS visitor sees the
                     finished state immediately rather than a blank hero.
     2. Scroll     — native `animation-timeline: view()`. No JavaScript, no
                     library, no scroll listener. Browsers without it simply
                     get the static composition.
     3. Hover      — pure CSS.

   Everything here sits inside `prefers-reduced-motion: no-preference`, so the
   reduced-motion path is not "animations that finish quickly" but no
   animation at all. Nothing animates the portrait's face, and nothing is
   scrubbed below 768px, per the performance budget.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {

  /* ---- 1. Entrance ------------------------------------------------------ */
  @keyframes hero-rise   { from { opacity: 0; transform: translateY(26px); } to { opacity: 1; transform: none; } }
  @keyframes hero-plate  { from { opacity: 0; transform: scale(1.06); }      to { opacity: .92; transform: none; } }
  @keyframes hero-figure { from { opacity: 0; transform: translateY(34px); } to { opacity: 1; transform: none; } }
  @keyframes hero-card   { from { opacity: 0; transform: translateY(20px) scale(.985); } to { opacity: 1; transform: none; } }

  .js .hero-nameplate,
  .js .hero-identity,
  .js .hero-copy > *,
  .js .hero-support,
  .js .hero-card,
  .js .hero-portrait { animation-duration: var(--dur-slow); animation-timing-function: var(--ease-out); animation-fill-mode: both; }

  .js .hero-nameplate { animation-name: hero-plate; }
  .js .hero-portrait  { animation-name: hero-figure; animation-delay: 120ms; }
  .js .hero-identity  { animation-name: hero-rise;   animation-delay: 220ms; }
  .js .hero-copy > h1 { animation-name: hero-rise;   animation-delay: 300ms; }
  .js .hero-copy > .hero-actions { animation-name: hero-rise; animation-delay: 400ms; }
  .js .hero-support   { animation-name: hero-rise;   animation-delay: 460ms; }
  .js .hero-card-traits   { animation-name: hero-card; animation-delay: 340ms; }
  .js .hero-card-evidence { animation-name: hero-card; animation-delay: 440ms; }

  /* The sparkle discs settle in last, then hold still. */
  @keyframes node-pop { from { opacity: 0; transform: scale(.4) rotate(-45deg); } to { opacity: 1; transform: none; } }
  .js .hero-card-node { animation: node-pop 420ms var(--ease-out) both; animation-delay: 620ms; }

  /* ---- 2. Scroll-linked, desktop only ----------------------------------- */
  /* `view()` drives these from the element's own progress through the viewport.
     No scroll handler runs, so there is nothing to throttle and nothing to leak.
     The budget allows 8 scrubbed elements; this uses 3.

     Each element carries TWO animations — the entrance on the document timeline
     and the drift on a view timeline — declared as parallel lists. Writing the
     drift as a plain `animation:` shorthand instead re-binds the entrance
     keyframes to the scroll timeline, which parks them on their 0% frame and
     leaves the hero invisible until you scroll. */
  @supports (animation-timeline: view()) {
    @media (min-width: 768px) {
      @keyframes plate-drift  { to { transform: translateY(-16%); opacity: .18; } }
      @keyframes figure-drift { to { transform: translateY(-4%) scale(.97); opacity: .5; } }
      @keyframes card-drift   { to { transform: translateY(-24px); opacity: .25; } }

      .js .hero-nameplate {
        animation-name: hero-plate, plate-drift;
        animation-duration: var(--dur-slow), auto;
        animation-timing-function: var(--ease-out), linear;
        animation-fill-mode: both, both;
        animation-timeline: auto, view();
        animation-range: normal, exit -10% exit 90%;
      }
      .js .hero-portrait {
        animation-name: hero-figure, figure-drift;
        animation-duration: var(--dur-slow), auto;
        animation-delay: 120ms, 0s;
        animation-timing-function: var(--ease-out), linear;
        animation-fill-mode: both, both;
        animation-timeline: auto, view();
        animation-range: normal, exit 0% exit 100%;
      }
      .js .hero-card-traits {
        animation-name: hero-card, card-drift;
        animation-duration: var(--dur-slow), auto;
        animation-delay: 340ms, 0s;
        animation-timing-function: var(--ease-out), linear;
        animation-fill-mode: both, both;
        animation-timeline: auto, view();
        animation-range: normal, exit 0% exit 80%;
      }
      .js .hero-card-evidence {
        animation-name: hero-card, card-drift;
        animation-duration: var(--dur-slow), auto;
        animation-delay: 440ms, 0s;
        animation-timing-function: var(--ease-out), linear;
        animation-fill-mode: both, both;
        animation-timeline: auto, view();
        animation-range: normal, exit 0% exit 80%;
      }
    }
  }

  /* ---- 3. Hover ---------------------------------------------------------- */
  .hero-card { transition: transform var(--dur-mid) var(--ease), box-shadow var(--dur-mid) var(--ease); }
  .hero-card:hover { transform: translateY(-4px); box-shadow: 0 2px 4px rgba(47,19,29,.05), 0 16px 36px rgba(47,19,29,.1); }
  .button span { transition: transform var(--dur-mid) var(--ease); }
  .button:hover span { transform: translate(2px, -2px); }
}

/* --------------------------------------------------------------------------
   18. MOTION
   -------------------------------------------------------------------------- */
.reveal { opacity: 1; transform: none; }
/* Only animate once JS has confirmed it is running (class set inline in <head>,
   so there is no flash of hidden content). Without JS, content is simply visible. */
/* The same long, soft settle the homepage uses: a little further to travel, a
   slower ease-out, and a per-element --rd stagger (script.js, from each
   element's place among its siblings) so groups arrive as a wave. */
.js .reveal {
  opacity: 0; transform: translateY(26px);
  transition: opacity .8s var(--ease-out) var(--rd, 0s), transform .95s var(--ease-out) var(--rd, 0s);
}
.js .reveal.visible { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; transition-delay: 0s !important;
  }
  .reveal, .js .reveal { opacity: 1 !important; transform: none !important; }
  .button:hover, .work-media:hover img, .play-button span { transform: none !important; }
  /* The thread renders fully drawn and static — never a fade-in, never a pulse. */
  .js .section-head.reveal:not(.visible) .eyebrow::before { opacity: 1 !important; }
  .thread-line { opacity: .5 !important; }
}

/* --------------------------------------------------------------------------
   19. RESPONSIVE

   Breakpoints: 360 · 390 · 430 · 768 · 1024 · 1280 · 1440 · 1920.
   Mobile is composed, not shrunk — the hero reorders so the positioning
   statement is read before the portrait, which is the single most important
   correction to the reference design.
   -------------------------------------------------------------------------- */

/* Large desktop — widen the measure, don't just centre a narrow column */
@media (min-width: 1440px) {
  :root { --container: 1340px; }
  body { font-size: 17.5px; }
}
@media (min-width: 1800px) {
  :root { --container: 1440px; }
}

/* ---- Tablet landscape ---------------------------------------------------- */
@media (max-width: 1024px) {
  .about-grid { grid-template-columns: 1fr; }
  .footer-grid { grid-template-columns: 1fr 1fr; }

  /* Cards pull toward the edges as the middle narrows, so they never ride over
     the portrait's face. Verified by the face-safety check at every width. */
  .hero-card-traits   { grid-column: 1 / 5; }
  .hero-card-evidence { grid-column: 9 / 13; }
  .hero-copy    { grid-column: 1 / 7; }
  .hero-support { grid-column: 8 / 13; }
  .hero-portrait img { max-height: min(58vh, 560px); }
}

/* ---- Tablet portrait: cards sit below the copy, portrait stays centred ---- */
@media (max-width: 900px) and (min-width: 768px) {
  /* The identity pair travels with .hero-copy now, so it needs no placement. */
  .hero-inner { grid-template-rows: auto auto auto auto; }
  .hero-portrait      { grid-column: 4 / 10; grid-row: 1 / 4; }
  .hero-copy          { grid-column: 1 / 8;  grid-row: 2; }
  .hero-support       { grid-column: 1 / 9;  grid-row: 3; margin-top: 24px; align-self: start; }
  .hero-card-traits   { grid-column: 1 / 5;  grid-row: 4; margin-top: 26px; }
  .hero-card-evidence { grid-column: 7 / 13; grid-row: 4; margin-top: 26px; }
}

/* ---- Below 768: drawer nav, single-column hero --------------------------- */
@media (max-width: 767px) {
  html { scroll-padding-top: 84px; }
  /* Tighter pill on phones: 10px from the top, 12px from each side. It is a
     touch more opaque from scroll 0 — there is less room here for the page to
     read through it. */
  .site-header { --pill-top: 10px; }
  .site-header .nav-wrap {
    width: calc(100% - 24px);
    min-height: 58px;
    padding-inline: 16px 7px;
  }
  .site-header .nav-wrap::before { background: rgba(253, 249, 243, .72); }

  /* Logo alone; the wordmark's meaning is carried by the visually-hidden span */
  .brand-name { display: none; }
  .brand-logo { width: 38px; height: 28px; }

  .menu-toggle { display: block; }
  /* The drawer drops out of the pill as a second, taller sheet of the same
     glass, with the same 12px inset — not a full-bleed slab. */
  .site-nav {
    position: fixed; top: 76px; left: 12px; right: 12px; z-index: 890;
    flex-direction: column; align-items: stretch; gap: 0;
    background: rgba(253, 249, 243, .9);
    -webkit-backdrop-filter: saturate(170%) blur(22px);
    backdrop-filter: saturate(170%) blur(22px);
    border: 1px solid rgba(90, 24, 48, .09); border-radius: 26px;
    box-shadow: 0 18px 48px rgba(40, 19, 29, .16);
    padding: 6px 20px 20px;
    max-height: calc(100dvh - 92px); overflow-y: auto;
    transform: translateY(-8px); opacity: 0; visibility: hidden;
    /* visibility interpolates DISCRETELY, so `visibility .25s` would leave the
       drawer computed-hidden for the first 125ms — long enough that focusing
       the first link on open silently failed and left the user on <body>.
       Closing: hold visible for the fade, then hide (0s duration, .25s delay).
       Opening: flip to visible immediately (0s duration, 0s delay). */
    transition: opacity .25s var(--ease), transform .25s var(--ease), visibility 0s .25s;
  }
  .site-nav.open {
    transform: none; opacity: 1; visibility: visible;
    transition: opacity .25s var(--ease), transform .25s var(--ease), visibility 0s 0s;
  }
  .site-nav a:nth-last-child(2) { border-bottom: 0; }
  .site-nav a:not(.nav-cta) { justify-content: flex-start; }
  .site-nav a { padding: 15px 0; border-bottom: 1px solid var(--line); font-size: 17px; min-height: 44px; display: flex; align-items: center; }
  .site-nav a:not(.nav-cta)[aria-current="true"]::after { display: none; }
  .nav-cta { margin-top: 16px; justify-content: center; border-bottom: 0 !important; }

  /* HERO ORDER SWAP — the required fix, kept from the previous build.
     nameplate → identity → headline + CTAs → lede → portrait → cards.
     The reference puts the headline roughly two-thirds down the composition;
     below 768px it is among the first things read. */
  .hero-inner { grid-template-columns: minmax(0, 1fr); grid-template-rows: repeat(5, auto); }
  .hero-nameplate     { grid-column: 1; grid-row: 1; opacity: .5; text-align: left; }
  .hero-copy          { grid-column: 1; grid-row: 2; margin-top: 18px; }
  .hero-support       { grid-column: 1; grid-row: 3; margin-top: 24px; align-self: start; }
  .hero-portrait      { grid-column: 1; grid-row: 4; justify-self: center; margin-top: clamp(18px, 4vw, 32px); }
  .hero-card-traits   { grid-column: 1; grid-row: 5; margin-top: 20px; }
  .hero-card-evidence { grid-column: 1; grid-row: 6; margin-top: 16px; }

  .hero-portrait img { max-height: min(48vh, 430px); }

  /* Translucency is dropped below 768 — a blurred ground on a small screen
     costs legibility and compositing cost for no visual gain. */
  .hero-card {
    background: var(--paper);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  /* The corner disc would collide with the gutter at this width. */
  .hero-card-node { top: -14px; inset-inline-start: auto; inset-inline-end: 16px; width: 36px; height: 36px; }
  .hero-card-node::after { width: 16px; height: 16px; }

  .work-card, .work-card.reverse { grid-template-columns: 1fr; gap: 22px; }
  .work-card.reverse .work-media { order: 1; }
  .work-card.reverse .work-body { order: 2; }

  .contact-grid { grid-template-columns: 1fr; }

  /* Service strip becomes a scroll-snap row rather than wrapping into a block */
  .strip-inner {
    flex-wrap: nowrap; overflow-x: auto; padding-block: 20px;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .strip-inner::-webkit-scrollbar { display: none; }
  .strip-item { scroll-snap-align: start; font-size: 1rem; }  /* never below 16px */
}

@media (max-width: 640px) {
  body { font-size: 16.5px; }
  h1 { font-size: clamp(2.15rem, 9vw, 3rem); }
  .form-row { grid-template-columns: 1fr; }
  .footer-grid { grid-template-columns: 1fr; }
  .modal-close { top: 8px; right: 8px; width: 42px; height: 42px; }
  .modal-caption { display: none; }
}

/* ---- CTAs stack full-width; primary always first -------------------------- */
@media (max-width: 560px) {
  .hero-actions { flex-direction: column; align-items: stretch; }
  .hero-actions .button { width: 100%; }
}

@media (max-width: 400px) {
  :root { --gutter: 18px; }
  .play-button span { width: 52px; height: 52px; }
}

/* --------------------------------------------------------------------------
   20. PRINT
   -------------------------------------------------------------------------- */
@media print {
  .site-header, .menu-toggle, .modal, .site-footer, .hero-actions,
  .thread, .hero-nameplate { display: none !important; }
  body { background: #fff; color: #000; font-size: 11pt; }
  .reveal, .js .reveal { opacity: 1 !important; transform: none !important; }
  .hero-portrait img { max-height: 90mm; }
}

/* --------------------------------------------------------------------------
   21. UTILITIES
   Small, named helpers used in place of inline style attributes.
   -------------------------------------------------------------------------- */
.u-mt-lg      { margin-top: 26px; }

/* CTA groups. Buttons offered together are one unit: one width, one height,
   one 12px gap. Equal 1fr tracks on a grid sized to fit its content make each
   button as wide as the longest label in the group, so a primary and its
   secondary always match; on phones the same grid becomes one full-width
   column. A button standing on its own is not a group and keeps its content
   width. (Buttons used to sit loose inside a <p>, where the space between
   wrapped rows was line-height rather than a gap — zero, on Lemon Refresh.) */
.cta-group, .case-links, .centered-actions {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: 12px; width: fit-content; max-width: 100%;
}
.cta-group > .button, .case-links > .button, .centered-actions > .button { width: 100%; }
.centered-actions { margin-inline: auto; }
.case-links + p { margin-top: 24px; }

/* Many short buttons (the iBlast reels): an even grid rather than one row. */
.cta-group--grid {
  grid-auto-flow: row; grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr));
  width: 100%; max-width: 44rem;
}
.cta-group--grid > .button { padding-inline: 16px; }

@media (max-width: 640px) {
  .cta-group, .case-links, .centered-actions { grid-auto-flow: row; width: 100%; max-width: 26rem; }
  .cta-group--grid { max-width: none; }
  /* The full CTA shadow is tuned for a button sitting on its own. Stacked at
     12px it reached the next control, so the shadow is the fix, not the gap. */
  .cta-group > .button-primary, .case-links > .button-primary, .centered-actions > .button-primary {
    box-shadow: 0 1px 3px rgba(90, 24, 48, .16), 0 5px 12px rgba(90, 24, 48, .10);
  }
  .case-links + p { margin-top: 26px; }
}
.u-my-md      { margin-block: 24px; }
.u-my-lg      { margin-block: 26px; }
.u-pt-0       { padding-top: 0; }
.u-border-b-0 { border-bottom: 0; }
.u-flush      { margin-top: 0; border-top: 0; }
.u-center     { justify-content: center; }
.u-measure    { max-width: 70ch; }
.u-note       { font-size: 14px; color: var(--muted); }
.u-note-lg    { font-size: 15px; color: var(--muted); }
/* min() with 100% matters on both of these. A bare `max-width: 640px` on a class
   beats the base `img { max-width: 100% }` on specificity, so the iBlast palette
   swatch rendered at a hard 640px and pushed 298px of horizontal overflow at
   360px wide. Capping against the container as well as the pixel value fixes it. */
.u-figure-sm  { max-width: min(360px, 100%); }
.u-swatch     { max-width: min(640px, 100%); height: auto; border-radius: var(--radius-lg); border: 1px solid var(--line); }
