/* ============================================================================
   WeFixed — animation classes. JS adds .is-visible when elements enter view.
   All effects mirror the Next.js motion-primitives and play on every device.
============================================================================ */

/* ---- Scroll reveal -------------------------------------------------------
   Content is VISIBLE BY DEFAULT. main.js adds html.reveal-js when it runs, which
   hides the blocks so the IntersectionObserver can animate them in on scroll
   (matches the Next build). If the script never runs, the class is never added
   and everything stays visible — the page can't be trapped blank. A JS failsafe
   also reveals anything still hidden after a few seconds. */
/* Default reveal — modern rise + fade + a hair of scale. Compositor-only
   (opacity + transform), so it stays off the main thread and doesn't touch LCP
   (below-the-fold only; the hero LCP element keeps its own fast word-reveal). */
html.reveal-js [data-reveal] {
  opacity: 0;
  transform: translateY(24px) scale(0.985);
  transition: opacity var(--dur-reveal) var(--ease-out), transform var(--dur-reveal) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}
/* Must out-specify the hide rule above (html.reveal-js [data-reveal]), or
   adding .is-visible never wins and the content stays trapped at opacity:0. */
html.reveal-js [data-reveal].is-visible { opacity: 1; transform: none; }

/* "Power-on" icons — when a device fault card (or process step) reveals, its
   icon snaps to life: scales up from small with a tiny overshoot, like a board
   getting power. Staggered behind the card's own reveal delay. Compositor-only
   (transform + opacity); plays once. Visible by default without JS. */
html.reveal-js .dev-issue[data-reveal] .dev-issue__icon,
html.reveal-js .about-value[data-reveal] .about-value__icon { opacity: 0; transform: scale(0.5); }
html.reveal-js .dev-issue[data-reveal].is-visible .dev-issue__icon,
html.reveal-js .about-value[data-reveal].is-visible .about-value__icon {
  animation: wf-power-on 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both;
  animation-delay: calc(var(--reveal-delay, 0ms) + 130ms);
}
@keyframes wf-power-on {
  0%   { opacity: 0; transform: scale(0.5); }
  62%  { opacity: 1; transform: scale(1.09); }
  100% { opacity: 1; transform: scale(1); }
}
/* Diagnostic page: the "what's included" check marks power on in sequence when
   their card scrolls into view (one shared reveal, so stagger via nth-child).
   Scroll-triggered → works the same on mobile. */
html.reveal-js .card[data-reveal] .diag-list .icon { opacity: 0; transform: scale(0.5); }
html.reveal-js .card[data-reveal].is-visible .diag-list li:nth-child(n) .icon {
  animation: wf-power-on 0.45s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
html.reveal-js .card[data-reveal].is-visible .diag-list li:nth-child(1) .icon { animation-delay: 0.14s; }
html.reveal-js .card[data-reveal].is-visible .diag-list li:nth-child(2) .icon { animation-delay: 0.21s; }
html.reveal-js .card[data-reveal].is-visible .diag-list li:nth-child(3) .icon { animation-delay: 0.28s; }
html.reveal-js .card[data-reveal].is-visible .diag-list li:nth-child(4) .icon { animation-delay: 0.35s; }
html.reveal-js .card[data-reveal].is-visible .diag-list li:nth-child(5) .icon { animation-delay: 0.42s; }
html.reveal-js .card[data-reveal].is-visible .diag-list li:nth-child(6) .icon { animation-delay: 0.49s; }
html.reveal-js .card[data-reveal].is-visible .diag-list li:nth-child(n+7) .icon { animation-delay: 0.56s; }

/* "Devices we repair" cards assemble in: when the section reveals, each category
   icon powers on (staggered by card), then the device chips pop in one after
   another in a wave. Compositor-only (transform + opacity); keyed off the single
   .cat-cards.is-visible, so it works in the mobile carousel too. No hover
   conflict (icons/chips don't have a transform hover). */
html.reveal-js .cat-cards .cat-card__icon { opacity: 0; transform: scale(0.5); }
html.reveal-js .cat-cards.is-visible .cat-card__icon { animation: wf-power-on 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both; }
.cat-cards.is-visible .cat-card:nth-child(1) .cat-card__icon { animation-delay: 0.10s; }
.cat-cards.is-visible .cat-card:nth-child(2) .cat-card__icon { animation-delay: 0.18s; }
.cat-cards.is-visible .cat-card:nth-child(3) .cat-card__icon { animation-delay: 0.26s; }
.cat-cards.is-visible .cat-card:nth-child(4) .cat-card__icon { animation-delay: 0.34s; }
html.reveal-js .cat-cards .cat-chip { opacity: 0; transform: translateY(8px) scale(0.85); }
html.reveal-js .cat-cards.is-visible .cat-chip { animation: wf-chip-pop 0.44s cubic-bezier(0.34, 1.56, 0.64, 1) both; }
.cat-cards.is-visible .cat-chip:nth-child(1) { animation-delay: 0.42s; }
.cat-cards.is-visible .cat-chip:nth-child(2) { animation-delay: 0.50s; }
.cat-cards.is-visible .cat-chip:nth-child(3) { animation-delay: 0.58s; }
.cat-cards.is-visible .cat-chip:nth-child(4) { animation-delay: 0.66s; }
.cat-cards.is-visible .cat-chip:nth-child(n+5) { animation-delay: 0.74s; }
@keyframes wf-chip-pop { from { opacity: 0; transform: translateY(8px) scale(0.85); } to { opacity: 1; transform: none; } }

/* Kinetic heading reveal (opt-in via data-kinetic). Each word rises out from
   behind a clip with a small stagger when the block scrolls in. Transform-only
   (GPU), JS just splits the words once. The padding/negative-margin pair gives
   descenders (g, y, p) room so the clip never shaves them. */
[data-kinetic] .kw { display: inline-block; overflow: hidden; vertical-align: top;
  padding-bottom: 0.14em; margin-bottom: -0.14em; }
[data-kinetic] .kw > span { display: inline-block; transform: translateY(115%);
  transition: transform 0.62s var(--ease-out); transition-delay: calc(var(--wi, 0) * 0.04s); }
html.reveal-js .is-visible [data-kinetic] .kw > span,
html.reveal-js [data-kinetic].is-visible .kw > span { transform: none; }
/* A reveal block holding kinetic words only fades (no block rise) so the words —
   not the container — carry the motion. Applies to any reveal wrapper, not just
   .section__head, so kinetic headings read the same everywhere. */
html.reveal-js [data-reveal]:has([data-kinetic]) { transform: none; }


/* ---- Word reveal: per-word rise, 40ms stagger --------------------------- */
/* NB: applied to the <h1> itself, so it must NOT force display:inline (that made
   the heading float next to the badge). Keep it a block; only the words are
   inline-block so they wrap normally. */
.word-reveal .word { display: inline-block; vertical-align: top; }
.word-reveal .word > span { display: inline-block; opacity: 0; }
/* The rise is applied FRESH only once JS adds .is-animate — which it does after
   the webfont has loaded, so the font swap never re-flows it mid-rise. CRITICAL:
   this animates ONLY opacity + transform (translateY) — the two properties the
   browser runs on the GPU compositor thread. No blur, no text-shadow, no filter:
   those force a repaint every frame and that is what made the headline stutter on
   mobile. Real DOM text, so LCP/SEO are untouched. */
.word-reveal.is-animate .word > span {
  animation: wf-word-up 0.78s cubic-bezier(0.16, 1, 0.3, 1) both;
  animation-delay: var(--word-delay, 0ms);
  will-change: transform, opacity;
}

/* Travel is em-based so it scales with the headline size — on a large mobile
   H1 the rise is proportionally as soft as on desktop (a fixed 14px read as a
   hard pop at big sizes). Compositor-only: opacity + transform. */
@keyframes wf-word-up {
  from { opacity: 0; transform: translateY(0.5em); }
  to   { opacity: 1; transform: none; }
}

/* ---- Rotating last word: cycles a list with a soft rise + blur ----------
   Sizers (one per word, stacked in the same grid cell) reserve the widest
   word's width so the headline never reflows. Built in JS from
   [data-rotating-word]. */
.rotating-word { display: inline-grid; vertical-align: bottom; }
.rotating-word__size {
  grid-area: 1 / 1;
  visibility: hidden;
  white-space: nowrap;
}
.rotating-word__live {
  grid-area: 1 / 1;
  overflow: hidden;
  display: inline-block;
}
.rotating-word__word {
  display: inline-block;
  white-space: nowrap;
  color: var(--accent);
  transform-origin: left center;
  transition: transform 0.55s var(--ease-out), opacity 0.55s var(--ease-out),
    filter 0.55s var(--ease-out);
}
.rotating-word__word.is-exiting {
  transform: translateY(-0.7em) scale(0.82);
  opacity: 0;
  filter: blur(7px);
}
.rotating-word__word.is-entering {
  transform: translateY(0.7em) scale(1.18);
  opacity: 0;
  filter: blur(7px);
  transition: none;
}

/* ---- Count-up: just a styling hook; value animated in JS ----------------- */
[data-countup] { font-variant-numeric: tabular-nums; }

/* ---- Pulse for the active tracker dot ----------------------------------- */
@keyframes wf-pulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--accent-glow); }
  50% { box-shadow: 0 0 0 8px transparent; }
}
.wf-pulse { animation: wf-pulse 1.6s var(--ease-out) infinite; }

/* ---- Marquee keyframe (used by .marquee__track) ------------------------- */
@keyframes wf-marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* ---- Generic small-dot live indicator ----------------------------------- */
@keyframes wf-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.wf-blink { animation: wf-blink 1.4s ease-in-out infinite; }

/* ---- CPU architecture: pulses travel each trace via offset-path ---------
   Shared, identical selectors with the Next build (globals.css). */
.b2b__cpu { display: flex; flex-direction: column; height: 100%; padding: 1.75rem 2rem;
  border: 1px solid var(--line-dark); border-radius: 14px; background: var(--ink-2); }
.b2b__cpu-cap { margin: 0; font-family: var(--font-mono, monospace); font-size: 11px;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-dk-2); }
.b2b__cpu-art { flex: 1; display: flex; align-items: center; justify-content: center; padding: 0.5rem 0; }
.b2b__cpu-art .cpu-svg { width: 100%; max-width: 34rem; height: auto; }
.cpu-architecture {
  offset-anchor: 10px 0px;
  animation: cpu-path-move 5s linear infinite both;
  will-change: offset-distance;
}
@keyframes cpu-path-move {
  0% { offset-distance: 0%; }
  100% { offset-distance: 100%; }
}
.cpu-line-1 { offset-path: path("M 10 20 h 79.5 q 5 0 5 5 v 24"); animation-duration: 5s; animation-delay: 1s; }
.cpu-line-2 { offset-path: path("M 180 10 h -69.7 q -5 0 -5 5 v 24"); animation-duration: 5s; animation-delay: 3s; }
.cpu-line-3 { offset-path: path("M 130 20 v 21.8 q 0 5 -5 5 h -10"); animation-duration: 6s; animation-delay: 2s; }
.cpu-line-4 { offset-path: path("M 170 80 v -21.8 q 0 -5 -5 -5 h -50"); animation-duration: 6s; animation-delay: 1.5s; }
.cpu-line-5 { offset-path: path("M 135 65 h 15 q 5 0 5 5 v 10 q 0 5 -5 5 h -39.8 q -5 0 -5 -5 v -20"); animation-duration: 6.5s; animation-delay: 2.5s; }
.cpu-line-6 { offset-path: path("M 94.8 95 v -36"); animation-duration: 4.5s; animation-delay: 1s; }
.cpu-line-7 { offset-path: path("M 88 88 v -15 q 0 -5 -5 -5 h -10 q -5 0 -5 -5 v -5 q 0 -5 5 -5 h 14"); animation-duration: 5.5s; animation-delay: 3s; }
.cpu-line-8 { offset-path: path("M 30 30 h 25 q 5 0 5 5 v 6.5 q 0 5 5 5 h 20"); animation-duration: 6s; animation-delay: 0.5s; }

/* Эф1 — halftone WORLD dot-map hero band (us-dotmap.php + dotmap.js).
   Shared .dm selectors with the Next build. */
.loc-hero { position: relative; overflow: hidden; min-height: 440px;
  border: 1px solid var(--line-dark); border-radius: 20px; background: var(--ink); }
@media (min-width: 640px) { .loc-hero { min-height: 480px; } }
.loc-hero__scrim { position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(90deg, var(--ink) 25%,
    color-mix(in srgb, var(--ink) 55%, transparent) 50%, transparent 100%); }
.loc-hero__text { position: relative; display: flex; flex-direction: column;
  justify-content: center; min-height: 440px; max-width: 34rem; padding: 56px 24px; }
@media (min-width: 640px) { .loc-hero__text { min-height: 480px; padding: 56px 48px; } }
.loc-hero__text .h2.on-dark { margin-top: 12px; color: var(--text-dk); }
.loc-hero__text .on-dark-2 { margin-top: 16px; max-width: 28rem; color: var(--text-dk-2); }

.dm-wrap { position: absolute; inset: 0; overflow: hidden; }
/* Framed on North America: the focus mask is centred on the USA marker (~28% of
   the world map) and the map is zoomed/positioned so the States sit in the
   visible right half, past the text scrim. */
.dm { position: absolute; top: 64%; left: 36%; width: 160%; height: auto;
  transform: translateY(-50%) translateX(calc(var(--p, 0) * -24px));
  color: #4f74b8; will-change: transform;
  -webkit-mask-image: radial-gradient(82% 86% at 27% 35%, #000 40%, transparent 74%);
  mask-image: radial-gradient(82% 86% at 27% 35%, #000 40%, transparent 74%); }
.dm-loc-dot { fill: var(--accent); }
.dm-loc-ring { fill: var(--accent); transform-box: fill-box; transform-origin: center;
  animation: dm-pulse 2.6s var(--ease-out) infinite; }
@keyframes dm-pulse { 0% { transform: scale(1); opacity: 0.5; } 100% { transform: scale(7); opacity: 0; } }

/* Flat-map ⇆ globe morph. dotmap-globe.js draws the world dots on this canvas and
   curls them from the flat map into a rotating globe based on the band's scroll. */
.loc-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; z-index: 0; }


/* ----------------------------------------------------------------------------
   Reduced motion (WCAG 2.2 — 2.2.2 Pause/Stop/Hide, 2.3.3 Animation from
   Interactions). When the OS requests reduced motion: stop every infinite
   decorative loop (marquee, pulse, aurora, scan, stream, blink…) and collapse
   transitions — while GUARANTEEING content is visible (reveals and headlines are
   never trapped at opacity:0). The JS modules read the same setting via
   window.WeFixed.prefersReduced and skip parallax / count-up / word-reveal.
---------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto !important; }
  *, *::before, *::after {
    animation: none !important;
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
  }
  [data-reveal] { opacity: 1 !important; transform: none !important; }
  [data-word-reveal] { opacity: 1 !important; }
  .word-reveal .word > span { opacity: 1 !important; transform: none !important; }
}
