/* ════════════ HOME ════════════ */

/* 1 · Hero ─────────────────────────────────────────────────── */
/* Pinned by ScrollTrigger for the long evaporation; fills exactly one
   viewport at rest so nothing below peeks (fix #3, #6). */
/* hero-colored backdrop so the black filler dome reads against it as it rises
   over the pinned hero's released space (fix #3.2 contrast) */
.stick-wrap { position: relative; background: var(--c-primary); }
.hero {
  position: relative;
  height: 100svh;
  background: var(--c-primary);
  display: flex; align-items: flex-end;
  padding: 0 var(--pad-x) clamp(3rem, 8vh, 7rem);
  overflow: hidden;
}
.hero__canvas {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
}
.hero__title {
  position: relative;
  font-family: var(--font-ui);
  font-weight: 500;
  font-size: clamp(30px, 11vw, 168px);   /* low floor → CAMPAIGN never overflows at 320–430px */
  line-height: 0.92;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--c-white);
}
.hero__line { display: block; }
/* ELEVATE / YOUR — Ivy Presto Italic (fix #3c) */
.hero__line--ital {
  font-family: var(--font-display);
  font-weight: 300;
  font-style: italic;
  letter-spacing: 0;
}
/* CAMPAIGN — wide, even tracking via flex `gap` (transform-safe: hover nudges
   don't reflow, so g/n stay pinned and the word's width never changes) (fix #3) */
.hero__line--camp {
  display: flex; align-items: baseline; flex-wrap: nowrap;
  gap: 0.16em;
}
.hero__line--camp .ltr {
  display: inline-block;
  flex: none;                      /* letters never shrink/reflow in overflow states */
  will-change: transform;          /* hover nudges use transform only — no reflow */
}
/* the 2nd "a" and "i" sit tighter at rest so the word reads evenly (fix #2.1);
   negative margin only adjusts the resting layout — hover uses transforms */
.hero__line--camp .ltr--ai { margin-inline: -0.05em; }
/* down arrow, right edge of the hero. Heavier stroke to balance the headline
   weight; its box bottom matches the hero padding-bottom so the arrow tip
   aligns with the bottom of CAMPAIGN (fix). */
.hero__arrow {
  position: absolute;
  right: var(--pad-x);
  bottom: clamp(3rem, 8vh, 7rem);   /* = hero's padding-bottom → aligns with CAMPAIGN's bottom */
  z-index: 2;
  height: clamp(42px, 5vw, 78px);
  width: auto;
  color: var(--c-white);
  overflow: visible;
  animation: bob 2.4s var(--ease-io) infinite;
}
@keyframes bob { 0%,100%{transform:translateY(0)} 50%{transform:translateY(6px)} }

/* 2 · Filler ───────────────────────────────────────────────── */
/* A wide, shallow convex dome spans the full top edge (fix #3.2). Built from
   an over-wide pseudo-element with a large elliptical radius so only the gentle
   apex of the arc shows across the viewport — the side "shoulders" sit
   off-screen. Rises over the pinned hero, revealing its color behind the arc. */
.filler {
  position: relative;
  z-index: 1;
  background: var(--c-ink);
  padding: clamp(7rem, 18vh, 16rem) var(--pad-x);
}
.filler::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 100%;                 /* flat bottom sits flush on the filler's top edge */
  transform: translateX(-50%);
  width: 150vw;                 /* much wider than the viewport */
  height: 20vw;                 /* dome rise pool */
  background: var(--c-ink);
  /* round ONLY the top corners → flat flush bottom, smooth convex dome on top */
  border-radius: 50% 50% 0 0 / 100% 100% 0 0;
  pointer-events: none;
}
.filler__stack {
  font-size: clamp(48px, 11vw, 168px);
  line-height: 0.98;
  text-transform: uppercase;
  font-weight: 500;
}
.filler__line { display: block; }
.filler__line--r { text-align: right; }
.filler__lead { font-family: var(--font-display); font-style: italic; font-weight: 300; text-transform: none; }
.filler__word { font-family: var(--font-ui); font-weight: 500; }

/* 2b · Engineered filler (new) — black bg, copy right on web, centered mobile */
.engineered {
  background: var(--c-ink);
  padding: var(--section) var(--pad-x);
  display: flex;
  justify-content: flex-end;
}
.engineered__inner { max-width: 560px; text-align: left; }
.engineered__title {
  font-family: var(--font-ui); font-weight: 500;
  font-size: var(--t-md);
  line-height: 1.25;
  margin-bottom: 1.4rem;
}
.engineered__copy {
  color: var(--c-grey-mid);
  font-size: var(--t-body);
  line-height: 1.6;
}

/* 3 · Featured banner ──────────────────────────────────────── */
.feat-banner {
  position: relative;
  min-height: 86vh;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: var(--section) var(--pad-x) clamp(2.5rem, 6vh, 4rem);  /* tight to works (fix #6.2) */
  overflow: hidden;
}
.feat-banner__bg { position: absolute; inset: 0; z-index: 0; }
.feat-banner__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.feat-banner__bg::after { content:""; position:absolute; inset:0; background: rgba(16,16,21,0.45); }
.feat-banner__inner { position: relative; z-index: 1; }
.feat-banner__title {
  font-size: clamp(56px, 12vw, 200px);
  line-height: 0.9; text-transform: uppercase;
}
/* FEATURED renders all caps (inherits uppercase — no more text-transform:none) (fix #6.1) */
.feat-banner__title .l1 { font-family: var(--font-display); font-style: italic; font-weight: 300; }
.feat-banner__title .l2 { font-family: var(--font-ui); font-weight: 500; }
.feat-banner__nda {
  max-width: 52ch; margin-top: 1.5rem;
  font-family: var(--font-display); font-style: italic; font-weight: 300;
  font-size: var(--t-sm); color: var(--c-grey-mid);
}
/* the Instagram link inside the NDA line needs a visible affordance (the
   global reset strips underlines) */
.feat-banner__nda a {
  text-decoration: underline;
  text-underline-offset: 2px;
  transition: color .3s;
}
.feat-banner__nda a:hover { color: var(--c-white); }

/* 4 · Featured works — full-width sticky stack ──────────────── */
/* Smooth sticky stack — each project pins full-bleed while the next rises over
   it; no scroll-snap / no page lock. The deceleration + end-of-image zoom is
   driven in home.js (jomor-style luxury feel). */
.works { position: relative; z-index: 1; background: var(--c-ink); }
.work-row {
  position: sticky; top: 0;
  height: 100vh;            /* legacy fallback: pre-iOS-15.4 has no svh */
  height: 100svh;
  width: 100%;
  overflow: hidden;
  isolation: isolate;   /* contain the title's blend mode — no cursor compositing fights */
}
.work-row__media { position: absolute; inset: 0; height: 100%; width: 100%; will-change: transform; }

/* real image fills the row; vignette ::after still layers over it */
.work-row__img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* scroll-driven darken layer (opacity 0 → ~.15 in home.js) */
.work-row__shade {
  position: absolute; inset: 0;
  background: #000;
  opacity: 0;
  pointer-events: none;
}
.work-row__media::after {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(120% 90% at 50% 50%, transparent 40%, rgba(16,16,21,0.55) 100%);
}
.work-row__title {
  position: absolute; inset: 0; z-index: 2;
  display: grid; place-items: center; text-align: center;
  padding: 0 var(--pad-x);
  font-family: var(--font-display); font-style: italic; font-weight: 300;
  font-size: clamp(40px, 7vw, 112px); line-height: 1;
  color: var(--c-white);
  mix-blend-mode: difference;
  pointer-events: none;        /* never intercept the pointer (cursor glitch fix) */
  transform: translateZ(0);    /* own compositing layer — no repaint thrash */
}
/* ── iOS + mobile: static work-stack panels ──────────────────
   The media no longer animates in these environments (see js/home.js), so the
   permanent compositor promotion is pure cost — WebKit holds a full-size layer
   backing store per row for multi-megapixel images. Chrome desktop AND desktop
   Safari keep the promotion because they keep the animation. */

/* ── WebKit: drop the colour inversion in the work stack — plain white ──
   is-safari covers desktop Safari AND iOS. mix-blend-mode also forces WebKit
   off the fast compositing path (the backdrop — four stacked sticky
   full-viewport panels of 2.7–3.2 MP imagery — must be re-blended every frame),
   so removing it helps performance as well as appearance. The nav keeps its
   inversion; this is scoped to the work stack only. Chrome matches neither
   class and is unaffected. */
html.is-safari .work-row__title,
html.is-safari .works__counter { mix-blend-mode: normal; }

/* step counter — shown only while the works section is on screen */
.works__counter {
  position: fixed; right: var(--pad-x); bottom: clamp(2rem, 5vh, 3rem);
  z-index: 50;
  display: flex; align-items: baseline; gap: 0.15em;
  font-family: var(--font-ui); font-weight: 500;
  font-size: clamp(13px, 1.4vw, 18px); letter-spacing: 0.18em; color: var(--c-white);
  mix-blend-mode: difference;
  opacity: 0; transform: translateY(8px);
  transition: opacity .5s var(--ease-expo), transform .5s var(--ease-expo);
  pointer-events: none;
}
.works__counter.is-active { opacity: 1; transform: none; }
.works__counter-sep { opacity: 0.5; margin: 0 0.1em; }
#work-index { display: inline-block; min-width: 1ch; text-align: right; }

/* ════════════════════════════════════════════════════════════
   HOME · mobile safety net — viewport containment ONLY
   Everything else on mobile runs the desktop code path. These rules exist
   solely so nothing on the home page is wider than the phone screen: iOS
   sizes its layout viewport to the widest scrollable content and then zooms
   the whole page out to fit, which is what produced the ~66%-width render
   with a black band down the right side.
   Scoped to home-page elements only — no other page is affected.
   ════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
  /* ── Filler overlays the hero (mobile) ──────────────────────────
     Pull the filler up by one viewport so it rides OVER the hero instead of
     following it down the page. Achieved entirely from this section: it
     already carries position:relative, z-index:1 (above the hero's auto) and
     an opaque background, so once it overlaps it paints on top and the dome
     sweeps up across CAMPAIGN, "AI" and the arrow.
     Nothing about the hero — position, height, canvas or particle code — is
     touched, so the dissolve keeps reading the same scroll offset.
     svh, not vh: the iOS address bar must not resize the overlap mid-scroll.
     The negative margin also shortens the page by the same amount, so the
     sections below shift up together and no gap opens. */
  .filler { margin-top: -100svh; }

  /* The filler now overlaps the hero, so every section that follows sits over
     the hero's box too — and .engineered was `static`, which paints BELOW a
     positioned element. Its background is already opaque black and it butts
     flush against the filler (measured gap: 0px), so nothing was see-through:
     the hero was simply painting on top of it, showing the headline and arrow.
     Lifting it into the same layer as .filler puts its black fill above the
     hero. (.feat-banner below it is already position:relative and later in the
     DOM, so it paints above the hero correctly — verified, left alone.) */
  .engineered { position: relative; z-index: 1; }

  /* ── Work stack: pure CSS sticky, one unit (svh), no JS ──────────
     The GSAP pin is desktop-only now (js/home.js). On touch the four panels
     pin and cover natively: each .work-row is position:sticky; top:0 in normal
     document flow, and .works has no explicit height — the browser derives it.
     Verified no sticky-breaker on any ancestor: html, body and .works carry no
     transform / filter / opacity / backdrop-filter / contain. (.work-row's own
     overflow:hidden does not affect its own stickiness.)

     Runway for the LAST panel: .works is exactly 4 x 100svh, so row 4 reaches
     the top with zero scroll remaining and can never hold. This in-flow spacer
     adds one viewport of real content, which is exactly one viewport of extra
     stick range for row 4 — no scroll math, no pin, no container height.

     The matching pull on Solutions is what makes it COVER rather than push:
     without it, Solutions only reaches the fold as row 4's hold ends. It does
     not eat the last panel's runway — the spacer above grants that runway and
     this reclaims the same amount, so the two cancel and the document ends
     exactly at the footer (no leftover strip). Both values are 100svh, so they
     cannot drift apart. */
  .works::after { content: ""; display: block; height: 100vh; height: 100svh; }
  .solutions-direct {
    margin-top: -100vh;
    margin-top: -100svh;
    /* The pull can only be absorbed if what follows is at least as tall as it.
       Solutions was 352px against a 932px pull on a tall phone, so .works ran
       175px past the footer — the strip showing the panel. Giving Solutions a
       full viewport guarantees (Solutions + footer) > the pull at EVERY size,
       so the document always ends exactly at the footer. It also means
       Solutions covers row 4 completely rather than partially. */
    min-height: 100vh;
    min-height: 100svh;
  }

  /* Footer stacking. .works is `position: relative; z-index: 1`, so its pinned
     panels paint above a `static` element — and .footer had no position and no
     z-index at all. That is why "Current" showed in a band between Explore
     Solutions and the footer: not a missing fill (the footer's background is
     already opaque --c-ink), but the panel painting OVER it. Explore Solutions
     covers correctly precisely because it is positioned with z-index 2; giving
     the footer the same treatment puts it in that layer, and being later in the
     DOM it paints above. Geometry/ordering fix — no extra background added. */
  .footer { position: relative; z-index: 2; }

  /* the dome pseudo-elements are the actual overflow source: 150vw overhangs
     the viewport by 25vw on EACH side. Full width instead — no overflow, and
     the arc still spans edge to edge. (Clipping the parent is not an option:
     the dome sits entirely outside its host's box, so a clip erases it.) */
  .filler::before {
    width: 100%; left: 0; transform: none;
    height: clamp(32px, 9vw, 56px);
  }
  /* nothing else may exceed the viewport either */
  .hero,
  .hero__canvas,
  .works,
  .work-row,
  .work-row__media,
  .work-row__img { max-width: 100%; }
  /* the canvas is sized in JS from the hero's measured rect — keep its CSS box
     inside the viewport regardless of the backing-store pixel width */
  .hero__canvas { width: 100%; }
}
