/* ============================================================
   Clockpunk Narrative Hero — engine
   Layout, the band shape, slide transitions, Ken Burns, the copy grid, the
   character cascade, the controls and the progress bar.

   No campaign lives here. A deck supplies colour, type, the signature
   effects and the payoff's look through its own stylesheet; the engine
   defines a default for every custom property a deck may set, so the band
   renders correctly with no deck stylesheet at all.

   Every custom property is defined on .cnh and never on :root, so nothing
   leaks out of the band and a theme's :root cannot reach in.
   ============================================================ */

.cnh{
  /* ---- deck-overridable tokens, all with engine defaults ---- */
  --gold:#e8c06a;
  --gold-deep:#c79a3e;
  --bone:#f2e9d6;
  --obsidian:#0a090d;

  --ui:system-ui, -apple-system, 'Segoe UI', sans-serif;
  --display:Palatino, 'Palatino Linotype', Georgia, serif;
  --line-weight:700;
  --line-track:.004em;

  --ease-out:cubic-bezier(.16,.84,.26,1);
  --ease-soft:cubic-bezier(.33,0,.1,1);

  /* The band's shape. A deck sets both; the phone one is a separate token
     rather than a media-query override of the same token, because a deck
     writes its tokens at .cnh[data-deck="x"] and would otherwise outrank
     the engine's own breakpoint. The media query below switches which
     token aspect-ratio reads, so the two never compete. */
  --cnh-ratio: 2.4 / 1;
  --cnh-ratio-phone: 4 / 5;
  --cnh-max-height: 88vh;

  /* Legibility scrim. Same two-token trick, for the same reason. */
  --scrim:.4;
  --scrim-phone:.5;
  --scrim-live:var(--scrim);

  /* Per-slide values the renderer writes inline; these are the fallbacks for
     a deck that omits one. */
  --line-shadow: 0 2px 30px rgba(0,0,0,.95), 0 2px 10px rgba(0,0,0,.85), 0 1px 3px rgba(0,0,0,.8);
  --kb-from:1.04;
  --kb-to:1.16;
  --kb-x:0%;
  --kb-y:0%;
  --kb-origin:50% 50%;
  --kb-dur:4670ms;
  --fog-x:50%;
  --t-text:330ms;
  --t-stagger:17ms;
  --t-fx:950ms;
  --t-in:870ms;
  --t-out:510ms;
  --logo-b-inset:8.4%;
}

/* ---------- containment ----------
   Astra and Kadence style these elements globally. The band is dropped into
   whatever page it lands on, so it resets the handful that would otherwise
   reach in and move the copy, recolour the call to action or put a border on
   a control. Scoped to .cnh, so nothing here escapes the band.

   Every element here is wrapped in :where(), which contributes no specificity,
   so the guard sits at (0,1,0) and ties with the band's own component rules;
   source order then decides and the components below win. Written as plain
   `.cnh button` the guard is (0,1,1) and strips the band's OWN controls: the
   prev, pause and next buttons lose their circles, the progress track loses its
   background and the product shot loses its width cap.

   Raising the components to (0,2,0) instead is worse, and was tried: it orphans
   every phone override in the breakpoint below, and it breaks the contract the
   whole engine/deck split rests on, which is that a deck overrides the engine at
   equal specificity and later load order.

   The one case :where() cannot carry is below. */
.cnh,
.cnh *,
.cnh *::before,
.cnh *::after{box-sizing:border-box}

.cnh :where(p, figure, img, svg, canvas){margin:0}

.cnh :where(a){color:inherit;text-decoration:none;box-shadow:none}

.cnh :where(button){
  margin:0;
  padding:0;
  border:0;
  background:none;
  font:inherit;
  color:inherit;
  line-height:normal;
  text-transform:none;
  letter-spacing:normal;
  box-shadow:none;
  border-radius:0;
  min-height:0;
  width:auto;
}

.cnh :where(svg){display:block;max-width:none}
.cnh :where(img){display:block;max-width:100%;height:auto;border-radius:0;box-shadow:none}

/* The band's three paragraphs, at (0,2,0). A plugin on the Monte Cook store
   serves `.entry-content p{margin-bottom:1.6em}` at (0,1,1), which outranks the
   guard above and gave every line in the band 82.6px of bottom margin. This is
   the vertical margins only: the kicker and the call to action carry a LEFT
   margin of their own, at (0,1,0), which this would otherwise flatten along
   with the phone breakpoint's override of it. */
.cnh .cnh-line,
.cnh .cnh-kicker,
.cnh .cnh-credit{margin-top:0;margin-bottom:0}

/* ---------- shell ---------- */
.cnh{
  position:relative;
  display:block;
  width:100%;
  max-width:100%;
  aspect-ratio:var(--cnh-ratio);
  /* The ceiling has to clear what the ratio actually asks for, or it eats the
     ratio silently. At 2.4/1 a 1920 band wants 800px, which is 85vh of a 1080
     screen, so anything under that quietly serves a shallower band than the
     one the deck asked for. Raise the ratio and check this too. */
  max-height:var(--cnh-max-height);
  min-height:360px;
  overflow:hidden;
  isolation:isolate;
  background:var(--obsidian);
  color:var(--bone);
  /* The band sets its own text basis rather than inheriting the host theme's.
     Astra serves a 15px root and a 1.857 body line-height here, which shrank
     every inherited size by 6% and made the call to action 8px taller than the
     design. Lengths in this file are px for the same reason: rem is tied to the
     host's root element and nothing inside the band can rebase it. */
  font-family:var(--ui);
  font-size:16px;
  line-height:normal;
  -webkit-font-smoothing:antialiased;
}

/* ---------- a slide ---------- */
.cnh-slide{
  position:absolute;
  inset:0;
  opacity:0;
  visibility:hidden;
  pointer-events:none;
  /* Pointer parallax is off, so these stay at 0. They are kept because the
     transforms they feed are what promote the stage, the planes and the copy
     to their own compositing layers. See the note on .cnh-plane. */
  --mx:0;
  --my:0;
}
.cnh-slide.cnh-is-active{opacity:1;visibility:visible;pointer-events:auto;z-index:2}
.cnh-slide.cnh-is-leaving{
  visibility:visible;
  z-index:1;
  animation:cnhSlideOut var(--t-out) var(--ease-soft) forwards;
}
.cnh-slide.cnh-is-entering{animation:cnhSlideIn var(--t-in) var(--ease-out) forwards}

@keyframes cnhSlideIn{
  from{opacity:0;transform:scale(1.12);filter:blur(18px) brightness(1.7)}
  60% {opacity:1}
  to  {opacity:1;transform:scale(1);filter:blur(0) brightness(1)}
}
@keyframes cnhSlideOut{
  from{opacity:1;transform:scale(1);filter:blur(0) brightness(1)}
  to  {opacity:0;transform:scale(1.07);filter:blur(16px) brightness(1.5)}
}

/* ---------- depth stage ---------- */
.cnh-stage{
  position:absolute;
  inset:0;
  perspective:1400px;
  transform-style:preserve-3d;
  transform:
    rotateY(calc(var(--mx) * 2.2deg))
    rotateX(calc(var(--my) * -1.6deg));
  transition:transform .9s var(--ease-soft);
}
/* These transforms evaluate to zero translation, because pointer parallax is
   off and --mx/--my are 0. They are NOT dead code, and removing them as such
   was a mistake that shipped: a 3D transform promotes the element to its own
   compositing layer, and without that promotion the Ken Burns transform on the
   art and the blur the incoming slide animates force a re-rasterization on
   every transition. The visible result is a hard flicker in the background
   image at each slide change, reported on the live home page 2026-10-22.
   will-change is part of the same promotion and stays with them. */
.cnh-plane{
  position:absolute;
  inset:-8%;
  transform:translate3d(
    calc(var(--mx) * var(--depth) * 1px),
    calc(var(--my) * var(--depth) * .55px),
    0);
  transition:transform .9s var(--ease-soft);
  will-change:transform;
}
.cnh-plane--art{--depth:14}
.cnh-plane--fog{--depth:70;inset:-14%}

.cnh-art-wrap{position:absolute;inset:0;overflow:hidden}
.cnh-art{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  max-width:none;
  object-fit:cover;
  filter:saturate(1.18) contrast(1.07) brightness(1.07);
  transform-origin:var(--kb-origin);
}
.cnh-slide.cnh-is-active .cnh-art{animation:cnhKenburns var(--kb-dur) linear forwards}
@keyframes cnhKenburns{
  from{transform:scale(var(--kb-from)) translate3d(0,0,0)}
  to  {transform:scale(var(--kb-to)) translate3d(var(--kb-x),var(--kb-y),0)}
}

/* Legibility scrim. Only darkens the side the copy sits on, plus a short band
   under the controls. */
.cnh-plane--fog{
  background:linear-gradient(to top, rgba(6,5,10,calc(var(--scrim-live) * .78)) 0%, rgba(6,5,10,calc(var(--scrim-live) * .2)) 26%, transparent 46%);
}
.cnh-slide[data-pos="left"] .cnh-plane--fog{
  background:
    linear-gradient(to right, rgba(6,5,10,var(--scrim-live)) 0%, rgba(6,5,10,calc(var(--scrim-live) * .42)) 30%, transparent 56%),
    linear-gradient(to top, rgba(6,5,10,calc(var(--scrim-live) * .7)) 0%, transparent 34%);
}
.cnh-slide[data-pos="right"] .cnh-plane--fog{
  background:
    linear-gradient(to left, rgba(6,5,10,var(--scrim-live)) 0%, rgba(6,5,10,calc(var(--scrim-live) * .42)) 30%, transparent 56%),
    linear-gradient(to top, rgba(6,5,10,calc(var(--scrim-live) * .7)) 0%, transparent 34%);
}
.cnh-slide[data-pos="center"] .cnh-plane--fog{
  background:
    radial-gradient(70% 120% at 50% 50%, rgba(6,5,10,calc(var(--scrim-live) * .92)) 0%, rgba(6,5,10,calc(var(--scrim-live) * .3)) 48%, transparent 72%),
    linear-gradient(to top, rgba(6,5,10,calc(var(--scrim-live) * .7)) 0%, transparent 34%);
}

.cnh-grain{
  position:absolute;inset:0;
  opacity:.03;
  pointer-events:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  mix-blend-mode:overlay;
}

/* ---------- copy ---------- */
.cnh-copy{
  position:absolute;
  inset:0;
  display:grid;
  align-content:center;
  padding:clamp(16px,2.6vw,48px) clamp(24px,5vw,96px) clamp(44.8px,5vw,73.6px);
  z-index:3;
  transform:translate3d(calc(var(--mx) * -9px), calc(var(--my) * -5px), 0);
  transition:transform 1.1s var(--ease-soft);
}
.cnh-copy::before{
  content:"";
  position:absolute;
  inset:0;
  z-index:-1;
  pointer-events:none;
  background:radial-gradient(50% 48% at var(--scrim-x,50%) 50%, rgba(6,5,10,.24), rgba(6,5,10,.07) 58%, transparent 78%);
}
.cnh-copy[data-pos="left"]  {justify-items:start;text-align:left;--scrim-x:30%}
.cnh-copy[data-pos="center"]{justify-items:center;text-align:center;--scrim-x:50%}
.cnh-copy[data-pos="right"] {justify-items:end;text-align:right;--scrim-x:70%}

/* Vertical placement. "bottom" carries its own padding because the default one
   only clears the controls from a centred block; a block actually pinned to
   the bottom puts an ornament through the progress bar. */
.cnh-copy[data-y="top"]   {align-content:start;padding-top:clamp(35.2px,4vw,54.4px)}
.cnh-copy[data-y="bottom"]{align-content:end;padding-bottom:clamp(73.6px,8vw,121.6px)}

.cnh-line{
  font-family:var(--display);
  font-weight:var(--line-weight);
  font-size:clamp(24px,3.5vw,56px);
  line-height:1.12;
  letter-spacing:var(--line-track);
  margin:0;
  max-width:var(--line-max,28ch);
  text-wrap:balance;
  /* The stack is defaulted on .cnh-copy, not here, so that a slide's inline
     --line-shadow wins and so an effect's keyframes can reach it by
     inheritance. An animated text-shadow replaces the property outright, so a
     keyframe that lists only its own glow drops the stack the line relies on. */
  text-shadow:var(--line-shadow);
}
.cnh-copy[data-pos="center"] .cnh-line{max-width:var(--line-max,30ch)}

.cnh-w{display:inline-block;white-space:nowrap}
.cnh-c{
  display:inline-block;
  opacity:0;
  transform:translateY(.42em) rotateX(-55deg);
  filter:blur(8px);
  transform-origin:50% 100%;
}
.cnh-slide.cnh-is-active .cnh-c{
  animation:cnhCharUp .85s var(--ease-out) forwards;
  animation-delay:calc(var(--t-text) + var(--i) * var(--t-stagger));
}
@keyframes cnhCharUp{
  to{opacity:1;transform:translateY(0) rotateX(0);filter:blur(0)}
}

/* ---------- split: structure only ----------
   Visible and static with no deck stylesheet. A deck animates the two lines
   apart; without one they simply sit there, readable. */
.cnh-split{display:grid;gap:.1em;justify-items:inherit}
.cnh-split .cnh-line{opacity:1}

/* ---------- payoff: layout only ----------
   The lockup's grid and the lede column belong to the engine, so a client with
   no effects stylesheet still gets a correctly laid out payoff. Everything
   below defaults to opacity 1; a deck animates each element in from 0. */
.cnh-copy[data-pos="wide"]{justify-items:stretch;text-align:left;--scrim-x:50%}
.cnh-payoff{
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1.04fr);
  align-items:center;
  gap:clamp(19.2px,3.4vw,57.6px);
  width:100%;
  /* Past this the columns keep growing while the lockup and the products stay
     capped, so they drift apart. */
  max-width:1400px;
  margin-inline:auto;
}
.cnh-lede{
  display:grid;
  justify-items:start;
  gap:clamp(9.6px,1.3vw,17.6px);
  width:100%;
  max-width:520px;
}
/* A logo file commonly carries transparent padding before its wordmark, so the
   kicker and the button are indented by that fraction to hang off it. A deck
   sets --logo-b-inset to its own logo's measurement. */
.cnh-kicker,
.cnh-cta{margin-left:var(--logo-b-inset)}
.cnh-lockup{position:relative;display:grid;place-items:center;width:100%}

.cnh-logo{
  position:relative;
  width:100%;
  opacity:1;
}
.cnh-kicker{
  font-family:var(--ui);
  font-size:clamp(11.52px,1.15vw,14.4px);
  letter-spacing:.42em;
  text-transform:uppercase;
  color:var(--gold);
  /* No margin shorthand here. This rule is declared after the margin-left that
     hangs the kicker off the logo's inset, and a shorthand would reset it. The
     guard above already zeroes the vertical margins. */
  opacity:1;
}
.cnh-products{
  width:100%;
  max-width:640px;
  justify-self:end;
  opacity:1;
}
.cnh-cta{
  position:relative;
  display:inline-flex;
  align-items:center;
  gap:.7em;
  padding:1em 2.2em;
  border:1px solid var(--gold-deep);
  border-radius:2px;
  background:linear-gradient(180deg, rgba(232,192,106,.16), rgba(232,192,106,.04));
  color:var(--bone);
  font-family:var(--ui);
  font-weight:600;
  font-size:clamp(13.12px,1.15vw,16px);
  letter-spacing:.17em;
  text-transform:uppercase;
  text-decoration:none;
  overflow:hidden;
  opacity:1;
}

/* ---------- ornaments ----------
   A deck declares named ornaments and supplies a <symbol id="cnh-NAME"> for
   each in its ornament.svg. The engine only places and hides them; size,
   colour and motion are the deck's. */
.cnh-ornaments{display:none}
.cnh-ornament{
  pointer-events:none;
  opacity:1;
}

/* ---------- global overlays ---------- */
.cnh-burst{
  position:absolute;inset:0;z-index:5;pointer-events:none;opacity:0;
  background:radial-gradient(60% 50% at 50% 55%, rgba(255,232,180,.6), rgba(232,192,106,.18) 42%, transparent 70%);
  mix-blend-mode:screen;
}
.cnh-burst.cnh-fire{animation:cnhBurst 900ms var(--ease-soft)}
@keyframes cnhBurst{0%{opacity:0}18%{opacity:1}100%{opacity:0}}

/* Explicit width and height are written by the script. A canvas with inset:0
   and no dimension attributes keeps its intrinsic 300x150 backing store and
   draws a stretched, blurry field. */
.cnh-motes{position:absolute;inset:0;width:100%;height:100%;display:block;z-index:4;pointer-events:none}

.cnh-edge{
  position:absolute;inset:0;z-index:6;pointer-events:none;
  box-shadow:inset 0 0 68px 0 rgba(5,4,8,.26);
}

/* ---------- controls ---------- */
.cnh-ui{
  position:absolute;
  left:0;right:0;bottom:0;
  z-index:8;
  display:flex;
  align-items:center;
  gap:clamp(12.8px,2vw,25.6px);
  padding:clamp(11.2px,1.5vw,20.8px) clamp(24px,5vw,96px);
}
.cnh-track{display:flex;gap:8.8px;flex:1}
.cnh-seg{
  position:relative;
  flex:1;
  height:2px;
  background:rgba(242,233,214,.2);
  border:0;
  padding:0;
  cursor:pointer;
  appearance:none;
}
.cnh-seg::after{
  content:"";
  position:absolute;
  inset:0;
  transform-origin:left center;
  transform:scaleX(0);
  background:var(--gold);
  box-shadow:0 0 12px rgba(232,192,106,.7);
}
.cnh-seg[aria-current="true"]::after{animation:cnhFill var(--seg-dur) linear forwards}
.cnh-seg.cnh-done::after{transform:scaleX(1);opacity:.45}
.cnh.cnh-paused .cnh-seg[aria-current="true"]::after{animation-play-state:paused}
@keyframes cnhFill{to{transform:scaleX(1)}}
.cnh-seg:focus-visible{outline:2px solid var(--gold);outline-offset:6px}

.cnh-btn{
  width:40px;height:40px;
  flex:0 0 auto;
  display:grid;place-items:center;
  border:1px solid rgba(242,233,214,.26);
  border-radius:50%;
  background:rgba(10,9,13,.35);
  backdrop-filter:blur(6px);
  color:var(--bone);
  cursor:pointer;
  transition:border-color .3s, background .3s, color .3s;
}
.cnh-btn:hover{border-color:var(--gold);color:var(--gold);background:rgba(10,9,13,.6)}
.cnh-btn:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
.cnh-btn svg{width:15px;height:15px;fill:currentColor}

.cnh-credit{
  position:absolute;
  right:clamp(24px,5vw,96px);
  top:clamp(11.2px,1.5vw,19.2px);
  z-index:8;
  font-size:10.56px;
  letter-spacing:.16em;
  text-transform:uppercase;
  color:rgba(242,233,214,.42);
  margin:0;
  text-align:right;
  pointer-events:none;
}

/* ---------- phone ---------- */
@media (max-width:760px){
  .cnh{
    aspect-ratio:var(--cnh-ratio-phone);
    max-height:none;
    --scrim-live:var(--scrim-phone);
  }
  .cnh-edge{box-shadow:inset 0 0 70px 2px rgba(5,4,8,.3)}
  .cnh-line{font-size:clamp(25.6px,7vw,38.4px);max-width:16ch}
  .cnh-copy[data-pos="wide"]{justify-items:center}
  .cnh-payoff{grid-template-columns:1fr;gap:16px;justify-items:center}
  .cnh-lede{justify-items:center;max-width:none}
  .cnh-kicker,
  .cnh-cta{margin-left:0}
  .cnh-products{justify-self:center;max-width:380px}
  .cnh-copy[data-pos]{justify-items:start;text-align:left}
  .cnh-credit{display:none}
  .cnh-ui{padding-bottom:25.6px}
}

/* ---------- reduced motion ----------
   Auto-advance and the ember field are stopped in the script. Everything that
   would otherwise animate in has to be placed visible here, or a visitor who
   asked for less motion gets an empty band. The pause button and the arrow
   keys keep working. */
@media (prefers-reduced-motion:reduce){
  .cnh,
  .cnh *,
  .cnh *::before,
  .cnh *::after{
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.01ms !important;
  }
  .cnh-c{opacity:1;transform:none;filter:none}
  .cnh-logo,
  .cnh-products,
  .cnh-kicker,
  .cnh-cta,
  .cnh-ornament,
  .cnh-split .cnh-line{opacity:1}
  .cnh-slide.cnh-is-active .cnh-art{animation:none}
  .cnh-motes{display:none}
}
