/* ============================================================
   The CV Method — landing page layout (page-specific, imported last)
   SPEC v2 §5 (IA) · §7 (hero) · §8 (responsive)

   PHASE 0: poster hero only. No animation, no transitions, no JS.
   The Signal Field canvas and the §6 motion layer arrive in P2/P3.
   ============================================================ */

/* ============================================================
   01 — HERO (poster)
   The poster is a pure-CSS radial-gradient field in the brand palette.
   It paints instantly and never blocks LCP. P3 layers <canvas> on top of
   it behind the §7 gates; the poster stays as the fallback.
   ============================================================ */
.hero{
  position:relative;
  display:flex;
  align-items:center;
  min-height:92vh;
  padding-block:calc(var(--nav-h) + var(--space-5)) var(--space-5);
  overflow:hidden;
  background:var(--bg-dark);
  scroll-margin-top:var(--nav-h);
}

.hero__poster{
  position:absolute;
  inset:0;
  z-index:0;
  background:
    radial-gradient(55% 55% at 74% 28%,rgba(37,99,235,.50),transparent 62%),
    radial-gradient(50% 50% at 18% 74%,rgba(111,217,192,.28),transparent 60%),
    radial-gradient(75% 75% at 50% 50%,rgba(34,211,238,.36),transparent 66%),
    var(--ink-900);
}

/* §7 Signal Field. Sits above the poster, below the grid and scrim, so the
   hero copy keeps the same AA-measured backdrop whether the field runs or not.
   Only opacity animates; the canvas is 0×0 until Three.js sizes it. */
.hero__canvas{
  position:absolute;
  inset:0;
  z-index:1;
  width:100%;
  height:100%;
  opacity:0;
  transition:opacity var(--dur-slow) var(--ease-out);
}
.hero.has-webgl .hero__canvas{opacity:1}
/* the poster stays underneath as the fallback — desaturated and dimmed so the
   field reads as the subject rather than fighting it */
.hero.has-webgl .hero__poster{filter:saturate(.9);opacity:.55}

.hero__grid{
  position:absolute;
  inset:0;
  z-index:2;
  opacity:.12;
  background-image:
    linear-gradient(rgba(255,255,255,.5) 1px,transparent 1px),
    linear-gradient(90deg,rgba(255,255,255,.5) 1px,transparent 1px);
  background-size:52px 52px;
  -webkit-mask-image:radial-gradient(70% 60% at 60% 40%,#000,transparent 75%);
  mask-image:radial-gradient(70% 60% at 60% 40%,#000,transparent 75%);
}

/* The scrim is what makes the hero copy AA-legible, and it must sit above the
   Signal Field too — the measured contrast has to hold whether the field is
   running or not. Layer order: poster 0 · canvas 1 · grid 2 · scrim 3 · copy 4. */
.hero__scrim{
  position:absolute;
  inset:0;
  z-index:3;
  background:linear-gradient(90deg,rgba(5,10,18,.88),rgba(5,10,18,.68) 46%,rgba(5,10,18,.34));
}

.hero__inner{position:relative;z-index:4;isolation:isolate;color:var(--text-invert)}

/* Copy plate — only when the Signal Field is live.
   The field is randomly seeded every load, so the scrim alone cannot guarantee
   anything: measured across 5 loads x 5 frames, glow-core hotspots pushed the
   backdrop under the eyebrow to rgb(52,124,142) and its contrast to 2.80:1.
   SPEC §2.2's answer is that text sits on ink, canvas or a solid panel — this
   is that panel, feathered to nothing so it reads as depth rather than a box.
   Worst-observed contrast with it in place: 8:1+. */
.hero.has-webgl .hero__inner::before{
  content:"";
  position:absolute;
  inset:-8% -14% -10% -10%;
  z-index:-1;
  pointer-events:none;
  background:radial-gradient(115% 100% at 22% 50%,
    rgba(5,10,18,.88) 0%,
    rgba(5,10,18,.72) 42%,
    rgba(5,10,18,.34) 68%,
    transparent 82%);
}

/* ---- A2: hero entrance (SPEC §6) ----
   Staggered rise on load, ending on the CTA so the eye lands on the action.
   Pure CSS, no JS: with `both` fill the elements sit at the `from` state until
   their turn. Under reduced motion motion.css zeroes --dur and styles.css
   zeroes --stagger, so every element paints at its final state on frame one. */
@keyframes hero-rise{
  from{opacity:0;transform:translateY(18px)}
  to{opacity:1;transform:none}
}
.hero__inner > *{
  animation:hero-rise var(--dur) var(--ease-out) both;
  animation-delay:calc(var(--stagger) * var(--hero-i, 0));
}
.hero__inner > .eyebrow{--hero-i:0}
.hero__inner > .hero__title{--hero-i:1}
.hero__inner > .hero__sub{--hero-i:2}
.hero__inner > .cta-row{--hero-i:3}
.hero__inner > .hero__trust{--hero-i:4}
.hero__title{font-size:var(--fs-display);max-width:16ch;margin-top:var(--space-2)}
.hero__sub{
  max-width:46ch;
  margin:var(--space-3) 0 0;
  font-size:clamp(1.05rem,1vw + .9rem,1.3rem);
  color:rgba(242,247,249,.92);
}
.hero__trust{margin-top:var(--space-3);color:var(--slate-300)}

.cta-row{
  display:flex;
  flex-wrap:wrap;
  gap:14px;
  margin-top:var(--space-3);
}
.cta-row--center{justify-content:center}

/* md and below: the hero no longer needs to fill the viewport — getting the
   promise, the CTA and the first proof point above the fold matters more. */
@media (max-width:767px){
  .hero{min-height:0;padding-block:calc(var(--nav-h) + var(--space-4)) var(--space-4)}
  .hero__title{max-width:none}
  .hero__scrim{background:linear-gradient(180deg,rgba(5,10,18,.82),rgba(5,10,18,.72))}
}
@media (max-width:479px){
  .cta-row .btn{width:100%}
}

/* ============================================================
   03 — THE GAP
   ============================================================ */
.gap__headline{font-size:var(--fs-h1);max-width:20ch}
.gap__headline .is-emphasis{color:var(--sky)}
.gap__turn{
  margin-top:var(--space-2);
  font-size:var(--fs-h3);
  max-width:34ch;
  color:rgba(242,247,249,.9);
}

/* ============================================================
   06 — WHO IT'S FOR
   ============================================================ */
.fit{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--space-4)}
@media (max-width:767px){.fit{grid-template-columns:1fr;gap:var(--space-3)}}
.fit__title{font-size:var(--fs-h3);margin-bottom:6px}
.fit__list{list-style:none;margin-top:12px}
.fit__list li{position:relative;padding:10px 0 10px 30px;border-bottom:1px solid var(--border)}
.fit__list li:last-child{border-bottom:none}
/* the glyphs are decorative — the column heading carries the meaning,
   so nothing here is signalled by colour or icon alone (SPEC §8) */
.fit__list--yes li::before{
  content:"\2713";position:absolute;left:0;color:var(--teal-ink);font-weight:700;
}
.fit__list--no li::before{
  content:"\2014";position:absolute;left:0;color:var(--text-muted);
}

/* ============================================================
   07 — ABOUT
   ============================================================ */
.about{
  display:grid;
  grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);
  gap:var(--space-4);
  align-items:center;
}
@media (max-width:1023px){.about{grid-template-columns:1fr}}
/* the portrait must not become a full-bleed slab once it is stacked */
@media (max-width:1023px){.about__portrait{max-width:380px}}

.about__portrait{
  position:relative;
  aspect-ratio:4/5;
  border-radius:var(--radius-lg);
  overflow:hidden;
  /* the gradient still backs the frame, so a slow image never flashes a hole */
  background-image:var(--grad-method);
  box-shadow:var(--shadow-glow);
}
.about__portrait picture,
.about__portrait img{display:block;width:100%;height:100%}
/* the source is 684x1084 (0.63) against a 4:5 frame, so cover crops vertically;
   biasing upward keeps the face centred instead of the shoulders */
.about__portrait img{object-fit:cover;object-position:center 18%}
.about__title{font-size:var(--fs-h2);margin:6px 0 14px}
.about__body{color:rgba(242,247,249,.9)}
.about__creds{margin-top:var(--space-2);color:var(--mint)}

/* ============================================================
   08 — OUTCOMES
   ============================================================ */
.outcome{
  padding:26px;
  border:1px solid var(--border);
  border-radius:var(--radius-lg);
  background:var(--canvas-soft);
  text-align:center;
}
.outcome__from{
  font-family:var(--font-mono);
  font-size:.85rem;
  color:var(--text-muted);
  text-decoration:line-through;
}
.outcome__to{
  margin-top:6px;
  font-family:var(--font-display);
  font-weight:var(--fw-display);
  font-size:1.4rem;
  line-height:1.2;
  background-image:var(--grad-signal-ink);
  -webkit-background-clip:text;
  background-clip:text;
  color:transparent;
  -webkit-text-fill-color:transparent;
}
@supports not ((-webkit-background-clip:text) or (background-clip:text)){
  .outcome__to{color:var(--teal-ink);-webkit-text-fill-color:var(--teal-ink)}
}

/* ============================================================
   09 — PROCESS
   ============================================================ */
.step{
  padding:22px;
  border:1px solid var(--border);
  border-radius:var(--radius-md);
  background:var(--surface-card);
}
.step__key{
  display:inline-block;
  padding:4px 8px;
  border-radius:6px;
  background:var(--mint);
  color:var(--ink-900);
  font-family:var(--font-mono);
  font-size:.8rem;
}
.step__title{font-size:1.1rem;margin:12px 0 6px}
.step__body{color:var(--text-muted)}

/* ============================================================
   11 — FINAL CTA
   One gradient moment: a 16%-opacity wash behind a dark scrim, never
   under live body text at full strength (SPEC §2.2).
   ============================================================ */
.final{position:relative;overflow:hidden;text-align:center}
.final::before{
  content:"";
  position:absolute;
  inset:0;
  background-image:var(--grad-method);
  opacity:.16;
}
.final__inner{position:relative;z-index:1}
.final__title{font-size:var(--fs-h1);max-width:18ch;margin-inline:auto}
.final__body{max-width:44ch;margin:var(--space-2) auto var(--space-3);color:rgba(242,247,249,.92)}
.final__note{margin-top:var(--space-2);color:var(--text-muted-dark)}

/* ---- calendar embed ----
   The height is reserved up front so mounting the iframe costs 0 CLS. The
   slot only ever renders when data-calendar-url is set on <html>; until then
   the mailto/Upwork fallbacks are the whole booking route. */
.booking-embed{
  max-width:var(--container-narrow);
  min-height:var(--booking-embed-h,640px);
  margin:var(--space-4) auto 0;
  border:1px solid var(--border-dark);
  border-radius:var(--radius-lg);
  overflow:hidden;
  background:var(--ink-800);
}
.booking-embed[hidden]{display:none}
.booking-embed iframe{display:block;width:100%;height:var(--booking-embed-h,640px);border:0}

/* pre-mount state: a real button, not a spinner — the embed is fetched only
   when someone asks for it (SPEC §10: "loads on interaction/idle") */
.booking-embed__prompt{
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:var(--space-2);
  min-height:var(--booking-embed-h,640px);
  padding:var(--space-3);
  text-align:center;
}
.booking-embed__prompt p{max-width:38ch;color:var(--text-muted-dark)}

@media (max-width:767px){
  :root{--booking-embed-h:560px}
}

/* the always-available fallback route beside the embed (SPEC §8) */
.booking-fallback{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  justify-content:center;
  gap:var(--space-1);
  margin-top:var(--space-3);
  color:var(--text-muted-dark);
  font-size:var(--fs-small);
}
/* its own tap target rather than a link buried mid-sentence, so it clears
   --hit-min without inflating the line box (SPEC §2.4) */
.booking-fallback__link{
  display:inline-flex;
  align-items:center;
  min-height:var(--hit-min);
  padding-inline:var(--space-1);
  color:var(--cyan);
  text-decoration:underline;
  text-underline-offset:3px;
}
.booking-fallback__link:hover{color:#fff}
