/* ==========================================================================
   YOPROFX / 07 — THE WORDMARK AND THE OPENING SEQUENCE
   --------------------------------------------------------------------------
   Two things live here:

     A. .mark      the monoline wordmark, at rest. Used anywhere.
     B. the etch   a once-per-session opening, driven entirely by two classes
                   on <html>: .coldopen-armed (pre-roll) and .is-etching (run).

   The sequence is CSS animation with delays, not JavaScript timers. JS only
   measures the path, throws the switch, and cleans up — so the browser owns
   the timing and the main thread stays free.

   THE TIMELINE            (all values below derive from these four tokens)
     0.16s   a point of light appears on the Y's first terminal
     0.46s   the laser begins its pass — 1.50s, linear, no easing
     1.96s   the pass completes; the hot spot dies, the heat haze cools
     2.05s   the blackout lifts
     2.12s   Experience Innovation Lab / Founded by Gavin Fox
     2.30s   the sentence
     2.54s   the two doors
     2.62s   the chrome
     2.70s   the reel marker
     ~3.3s   at rest; JS removes the classes and the page is ordinary again

   Nothing here is load-bearing. With JavaScript off, .coldopen-armed is never
   applied, no rule in section B matches, and the wordmark is simply drawn.
   ========================================================================== */


/* ==========================================================================
   A — THE WORDMARK AT REST
   ========================================================================== */

.mark {
  /* Stroke widths are in viewBox user units, NOT screen pixels, and
     vector-effect is deliberately absent.

     WHY THIS MATTERS: non-scaling-stroke resolves stroke-dasharray in the
     host (screen) coordinate space, while getTotalLength() always returns
     user units. On a mark scaled 2x the dash would cover only half the
     path and the tail of every stroke would sit there visible before the
     laser ever reached it. Everything stays in one coordinate space.

     Because the stroke now scales with the mark, --mark-stroke is stepped
     by breakpoint to hold the rendered kerf in a 1.6–2.4px band at every
     size — a laser cut looks the same width whatever you cut. */
  --mark-stroke: 2.4;
  --mark-hot: #fff1dc;          /* hot filament, not neon */

  display: block;
  width: 100%;
  max-width: var(--mark-w, clamp(17rem, 54vw, 48rem));
  height: auto;
  overflow: visible;            /* the halo sits outside the viewBox */
}

@media (min-width: 40rem) { .mark { --mark-stroke: 1.6; } }
@media (min-width: 64rem) { .mark { --mark-stroke: 1.15; } }

.mark path {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.mark__bloom { opacity: 0; }
.mark__bloom path { stroke: var(--c-bone); stroke-width: calc(var(--mark-stroke) * 3.5); }

.mark__etch { opacity: 0.95; }
.mark__etch path { stroke: var(--c-bone); stroke-width: var(--mark-stroke); }

.mark__heat { opacity: 0; }
.mark__heat-far  { opacity: 0.07; }
.mark__heat-mid  { opacity: 0.18; }
.mark__heat-core { opacity: 1; }
.mark__heat-far  path { stroke: var(--mark-hot); stroke-width: calc(var(--mark-stroke) * 8); }
.mark__heat-mid  path { stroke: var(--mark-hot); stroke-width: calc(var(--mark-stroke) * 3.5); }
.mark__heat-core path { stroke: var(--mark-hot); stroke-width: calc(var(--mark-stroke) * 1.15); }

/* Hidden by default so that `forwards` fill leaves every stroke dark until
   its own slice of the pass — otherwise a hot spot parks itself at the start
   of all eleven strokes and the effect reads as fairy lights. */
.mark__heat path { opacity: 0; }

/* The pass animates stroke-dashoffset — a paint property, not a composited
   one — across 11 strokes in every layer. On a small screen the mark is
   ~270px wide and the two outer glow layers are invisible at that size, so
   they are dropped: same effect, 22 fewer concurrent animations. */
@media (max-width: 40rem) {
  .mark__heat-far,
  .mark__heat-mid { display: none; }
}

.mark__spark { opacity: 0; transform-box: view-box; transform-origin: 9px 21px; }
.mark__spark circle:nth-child(1) { fill: var(--mark-hot); opacity: 0.12; }
.mark__spark circle:nth-child(2) { fill: var(--mark-hot); opacity: 0.30; }
.mark__spark circle:nth-child(3) { fill: #fffaf2; }


/* ==========================================================================
   THE LOCKUP
   Wordmark, a hairline, and two mono lines: what this is, and who started it.
   The credit sits on the same rule as the kicker, at the same size, at the
   far end — an attribution plaque, not a byline.
   ========================================================================== */

.lockup {
  /* One width for the mark and the plate beneath it, so the hairline ends
     exactly where the X does. A lockup, not two stacked elements. */
  --mark-w: clamp(17rem, 54vw, 48rem);

  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
  width: var(--mark-w);
  max-width: 100%;
}

.lockup__mark { margin: 0; }

.lockup__plate {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2xs) var(--sp-lg);
  padding-top: var(--sp-xs);
  border-top: var(--hair) solid var(--c-line);
}

.lockup__kicker { color: var(--c-chalk); }
.lockup__credit { color: var(--c-graphite); }

@media (min-width: 40rem) {
  .lockup__credit { margin-inline-start: auto; }
}


/* ==========================================================================
   HERO
   Sparse by construction: a wordmark, a plate, a sentence, two doors.
   ========================================================================== */

.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: min(94svh, 62rem);
  padding-top: calc(var(--chrome-h) + var(--sp-xl));
  padding-bottom: var(--sp-xl);
}

.hero__stage {
  position: relative;
  z-index: 6;                   /* above .hero__blackout */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(var(--sp-lg), 4vh, var(--sp-2xl));
}

.hero__reel { order: -1; }      /* sits first, arrives last */

/* The sentence yields to the wordmark: still the largest text on the page,
   but clearly the second thing you read. */
.hero__sentence {
  font-size: clamp(1.5rem, 3.3vw, 2.875rem);
  max-width: 42ch;
  text-wrap: pretty;
}

.hero__acts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-sm);
}

/* The four lit words — the site's first navigation. */
.hero__word {
  position: relative;
  display: inline-block;
  color: var(--c-bone);
  white-space: nowrap;
}

.hero__word::after {
  content: "";
  position: absolute;
  inset: auto 0 0.1em 0;
  height: 1px;
  background: var(--territory);
  opacity: 0.7;
  transition: opacity var(--dur-base) var(--ease-dimmer),
              height var(--dur-base) var(--ease-gate);
}

.hero__word:hover,
.hero__word:focus-visible { color: var(--territory); }
.hero__word:hover::after,
.hero__word:focus-visible::after { opacity: 1; height: 2px; }

/* The only ambient light in the house, and it has no hue. */
.hero__glow {
  position: absolute;
  top: 8%;
  left: 0;
  width: min(70rem, 100%);
  aspect-ratio: 1.6;
  pointer-events: none;
  z-index: 0;
  /* Kept inside the container. It used to be 120% wide at -14%, which put
     46px of empty, scrollable page to the right of every tablet. */
  background: radial-gradient(48% 48% at 30% 45%,
              rgba(239, 234, 225, 0.055), transparent 72%);
}

/* Guarantees the "black screen" premise even on a short viewport, where a
   section below might otherwise peek in. Only exists while armed. */
.hero__blackout {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 5;
  background: var(--c-vault);
  pointer-events: none;
}



/* ==========================================================================
   B — THE OPENING SEQUENCE
   ========================================================================== */

:root {
  --etch-spark: 0.16s;
  --etch-start: 0.46s;
  --etch-dur:   1.50s;
  --etch-end:   1.96s;          /* = --etch-start + --etch-dur */

  /* The same duration as a bare number of milliseconds. js/ypx.js reads it
     when it slices the pass, so the timing has exactly one home: this file. */
  --etch-dur-ms: 1500;
}

/* --- PRE-ROLL ------------------------------------------------------------
   Applied before first paint by the inline script in partials/head.php, so
   there is no flash of the finished page. Everything is dark and undrawn.

   Per-stroke variables, handed out by js/ypx.js after it measures:
     --len       this stroke's length, in path units
     --len-at    when its share of the pass begins, as a fraction of the whole
     --len-dur   how long its share lasts
   The CSS defaults are deliberate over-estimates, so the pre-roll state is
   hidden even in the frame before JS has measured anything. */

html.coldopen-armed .hero__blackout { display: block; }

html.coldopen-armed .chrome,
html.coldopen-armed [data-open] { opacity: 0; }

html.coldopen-armed .mark__bloom path,
html.coldopen-armed .mark__etch path {
  stroke-dasharray: var(--len, 400);
  stroke-dashoffset: var(--len, 400);
}

html.coldopen-armed .mark__heat path {
  stroke-dasharray: 5 9999;     /* one 5-unit hot spot per stroke */
  stroke-dashoffset: 0;
}

/* --- THE RUN -------------------------------------------------------------
   One constant feed rate across the whole wordmark: each stroke gets a slice
   of --etch-dur proportional to its own length, starting where the previous
   one finished. The pen appears to lift and reposition between strokes. */

/* Dies at 0.61s, just after the pass begins at 0.46s: the point of light
   does not sit there watching, it becomes the laser head. */
html.is-etching .mark__spark {
  animation: mk-spark 0.45s var(--ease-dolly) var(--etch-spark) both;
}

html.is-etching .mark__bloom path,
html.is-etching .mark__etch path {
  animation: mk-draw var(--len-dur, 0.1s) linear
             calc(var(--etch-start) + var(--len-at, 0s)) both;
}

html.is-etching .mark__heat { opacity: 1; }

html.is-etching .mark__heat path {
  animation: mk-heat var(--len-dur, 0.1s) linear
             calc(var(--etch-start) + var(--len-at, 0s)) forwards;
}

html.is-etching .mark__bloom {
  animation: mk-bloom 2.1s var(--ease-dimmer) var(--etch-start) both;
}

html.is-etching .mark__etch {
  animation: mk-cool 0.7s var(--ease-dimmer) var(--etch-end) both;
}

html.is-etching .hero__blackout {
  animation: mk-lift 0.9s var(--ease-dimmer) 2.05s both;
}

html.is-etching [data-open] {
  animation: mk-expose 0.7s var(--ease-dolly) var(--open-at, 2.3s) both;
}

html.is-etching .chrome {
  animation: mk-expose 0.8s var(--ease-dolly) 2.62s both;
}

/* --- KEYFRAMES ----------------------------------------------------------- */

@keyframes mk-spark {
  0%   { opacity: 0; transform: scale(0.12); }
  38%  { opacity: 1; transform: scale(1); }
  62%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(0.55); }
}

@keyframes mk-draw {
  to { stroke-dashoffset: 0; }
}

/* One hot spot, travelling the length of one stroke, alight only while it
   travels. `forwards` fill is load-bearing here: see .mark__heat path. */
@keyframes mk-heat {
  0%   { opacity: 1; stroke-dashoffset: 0; }
  92%  { opacity: 1; }
  100% { opacity: 0; stroke-dashoffset: calc(var(--len, 400) * -1); }
}

/* Heat haze around the fresh cut, revealed with the pass, then cooling. */
@keyframes mk-bloom {
  0%   { opacity: 0; }
  8%   { opacity: 0.16; }
  72%  { opacity: 0.16; }
  100% { opacity: 0; }
}

/* The luminous line settling into a permanent mark. */
@keyframes mk-cool {
  from { opacity: 1; }
  to   { opacity: 0.92; }
}

@keyframes mk-lift {
  to { opacity: 0; }
}

@keyframes mk-expose {
  from { opacity: 0; transform: translate3d(0, 6px, 0); }
  to   { opacity: 1; transform: none; }
}

/* --- SKIP ----------------------------------------------------------------
   Any deliberate input during the sequence lands here: the pass completes
   at once, everything else arrives over 260ms. The end state of .is-hurry is
   identical to the page's ordinary resting state, so removing the class a
   moment later changes nothing on screen. */

html.is-hurry .mark__spark,
html.is-hurry .mark__heat { animation: none !important; opacity: 0 !important; }

html.is-hurry .mark__bloom { animation: none !important; opacity: 0 !important; }

html.is-hurry .mark__bloom path,
html.is-hurry .mark__etch path {
  animation: none !important;
  stroke-dashoffset: 0 !important;
}

html.is-hurry .mark__etch { animation: none !important; opacity: 0.95 !important; }

html.is-hurry .chrome,
html.is-hurry [data-open] {
  animation: mk-expose 0.26s var(--ease-dimmer) 0s both !important;
}

html.is-hurry .hero__blackout {
  animation: mk-lift 0.26s linear 0s both !important;
}


/* ==========================================================================
   REDUCED MOTION
   The mark is already etched. No point of light, no pass, no blackout —
   the page simply exists, which is the honest version of the same idea.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html.coldopen-armed .hero__blackout { display: none; }

  html.coldopen-armed .chrome,
  html.coldopen-armed [data-open] { opacity: 1; }

  html.coldopen-armed .mark__bloom path,
  html.coldopen-armed .mark__etch path {
    stroke-dasharray: none;
    stroke-dashoffset: 0;
  }

  .mark__heat,
  .mark__spark,
  .mark__bloom { display: none; }

  html.is-etching *,
  html.is-etching *::before,
  html.is-etching *::after { animation: none !important; }
}
