/* ============================================================
   APEX — Enhancement Layer (additive, on top of style.css)
   Scroll choreography, hover polish, and small "mission-control"
   texture. Nothing here overrides existing component structure —
   it only adds motion/finish on top of it.
   ============================================================ */

/* ---------- 3D cursor-tilt for static (non-clickable) panels ----------
   Applies to .panel only — the info/stat boxes (THE PROBLEM/OUR ANSWER, WHY
   APEX, workshop stats, compare table wrapper, etc.), never the clickable
   .card product tiles, which already have their own hover treatment.
   No transition here on purpose — a CSS transition on transform would
   re-trigger on every single mousemove update and make the tilt perpetually
   chase/lag behind the cursor instead of tracking it live. JS applies a
   transition only for the mouse-leave "snap back to flat" reset. */
.panel {
  position: relative;
  overflow: hidden;
  will-change: transform;
}
.apex-tilt-glare {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(circle at var(--gx, 50%) var(--gy, 50%), rgba(255, 255, 255, 0.22), transparent 60%);
  transition: opacity 0.25s ease;
  border-radius: inherit;
}
.apex-tilt-glare.apex-glare-active {
  opacity: 1;
}
@media (hover: none), (pointer: coarse), (prefers-reduced-motion: reduce) {
  .apex-tilt-glare { display: none; }
}

/* ---------- Grain / texture overlay (kills the flat-black look) ---------- */
.apex-grain {
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: 0.035;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- Scroll progress bar ---------- */
.apex-scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 2px;
  width: 0%;
  background: var(--grad-full);
  z-index: 1100;
  pointer-events: none;
  transition: width 0.08s linear;
}

/* ---------- Scroll-reveal ---------- */
.apex-reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 0.6s cubic-bezier(.16,.84,.44,1), transform 0.6s cubic-bezier(.16,.84,.44,1);
  transition-delay: var(--apex-reveal-delay, 0ms);
}
.apex-reveal.apex-in {
  opacity: 1;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .apex-reveal {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* ---------- Nav link underline draw-in ---------- */
.nav-links a:not(.active) {
  background-image: linear-gradient(var(--teal), var(--teal));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 2px;
  transition: background-size 0.3s ease, color 0.25s ease;
}
.nav-links a:not(.active):hover {
  background-size: 100% 2px;
}
.nav-dropdown-toggle {
  background-image: linear-gradient(var(--teal), var(--teal));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 2px;
  transition: background-size 0.3s ease, color 0.25s ease;
}
.nav-dropdown-toggle:hover {
  background-size: 100% 2px;
}

/* ---------- Card border-trace hover ---------- */
.card {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.card::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: conic-gradient(from var(--apex-trace-angle, 0deg), transparent 0%, var(--teal) 8%, var(--blue) 16%, transparent 26%, transparent 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0;
  transition: opacity 0.35s ease;
  pointer-events: none;
  z-index: -1;
  animation: apex-trace-spin 3.5s linear infinite paused;
}
.card:hover::before {
  opacity: 1;
  animation-play-state: running;
}
@keyframes apex-trace-spin {
  to { --apex-trace-angle: 360deg; }
}
@property --apex-trace-angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

/* ---------- Panel border-glow trace (the "other" panel effect) ----------
   Large standalone panels (story text, forms, the compare table, workshop
   session items) get this instead of the 3D tilt — a magenta/pink trace so
   it also reads as visually distinct from the card's teal/blue one. Same
   conic-gradient mechanism, just its own color pass and slower spin. */
.apex-panel-trace {
  isolation: isolate;
}
.apex-panel-trace::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: conic-gradient(from var(--apex-trace-angle, 0deg), transparent 0%, var(--magenta) 8%, var(--pink) 16%, transparent 26%, transparent 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0;
  transition: opacity 0.35s ease;
  pointer-events: none;
  z-index: -1;
  animation: apex-trace-spin 5s linear infinite paused;
}
.apex-panel-trace:hover::before {
  opacity: 1;
  animation-play-state: running;
}

/* ---------- Button magnetic + glow hover ---------- */
.btn {
  will-change: transform;
}
.btn-outline:hover {
  box-shadow: 0 6px 20px rgba(45, 212, 191, 0.18);
}

/* ---------- Marquee spec ticker ---------- */
.apex-marquee-wrap {
  position: relative;
  z-index: 1;
  overflow: hidden;
  border-top: 1px solid var(--navy-line);
  border-bottom: 1px solid var(--navy-line);
  padding: 16px 0;
  background: rgba(8, 8, 15, 0.5);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.apex-marquee-track {
  display: flex;
  width: max-content;
  gap: 0;
  animation: apex-marquee-scroll 32s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .apex-marquee-track { animation: none; }
  .apex-marquee-wrap { overflow-x: auto; }
}
.apex-marquee-track span {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.14em;
  color: var(--slate-dim);
  white-space: nowrap;
  padding: 0 22px;
  display: flex;
  align-items: center;
  gap: 22px;
}
.apex-marquee-track span::after {
  content: "·";
  color: var(--teal);
  margin-left: 22px;
}
@keyframes apex-marquee-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* ---------- Gradient shimmer sweep (opt-in via class) ---------- */
.grad-shimmer {
  background: linear-gradient(100deg, var(--teal) 0%, var(--blue) 30%, var(--magenta) 55%, var(--pink) 75%, var(--teal) 100%);
  background-size: 250% auto;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: apex-shimmer-sweep 6s ease-in-out infinite;
}
@keyframes apex-shimmer-sweep {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}
@media (prefers-reduced-motion: reduce) {
  .grad-shimmer { animation: none; }
}

/* ============================================================
   Hero intro sequence — plays once per browser session on the
   homepage: header logo hidden → white Morse code → decodes to
   "APEX" → crossfades into the real logo → logo flies to the
   header → headline types letter by letter → "Run one." appears
   at once → eyebrow/subtext/buttons reveal together.
   Everything here is scoped under html.apex-intro-pending, a class
   an inline <script> in <head> adds synchronously (before first
   paint) only when the intro is actually about to play — so on
   repeat visits, other pages, and no-JS/reduced-motion the plain
   static markup just renders normally with none of this applying.
   ============================================================ */

/* No scrolling away mid-intro — locked until the whole sequence (through the
   final eyebrow/subtext/buttons reveal) has finished, same pattern already
   used for open modals (body.modal-active). */
html.apex-intro-pending,
html.apex-intro-pending body {
  overflow: hidden;
  height: 100%;
}

html.apex-intro-pending .navbar .logo-link img {
  opacity: 0;
}
html.apex-intro-pending .navbar .logo-link img.apex-logo-in {
  opacity: 1;
  transition: opacity 0.3s ease;
}

/* Nav links, hamburger, and the cart/status bubble all live inside
   .navbar-right — stay hidden until the logo actually lands in the header. */
html.apex-intro-pending .navbar-right {
  opacity: 0;
  pointer-events: none;
}
html.apex-intro-pending .navbar-right.apex-nav-in {
  opacity: 1;
  pointer-events: auto;
  transition: opacity 0.35s ease;
}

/* Pixie stays out of the way until the whole intro has finished */
html.apex-intro-pending #pixie-toggle-btn,
html.apex-intro-pending #pixie-greeting-bubble {
  opacity: 0 !important;
  pointer-events: none !important;
}

/* Background stays plain black (no starfield/planet/rotator) through the
   Morse + decode + big-logo beats — the JS fades it in right as the logo
   lifts off toward the header, so the scene "powers on" with it. */
html.apex-intro-pending #space-canvas {
  opacity: 0;
  transition: opacity 1.1s ease;
}
html.apex-intro-pending #space-canvas.apex-bg-in {
  opacity: 1;
}

/* ---------- Rocket-launch video (plays first, then hands off to the
   existing logo-punch + fly-to-header + typed-headline sequence below) ---------- */
.apex-launch-intro {
  display: none;
}
html.apex-intro-pending .apex-launch-intro {
  display: block;
  position: fixed;
  inset: 0;
  /* Clears every other z-index in the codebase (highest existing is 3000 on
     .apex-toast) — has to sit above literally everything since it's the very
     first thing on the page. */
  z-index: 9999;
  background: #000;
  opacity: 1;
  transition: opacity 0.3s ease;
}
html.apex-intro-pending .apex-launch-intro.apex-fade-out {
  opacity: 0;
}
#apex-launch-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Crop to fill on every screen size (phones included) rather than
     letterboxing. */
  object-fit: cover;
  background: #000;
}
.apex-morse-intro {
  display: none;
}
html.apex-intro-pending .apex-morse-intro {
  display: block;
  /* Fixed to the viewport (not the hero section, not a flex stack with the
     logo box) so the Morse line sits dead-center on the window no matter how
     big the logo box gets — the two no longer share a stacked flex column,
     which is what was pushing the text up off-screen under the navbar. */
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  opacity: 1;
  transition: opacity 0.3s ease;
}
html.apex-intro-pending .apex-morse-intro.apex-fade-out {
  opacity: 0;
}

.apex-morse-code {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  gap: 0.9em;
  font-family: var(--font-mono);
  font-size: clamp(28px, 5.5vw, 56px);
  color: var(--cream);
  white-space: nowrap;
  text-shadow: 0 0 26px rgba(245, 240, 230, 0.4);
}
/* Groups only exist for final spacing (gap between letters vs. gap between
   symbols within a letter) — every dot/dash inside still animates on its
   own via .apex-morse-letter; grouping never governs the flight itself. */
.apex-morse-group {
  display: flex;
  gap: 0.12em;
}

/* Each letter starts scattered all around the frame (JS sets --sx/--sy/--srot)
   and is already flying straight toward the center from frame one — one
   continuous flight, no separate "hover in place" phase. --wx/--wy (set by JS
   to a point on that same straight line) is a mid-flight checkpoint used only
   for pacing, not to bend the path; the final stretch then snaps in hard with
   a brief overshoot (--ox/--oy/--orot) — the "shock" impact, concentrated
   into just the last beat rather than the whole flight. Per-keyframe
   animation-timing-function is what makes the early stretch smooth/eased and
   the final stretch sudden, within a single animation. Once assembled, the
   whole thing is snapshotted to a canvas and blown apart as glass shards
   (see .apex-glass-shard below), so these spans just need to disappear at
   that point — visibility is toggled off in JS, not animated. */
.apex-morse-letter {
  display: inline-block;
  opacity: 0;
  transform: translate(var(--sx, 0px), var(--sy, 0px)) rotate(var(--srot, 0deg)) scale(0.45);
}
.apex-morse-letter.apex-fly {
  animation: apex-morse-fly 2.1s forwards;
}
@keyframes apex-morse-fly {
  0% {
    opacity: 0;
    transform: translate(var(--sx, 0px), var(--sy, 0px)) rotate(var(--srot, 0deg)) scale(0.45);
  }
  10% {
    opacity: 1;
    animation-timing-function: cubic-bezier(.35, .04, .4, 1);
  }
  58% {
    /* mid-flight checkpoint, straight line — just for pacing/rotation/scale */
    transform: translate(var(--wx, 0px), var(--wy, 0px)) rotate(calc(var(--srot, 0deg) * .3)) scale(.72);
    animation-timing-function: cubic-bezier(.7, 0, .18, 1);
  }
  86% {
    transform: translate(calc(var(--wx, 0px) * .12), calc(var(--wy, 0px) * .12)) rotate(calc(var(--srot, 0deg) * .05)) scale(.94);
    animation-timing-function: cubic-bezier(.15, .9, .1, 1.55);
  }
  93% {
    /* the shock: a brief hard overshoot right at impact */
    transform: translate(var(--ox, 6px), var(--oy, -4px)) rotate(var(--orot, 6deg)) scale(1.06);
    animation-timing-function: ease-out;
  }
  100% {
    opacity: 1;
    transform: translate(0, 0) rotate(0deg) scale(1);
  }
}

/* Brief radial flash right at the instant of impact, when everything snaps
   together — sells the "shock" without a global screen-shake. */
.apex-morse-impact {
  position: fixed;
  top: 50%;
  left: 50%;
  width: 14px;
  height: 14px;
  margin-left: -7px;
  margin-top: -7px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,255,255,.95) 0%, rgba(245,240,230,.55) 35%, rgba(245,240,230,0) 70%);
  z-index: 6;
  pointer-events: none;
  opacity: 0;
}
.apex-morse-impact.apex-flash {
  animation: apex-impact-flash .45s ease-out forwards;
}
@keyframes apex-impact-flash {
  0%   { opacity: .95; transform: scale(.3); }
  100% { opacity: 0;   transform: scale(11); }
}

/* ---------- Glass shards: the assembled Morse code, snapshotted to a canvas
   and sliced into jittered irregular polygon pieces (JS builds these), then
   blown outward from the logo's punch point and fall away. ---------- */
#apex-glass-wrap {
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
}
.apex-glass-shard {
  position: fixed;
  background-repeat: no-repeat;
  pointer-events: none;
  will-change: transform, opacity;
  opacity: 1;
  transform: translate(0, 0) rotate(0deg);
  transition: transform 0.9s cubic-bezier(.13, .77, .3, .99), opacity 0.75s ease 0.28s;
}

.apex-morse-visual {
  /* Centered via fixed top/left + negative margins (not `transform`) —
     `transform` itself is left free for the JS-driven FLIP flight to animate
     (translate + scale) later without fighting a base centering transform. */
  --apex-visual-w: clamp(480px, 90vw, 980px);
  --apex-visual-h: clamp(480px, 80vh, 980px);
  position: fixed;
  top: 50%;
  left: 50%;
  width: var(--apex-visual-w);
  height: var(--apex-visual-h);
  margin-left: calc(var(--apex-visual-w) / -2);
  margin-top: calc(var(--apex-visual-h) / -2);
}
.apex-morse-word,
.apex-morse-logo {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transform: scale(0.3);
  transition: opacity 0.4s ease, transform 0.4s ease;
}
/* The logo punches out from the exact center of the (now-shattering) Morse
   code — starts tiny right at that point and overshoots past full size on
   its way in, selling the "burst through" impact. */
.apex-morse-logo.apex-show {
  transition: opacity 0.35s ease, transform 0.65s cubic-bezier(.2, 1.9, .4, 1);
}
.apex-morse-word {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(52px, 9vw, 88px);
  letter-spacing: 0.08em;
  background: var(--grad-full);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.apex-morse-logo {
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* The punch-in bounce (scale 0.3 → overshoot → 1) promotes this to its own
     GPU layer; without an early hint Chrome/Firefox sometimes raster that
     layer at the small starting scale and just stretch it through the rest
     of the animation, reading as soft/blurry until the next transform swap
     (flyToHeader) forces a fresh, correctly-scaled raster. Hinting the
     layer up front makes it raster at the settled size from the start. */
  will-change: transform, opacity;
  backface-visibility: hidden;
}
.apex-morse-word.apex-show,
.apex-morse-logo.apex-show {
  opacity: 1;
  transform: scale(1);
}
/* The FLIP-computed flight transform is set inline via JS; this just
   declares the transition so that inline transform change animates. */
.apex-morse-visual.apex-flying {
  /* Expo-out: eases smoothly rather than linear-sliding, but slow enough
     that shrinking-and-moving into the header slot reads as a deliberate
     glide, not a snap. */
  transition: transform 1.6s cubic-bezier(.16,1,.3,1);
}
.apex-morse-visual.apex-landed {
  transition: opacity 0.3s ease;
  opacity: 0;
}

/* ---------- Headline: typed line 1, "at once" line 2 ---------- */
html.apex-intro-pending #apex-hero-h1 {
  opacity: 0;
}
html.apex-intro-pending #apex-hero-h1.apex-h1-active {
  opacity: 1;
}
.apex-type-line.apex-typing::after {
  content: "";
  display: inline-block;
  width: 3px;
  height: 0.85em;
  margin-left: 3px;
  background: var(--teal);
  vertical-align: -0.1em;
  animation: apex-cursor-blink 0.9s steps(1) infinite;
}
@keyframes apex-cursor-blink {
  50% { opacity: 0; }
}
html.apex-intro-pending .apex-line2 {
  opacity: 0;
  transform: scale(0.9);
  display: inline-block;
  transition: opacity 0.5s ease, transform 0.5s ease;
}
html.apex-intro-pending .apex-line2.apex-in {
  opacity: 1;
  transform: scale(1);
}

/* ---------- Eyebrow / subtext / buttons — revealed together, last ---------- */
html.apex-intro-pending .hero [data-hero-line] {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.7s cubic-bezier(.16,.84,.44,1), transform 0.7s cubic-bezier(.16,.84,.44,1);
}
html.apex-intro-pending .hero [data-hero-line].apex-in {
  opacity: 1;
  transform: translateY(0);
}

/* ---------- Cursor companion: trailing satellite + thruster trail ---------- */
.apex-cursor-companion {
  position: fixed;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  /* Deliberately low: above the background starfield canvas (also z-index 0,
     but earlier in the DOM so this paints over it) and behind every real page
     element — main/footer/navbar all sit at z-index 1 or higher — so the
     satellite drifts behind text, cards, and buttons instead of over them. */
  z-index: 0;
  pointer-events: none;
  will-change: transform;
}
.apex-cursor-sat-icon {
  position: absolute;
  top: -13px;
  left: -13px;
  width: 26px;
  height: 26px;
  background-color: var(--cream);
  -webkit-mask-image: url("../assets/cursor-satellite.svg");
  mask-image: url("../assets/cursor-satellite.svg");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  filter: drop-shadow(0 0 6px rgba(45, 212, 191, .55));
  opacity: 0;
  transition: opacity 0.4s ease;
}
.apex-cursor-companion.apex-cursor-active .apex-cursor-sat-icon {
  opacity: 0.92;
}

.apex-cursor-thruster {
  position: fixed;
  top: 0;
  left: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: radial-gradient(circle, #ffd27a 0%, var(--orange) 45%, rgba(245, 158, 11, 0) 75%);
  pointer-events: none;
  z-index: 0;
  opacity: 0.9;
  animation: apex-thruster-fade 0.85s ease-out forwards;
}
@keyframes apex-thruster-fade {
  to {
    opacity: 0;
    transform: translate(var(--tx, 0px), var(--ty, 0px)) scale(0.25);
  }
}

@media (hover: none), (pointer: coarse) {
  .apex-cursor-companion, .apex-cursor-thruster { display: none !important; }
}
@media (prefers-reduced-motion: reduce) {
  .apex-cursor-companion, .apex-cursor-thruster { display: none !important; }
}

/* ---------- Compare table polish (products.html) ---------- */
#apex-compare-body td:not(:first-child),
#apex-compare-body th:not(:first-child) {
  font-family: var(--font-mono);
  font-size: 13.5px;
}
#apex-compare-body tr {
  transition: background 0.2s ease;
}
#apex-compare-body tr:hover {
  background: var(--navy-raised);
}

/* ---------- Line-by-line text reveal (About story panel) ----------
   JS wraps each word in a .apex-word span; lines are grouped at runtime by
   their actual rendered position (so it stays correct across any viewport
   width/reflow) and revealed top to bottom, one line at a time. */
.apex-word {
  display: inline-block;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.5s ease, transform 0.5s ease;
}
.apex-word.apex-in {
  opacity: 1;
  transform: translateY(0);
}
/* Whichever word the cursor is over picks up the site's signature gradient —
   just that one word, nothing else in the paragraph. */
.apex-word.apex-in:hover {
  background: var(--grad-full);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  transition: color 0.2s ease;
}

/* ---------- Count-up target (no layout change, just a hook) ---------- */
[data-countup] { font-variant-numeric: tabular-nums; }
