/* ============================================================================
   MOTION LAYER — load choreography + scroll reveals
   ----------------------------------------------------------------------------
   Ported from https://solaris-energy.framer.website (Framer site
   1wGLiqdHAIHLNS51d9J0lY). Every number here is the reference site's own value,
   read out of `#__framer__appearAnimationsContent` and the page chunk's
   `__framer__enter` / `__framer__animate` / `__framer__threshold` props — not
   eyeballed. The springs below are that site's spring configs run through a
   port of framer-motion's own findSpring + generateLinearEasing, so the
   `linear()` curves ARE what Framer resolves them to.

   TWO HARD CONSTRAINTS, both because the Figma render is locked:

   1. This file never declares `transform` or `transition`, and never touches a
      box-model property. The approved hover/card layer at the end of
      home-sections.css already owns `transform` + `transition` on the very
      elements we reveal (.blog-card, .sob-card, .pvc-card, .psh-card,
      .icar-slide, .qna-faq). So reveals use the STANDALONE `translate` /
      `scale` properties, which compose with `transform` instead of replacing
      it, and CSS animations instead of transitions.

   2. No reveal ever declares a final value. Each @keyframes has only a `from`
      block, so the implicit `to` is the element's own underlying value. That is
      load-bearing: a .pvc-card in one of §3's faded slots rests at Figma's
      `opacity: .7` (now via the conveyor's `--pv-o`), and a reveal that ended
      at `opacity: 1` would silently brighten it — and would then fight the
      scroll-driven value for the rest of the page's life.

   Everything is gated three ways:
     html.rv-on          rendered server-side by layout.tsx so it is there for the
                         first paint; layout's failsafe timer takes it back off if
                         the bundle never boots, so a broken deploy degrades to
                         "no animation" rather than "blank page".
     scripting: enabled  with JS off there is no engine to reveal anything, so the
                         hidden state must not exist at all. This is why the query
                         is here and not a <noscript> override — an override would
                         mean a third copy of the selector list.
     prefers-reduced-motion: no-preference
                         the reference site ignores this query. We do not.

   Sizes are rem, per the standing fluid-scale directive: at the 1920 canvas the
   root is 16px so 6.25rem == Figma's 100px exactly, and above 1200px the
   travel scales with the design instead of staying pinned.

   GENERATED BLOCKS — do not hand-edit between the GEN markers. Source of truth
   is components/motion/motion-spec.json; regenerate with
       node design-reference/_meta/gen-motion-css.js
   ========================================================================== */

:root {
  /* --- springs, generated from the reference site's spring configs --- */
  /* GEN:SPRINGS:START */
  /* canonical block reveal — 20 of the 26 wrappers
     stiffness 250.00 · damping 80.00 · zeta 2.5298 · at rest after 1650ms
     framer emits 165 stops @10ms; 96 stops here, max deviation 0.457% of travel */
  --rv-sp-block-d: 1650ms;
  --rv-sp-block: linear(0, 0.0248, 0.0705, 0.1195, 0.1674, 0.2131, 0.2563, 0.2972, 0.3359, 0.3724, 0.4069, 0.4395, 0.4704, 0.4995, 0.527, 0.5531, 0.5776, 0.6009, 0.6228, 0.6436, 0.6632, 0.6817, 0.6992, 0.7158, 0.7314, 0.7462, 0.7601, 0.7733, 0.7858, 0.7976, 0.8087, 0.8192, 0.8292, 0.8386, 0.8475, 0.8559, 0.8638, 0.8713, 0.8784, 0.885, 0.8914, 0.8973, 0.903, 0.9083, 0.9134, 0.9181, 0.9226, 0.9269, 0.9309, 0.9347, 0.9383, 0.9417, 0.9449, 0.9479, 0.9508, 0.9535, 0.9561, 0.9585, 0.9608, 0.9629, 0.965, 0.9669, 0.9687, 0.9704, 0.9721, 0.9736, 0.9751, 0.9764, 0.9777, 0.9789, 0.9801, 0.9812, 0.9822, 0.9832, 0.9841, 0.985, 0.9858, 0.9866, 0.9873, 0.988, 0.9887, 0.9893, 0.9899, 0.9905, 0.991, 0.9915, 0.992, 0.9924, 0.9928, 0.9932, 0.9936, 0.9939, 0.9943, 0.9946, 0.9949, 0.9952);
  /* hero imagery
     stiffness 400.00 · damping 80.00 · zeta 2.0000 · at rest after 1100ms
     framer emits 110 stops @10ms; 96 stops here, max deviation 0.172% of travel */
  --rv-sp-hero-d: 1100ms;
  --rv-sp-hero: linear(0, 0.0201, 0.0621, 0.1114, 0.1619, 0.2111, 0.258, 0.3024, 0.3443, 0.3837, 0.4208, 0.4556, 0.4884, 0.5191, 0.5481, 0.5753, 0.6008, 0.6248, 0.6474, 0.6686, 0.6886, 0.7073, 0.7249, 0.7415, 0.757, 0.7716, 0.7854, 0.7983, 0.8104, 0.8218, 0.8325, 0.8426, 0.8521, 0.861, 0.8694, 0.8772, 0.8846, 0.8915, 0.8981, 0.9042, 0.91, 0.9154, 0.9205, 0.9253, 0.9298, 0.934, 0.938, 0.9417, 0.9452, 0.9485, 0.9516, 0.9545, 0.9572, 0.9598, 0.9622, 0.9645, 0.9666, 0.9686, 0.9705, 0.9723, 0.974, 0.9755, 0.977, 0.9784, 0.9797, 0.9809, 0.9821, 0.9831, 0.9842, 0.9851, 0.986, 0.9868, 0.9876, 0.9884, 0.9891, 0.9897, 0.9904, 0.9909, 0.9915, 0.992, 0.9925, 0.9929, 0.9934, 0.9938, 0.9941, 0.9945, 0.9948, 0.9951, 0.9954, 0.9957, 0.996, 0.9962, 0.9964, 0.9966, 0.9968, 0.997);
  /* FAQ list, position-only
     stiffness 400.00 · damping 60.00 · zeta 1.5000 · at rest after 850ms
     framer emits 85 stops @10ms; 85 stops here, max deviation 0.000% of travel */
  --rv-sp-slide-d: 850ms;
  --rv-sp-slide: linear(0, 0.0168, 0.0561, 0.1064, 0.1611, 0.2166, 0.2708, 0.3227, 0.3716, 0.4175, 0.4604, 0.5002, 0.5372, 0.5716, 0.6034, 0.6329, 0.6601, 0.6854, 0.7088, 0.7305, 0.7505, 0.7691, 0.7863, 0.8022, 0.8169, 0.8305, 0.8431, 0.8548, 0.8656, 0.8756, 0.8848, 0.8934, 0.9013, 0.9087, 0.9155, 0.9218, 0.9276, 0.933, 0.9379, 0.9426, 0.9468, 0.9508, 0.9545, 0.9578, 0.961, 0.9639, 0.9666, 0.9691, 0.9714, 0.9735, 0.9755, 0.9773, 0.979, 0.9805, 0.982, 0.9833, 0.9846, 0.9857, 0.9868, 0.9878, 0.9887, 0.9895, 0.9903, 0.991, 0.9917, 0.9923, 0.9929, 0.9934, 0.9939, 0.9944, 0.9948, 0.9952, 0.9955, 0.9959, 0.9962, 0.9964, 0.9967, 0.997, 0.9972, 0.9974, 0.9976, 0.9978, 0.9979, 0.9981, 0.9982);
  /* header drop-in — {duration:.8, bounce:0}
     stiffness 133.21 · damping 23.08 · zeta 1.0000 · at rest after 750ms
     framer emits 75 stops @10ms; 75 stops here, max deviation 0.000% of travel */
  --rv-sp-nav-d: 750ms;
  --rv-sp-nav: linear(0, 0.0063, 0.0235, 0.0489, 0.0806, 0.117, 0.1565, 0.198, 0.2406, 0.2837, 0.3264, 0.3685, 0.4095, 0.4491, 0.4872, 0.5235, 0.5581, 0.5909, 0.6218, 0.6509, 0.6782, 0.7037, 0.7275, 0.7496, 0.7702, 0.7893, 0.807, 0.8233, 0.8384, 0.8523, 0.8651, 0.8769, 0.8877, 0.8976, 0.9067, 0.9151, 0.9227, 0.9297, 0.9361, 0.9419, 0.9473, 0.9521, 0.9565, 0.9606, 0.9642, 0.9676, 0.9706, 0.9734, 0.9759, 0.9782, 0.9803, 0.9821, 0.9838, 0.9854, 0.9868, 0.9881, 0.9892, 0.9903, 0.9912, 0.9921, 0.9928, 0.9935, 0.9942, 0.9947, 0.9952, 0.9957, 0.9961, 0.9965, 0.9969, 0.9972, 0.9974, 0.9977, 0.9979, 0.9981, 0.9983);
  /* hero tag & small pieces — {duration:1, bounce:.2}
     stiffness 80.90 · damping 14.39 · zeta 0.8000 · at rest after 950ms
     framer emits 95 stops @10ms; 95 stops here, max deviation 0.000% of travel */
  --rv-sp-tag-d: 950ms;
  --rv-sp-tag: linear(0, 0.0039, 0.015, 0.0321, 0.0544, 0.0809, 0.1109, 0.1436, 0.1785, 0.215, 0.2526, 0.2908, 0.3293, 0.3678, 0.4059, 0.4434, 0.4801, 0.5159, 0.5505, 0.5839, 0.616, 0.6466, 0.6759, 0.7037, 0.7299, 0.7548, 0.7781, 0.8, 0.8204, 0.8395, 0.8572, 0.8736, 0.8888, 0.9028, 0.9156, 0.9273, 0.938, 0.9478, 0.9566, 0.9646, 0.9717, 0.9781, 0.9838, 0.9889, 0.9933, 0.9972, 1.0006, 1.0036, 1.0061, 1.0082, 1.01, 1.0114, 1.0126, 1.0135, 1.0142, 1.0147, 1.015, 1.0151, 1.0152, 1.0151, 1.0148, 1.0146, 1.0142, 1.0138, 1.0133, 1.0128, 1.0122, 1.0117, 1.0111, 1.0105, 1.0099, 1.0094, 1.0088, 1.0082, 1.0077, 1.0071, 1.0066, 1.0061, 1.0056, 1.0052, 1.0047, 1.0043, 1.0039, 1.0036, 1.0032, 1.0029, 1.0026, 1.0023, 1.0021, 1.0018, 1.0016, 1.0014, 1.0012, 1.001, 1.0009);
  /* buttons — {duration:2, bounce:0}
     stiffness 21.31 · damping 9.23 · zeta 1.0000 · at rest after 1650ms
     framer emits 165 stops @10ms; 96 stops here, max deviation 0.074% of travel */
  --rv-sp-btn-d: 1650ms;
  --rv-sp-btn: linear(0, 0.003, 0.0116, 0.0247, 0.0417, 0.0618, 0.0845, 0.1093, 0.1357, 0.1634, 0.1919, 0.221, 0.2503, 0.2798, 0.3092, 0.3384, 0.3671, 0.3954, 0.423, 0.45, 0.4763, 0.5017, 0.5264, 0.5502, 0.5732, 0.5952, 0.6165, 0.6368, 0.6563, 0.675, 0.6928, 0.7098, 0.726, 0.7414, 0.7561, 0.77, 0.7833, 0.7958, 0.8078, 0.8191, 0.8298, 0.8399, 0.8495, 0.8585, 0.8671, 0.8751, 0.8827, 0.8899, 0.8967, 0.9031, 0.9091, 0.9148, 0.9201, 0.9251, 0.9298, 0.9342, 0.9384, 0.9423, 0.946, 0.9495, 0.9527, 0.9557, 0.9586, 0.9613, 0.9638, 0.9661, 0.9683, 0.9704, 0.9723, 0.9742, 0.9759, 0.9775, 0.9789, 0.9803, 0.9816, 0.9829, 0.984, 0.9851, 0.9861, 0.987, 0.9879, 0.9887, 0.9894, 0.9901, 0.9908, 0.9914, 0.992, 0.9926, 0.9931, 0.9935, 0.994, 0.9944, 0.9948, 0.9951, 0.9955, 0.9958);
  /* GEN:SPRINGS:END */

  /* --- tween easings, verbatim from the reference site --- */
  --rv-e-word: cubic-bezier(0.12, 0.23, 0.17, 0.98); /* per-word text effect */
  --rv-e-tween: cubic-bezier(0.44, 0, 0.56, 1); /* badges / plain fades   */
  --rv-d-word: 700ms;
  --rv-d-tween: 600ms;

  /* --- travel distances (Figma px / 16) --- */
  --rv-up-y: 6.25rem; /* 100px — canonical block reveal */
  --rv-rise-y: 1.25rem; /*  20px — small pieces, buttons   */
  --rv-badge-y: 2.5rem; /*  40px — section badges          */
  --rv-drop-y: -1.25rem; /* -20px — header drops in         */
  --rv-slide-y: 3.125rem; /*  50px — FAQ list, no fade       */
  --rv-zoom-s: 1.06; /* hero imagery settles out of a 6% overscan */
  --rv-word-y: 1.25rem;
  --rv-word-blur: 0.625rem; /* 10px */

  /* --- heading colour sweep — the one thing here NOT from the Framer site ---
     Lifted from https://www.sigenergy.com/en/ (`initAnimateText`, read out of
     static/pages/js/main.js), which models three states per character:

       at rest      15% alpha of the final colour   (its reset() gsap.set)
       at t = 0     snaps to the accent             (in a GSAP `keyframes` array
                                                     the FIRST value IS the from,
                                                     verified by stepping a paused
                                                     tween on the live site)
       t = 0 -> 1   eases accent -> the final colour

     WE DROP THE FIRST OF THOSE THREE, and it is the one deviation from Sigenergy.
     Its 15% ghost is what makes a heading read as half-faded ahead of the edge, and
     that is precisely what the user rejected (2026-08-13: "jo us heading ki final
     stage hai wohi show ho just us pe light guzare … opacity ko 100% rkh do"). So
     ours goes final -> accent -> final: a character is at its own colour until the
     light reaches it. Sigenergy's snap-and-settle shape is kept exactly; only what
     sits AHEAD of the edge changes, from ghost to done.

     The word reveal underneath STAYS — that was checked twice. Removing it left the
     heading with no entrance at all, and the user asked for it back in those words:
     "ghost animation b rkho jo pehle thi … jese pehle heading opacity 0 se 100 hoti
     thi or niche se uper ki traf slide ho k aati thi wo wali na k 15% wali". So the
     heading still fades up from below, and the light then passes over it.

     Its own numbers, verbatim: 500ms, a 250ms lead, and a per-heading stagger of
     clamp(0.2 - chars*0.005, 0.018, 0.035)s — the stagger is the only
     text-dependent one, so MotionLayer computes it into --rv-cstep.

     The accent is the single authored value in this block. Sigenergy's own is its
     brand teal #00A9A3; on the user's instruction (2026-08-13, "color blue ki
     jaga humara yallow use kro") ours is the brand accent --accent / #E2FA5A
     instead, so the sweep reads as this build's colour rather than theirs. The
     mechanism is identical either way — this is one token.

     Known consequence of that swap, and it is a real difference rather than a
     bug: #E2FA5A is very light, so against §1-§4 / §6-§9 / §11's near-white
     #FEFFF9 ground the edge reads as the letters brightening as they fill in,
     while on §5's and §10's black bands it reads as the bright flash Sigenergy
     gets everywhere. Same animation, and the ghost -> accent -> settle order
     still carries the movement on both. */
  --rv-sweep-accent: #e2fa5a;
  /* Sigenergy's own duration and lead, verbatim. --rv-cstep is its per-heading
     stagger, also verbatim, written by MotionLayer because it depends on the
     character count. There is deliberately NO `--rv-sweep-dim`: see the 15%-ghost
     note in the rules at the bottom of this file. */
  --rv-d-sweep: 500ms;
  --rv-sweep-lead: 250ms;
  /* THE ONE SPEED DIAL (user, 2026-08-13: "speed slow krdo jo light guzarti
     hai"). It scales the per-character duration and the travel of the edge
     TOGETHER, which is what has to happen: slowing only the stagger stretches the
     line without making any single letter linger, and slowing only the duration
     leaves the edge crossing at the same rate. 1 is Sigenergy's own speed.

     Why it earns a multiplier instead of just bigger numbers: the two values it
     scales are lifted verbatim and are worth keeping legible as theirs, and the
     stagger is computed in JS from the character count, so a hand-edited value
     could not stay adaptive.

     Settled at 1.5 over two passes: 2.2 was "boht zyada slow", 1 is Sigenergy's
     own and read as a flicker. At 1.5 a character takes 750ms and the edge steps
     27ms (a 50-character heading) to 52.5ms (a short one), so a heading crosses in
     roughly 1.1-1.3s. Note the curve below is heavily front-loaded — the accent is
     visible for about the first third of each character's slice, not all of it —
     which is why the useful range here starts above 1 rather than at it. */
  --rv-sweep-slow: 1.5;
  /* GSAP composes TWO eases here and neither one alone is the curve: the tween's
     `power2.out` (cubic-out) maps time, and then `keyframes`' default `easeEach`
     (`power1.inOut`, quad-in-out) runs inside the single colour segment. So the
     real curve is quadInOut(cubicOut(t)). Verified against the live site rather
     than assumed — a paused tween stepped to t = .1 / .25 / .5 reads r = 37 / 164
     / 247, which this reproduces exactly, where either ease alone is 30-80 off.
     50 stops, i.e. this file's usual 10ms resolution for a 500ms animation; max
     deviation 0.176% of travel, inside the 0.5% gate gen-motion-css.js enforces
     on the springs. */
  --rv-e-sweep: linear(0, 0.0072, 0.0276, 0.0596, 0.1017, 0.1523, 0.2102, 0.2742, 0.3431, 0.4159, 0.4916, 0.5649, 0.6293, 0.6855, 0.7344, 0.7768, 0.8134, 0.8448, 0.8718, 0.8947, 0.9141, 0.9304, 0.944, 0.9554, 0.9647, 0.9724, 0.9786, 0.9836, 0.9876, 0.9908, 0.9932, 0.9951, 0.9965, 0.9976, 0.9984, 0.9989, 0.9993, 0.9996, 0.9997, 0.9999, 0.9999, 1, 1, 1, 1, 1, 1, 1, 1, 1);
}

/* ============================================================================
   Lenis smooth scroll — inlined verbatim from node_modules/lenis/dist/lenis.css
   (lenis 1.3.23, the exact version the reference site loads). Inlined rather
   than imported so the whole styling story stays in public/assets/css.
   ========================================================================== */
html.lenis,
html.lenis body {
  height: auto;
}
.lenis:not(.lenis-autoToggle).lenis-stopped {
  overflow: clip;
}
.lenis [data-lenis-prevent],
.lenis [data-lenis-prevent-wheel],
.lenis [data-lenis-prevent-touch],
.lenis [data-lenis-prevent-vertical],
.lenis [data-lenis-prevent-horizontal] {
  overscroll-behavior: contain;
}
.lenis.lenis-smooth iframe {
  pointer-events: none;
}
.lenis.lenis-autoToggle {
  transition-property: overflow;
  transition-duration: 1ms;
  transition-behavior: allow-discrete;
}

@media (prefers-reduced-motion: no-preference) and (scripting: enabled) {
  /* ==========================================================================
     PRE-JS HIDDEN STATE
     This block is why the selector list has to live in CSS at all: the engine
     cannot add a class before the browser paints the SSR HTML, so without these
     rules every section would flash in at full opacity and then jump back to
     hidden. Generated from motion-spec.json so it can never drift out of sync
     with what MotionLayer.tsx actually observes.
     ========================================================================== */
  /* GEN:HIDE:START */
  /* drop — 4 selectors */
  html.rv-on :is(
      .site-header,
      .qz-back,
      .qz-help,
      .qr-tabs
    ):not(.rv-in) {
    opacity: 0;
    translate: 0 var(--rv-drop-y);
  }
  /* zoom — 1 selector */
  html.rv-on :is(
      .hero-bg
    ):not(.rv-in) {
    opacity: 0;
    scale: var(--rv-zoom-s);
  }
  /* up — 19 selectors */
  html.rv-on :is(
      .hero-houses,
      .excl-certs,
      .excl-card,
      .excl-video,
      .pvc-slot-rv .pvc-card,
      .psh-card,
      .tband-col,
      .icar-viewport,
      .iexc-chips,
      .iexc-right,
      .sob-band,
      .sob-card,
      .blog-card,
      .ftr-photo,
      .ftr-bottom,
      .qr-card,
      .qr-footer,
      .qc-row,
      .qb-card
    ):not(.rv-in) {
    opacity: 0;
    translate: 0 var(--rv-up-y);
  }
  /* rise — 14 selectors */
  html.rv-on :is(
      .hero-checks li,
      .hero-reviews,
      .hero-callout,
      .psh-checks li,
      .psh-callout,
      .psh-reviews,
      .icar-nav,
      .iexc-reviews,
      .qna-card,
      .ftr-legal,
      .qz-side,
      .qz-search,
      .qr-side,
      .qr-contact
    ):not(.rv-in) {
    opacity: 0;
    translate: 0 var(--rv-rise-y);
  }
  /* btn — 10 selectors */
  html.rv-on :is(
      .hero-inner > a.btn-dark,
      .excl-left > a.btn-dark,
      .psh-btn-reviews,
      .iexc-cta,
      .blog-cta,
      .fbiz-btn,
      .ftr-cta,
      .qz-cta,
      .qc-continue,
      .qb-submit
    ):not(.rv-in) {
    opacity: 0;
    translate: 0 var(--rv-rise-y);
  }
  /* badge — 5 selectors */
  html.rv-on :is(
      .excl-eyebrow,
      .pv-how,
      .iexc-eyebrow,
      .blog-sub,
      .qb-dur
    ):not(.rv-in) {
    opacity: 0;
    translate: 0 var(--rv-badge-y);
  }
  /* fade — 3 selectors */
  html.rv-on :is(
      .fbiz-photo,
      .qz-cant,
      .qz-dots
    ):not(.rv-in) {
    opacity: 0;
  }
  /* slide — 2 selectors */
  html.rv-on :is(
      .qna-faqs,
      .qr-faqs
    ):not(.rv-in) {
    translate: 0 var(--rv-slide-y);
  }
  /* GEN:HIDE:END */

  /* ==========================================================================
     POST-JS HIDDEN STATE
     Same states, keyed on the variant class the engine stamps on each element.
     Redundant with the generated block above by design — it keeps the hidden
     state correct for anything the engine picks up dynamically.
     ========================================================================== */
  html.rv-on .rv-v-up:not(.rv-in) {
    opacity: 0;
    translate: 0 var(--rv-up-y);
  }
  html.rv-on .rv-v-rise:not(.rv-in),
  html.rv-on .rv-v-btn:not(.rv-in) {
    opacity: 0;
    translate: 0 var(--rv-rise-y);
  }
  html.rv-on .rv-v-badge:not(.rv-in) {
    opacity: 0;
    translate: 0 var(--rv-badge-y);
  }
  html.rv-on .rv-v-drop:not(.rv-in) {
    opacity: 0;
    translate: 0 var(--rv-drop-y);
  }
  html.rv-on .rv-v-zoom:not(.rv-in) {
    opacity: 0;
    scale: var(--rv-zoom-s);
  }
  /* .slide is position-only — the reference's FAQ container enters at
     `opacity: 1, y: 50`, so it must NOT get an opacity keyframe. */
  html.rv-on .rv-v-slide:not(.rv-in) {
    translate: 0 var(--rv-slide-y);
  }
  html.rv-on .rv-v-fade:not(.rv-in) {
    opacity: 0;
  }

  /* --- per-word tokens ---
     .rv-w is a span the engine created around a single word, so inline-block on
     it is layout-neutral (one word cannot wrap inside itself) and the spaces
     between words survive as real text nodes, so line breaking is unchanged.
     .rv-el is an EXISTING element kept whole as one token — every
     gradient-clipped heading span lands here. It deliberately gets no `display`
     change: `.hero h1 .hl` paints a highlight bar positioned against its own
     box, and inline vs inline-block would move that bar. `translate` is a no-op
     on a non-replaced inline element, which is exactly the right fallback. */
  html.rv-on .rv-w {
    display: inline-block;
  }
  html.rv-on .rv-w:not(.rv-in),
  html.rv-on .rv-el:not(.rv-in) {
    opacity: 0.001;
    filter: blur(var(--rv-word-blur));
    translate: 0 var(--rv-word-y);
  }
  /* the parent stays blank until the engine has split it, so the unsplit text
     is never visible for a frame */
  /* GEN:WORDPARENT:START */
  /* 65 text blocks the engine splits into per-word tokens */
  html.rv-on :is(
      .hero-inner h1,
      .hero-sub,
      .excl-h,
      .pv-h,
      .psh-h,
      .tband-title,
      .icar-h,
      .icar-sub,
      .iexc-h,
      .iexc-sub,
      .sob-h,
      .sob-p,
      .sob-note,
      .blog-h,
      .fbiz-h,
      .fbiz-p,
      .qna-h,
      .qna-sub,
      .ftr-h,
      .ftr-sub,
      .qz-h,
      .qr-restart-label,
      .qr-reco-lines p,
      .qr-reco-answers,
      .qr-help,
      .qr-tab-label,
      .qr-topbar h1,
      .qr-topbar-link,
      .qr-pkg-h,
      .qr-blurb,
      .qr-inc-t,
      .qr-spec-t,
      .qr-price-cap,
      .qr-price-val,
      .qr-cta-label,
      .qr-save-pc,
      .qr-save-t,
      .qr-save-how,
      .qr-faq-h,
      .qr-faq-sub,
      .qr-contact p,
      .qr-reviews-t,
      .qr-faq-q,
      .qr-ftr-blurb,
      .qr-ftr-navh,
      .qr-ftr-nav li,
      .qr-ftr-legal p,
      .qc-save,
      .qc-h,
      .qc-blurb,
      .qc-spec-t,
      .qc-price-cap,
      .qc-price-val,
      .qc-add-t,
      .qb-h,
      .qb-submit-t,
      .qb-back-label,
      .qb-prod-t,
      .qb-prod-sub,
      .qb-inc-t,
      .qb-tbf-t,
      .qb-tbf-body,
      .qb-price-cap,
      .qb-price-val,
      .qb-price-mo-val
    ):not(.rv-split) {
    opacity: 0;
  }
  /* GEN:WORDPARENT:END */

  /* ==========================================================================
     THE REVEALS
     Only a `from` block in every keyframe — see constraint 2 in the header.
     `animation-fill-mode: backwards` holds that from-state through the delay and
     then releases the element entirely once the animation ends, so nothing here
     outlives the reveal or shadows a hover state.
     ========================================================================== */
  @keyframes rv-up {
    from {
      opacity: 0;
      translate: 0 var(--rv-up-y);
    }
  }
  @keyframes rv-rise {
    from {
      opacity: 0;
      translate: 0 var(--rv-rise-y);
    }
  }
  @keyframes rv-badge {
    from {
      opacity: 0;
      translate: 0 var(--rv-badge-y);
    }
  }
  @keyframes rv-drop {
    from {
      opacity: 0;
      translate: 0 var(--rv-drop-y);
    }
  }
  @keyframes rv-zoom {
    from {
      opacity: 0;
      scale: var(--rv-zoom-s);
    }
  }
  @keyframes rv-slide {
    from {
      translate: 0 var(--rv-slide-y);
    }
  }
  @keyframes rv-fade {
    from {
      opacity: 0;
    }
  }
  @keyframes rv-word {
    from {
      opacity: 0.001;
      filter: blur(var(--rv-word-blur));
      translate: 0 var(--rv-word-y);
    }
  }

  html.rv-on :is(
      .rv-v-up,
      .rv-v-rise,
      .rv-v-btn,
      .rv-v-badge,
      .rv-v-drop,
      .rv-v-zoom,
      .rv-v-slide,
      .rv-v-fade,
      .rv-w,
      .rv-el
    ).rv-in {
    /* --rv-i is stamped per element by the engine; --rv-lead / --rv-step are
       inherited from the group container. */
    animation-delay: calc(var(--rv-lead, 0ms) + var(--rv-i, 0) * var(--rv-step, 0ms));
    animation-fill-mode: backwards;
    animation-iteration-count: 1;
  }

  html.rv-on .rv-v-up.rv-in {
    animation-name: rv-up;
    animation-duration: var(--rv-sp-block-d);
    animation-timing-function: var(--rv-sp-block);
  }
  html.rv-on .rv-v-rise.rv-in {
    animation-name: rv-rise;
    animation-duration: var(--rv-sp-tag-d);
    animation-timing-function: var(--rv-sp-tag);
  }
  html.rv-on .rv-v-btn.rv-in {
    animation-name: rv-rise;
    animation-duration: var(--rv-sp-btn-d);
    animation-timing-function: var(--rv-sp-btn);
  }
  html.rv-on .rv-v-badge.rv-in {
    animation-name: rv-badge;
    animation-duration: var(--rv-d-tween);
    animation-timing-function: var(--rv-e-tween);
  }
  html.rv-on .rv-v-drop.rv-in {
    animation-name: rv-drop;
    animation-duration: var(--rv-sp-nav-d);
    animation-timing-function: var(--rv-sp-nav);
  }
  html.rv-on .rv-v-zoom.rv-in {
    animation-name: rv-zoom;
    animation-duration: var(--rv-sp-hero-d);
    animation-timing-function: var(--rv-sp-hero);
  }
  html.rv-on .rv-v-slide.rv-in {
    animation-name: rv-slide;
    animation-duration: var(--rv-sp-slide-d);
    animation-timing-function: var(--rv-sp-slide);
  }
  html.rv-on .rv-v-fade.rv-in {
    animation-name: rv-fade;
    animation-duration: var(--rv-d-tween);
    animation-timing-function: var(--rv-e-tween);
  }
  html.rv-on .rv-w.rv-in,
  html.rv-on .rv-el.rv-in {
    animation-name: rv-word;
    animation-duration: var(--rv-d-word);
    animation-timing-function: var(--rv-e-word);
  }

  /* ==========================================================================
     THE HEADING COLOUR SWEEP  (see the --rv-sweep-* tokens for the source)
     Additive to everything above: `.rv-c` characters live INSIDE the word tokens,
     so the word reveal still owns opacity / blur / translate and this owns colour
     alone. Two animations on two different elements with no property in common,
     which is why neither had to change to make room for the other — and why both
     are wanted here: the heading fades up from below, then the light passes over it.

     THERE IS NO HIDDEN STATE OF ITS OWN, so there is no `--rv-sweep-dim` and no
     pre-JS ghost rule to keep in sync with the sections. A character rests at its
     final colour, which is also why the same keyframe serves an ink heading, a white
     one and a gradient one with no per-heading value anywhere.

     `.rv-c` deliberately gets NO `display` change — unlike `.rv-w`, which needs
     inline-block for its translate. Staying inline is the whole reason this is
     safe to put on a locked render: a per-character span does break text shaping
     at every letter pair, and measured across all twelve headings at 1920 that
     costs at most 0.172px of width, changes not one line count and leaves
     document.scrollHeight at 14272 exactly.

     It animates `-webkit-text-fill-color`, not `color`, because half of these
     headings paint a `background-clip: text` gradient on an inner span. A
     character inside one inherits `transparent`, so an implicit keyframe lands it
     back on the gradient; a character in plain text lands on its own
     `currentcolor`. Verified both: accent -> #111 for the solid case, accent ->
     rgba(0,0,0,0) for the gradient one. And because BOTH ends are implicit here,
     the same rule needs no per-heading colour at all — no ink-vs-white token, no
     `.c-black` equivalent, nothing to keep in sync with the sections.

     UNLIKE EVERY OTHER REVEAL IN THIS FILE, THIS ONE IS NOT ANIMATE-ONCE (user,
     2026-08-13: "jb b wo view main ayen gi tb tb ye animation chlni chaahiye").
     So it hangs off `.rv-sw`, which MotionLayer's second observer toggles on the
     HEADING every time it enters and leaves the viewport, rather than off the
     tokens' one-way `.rv-in` — which is also why the word reveal underneath stays
     exactly as once-only as it was. Taking the class away is what re-arms the
     animation, and it leaves every character at its own colour rather than at a
     from-state, so the reset is invisible even if it happens on screen.
     ========================================================================== */
  @keyframes rv-sweep {
    /* BOTH 0% and 100% are left implicit, so they are the character's own resting
       colour and the light is the only thing that ever happens to it. 1% is ~7ms:
       GSAP's own arrival at the accent is a single-frame snap, so this is that
       snap without the discontinuity, and everything after it is Sigenergy's
       settle — running back to `final` now instead of down from a ghost. */
    1% {
      -webkit-text-fill-color: var(--rv-sweep-accent);
    }
  }
  html.rv-on .rv-sweep.rv-sw .rv-c {
    animation-name: rv-sweep;
    animation-duration: calc(var(--rv-d-sweep) * var(--rv-sweep-slow));
    animation-timing-function: var(--rv-e-sweep);
    /* --rv-lead and --rv-cstep are inherited from the heading, --rv-ci is stamped
       per character and runs across the whole heading, not per word — so the light
       crosses the line once instead of restarting inside every word. The lead is
       NOT scaled by --rv-sweep-slow: it is when the sweep starts, not how fast it
       goes, and stretching it would just delay the heading. */
    animation-delay: calc(
      var(--rv-lead, 0ms) + var(--rv-sweep-lead) +
        var(--rv-ci, 0) * var(--rv-cstep, 0ms) * var(--rv-sweep-slow)
    );
    /* No `animation-fill-mode`, deliberately: there is no from-state to hold
       through the delay — the character is simply at its own colour until the
       light reaches it — and `backwards` here would only imply there was one. */
    animation-iteration-count: 1;
  }
}
