/* ==========================================================================
   Reising — hero
   A blueprint of US 1,109,775 (Elijah McCoy's lubricator, 1914) drawn on the
   firm's navy, annotated in gold the way an attorney marks up an invention.

   Motion is one orchestrated load sequence, then near-stillness. Everything
   animates transform / opacity / stroke-dashoffset only, so nothing touches
   layout and INP is unaffected. Fully gated behind prefers-reduced-motion.
   ========================================================================== */

.hero {
  position: relative;
  overflow: hidden;
  background: var(--navy-deep);
  color: var(--white);
  isolation: isolate;
}

/* ── drafting grid ────────────────────────────────────────────────────────
   Two layered gradients: a fine 28px field and a heavier 140px rule, the
   way a drafting sheet is printed. Pure CSS, no image request.

   NOTE: impeccable's `codex-grid-background` rule flags two-axis gradient
   grids as a generated-UI tell, and it is right to — but it exempts
   "canvas, map, blueprint, or measurement surfaces". This hero *is* a
   blueprint: the grid is the sheet the 1914 drawing is printed on. Kept
   deliberately. */
.hero__grid {
  position: absolute;
  inset: -10% -5% -10% -5%;
  z-index: 1;
  pointer-events: none;
  background-image:
    linear-gradient(to right,  rgba(199, 165, 109, 0.055) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(199, 165, 109, 0.055) 1px, transparent 1px),
    linear-gradient(to right,  rgba(199, 165, 109, 0.10)  1px, transparent 1px),
    linear-gradient(to bottom, rgba(199, 165, 109, 0.10)  1px, transparent 1px);
  background-size: 28px 28px, 28px 28px, 140px 140px, 140px 140px;
  /* fade the grid out toward the edges so it reads as light, not wallpaper */
  -webkit-mask-image: radial-gradient(ellipse 78% 68% at 62% 45%, #000 35%, transparent 78%);
          mask-image: radial-gradient(ellipse 78% 68% at 62% 45%, #000 35%, transparent 78%);
  opacity: 0;
}

/* a slow vertical drift, the only ambient motion on the page */
.hero__glow {
  position: absolute;
  z-index: 1;
  top: -30%;
  /* Was right:-10% with width:70vw, which put its box 23px past the viewport
     and contributed real horizontal scroll. It is a soft radial with nothing
     legible at the edge, so pulling it inside costs nothing visually. */
  right: 0;
  width: 70vw;
  height: 130%;
  pointer-events: none;
  background: radial-gradient(ellipse at center, rgba(199, 165, 109, 0.09), transparent 62%);
  opacity: 0;
}

.hero__inner {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.02fr);
  align-items: center;
  gap: clamp(28px, 5vw, 72px);
  /* Sized to the content rather than the viewport: an 88vh hero pushed the
     headline ~270px down the page on a laptop before anything was read. */
  min-height: 0;
  /* No top padding on the container: the plate is pulled flush to the top of
     the hero (align-self: start) so it gets the full height, while the copy
     column centres itself against that taller plate and keeps its own
     breathing room. */
  padding-block: 0 clamp(24px, 3vw, 44px);
}

@media (max-width: 900px) {
  .hero__inner {
    grid-template-columns: minmax(0, 1fr);
    min-height: 0;
    gap: 8px;
  }
}

/* ── copy ─────────────────────────────────────────────────────────────── */
/* The docket line now lives inside the plate SVG (.hero__docket-svg), set
   over the drawing where a sheet header belongs. */
.hero__docket-text {
  fill: var(--gold);
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.13em;
  opacity: 0;
}

.hero__docket-dot {
  fill: var(--gold);
  opacity: 0;
}

.hero h1 {
  font-size: clamp(2.05rem, 4.15vw, 3.55rem);
  line-height: 1.02;
  font-weight: 700;
  letter-spacing: -0.022em;
  color: var(--white);
  margin: 0 0 clamp(18px, 2.6vw, 30px);
  text-wrap: balance;
  max-width: 17ch;
  padding-top: 0;
}

/* each word is its own overflow-clipped line so the rise reads cleanly */
.hero h1 .w {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
}

.hero h1 .w > span {
  display: inline-block;
  will-change: transform;
}

.hero__lead {
  font-size: clamp(0.98rem, 1.15vw, 1.08rem);
  line-height: 1.68;
  color: rgba(255, 255, 255, 0.76);
  max-width: 46ch;
  margin: 0 0 clamp(24px, 3.4vw, 36px);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}

/* The hero sits on navy, so the outline button needs light strokes. */
.hero .button-secondary {
  background: transparent;
  color: var(--white);
  border-color: rgba(255, 255, 255, 0.45);
}

.hero .button-secondary:hover,
.hero .button-secondary:focus-visible {
  background: var(--white);
  color: var(--navy-deep);
  border-color: var(--white);
}

/* ── the plate ────────────────────────────────────────────────────────── */
.hero__plate {
  position: relative;
  justify-self: center;
  width: 100%;
  /* Flush to the top of the hero, not aligned with the copy. */
  align-self: start;
  /* Cap by VIEWPORT HEIGHT, not width. The plate is landscape since the
     assembly view replaced the sectional one: aspect is 716/620 = 1.155, so
     bounding width to (height x 1.321) keeps the headline, lead and both CTAs
     above the fold while giving the drawing as much height as that allows. */
  max-width: min(620px, calc(72vh * 1.155));
}

@media (max-width: 900px) {
  /* 340px was sized for the portrait sectional plate. The assembly view is
     landscape, so the same cap rendered it small enough that the reference
     letters stopped being legible; it gets the column width instead. */
  .hero__plate { max-width: 460px; margin-top: 8px; }
}
@media (max-width: 560px) {
  .hero__plate { max-width: 260px; }
}

.hero__svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  margin-top: 0;
}

/* the scanned drawing, white ink with alpha, so it tints to the ground.
   No reveal wipe: the plate is simply present as soon as it decodes (~8ms),
   which is faster and avoids reading as a photo fading in behind the art. */
.hero__drawing {
  opacity: 0.93;
}

/* leader lines and reference letters — hand-authored centre lines */
.hero__lead-line {
  fill: none;
  stroke: var(--gold);
  stroke-width: 1.15;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: var(--len, 300);
  stroke-dashoffset: var(--len, 300);
}

.hero__anchor {
  fill: var(--gold);
  opacity: 0;
}

.hero__ref {
  fill: var(--gold);
  font-family: var(--font-mono);
  font-size: 21px;
  font-style: italic;
  opacity: 0;
}

.hero__reflabel {
  fill: rgba(255, 255, 255, 0.62);
  font-family: var(--font-mono);
  font-size: 12.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0;
}

/* corner registration marks — drafting-sheet furniture */
.hero__reg {
  fill: none;
  stroke: rgba(199, 165, 109, 0.5);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  opacity: 0;
}

.hero__caption {
  margin: 22px 0 0;
  font-family: var(--font-mono);
  /* 11px floor for functional text, and sentence case: this is a full
     citation, not a micro-label, so all-caps hurt it. */
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  color: rgba(255, 255, 255, 0.55);
  text-align: center;
  line-height: 1.7;
  opacity: 0;
}

/* ── credentials strip ────────────────────────────────────────────────── */
.hero__strip {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(14px, 2.4vw, 32px);
  padding: clamp(20px, 2.6vw, 30px) 0 clamp(26px, 3.4vw, 40px);
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}

@media (max-width: 700px) {
  .hero__strip { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px 14px; }
}

.hero__stat { opacity: 0; }

.hero__stat b {
  display: block;
  font-size: clamp(1.5rem, 2.5vw, 2.05rem);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.018em;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}

.hero__stat span {
  display: block;
  margin-top: 5px;
  font-size: 0.78rem;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.6);
}

/* ══ the load sequence ═════════════════════════════════════════════════
   Two gates, not one.

   .is-ready  — the ground and the copy, after the hero image has decoded.
   .is-plated — everything drawn ON the plate: the gold leader lines, anchors,
                reference letters, labels, docket and registration marks. Held
                until the plate image itself has decoded, so the annotation
                never floats over an empty box while the drawing loads.

   Nothing here blocks paint: the first frame of every element is its final
   layout position, only opacity/transform move.                             */

.hero.is-ready .hero__grid      { animation: heroFade 1100ms ease-out 60ms forwards; }
.hero.is-ready .hero__glow      { animation: heroGlow 1600ms ease-out 200ms forwards; }
.hero.is-ready .hero h1 .w > span { animation: heroWord 760ms cubic-bezier(.16,.84,.3,1) both; }
.hero.is-ready .hero__lead      { animation: heroRise 700ms cubic-bezier(.2,.7,.3,1) 520ms both; }
.hero.is-ready .hero__actions   { animation: heroRise 700ms cubic-bezier(.2,.7,.3,1) 620ms both; }

/* ── gold on the plate: gated on the drawing being decoded ──────────────── */
.hero.is-plated .hero__reg       { animation: heroFade 500ms ease-out 260ms forwards; }
.hero.is-plated .hero__docket-text,
.hero.is-plated .hero__docket-dot { animation: heroFade 620ms ease-out 240ms forwards; }
.hero.is-plated .hero__caption   { animation: heroFade 700ms ease-out 1500ms forwards; }

/* leader lines ink in after the drawing has developed past them */
.hero.is-plated .hero__lead-line { animation: heroDraw 620ms cubic-bezier(.45,.05,.25,1) both; }
.hero.is-plated .hero__anchor,
.hero.is-plated .hero__ref,
.hero.is-plated .hero__reflabel  { animation: heroFade 420ms ease-out both; }

/* four callouts, staggered */
.hero.is-plated [data-call="1"] .hero__lead-line { animation-delay: 1020ms; }
.hero.is-plated [data-call="2"] .hero__lead-line { animation-delay: 1160ms; }
.hero.is-plated [data-call="3"] .hero__lead-line { animation-delay: 1300ms; }
.hero.is-plated [data-call="4"] .hero__lead-line { animation-delay: 1440ms; }
.hero.is-plated [data-call="5"] .hero__lead-line { animation-delay: 1580ms; }

.hero.is-plated [data-call="1"] .hero__anchor { animation-delay: 1020ms; }
.hero.is-plated [data-call="2"] .hero__anchor { animation-delay: 1160ms; }
.hero.is-plated [data-call="3"] .hero__anchor { animation-delay: 1300ms; }
.hero.is-plated [data-call="4"] .hero__anchor { animation-delay: 1440ms; }
.hero.is-plated [data-call="5"] .hero__anchor { animation-delay: 1580ms; }

.hero.is-plated [data-call="1"] .hero__ref,
.hero.is-plated [data-call="1"] .hero__reflabel { animation-delay: 1560ms; }
.hero.is-plated [data-call="2"] .hero__ref,
.hero.is-plated [data-call="2"] .hero__reflabel { animation-delay: 1700ms; }
.hero.is-plated [data-call="3"] .hero__ref,
.hero.is-plated [data-call="3"] .hero__reflabel { animation-delay: 1840ms; }
.hero.is-plated [data-call="4"] .hero__ref,
.hero.is-plated [data-call="4"] .hero__reflabel { animation-delay: 1980ms; }
.hero.is-plated [data-call="5"] .hero__ref,
.hero.is-plated [data-call="5"] .hero__reflabel { animation-delay: 2120ms; }

/* headline words */
.hero.is-ready h1 .w:nth-child(1) > span { animation-delay: 200ms; }
.hero.is-ready h1 .w:nth-child(2) > span { animation-delay: 258ms; }
.hero.is-ready h1 .w:nth-child(3) > span { animation-delay: 316ms; }
.hero.is-ready h1 .w:nth-child(4) > span { animation-delay: 374ms; }
.hero.is-ready h1 .w:nth-child(5) > span { animation-delay: 432ms; }
.hero.is-ready h1 .w:nth-child(6) > span { animation-delay: 490ms; }
.hero.is-ready h1 .w:nth-child(7) > span { animation-delay: 548ms; }

/* stats */
.hero.is-ready .hero__stat { animation: heroRise 640ms cubic-bezier(.2,.7,.3,1) both; }
.hero.is-ready .hero__stat:nth-child(1) { animation-delay: 1560ms; }
.hero.is-ready .hero__stat:nth-child(2) { animation-delay: 1650ms; }
.hero.is-ready .hero__stat:nth-child(3) { animation-delay: 1740ms; }
.hero.is-ready .hero__stat:nth-child(4) { animation-delay: 1830ms; }

@keyframes heroFade    { from { opacity: 0; } to { opacity: 1; } }
@keyframes heroGlow    { from { opacity: 0; } to { opacity: 1; } }
@keyframes heroDraw    { to   { stroke-dashoffset: 0; } }

@keyframes heroWord {
  from { transform: translateY(105%); }
  to   { transform: translateY(0); }
}

@keyframes heroRise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── ambient parallax ─────────────────────────────────────────────────────
   The plate drifts slightly slower than the page. Native scroll-timeline
   where supported, so it runs on the compositor with no scroll handler.
   Unsupported browsers simply get a static plate. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 901px) {
    .hero__svg {
      animation: heroDrift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
    @keyframes heroDrift {
      from { transform: translateY(-14px); }
      to   { transform: translateY(30px); }
    }
  }
}

/* ── reduced motion ───────────────────────────────────────────────────────
   Everything resolves to its finished state instantly. This is also the
   fastest path, so it is a legitimate experience, not a degraded one. */
@media (prefers-reduced-motion: reduce) {html:not(.force-motion) .hero *,
  html:not(.force-motion) .hero.is-ready *,
  html:not(.force-motion) .hero.is-plated * {
    animation: none !important;
    transition: none !important;
  }html:not(.force-motion) .hero__grid,
  html:not(.force-motion) .hero__glow,
  html:not(.force-motion) .hero__anchor,
  html:not(.force-motion) .hero__ref,
  html:not(.force-motion) .hero__reflabel,
  html:not(.force-motion) .hero__reg,
  html:not(.force-motion) .hero__caption,
  html:not(.force-motion) .hero__stat,
  html:not(.force-motion) .hero__docket-text,
  html:not(.force-motion) .hero__docket-dot { opacity: 1; }html:not(.force-motion) .hero__glow { opacity: 0.85; }html:not(.force-motion) .hero__lead-line { stroke-dashoffset: 0; }html:not(.force-motion) .hero h1 .w > span { transform: none; }
}

/* No-JS: .is-ready never lands, so show the finished hero rather than a
   blank one. */
.no-js .hero__grid,
.no-js .hero__glow,
.no-js .hero__anchor,
.no-js .hero__ref,
.no-js .hero__reflabel,
.no-js .hero__reg,
.no-js .hero__caption,
.no-js .hero__stat,
.no-js .hero__docket-text,
.no-js .hero__docket-dot { opacity: 1; }
.no-js .hero__lead-line { stroke-dashoffset: 0; }




/* ══════════════════════════════════════════════════════════════════════════
   GRID SPARKS
   Gold bursts that run along the drafting grid lines already in the hero
   background: vertical ones fall, horizontal ones cross, at staggered
   durations and delays so the pattern never repeats visibly.

   Deliberately NOT gated on .hero.is-ready and requiring no JavaScript, so
   it runs even if the hero script never executes.
   ══════════════════════════════════════════════════════════════════════════ */
.hero__sparks {
  position: absolute;
  inset: -10% -5% -10% -5%;   /* matches .hero__grid so sparks land on its lines */
  /* Lowest layer above the navy ground: the grid, the plate and all copy
     stack above, so the gold never crosses a graphic or a word. */
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  /* same falloff as the grid, so bursts fade out toward the edges */
  -webkit-mask-image: radial-gradient(ellipse 78% 68% at 62% 45%, #000 35%, transparent 78%);
          mask-image: radial-gradient(ellipse 78% 68% at 62% 45%, #000 35%, transparent 78%);
}

.hspark {
  position: absolute;
  display: block;
  will-change: transform;
  /* Invisible until an animation takes over. Without this a spark renders in
     its plain CSS state during animation-delay: a static gold band that sits
     there, then vanishes when the delay elapses and the first keyframe
     applies. `backwards` below makes the delay period use the 0% frame
     (opacity 0) instead, so a spark is only ever visible while moving. */
  opacity: 0;
}

/* vertical: a 2px gold filament falling down a grid line */
.hspark--v {
  left: var(--x);
  top: 0;
  width: 1px;
  height: var(--len, 90px);
  filter: drop-shadow(0 0 2px rgba(255, 214, 130, 0.22));
  background: linear-gradient(
    to bottom,
    transparent 0%,
    rgba(199, 165, 109, 0.30) 45%,
    rgba(238, 210, 156, 0.52) 78%,
    rgba(246, 232, 199, 0.60) 92%,
    transparent 100%
  );
  animation: sparkFall var(--t, 4s) linear var(--d, 0s) infinite backwards;
}

@keyframes sparkFall {
  0%   { transform: translateY(-260px); opacity: 0; }
  12%  { opacity: 0.42; }
  26%  { opacity: 0.10; }
  42%  { opacity: 0.58; }
  58%  { opacity: 0.14; }
  74%  { opacity: 0.46; }
  88%  { opacity: 0.08; }
  100% { transform: translateY(1180px); opacity: 0; }
}

/* horizontal: the same filament crossing a grid line */
.hspark--h {
  top: var(--y);
  left: 0;
  height: 1px;
  width: var(--len, 110px);
  filter: drop-shadow(0 0 2px rgba(255, 214, 130, 0.22));
}

.hspark--r {
  background: linear-gradient(
    to right,
    transparent 0%,
    rgba(199, 165, 109, 0.28) 45%,
    rgba(238, 210, 156, 0.52) 78%,
    rgba(246, 232, 199, 0.56) 92%,
    transparent 100%
  );
  animation: sparkRight var(--t, 5s) linear var(--d, 0s) infinite backwards;
}

.hspark--l {
  background: linear-gradient(
    to left,
    transparent 0%,
    rgba(199, 165, 109, 0.28) 45%,
    rgba(238, 210, 156, 0.52) 78%,
    rgba(246, 232, 199, 0.56) 92%,
    transparent 100%
  );
  animation: sparkLeft var(--t, 5s) linear var(--d, 0s) infinite backwards;
}

@keyframes sparkRight {
  0%   { transform: translateX(-320px); opacity: 0; }
  12%  { opacity: 0.42; }
  26%  { opacity: 0.10; }
  42%  { opacity: 0.58; }
  58%  { opacity: 0.14; }
  74%  { opacity: 0.46; }
  88%  { opacity: 0.08; }
  100% { transform: translateX(1800px); opacity: 0; }
}

@keyframes sparkLeft {
  0%   { transform: translateX(1800px); opacity: 0; }
  12%  { opacity: 0.42; }
  26%  { opacity: 0.10; }
  42%  { opacity: 0.58; }
  58%  { opacity: 0.14; }
  74%  { opacity: 0.46; }
  88%  { opacity: 0.08; }
  100% { transform: translateX(-320px); opacity: 0; }
}

/* Reduced motion: the bursts stop travelling but a few stay lit, so the grid
   still reads as gold-marked rather than the effect vanishing entirely. */
@media (prefers-reduced-motion: reduce) {html:not(.force-motion) .hspark { animation: none !important; opacity: 0.5; }html:not(.force-motion) .hspark--v { transform: translateY(320px); }html:not(.force-motion) .hspark--h { transform: none; }
}


/* ── explicit motion opt-in ────────────────────────────────────────────────
   The reduced-motion default above is correct and stays. This lets the
   animation be previewed on a machine that has reduced motion enabled, via
   ?motion=on, which is an explicit user request and so outranks the system
   default. Remove this block if you never want the override to exist. */
html.force-motion .hspark--v {
  animation: sparkFall var(--t, 4s) linear var(--d, 0s) infinite backwards !important;
  transform: none;
}

html.force-motion .hspark--r {
  animation: sparkRight var(--t, 5s) linear var(--d, 0s) infinite backwards !important;
}

html.force-motion .hspark--l {
  animation: sparkLeft var(--t, 5s) linear var(--d, 0s) infinite backwards !important;
}

/* ── The Real McCoy signature ───────────────────────────────────────────────
   Elijah McCoy's lubricators were imitated so widely that buyers began
   asking for "the real McCoy"; this practice was the attorney of record on
   US 1,109,775. So the drawing gets signed, the way an attorney signs a sheet.

   The glyphs are a CENTRELINE path — the route the nib travelled — not a font
   outline. Stroking an outline draws each letter twice and a draw-on reads as
   a double image, which is why no font and no auto-trace can produce this.
   Authored in _sig.py; regenerate from there rather than editing the `d`.

   Per-stroke --len / --t0 / --dur are baked at build time from real arc
   length, so the pen travels at constant speed and the effect needs no JS. */
.hero__sig {
  position: absolute;
  /* The drawing now fills the sheet the registration marks describe, so the
     signature goes in the one part of the figure that is genuinely open: the
     lower right, below the pump and clear of every leader line. It runs past
     the drawing's right edge into the sheet margin, which is empty at this
     height — every reference label sits well above it. Much beyond 29% and
     "Coy" reaches the label column. */
  left: 53.5%;
  bottom: 6%;
  width: 28.5%;
  overflow: visible;
  pointer-events: none;
  transform: rotate(-4.5deg);
  transform-origin: left bottom;
}

/* The strokes ship with stroke-dashoffset="1" and write on their own --t0
   timers, so the signature needs no JS. On the landing page it signs the
   drawing, so it is held until the drawing is actually there: paused — which
   freezes each stroke's delay as well as its travel — and released with
   .is-plated. Both gates wait for the shared image-decode promise.

   Scoped to html:not(.no-js): hero.js drops .no-js as it starts, so if the
   script never runs the gate never applies and the signature still writes.
   .sig / .sig-stroke and the keyframes live in styles.css so the About page
   gets them without loading hero.css. */
.hero__sig .sig-stroke { animation-play-state: paused; }

/* Released by the plate landing — or immediately if the script never runs, so
   a no-JS visitor still gets the signature. Pausing by DEFAULT (rather than
   gating on html:not(.no-js)) matters: hero.js is deferred, so a gate keyed to
   .no-js leaves the strokes running for the whole parse-to-execute window and
   the first strokes can appear before the drawing does. */
.hero.is-plated .hero__sig .sig-stroke,
.no-js .hero__sig .sig-stroke { animation-play-state: running; }

/* The hero stacks below 900px and the plate shrinks; the signature would
   cross the drawing's stem, so it moves under the plate instead. */
@media (max-width: 900px) {
  .hero__sig {
    position: static;
    width: 78%;
    margin: 14px auto 0;
    transform: none;
  }
}

