/* ============================================================
   RSQUARE TECHNOLOGIES — Design System v3 "Clean Dynamic"
   Deep-space navy base · Electric azure · Signature red
   Tokenized motion/radius/elevation · no decorative glow
   ============================================================ */

:root {
  /* Brand core (evolved from legacy #2D85C6 / #1C2B3C / #CD1227) */
  --ink-950: #060B14;      /* near-black navy — page dark base */
  --ink-900: #0A1220;
  --ink-800: #101B2E;
  --ink-700: #1C2B3C;      /* legacy navy, kept as mid tone */
  --ink-600: #2C3E54;

  --azure-600: #1E6FD9;
  --azure-500: #2D85C6;    /* legacy azure, anchor */
  --azure-400: #38A4F0;
  --azure-300: #6EC1F7;
  --cyan-400: #22D3EE;

  --red-500: #E5122B;      /* signature R red, brightened */
  --red-400: #FF3B52;

  --paper: #F7F9FC;
  --white: #FFFFFF;
  --mist: #A8B6C8;         /* muted text on dark */
  --slate: #56677D;        /* muted text on light */

  /* hairline + surface tokens (one border language everywhere) */
  --line: rgba(148, 170, 197, 0.11);
  --line-strong: rgba(148, 170, 197, 0.22);
  --surface: var(--ink-900);
  --surface-raised: var(--ink-800);

  /* single elevation scale — neutral black, no colored glows */
  --shadow-1: 0 2px 8px -2px rgba(0, 0, 0, 0.35);
  --shadow-2: 0 16px 40px -16px rgba(0, 0, 0, 0.55);

  --grad-hero: linear-gradient(135deg, #38A4F0 0%, #2D85C6 40%, #22D3EE 100%);
  --grad-text: linear-gradient(100deg, #6EC1F7, #22D3EE 55%, #38A4F0);

  --font: "Poppins", "Segoe UI", -apple-system, system-ui, sans-serif;
  --font-num: "Poppins", "Segoe UI", -apple-system, system-ui, sans-serif;

  /* radius scale */
  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-xl: 24px;

  /* motion tokens — one rhythm across the whole site */
  --dur-1: 160ms;   /* micro: hover, press */
  --dur-2: 280ms;   /* standard: menus, toggles */
  --dur-3: 520ms;   /* large: reveals, slide swaps */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);

  --nav-h: 76px;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
/* Lenis overrides browser smooth-scroll to prevent double-smoothing */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-stopped { overflow: hidden; }
body {
  font-family: var(--font);
  background: var(--ink-950);
  color: var(--white);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}
img { max-width: 100%; display: block; }

/* ---------- RTL (Arabic) ----------
   direction:rtl on its own mirrors every flex/grid layout on the site
   (nav order, card grids, footer columns, etc.) since the browser's own
   flex/grid algorithms are direction-aware — the one thing that does NOT
   auto-flip is anything using a hardcoded physical left/right (not a
   logical property), like the nav underline's absolute positioning below.
   Known gap for a later pass: a few purely decorative bits (progress bar
   fill direction, marquee scroll direction) still visually read
   left-to-right in RTL; not a correctness issue, just not mirrored yet. */
html[dir="rtl"] body { direction: rtl; }
html[dir="rtl"] .nav-links > li > a::after { left: auto; right: 0; }
a { color: inherit; text-decoration: none; }
.container { width: min(1180px, 92vw); margin: 0 auto; }

::selection { background: var(--azure-500); color: #fff; }

/* ---------- scroll progress bar ---------- */
#progress {
  position: fixed; top: 0; left: 0; height: 2px; width: 100%;
  background: var(--grad-hero); z-index: 1000;
  transform: scaleX(0); transform-origin: left center; will-change: transform;
}

/* ---------- nav ---------- */
.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 900;
  height: var(--nav-h);
  display: flex; align-items: center;
  transition: background-color var(--dur-3) var(--ease-out), box-shadow var(--dur-3) var(--ease-out), height var(--dur-2);
}
.nav.scrolled {
  height: 62px;
  background-color: rgba(6, 11, 20, 0.94);
  box-shadow: 0 1px 0 var(--line);
}
.nav-inner { display: flex; align-items: center; justify-content: space-between; width: min(1280px, 94vw); margin: 0 auto; }
.nav-logo img { height: 30px; filter: brightness(0) invert(1); opacity: 0.95; }
.nav-links { position: relative; display: flex; gap: 34px; list-style: none; font-size: 14.5px; font-weight: 500; color: var(--mist); }
.nav-links > li > a {
  position: relative; padding: 6px 0;
  transition: color var(--dur-1); display: inline-flex; align-items: center; gap: 6px;
}
.nav-links > li > a:hover { color: #fff; }
.nav-links > li > a::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 1.5px; width: 0;
  background: var(--azure-400); transition: width var(--dur-2) var(--ease-out);
}
.nav-links > li > a:hover::after { width: 100%; }

/* ---------- nav dropdowns ----------
   Full-width panel attached flush under the nav bar, holding a grid of
   rounded cards (icon + name + one-line description). Panels anchor to
   .nav (fixed), not the <li>, so they span the bar; <li>s are full nav
   height so the hover never breaks between trigger and panel. */
.nav-inner { height: 100%; }
.nav-links { height: 100%; }
.nav-links > li { height: 100%; display: flex; align-items: center; }
.has-dd { position: static; }
.has-dd .chev { color: var(--red-500); transition: transform var(--dur-2) var(--ease-out); flex-shrink: 0; }
.has-dd.active .chev, .has-dd:hover .chev { transform: rotate(180deg); }

/* solidify the bar whenever a menu is open so panel + nav read as one module */
.nav:has(.has-dd:hover), .nav.dd-open {
  background-color: rgba(6, 11, 20, 0.97);
  box-shadow: 0 1px 0 var(--line);
}

/* ---------- shared morphing popover ----------
   One popover box under the bar, shared by every menu — JS (see "nav
   dropdowns" in main.js) measures the active pane and the hovered trigger,
   then sets this box's width/height/translateX; the CSS transitions below
   are what produce the "panel morphs between sections" effect the
   reference component got from framer-motion's layoutId. Content panes
   (.dd-pane, one per menu, all children of the popover) cross-fade while
   the box resizes around them. Sized to content (width: max-content on
   panes) instead of the old full-viewport-width panel — which is also what
   fixes the cut-off content: the old left:0/right:0 panel clipped any pane
   content wider than the bar's inner column. */
.dd-popover {
  position: absolute; top: 100%; left: 0;
  border-radius: 16px;
  border: 1px solid var(--line);
  background: rgba(6, 11, 20, 0.97);
  box-shadow: var(--shadow-2);
  overflow: hidden;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: width var(--dur-3) var(--ease-out), height var(--dur-3) var(--ease-out),
              transform var(--dur-3) var(--ease-out),
              opacity 200ms var(--ease-out), visibility 200ms;
  z-index: 50;
}
.dd-popover.open { opacity: 1; visibility: visible; pointer-events: auto; }
/* geometry set instantly on first open (else the box would slide in from
   its stale last-closed position); only the fade animates */
.dd-popover.no-anim { transition: opacity 200ms var(--ease-out), visibility 200ms; }
.dd-pane {
  position: absolute; top: 0; left: 0;
  width: max-content; margin: 0; padding: 22px 24px;
  opacity: 0; pointer-events: none;
  transition: opacity 180ms var(--ease-out);
}
.dd-pane.active { opacity: 1; pointer-events: auto; }

/* card grid */
.dd-cards { display: grid; gap: 14px; }
.dd-cards.cols-2 { grid-template-columns: repeat(2, 1fr); }
.dd-cards.cols-3 { grid-template-columns: repeat(3, 1fr); }
.dd-cards.cols-4 { grid-template-columns: repeat(4, 1fr); }
/* Row layout (icon box left, name+desc stacked right) — ported from a
   mega-menu reference component, adapted to brand: our icons are colorful
   isometric illustrations (not single-color glyphs), so the reference's
   "invert to solid white bg + dark icon" hover doesn't translate; instead
   the icon box border brightens and a soft azure glow fades in behind it,
   consistent with the glow language used elsewhere on the site. No HTML
   changes needed — dd-ico/dd-name/dd-desc are already siblings in this
   order, grid placement alone reflows them into a row. */
.dd-card {
  display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto;
  column-gap: 12px; row-gap: 2px; align-items: start; align-content: start;
  padding: 10px 8px; border-radius: var(--r-md);
  background: transparent; border: 1px solid transparent;
  transition: background var(--dur-1), transform var(--dur-2) var(--ease-out);
}
.dd-card:hover { background: rgba(255,255,255,0.04); }
.dd-ico {
  grid-row: 1 / 3; grid-column: 1;
  width: 42px; height: 42px; display: grid; place-items: center;
  border-radius: var(--r-sm); border: 1px solid var(--line-strong);
  background: rgba(255,255,255,0.03);
  transition: border-color var(--dur-2) var(--ease-out), background var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
}
.dd-card:hover .dd-ico {
  border-color: rgba(56,164,240,0.55);
  background: rgba(56,164,240,0.14);
  box-shadow: 0 0 0 4px rgba(56,164,240,0.1);
}
.dd-ico svg { width: 16px; height: 16px; color: var(--azure-300); }
.dd-ico img { width: 42px; height: 42px; object-fit: contain; transition: transform var(--dur-2) var(--ease-out); }
.dd-card:hover .dd-ico img { transform: scale(1.08); }
.dd-ico.dd-ico-lg img { width: 63px !important; height: 63px !important; }
.dd-name { grid-row: 1; grid-column: 2; width: 190px; font-size: 14.5px; font-weight: 500; color: #fff; letter-spacing: -0.01em; }
/* Fixed width (not just a cap) forces the description to wrap during
   width:max-content measurement too — without it, "max-content" measures
   each description as one unwrapped line, and a long line like "LLM
   integrations, RAG, and automated workflows." balloons the whole card
   (and therefore the whole popover, and therefore how much room there is
   to center it) out to 280-300px+ instead of the intended compact width. */
.dd-desc { grid-row: 2; grid-column: 2; width: 190px; font-size: 12.5px; color: var(--mist); line-height: 1.45; transition: color var(--dur-1); }
.dd-card:hover .dd-desc { color: var(--mist); }

/* products mega: 4 labeled columns of compact cards */
.dd-mega-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; }
.dd-group { display: flex; flex-direction: column; gap: 10px; }
.dd-group h4 {
  font-size: 11.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--azure-300);
  font-weight: 500; padding: 0 2px 2px;
}
.dd-card.sm { padding: 8px 6px; column-gap: 10px; row-gap: 1px; }
/* Solutions dropdown only — bumped to match the row-gap already used by
   the base (non-.sm) card variant elsewhere in the dropdown system. */
.dd-popover .dd-pane:first-child .dd-card.sm { row-gap: 2px; }
.dd-card.sm .dd-ico { width: 34px; height: 34px; }
.dd-card.sm .dd-ico svg { width: 13px; height: 13px; }
.dd-card.sm .dd-ico img { width: 34px; height: 34px; }
/* dd-ico-lg's 63px override (below) was sized for the larger 42px-icon
   context (Solutions/Products/Industries "regular" cards) — inside a
   compact .sm card (34px icon box) that blows the icon past the box and
   it gets clipped by the popover's overflow:hidden. This scoped override
   gives .sm cards a smaller, proportionate boost instead. */
.dd-card.sm .dd-ico.dd-ico-lg img { width: 40px !important; height: 40px !important; transform: translateY(-4px); }
.dd-card.sm .dd-name { width: 150px; font-size: 13.5px; }
.dd-card.sm .dd-desc { width: 150px; font-size: 11.5px; }

.dd-all {
  display: inline-flex; align-items: center; gap: 8px;
  margin-top: 18px; padding: 10px 18px; border-radius: var(--r-sm);
  font-size: 13.5px; font-weight: 500; color: var(--azure-300);
  background: rgba(56,164,240,0.06); border: 1px solid rgba(56,164,240,0.18);
  transition: background var(--dur-1), color var(--dur-1);
}
.dd-all:hover { color: #fff; background: rgba(56,164,240,0.14); }

/* ---------- buttons — flat, rectangular, one elevation ---------- */
.btn {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 13px 26px; border-radius: 10px;
  font-weight: 300; font-size: 15px; border: none; cursor: pointer;
  transition: background var(--dur-1), border-color var(--dur-1), transform var(--dur-1) var(--ease-out), box-shadow var(--dur-1);
}
.btn:active { transform: scale(0.98); }
.btn-primary {
  background: var(--azure-600); color: #fff;
  box-shadow: var(--shadow-1);
}
.btn-primary:hover { background: var(--azure-500); transform: translateY(-1px); box-shadow: var(--shadow-2); }
.btn-ghost {
  background: transparent; color: #fff;
  border: 1px solid var(--line-strong);
}
.btn-ghost:hover { border-color: var(--azure-400); background: rgba(56,164,240,0.06); transform: translateY(-1px); }
.nav .btn { padding: 10px 22px; font-size: 14px; }

/* ── Traveling border button ── */
@property --travel-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}
.btn-travel {
  position: relative;
  background: transparent;
  color: #fff;
  border: none;
  isolation: isolate;
}
.btn-travel::before {
  content: '';
  position: absolute;
  inset: -0.5px;
  border-radius: 10.5px;
  background: conic-gradient(
    from var(--travel-angle),
    #0a1628 0deg,
    #0a1628 215deg,
    #1a5fa8 230deg,
    #22D3EE 245deg,
    #1a5fa8 260deg,
    #0a1628 275deg,
    #0a1628 360deg
  );
  animation: btn-border-travel 6s linear infinite;
  z-index: -1;
}
.btn-travel::after {
  content: '';
  position: absolute;
  inset: 0.5px;
  border-radius: 9.5px;
  background: #060B14;
  z-index: -1;
}
.btn-travel:hover { transform: translateY(-1px); }
.btn-travel:hover::before { animation-duration: 1.5s; }
/* Pill variant of the traveling border — same trace, but a 999px radius
   on both the conic-gradient ring and its inner mask so it wraps a
   fully-rounded badge (not a 10px-radius button) without squaring off
   the ends. Reuses --travel-angle and the keyframe below as-is. */
.badge-travel { position: relative; isolation: isolate; border-color: transparent !important; }
.badge-travel::before {
  content: ''; position: absolute; inset: -0.5px; border-radius: 999px;
  background: conic-gradient(
    from var(--travel-angle),
    #0a1628 0deg,
    #0a1628 215deg,
    #1a5fa8 230deg,
    #22D3EE 245deg,
    #1a5fa8 260deg,
    #0a1628 275deg,
    #0a1628 360deg
  );
  animation: btn-border-travel 6s linear infinite;
  z-index: -1;
}
.badge-travel::after {
  content: ''; position: absolute; inset: 0.5px; border-radius: 999px;
  background: var(--surface-raised);
  z-index: -1;
}
@keyframes btn-border-travel {
  to { --travel-angle: 360deg; }
}

/* ---------- language switch ---------- */
.lang-switch {
  /* Stays LTR regardless of page direction: its own content is language
     codes ("EN"/"AR"), not translated text, and the thumb's position is a
     hardcoded translateX — under RTL, flex would visually reorder the two
     buttons but the thumb wouldn't know to follow, landing under the
     wrong one. Keeping this one control LTR-ordered avoids that entirely. */
  direction: ltr;
  position: relative; display: inline-flex; align-items: center;
  width: 84px; height: 36px; padding: 3px; border-radius: 10px;
  background: rgba(255,255,255,0.05); border: 1px solid var(--line-strong);
}
.lang-thumb {
  position: absolute; top: 3px; left: 3px; width: 39px; height: 28px;
  border-radius: 7px; background: var(--azure-600);
  transition: transform var(--dur-2) var(--ease-out);
  pointer-events: none;
}
.lang-switch.is-ar .lang-thumb { transform: translateX(39px); }
.lang-opt {
  position: relative; z-index: 1; flex: 1; height: 30px;
  border: none; background: transparent; cursor: pointer;
  font-family: var(--font); font-size: 12.5px; font-weight: 500; letter-spacing: 0.03em;
  color: var(--mist); transition: color var(--dur-2);
  display: flex; align-items: center; justify-content: center;
}
.lang-opt.active { color: #fff; }

/* ---------- hero ---------- */
.hero {
  position: relative; min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  overflow: hidden; padding-top: var(--nav-h);
}
.hero-bg { position: absolute; inset: 0; z-index: 0; }
.orb { position: absolute; border-radius: 50%; }
.orb-1 { width: 80vw; height: 80vw; background: radial-gradient(circle, rgba(30,111,217,0.28) 0%, rgba(30,111,217,0.14) 30%, rgba(30,111,217,0.05) 55%, transparent 72%); top: -30vw; right: -22vw; }
.orb-2 { width: 58vw; height: 58vw; background: radial-gradient(circle, rgba(34,211,238,0.15) 0%, rgba(34,211,238,0.07) 35%, rgba(34,211,238,0.02) 58%, transparent 75%); bottom: -18vw; left: -16vw; }
.orb-3 { display: none; }

/* ---------- floating paths (hero background) ----------
   Port of the "Background Paths" component, rendered on <canvas>
   instead of 72 animated SVG/CSS elements. Two prior CSS/SVG passes
   both failed on real (large, high-DPI) browser windows: capping paint
   size via a transform:scale wrapper only reduced RASTER cost, but the
   dominant cost turned out to be STYLE RECALCULATION — the browser
   re-evaluating stroke-dasharray/dashoffset/opacity across 72 separate
   DOM elements every frame, independent of screen size. That pipeline
   got so backed up that repaints stalled entirely until a click/mousemove
   forced a flush (which is why content only "appeared" after interacting).
   Canvas has no per-element style system to invalidate: one JS loop
   issues 72 stroke() calls into a single bitmap every frame, so cost is
   fully self-controlled rather than left to the browser's CSS engine.
   The canvas's internal pixel buffer is pinned at a small fixed
   resolution (independent of real screen size) and stretched to its
   display size via the browser's native bitmap upscaling — the same
   "cap raster cost" principle as before, but this time actually
   addressing the whole cost, not just paint. */
.hero-paths-canvas {
  position: absolute; pointer-events: none;
  transform: translateZ(0); /* pin a stable compositor layer up front — avoid mid-scroll layer promotion churn */
  will-change: transform;
  /* size/layout/style/paint containment — explicit width/height are set in JS
     and this element has no children, so it's safe to tell the browser its
     box can never affect (or be affected by) anything outside it. Keeps the
     rest of the hero's paint/layout work from ever needing to consider this
     layer, and vice versa. */
  contain: strict;
  /* The center-hole + bottom fade used to be a CSS mask-image (mask-composite:
     intersect). That forced the compositor to re-evaluate a dual-mask shader
     pass on a ~viewport-size layer, seemingly on every vsync regardless of
     whether the canvas texture had actually changed — the dominant cost
     behind hero scroll jank, worse than the ribbon drawing itself. The fade
     is now baked directly into the canvas alpha channel by the drawing code
     (see ribbons-worker.js / the fallback loop in main.js), so the browser
     composites this element as a plain alpha-blended texture — its cheapest
     path — with no CSS mask on it at all. */
}

/* hero-content transform/opacity are scroll-scrubbed from main.js */
.hero-content { position: relative; z-index: 2; text-align: center; will-change: transform, opacity; }
.hero-eyebrow {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 8px 18px; border-radius: 100px; font-size: 13px; font-weight: 500;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--azure-300);
  background: rgba(56, 164, 240, 0.07); border: 1px solid rgba(56, 164, 240, 0.2);
  margin-bottom: 30px;
}
.hero-eyebrow .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--cyan-400); animation: pulse 2.4s infinite; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
.hero h1 {
  /* Slope chosen so the max is only reached on genuinely wide screens
     (~1680px+) — vw-only sizing made 16:10 laptops (1440x900, 1280x800)
     render near-monitor type sizes into a much shorter viewport. */
  font-size: clamp(38px, 5vw, 84px); line-height: 1.02;
  font-weight: 400; letter-spacing: -0.035em; margin-bottom: 26px;
}
/* Aurora text — brand palette (azure/cyan) version of the MagicUI
   "Aurora Text" effect: an oversized gradient drifting behind the text
   via background-position, clipped to the glyphs.
   main.js's word-reveal walker skips recursing into .grad (so "runs the
   enterprise." stays one unit, not split word-by-word) but still tags it
   with .w for the shared rise-in animation — so .grad always carries both
   classes in the DOM. Both rules set the `animation` shorthand on the same
   element with equal specificity; whichever is declared later in the
   cascade wins outright and drops the other entirely (not a merge). Target
   .grad.w directly and list both animations in one rule so they compose
   instead of one silently overwriting the other. */
.hero h1 .grad.w {
  position: relative;
  display: inline-block;
  background-image: linear-gradient(135deg,
    var(--azure-300), var(--cyan-400), var(--azure-400), var(--azure-500), var(--azure-300));
  background-size: 200% auto;
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: wordIn 0.7s var(--ease-out) forwards, auroraText 8s ease-in-out infinite alternate;
}
/* background-position (unlike transform/opacity) forces a repaint of the
   text-clip mask every frame it changes — it can't be made compositor-only.
   main.js toggles .is-scrolling on <html> for the duration of active
   scrolling (plus a short settle window) so that repaint cost never
   competes with scroll's own frame budget; the second animation-play-state
   value targets only auroraText, leaving the one-shot entrance untouched. */
.is-scrolling .hero h1 .grad.w { animation-play-state: running, paused; }
@media (prefers-reduced-motion: reduce) {
  .hero h1 .grad.w { animation: none; background: var(--grad-text); -webkit-background-clip: text; background-clip: text; color: transparent; }
}
/* Reusable version of the same effect for headings outside the hero (no
   word-reveal entrance to compose with — just the gradient drift). Reuses
   .is-scrolling from main.js so it pauses during active scroll exactly like
   the hero's, for the same reason (background-position can't be made
   compositor-only). */
.aurora-text {
  background-image: linear-gradient(135deg,
    var(--azure-300), var(--cyan-400), var(--azure-400), var(--azure-500), var(--azure-300));
  background-size: 200% auto;
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: auroraText 8s ease-in-out infinite alternate;
}
.is-scrolling .aurora-text { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
  .aurora-text { animation: none; background: var(--grad-text); -webkit-background-clip: text; background-clip: text; color: transparent; }
}
@keyframes auroraText {
  0%   { background-position: 0% 50%;   }
  25%  { background-position: 50% 100%; }
  50%  { background-position: 100% 50%; }
  75%  { background-position: 50% 0%;   }
  100% { background-position: 0% 50%;   }
}
.hero p.lede {
  font-size: clamp(17px, 2vw, 21px); color: var(--mist);
  max-width: 640px; margin: 0 auto 42px; line-height: 1.6;
}
.hero-ctas { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; }

/* word-by-word hero reveal — clean rise, no rotation */
.hero h1 .w { display: inline-block; opacity: 0; transform: translateY(0.55em); animation: wordIn 0.7s var(--ease-out) forwards; }
@keyframes wordIn { to { opacity: 1; transform: none; } }

/* Sub-page heroes carry their h1 size as an inline style (28 static
   pages); stylesheet !important is the one thing that outranks an inline
   declaration, so laptop-width retuning happens here rather than by
   editing every page. */
.hero-content h1[style] { font-size: clamp(32px, 3.6vw, 54px) !important; }

/* hero stat strip (scroll-scrubbed alongside hero-content) */
.hero-stats {
  position: relative; z-index: 2;
  display: flex; justify-content: center; gap: clamp(28px, 6vw, 90px);
  /* svh-aware top margin + a bottom band tall enough to hold the scroll
     cue: on 900/800px-tall laptops the hero column used to overflow
     100svh and push the stats into the absolutely-positioned cue. */
  margin-top: clamp(40px, 6svh, 72px); padding-bottom: 96px; flex-wrap: wrap;
  will-change: transform, opacity;
}
.hstat { text-align: center; }
.hstat .num { font-family: var(--font-num); font-size: clamp(26px, 2.5vw, 40px); font-weight: 500; letter-spacing: -0.02em; }
.hstat .num span.plus { color: var(--cyan-400); }
.hstat .lbl { font-size: 13px; color: var(--mist); margin-top: 4px; letter-spacing: 0.04em; text-transform: uppercase; }

.scroll-cue {
  position: absolute; bottom: 26px; left: 50%; transform: translateX(-50%);
  z-index: 2; color: var(--mist); font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase;
  display: flex; flex-direction: column; align-items: center; gap: 8px; opacity: 0.7;
}
.scroll-cue .line { width: 1px; height: 42px; background: linear-gradient(var(--red-500), transparent); animation: cue 1.8s infinite; }
/* On short viewports there is no clean band left for the cue — drop it
   rather than let it collide with the stat strip. */
@media (max-height: 760px) {
  .scroll-cue { display: none; }
  .hero-stats { padding-bottom: 48px; }
}
@keyframes cue { 0% { transform: scaleY(0); transform-origin: top; } 50% { transform: scaleY(1); transform-origin: top; } 51% { transform-origin: bottom; } 100% { transform: scaleY(0); transform-origin: bottom; } }

/* ---------- logo marquee (JS-driven; CSS animation is no-JS fallback) ---------- */
.marquee-section {
  padding: 54px 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  /* Soft azure/cyan wash at the very top, echoing the hero's faded-out
     line color so the seam between sections reads as a continuation
     rather than an abrupt background-color change. */
  background:
    linear-gradient(to bottom, rgba(56,164,240,0.06), transparent 45%),
    var(--ink-900);
}
.marquee-label { text-align: center; font-size: 12.5px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--slate); margin-bottom: 30px; }
/* Edge fade used to be a CSS mask-image on this element — the same class of
   bug as the old hero canvas mask: a masked layer whose content (marquee-
   track) gets a transform write every visible frame forces the compositor
   to re-evaluate the mask shader every frame too, right at the section
   boundary immediately after the hero. Replaced with two static gradient
   overlays painted on top instead of a mask on the moving content — these
   never change, so they cost nothing per frame regardless of what's
   scrolling underneath. var(--ink-900) matches this section's background
   by the point the marquee starts (the azure wash above it has already
   fully faded out by then). */
.marquee { overflow: hidden; position: relative; }
.marquee::before, .marquee::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 90px; z-index: 2; pointer-events: none;
}
.marquee::before { left: 0; background: linear-gradient(to right, var(--ink-900), transparent); }
.marquee::after { right: 0; background: linear-gradient(to left, var(--ink-900), transparent); }
.marquee-track { display: flex; gap: 72px; width: max-content; will-change: transform; }
@keyframes marquee { to { transform: translateX(-50%); } } /* kept as no-JS fallback only */
.mq-item { font-size: 19px; font-weight: 500; color: rgba(168, 182, 200, 0.55); white-space: nowrap; letter-spacing: 0.02em; transition: color var(--dur-2); }
.mq-item:hover { color: var(--azure-300); }

/* ---------- generic section scaffolding ---------- */
.section { padding: clamp(90px, 12vw, 150px) 0; position: relative; contain: layout style; content-visibility: auto; contain-intrinsic-size: auto 600px; }
/* UniRecon page only — the product page packs in more sections back to
   back than most, so the default padding (tuned for sparser pages)
   stacked into a lot of dead space between them. */
.page-unirecon .section { padding: clamp(56px, 7vw, 96px) 0; }
.section.no-pad { padding: 0; }
.section-head { max-width: 720px; margin-bottom: 64px; }
.section-head.center { margin-left: auto; margin-right: auto; text-align: center; }
.kicker {
  display: inline-block; font-size: 13px; font-weight: 500; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--azure-400); margin-bottom: 16px;
}
.kicker::before { content: ""; }
.section-head h2 { font-size: clamp(28px, 3vw, 48px); font-weight: 400; letter-spacing: -0.03em; line-height: 1.08; }
.section-head p { margin-top: 18px; color: var(--mist); font-size: 17.5px; line-height: 1.65; }

/* reveal-on-scroll */
.reveal { opacity: 0; transform: translateY(28px); transition: opacity var(--dur-3) var(--ease-out), transform var(--dur-3) var(--ease-out); }
.reveal.in { opacity: 1; transform: none; }
.reveal[data-delay="1"] { transition-delay: 0.08s; }
.reveal[data-delay="2"] { transition-delay: 0.16s; }
.reveal[data-delay="3"] { transition-delay: 0.24s; }
.reveal[data-delay="4"] { transition-delay: 0.32s; }

/* ---------- solutions grid — flat surfaces, hairline borders ---------- */
.sol-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 22px; }
@media (max-width: 980px) { .sol-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .sol-grid { grid-template-columns: 1fr; } }
/* Card art sits behind the text (transparent-background isometric SVG,
   desaturated + dimmed at rest) and both fully saturate plus a color wash
   fades in on hover — the same "shadowy until touched" treatment as the
   Arc reference. Fixed height (not min-height) is what actually guarantees
   all four cards match regardless of copy length differences. */
.sol-card {
  /* min-height (not fixed height): the grid's default stretch alignment
     keeps all cards in a row equal anyway, and a hard height clipped card
     copy at narrow laptop widths where text wraps to more lines. */
  position: relative; min-height: 483px; padding: 32px 28px; border-radius: var(--r-lg);
  background: var(--surface);
  border: 1px solid var(--line);
  transition: transform var(--dur-3) var(--ease-out), border-color var(--dur-3) var(--ease-out), box-shadow var(--dur-3) var(--ease-out);
  overflow: hidden;
  display: flex; flex-direction: column;
}
.sol-card:hover { transform: translateY(-4px); border-color: rgba(56,164,240,0.32); box-shadow: var(--shadow-2); }
/* Compact variant — for wide 2-up text-only cards (no art) where the
   default 483px min-height (tuned for narrow 4-up art-backed cards)
   would leave a huge block of empty space below a couple of sentences. */
.sol-card.compact { min-height: 0; }
.sol-art {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-repeat: no-repeat; background-position: center 78%; background-size: 78%;
  opacity: 0.4; filter: saturate(0.25) brightness(0.75);
  transition: opacity var(--dur-3) var(--ease-out), filter var(--dur-3) var(--ease-out), transform var(--dur-3) var(--ease-out);
}
.sol-card:hover .sol-art { opacity: 0.95; filter: saturate(1) brightness(1); transform: scale(1.05); }
.sol-card::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: radial-gradient(120% 100% at 75% 100%, rgba(56,164,240,0.28), transparent 60%);
  opacity: 0; transition: opacity var(--dur-3) var(--ease-out);
}
.sol-card:hover::after { opacity: 1; }
.sol-body { position: relative; z-index: 1; }
.sol-card h3 { font-size: 19px; font-weight: 400; line-height: 1.3; min-height: 50px; margin-bottom: 10px; letter-spacing: -0.01em; }
.sol-card p { font-size: 14.5px; color: var(--mist); line-height: 1.6; margin-top: 19.3px; }
.sol-card .learn {
  position: relative; z-index: 1; margin-top: auto; align-self: flex-start; padding-bottom: 2px;
  display: inline-flex; align-items: center; font-size: 14px; font-weight: 500; color: var(--azure-300);
}
.sol-card .learn::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 1.5px; width: 0;
  background: var(--azure-400); transition: width var(--dur-2) var(--ease-out);
}
.sol-card:hover .learn::after { width: 100%; }

/* ---------- products — dual-row card marquee ----------
   Same architecture as the client-logo marquee: JS drives translate3d
   drift at a fixed px/s (see tickProdMarquee in main.js), content
   duplicated once per row so each wraps seamlessly at -50%, edge fade is
   two static gradient overlays rather than a CSS mask (a mask on a layer
   that's translated every frame forces the compositor to re-evaluate the
   mask shader every frame too — the same bug fixed earlier on the hero
   canvas and the logo marquee). Cards are real links to each product page. */
.showcase { background: var(--ink-900); padding-bottom: clamp(90px, 12vw, 150px); }
.prod-marquee { overflow: hidden; position: relative; margin-top: 54px; display: flex; flex-direction: column; gap: 20px; }
.prod-marquee::before, .prod-marquee::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 90px; z-index: 2; pointer-events: none;
}
.prod-marquee::before { left: 0; background: linear-gradient(to right, var(--ink-900), transparent); }
.prod-marquee::after { right: 0; background: linear-gradient(to left, var(--ink-900), transparent); }
.prod-track { display: flex; gap: 16px; width: max-content; will-change: transform; padding: 10px 0; }
.prod-item {
  flex: 0 0 314px;
  border-radius: var(--r-lg); padding: 16px 18px; height: 128px; box-sizing: border-box; overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line);
  display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center;
  color: inherit; text-decoration: none;
  transition: transform var(--dur-2) var(--ease-out), border-color var(--dur-2);
}
.prod-item:hover { transform: translateY(-4px); border-color: rgba(56,164,240,0.35); }
.prod-item .ico { display: block; }
.prod-item .ico svg { width: 20px; height: 20px; color: var(--cyan-400); }
.prod-item h3 {
  font-size: 15px; font-weight: 400; margin-top: 10px; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.prod-item p {
  font-size: 12.5px; color: var(--mist); margin-top: 4px; line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
}

/* ---------- big stats band — flat surface, solid numerals ---------- */
.stats-band {
  position: relative;
  background:
    radial-gradient(900px 400px at 80% 0%, rgba(30,111,217,0.1), transparent 60%),
    var(--ink-800);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  /* Noticeably shorter than the inherited .section padding (which is
     tuned for full content sections) — a stat band is just 4 numbers,
     so the generous 90-150px padding left it mostly empty space. Stays
     symmetric top/bottom (see the stage-grid centering fix earlier). */
  padding: clamp(48px, 6vw, 80px) 0;
}
/* Same traveling-highlight technique as .btn-travel, adapted from a
   rotating conic-gradient ring (works on a closed rectangle) to a sweeping
   linear-gradient (needed on an open horizontal edge) — a comet-shaped
   bright segment drifts across each edge on a loop, same color stops and
   duration as the hero button so both traces read as one family. */
.stats-band::before, .stats-band::after {
  content: ""; position: absolute; left: 0; right: 0; height: 2px;
  background-image: linear-gradient(90deg, transparent, #1a5fa8 35%, #22D3EE 50%, #1a5fa8 65%, transparent);
  background-size: 40% 100%;
  background-repeat: no-repeat;
  filter: drop-shadow(0 0 6px rgba(34, 211, 238, 0.85));
  animation: edge-trace 6s linear infinite;
  pointer-events: none;
}
.stats-band::before { top: -1px; }
.stats-band::after { bottom: -1px; }
@keyframes edge-trace {
  from { background-position: -60% 0; }
  to { background-position: 160% 0; }
}
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 40px; text-align: center; }
/* Flex column with the number bottom-anchored in a reserved 2-line box:
   a longer stat value (e.g. "RAG + RPA" vs "24/7") wraps to two lines at
   this font size while its neighbors stay on one, which used to push
   just that label down and break the row's alignment. Reserving room for
   the worst case and bottom-aligning the number keeps every label
   sitting at the same y position regardless of how any one value wraps. */
.bstat { display: flex; flex-direction: column; align-items: center; }
.bstat .num {
  font-family: var(--font-num); font-size: clamp(38px, 4.2vw, 64px); font-weight: 400;
  letter-spacing: -0.04em; color: #fff; line-height: 1.15;
  display: flex; align-items: center; justify-content: center;
  /* px, not em: an em-based min-height recalculates against each
     element's OWN font-size, so a stat with a smaller inline font-size
     override (long text sized down to fit one line) got a shorter
     reserved box than its full-size neighbors and drifted out of
     alignment again. Sized for one line of the largest clamp value —
     the one stat that used to need 2 lines (AI page's "RAG + RPA") now
     has its own nowrap override, so nothing on the site wraps anymore;
     the old 150px (2-line) reservation just left a big gap before the
     label below. */
  min-height: 78px;
}
.bstat .num span.plus { color: var(--cyan-400); }
.bstat .lbl { color: var(--mist); font-size: 15px; margin-top: 8px; }

/* ---------- industries — card art (ported from .sol-card) ----------
   Same treatment as the Solutions cards: a transparent-background
   isometric illustration sits behind the text, dimmed/desaturated at
   rest, fully saturating plus a color wash fading in on hover. 3-column
   grid (was auto-fit/min-height) sized to give the art real room, same
   breakpoints as .sol-grid. */
/* ---------- industries — 3D drag carousel ----------
   Ported from a React/framer-motion "cylinder" carousel: faces arranged in
   a ring via rotateY + translateZ, dragged around a shared Y axis, with
   momentum after release. No framer-motion here, so the drag/inertia loop
   is plain pointer-event math (see the "industries carousel" section in
   main.js) instead of a spring — same rotate-and-coast feel. The reference
   also opened a full-screen photo viewer on card click; skipped here since
   these are content cards (hover already reveals the art/glow), not
   photos to zoom into. */
.ind-carousel-wrap { perspective: 1400px; }
/* Width matches the ring's actual visual diameter (set by JS as
   --ind-ring-w, based on the rotation radius) instead of the full section
   width — the drag hit-box lives on THIS element (not the 3D-rotated
   track inside it), so it needs to track where the ring is actually
   visible, not the much wider container. Without this, dragging worked in
   the empty margins beside the ring, and failed over cards that projected
   near the ring's own edge at some rotation angles. */
.ind-carousel {
  /* left:50% + translateX(-50%) instead of margin:0 auto — auto-margins
     silently collapse to 0 (not negative) once the ring is wider than its
     container, which happens on every phone width; that made the ring
     render flush against the left edge instead of centered with
     symmetric overflow. Transform-centering has no such width ceiling. */
  position: relative; left: 50%; transform: translateX(-50%);
  height: 500px; width: var(--ind-ring-w, 900px);
  display: flex; align-items: center; justify-content: center;
  cursor: grab; touch-action: pan-y;
  user-select: none; -webkit-user-select: none;
}
.ind-carousel:active { cursor: grabbing; }
.ind-carousel-track {
  position: relative; height: 462px; width: 100%;
  transform-style: preserve-3d; will-change: transform;
}
.ind-carousel-hint { text-align: center; margin-top: 14px; font-size: 13px; color: var(--slate); }
.ind-carousel .ind-card {
  position: absolute; top: 0; left: 50%;
  width: var(--ind-face-w, 340px); margin-left: calc(var(--ind-face-w, 340px) / -2);
  backface-visibility: visible;
}
/* Card box itself (background/border) stays visible from the back so the
   ring reads as solid rotating shapes with no gaps — only the content
   inside (art + text) hides when facing away. */
.ind-carousel .ind-card .ind-art,
.ind-carousel .ind-card .ind-body {
  backface-visibility: hidden;
}
.ind-card {
  position: relative; height: 462px; padding: 32px 28px; border-radius: var(--r-lg);
  background: var(--surface);
  border: 1px solid var(--line);
  overflow: hidden;
  display: flex; flex-direction: column;
  transition: transform var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
}
.ind-card:hover { transform: translateY(-4px); border-color: rgba(56,164,240,0.35); box-shadow: var(--shadow-2); }
.ind-art {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-repeat: no-repeat; background-position: center 75%; background-size: 78%;
  opacity: 0.4; filter: saturate(0.25) brightness(0.75);
  transition: opacity var(--dur-3) var(--ease-out), filter var(--dur-3) var(--ease-out), transform var(--dur-3) var(--ease-out);
}
.ind-card:hover .ind-art { opacity: 0.95; filter: saturate(1) brightness(1); transform: scale(1.05); }
.ind-card::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: radial-gradient(120% 100% at 75% 100%, rgba(56,164,240,0.28), transparent 60%);
  opacity: 0; transition: opacity var(--dur-3) var(--ease-out);
}
.ind-card:hover::after { opacity: 1; }
.ind-body { position: relative; z-index: 1; }
.ind-card h3 { font-size: 20px; font-weight: 400; }
.ind-card p { font-size: 14px; color: var(--mist); margin-top: 8px; line-height: 1.55; }

/* ---------- hub-page card icons (products/industries index) ----------
   .sol-icon normally holds an inline 24x24 line-icon SVG with no width/
   height attrs, which stretches to fill the flex column (~220px) — fine
   for a single big decorative glyph. Hub listing cards instead use the
   small colored nav-icon SVG files via <img>, which carry their own
   intrinsic size, so they need an explicit cap here. */
.sol-card .sol-icon img { width: 56px; height: 56px; object-fit: contain; }
/* .compact cards are wide 2-up text blocks (no art), where the "big
   decorative glyph" stretch above would blow the icon up to 300px+ —
   cap it back down to a normal small icon for this variant only. */
.sol-card.compact .sol-icon { align-self: flex-start; width: 44px; height: 44px; }
.sol-card.compact .sol-icon svg { width: 24px; height: 24px; }

/* ---------- comparison table ---------- */
.comp-table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--r-xl); background: var(--surface-raised); }
.comp-table { width: 100%; border-collapse: collapse; min-width: 640px; }
.comp-table th, .comp-table td { padding: 18px 26px; text-align: left; font-size: 14.5px; white-space: nowrap; }
.comp-table th { color: var(--mist); font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600; border-bottom: 1px solid var(--line); }
.comp-table td { border-bottom: 1px solid var(--line); color: var(--mist); }
.comp-table tr:last-child td { border-bottom: none; }
.comp-table td { white-space: normal; }
.comp-table td:first-child { color: #fff; font-weight: 500; }
.comp-table td.yes { color: var(--azure-300); font-weight: 600; }
.comp-table td.limited { color: var(--slate); }
@media (max-width: 700px) { .comp-table th, .comp-table td { padding: 14px 18px; } }

/* ---------- testimonial ---------- */
.quote-wrap { max-width: 860px; margin: 0 auto; text-align: center; }
.quote-mark { font-size: 90px; line-height: 0.6; color: var(--red-500); font-family: Georgia, serif; }
blockquote { font-size: clamp(22px, 3vw, 32px); font-weight: 500; line-height: 1.4; letter-spacing: -0.015em; margin: 30px 0; }
.quote-attr { color: var(--mist); font-size: 15px; }
.quote-attr b { color: #fff; font-weight: 500; }

/* ---------- testimonials v2 (ported from a pasted React/framer-motion
   component) — rotating quote carousel: oversized parallax index number,
   vertical rail progress, word-by-word quote reveal, faint scrolling
   company ticker. Framer's spring-driven parallax is approximated with a
   CSS transition; AnimatePresence enter/exit swaps are approximated by
   replacing DOM content on each change so fresh nodes replay their CSS
   entrance animation on their own — see render()/restartAnim() in main.js
   for the two nodes (badge, author) that persist and need an explicit
   reflow-restart rather than being freshly created. */
.testi2 { position: relative; padding: 20px 0 10px; }
.testi2-index {
  position: absolute; left: clamp(-40px, -4vw, -10px); top: 46%; transform: translateY(-50%);
  font-family: var(--font-num); font-size: clamp(140px, 19vw, 320px); font-weight: 700;
  color: rgba(56, 164, 240, 0.16); line-height: 1; letter-spacing: -0.04em;
  pointer-events: none; user-select: none; z-index: 0;
}
.testi2-index-inner { display: block; animation: testi2NumIn 0.6s var(--ease-out); }
@keyframes testi2NumIn {
  from { opacity: 0; transform: scale(0.85); filter: blur(10px); }
  to { opacity: 1; transform: scale(1); filter: blur(0); }
}
.testi2-body { position: relative; z-index: 1; display: flex; gap: clamp(32px, 5vw, 64px); }
.testi2-main { flex: 1; min-width: 0; padding: 8px 0 8px clamp(48px, 6vw, 100px); }
.testi2-top { display: flex; align-items: center; gap: 20px; margin-bottom: 30px; }
.testi2-label {
  font-size: clamp(28px, 3vw, 48px); font-weight: 500; letter-spacing: -0.03em;
  white-space: nowrap;
}
.testi2-progress { position: relative; width: 100%; height: 2px; background: var(--line); }
.testi2-progress span { position: absolute; top: 0; left: 0; height: 100%; background: #fff; transition: width 0.5s var(--ease-out); }
.testi2-quote {
  /* Fixed height (not min-height) — the longest testimonials run to 4
     lines and the shortest to 2; a variable height here shifts the
     oversized index number below (positioned via top: 46% of the whole
     .testi2 block) between slides. Same fix as .sol-card elsewhere. */
  font-size: clamp(22px, 2.6vw, 38px); font-weight: 300; line-height: 1.2; letter-spacing: -0.01em;
  color: #fff; height: 4.8em; margin-bottom: 40px;
  transform: translateY(15%);
}
.testi2-quote .w {
  display: inline-block; opacity: 0; transform: translateY(20px) rotateX(70deg); transform-origin: bottom;
  animation: testi2WordIn 0.5s var(--ease-out) forwards;
}
@keyframes testi2WordIn { to { opacity: 1; transform: none; } }
.testi2-foot { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; flex-wrap: wrap; }
.testi2-author { display: flex; align-items: center; gap: 16px; transform: translateY(19.8%); animation: testi2FadeUp 0.5s var(--ease-out) 0.1s backwards; }
.testi2-rule { width: 32px; height: 2px; background: var(--red-500); flex-shrink: 0; }
.testi2-name { font-size: 15.5px; font-weight: 500; color: #fff; }
.testi2-role { font-size: 13.5px; color: var(--mist); margin-top: 2px; }
@keyframes testi2FadeUp { from { opacity: 0; transform: translateY(calc(19.8% + 16px)); } to { opacity: 1; transform: translateY(19.8%); } }
.testi2-nav-wrap { display: flex; flex-direction: column; align-items: center; gap: 14px; transform: translateY(19.55px); }
.testi2-nav { display: flex; gap: 12px; }
.testi2-btn {
  position: relative; width: 46px; height: 46px; border-radius: 50%; border: 1px solid var(--line);
  background: none; color: #fff; display: flex; align-items: center; justify-content: center;
  cursor: pointer; overflow: hidden; flex-shrink: 0; padding: 0;
}
.testi2-btn::before { content: ""; position: absolute; inset: 0; background: #fff; transform: scaleX(0); transition: transform var(--dur-2) var(--ease-out); z-index: 0; }
.testi2-btn.prev::before { transform-origin: left; }
.testi2-btn.next::before { transform-origin: right; }
.testi2-btn:hover::before { transform: scaleX(1); }
.testi2-btn svg { position: relative; z-index: 1; transition: color var(--dur-1); }
.testi2-btn:hover svg { color: var(--ink-950); }
.testi2-btn:active { transform: scale(0.94); }
.testi2-ticker { overflow: hidden; opacity: 0.06; pointer-events: none; margin-top: 48px; }
.testi2-ticker-track { display: inline-flex; white-space: nowrap; font-size: 46px; font-weight: 700; letter-spacing: -0.02em; color: var(--azure-300); }
@media (max-width: 760px) {
  .testi2-main { padding-left: 0; }
  .testi2-top { flex-wrap: wrap; gap: 12px; }
  .testi2-index { display: none; }
  .testi2-ticker-track { font-size: 28px; }
  /* Quotes wrap to far more lines at phone widths — the desktop fixed
     height would overlap the author row. The oversized index number is
     hidden here, so nothing depends on a constant section height. */
  .testi2-quote { height: auto; min-height: 3.5em; transform: none; margin-bottom: 28px; }
  .testi2-author { transform: none; animation: none; }
  .testi2-nav-wrap { transform: none; align-self: center; }
  .testi2-foot { flex-direction: column; align-items: flex-start; gap: 28px; }
  .testi2-progress { width: 140px; }
}

/* ---------- numbered stage list (static, no carousel/animation —
   same oversized faint index number as .testi2, just laid out as a
   plain 3-up row instead of a rotating single quote). ---------- */
.stage-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(32px, 4vw, 56px); }
.stage-item { position: relative; display: flex; flex-direction: column; align-items: flex-start; }
.stage-index {
  font-family: var(--font-num); font-size: clamp(130px, 13vw, 195px); font-weight: 700;
  color: rgba(56, 164, 240, 0.16); line-height: 1; letter-spacing: -0.04em;
  pointer-events: none; user-select: none;
  transform: translateX(-15%);
}
.stage-body { position: relative; z-index: 1; padding-left: clamp(36.4px, 4.42vw, 62.4px); margin-top: -40px; }
.stage-body h3 { font-size: 19px; font-weight: 400; line-height: 1.3; margin-bottom: 10px; letter-spacing: -0.01em; }
.stage-body p { font-size: 14.5px; color: var(--mist); line-height: 1.6; }
@media (max-width: 760px) {
  .stage-grid { grid-template-columns: 1fr; gap: 40px; }
}

/* ---------- CTA ---------- */
.cta {
  position: relative; overflow: hidden; text-align: center;
  border-radius: var(--r-xl); padding: clamp(64px, 9vw, 110px) 30px;
  background:
    radial-gradient(700px 320px at 50% -10%, rgba(56,164,240,0.14), transparent 70%),
    var(--surface-raised);
  border: 1px solid var(--line);
}
.cta h2 { font-size: clamp(28px, 3.1vw, 48px); font-weight: 400; letter-spacing: -0.03em; margin-bottom: 18px; }
.cta p { color: var(--mist); font-size: 17px; max-width: 520px; margin: 0 auto 38px; line-height: 1.6; }

/* ---------- CTA beams background (ported from a React/framer-motion
   canvas component) — soft colored light beams rise and pulse behind the
   card content. Sized to the card itself here (the reference covered the
   full viewport for a standalone hero); driven by main.js's tickCtaBeams,
   gated by the same pageHidden/IntersectionObserver/reduceMotion pattern
   as the hero canvas and marquees. The reference's breathing overlay
   (framer-motion animating opacity 0.05→0.15→0.05 over 10s) is plain CSS
   here since it's a simple periodic value with no interaction driving it. */
.cta-beams { position: absolute; inset: 0; width: 100%; height: 100%; filter: blur(15px); pointer-events: none; }
.cta-overlay {
  position: absolute; inset: 0; background: rgba(6, 11, 20, 0.05);
  backdrop-filter: blur(50px); pointer-events: none;
  animation: ctaOverlayBreathe 10s ease-in-out infinite;
}
@keyframes ctaOverlayBreathe { 0%, 100% { opacity: 0.05; } 50% { opacity: 0.15; } }
.cta-content { position: relative; z-index: 1; }

/* ---------- footer ---------- */
footer { padding: 70px 0 40px; border-top: 1px solid var(--line); background: var(--ink-900); }
.foot-grid { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 40px; margin-bottom: 54px; }
.foot-grid img { height: 26px; filter: brightness(0) invert(1); opacity: 0.9; margin-bottom: 18px; }
.foot-grid p { color: var(--slate); font-size: 14px; line-height: 1.6; max-width: 300px; }
.foot-col h4 { font-size: 13px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--mist); margin-bottom: 18px; }
.foot-col ul { list-style: none; display: grid; gap: 12px; }
.foot-col a { color: var(--slate); font-size: 14.5px; transition: color var(--dur-1); }
.foot-col a:hover { color: var(--azure-300); }
.foot-col li > span { color: var(--slate); font-size: 14.5px; }
.foot-base { display: flex; justify-content: space-between; align-items: center; padding-top: 28px; border-top: 1px solid var(--line); color: var(--slate); font-size: 13px; flex-wrap: wrap; gap: 14px; }

/* ---------- mobile nav (burger + drawer injected by main.js) ----------
   The bar keeps logo + language switch + burger on small screens; the
   drawer is built at runtime by cloning the existing .nav-links structure
   (see "mobile nav" in main.js), so the 29 static pages need no per-page
   markup for it. */
.nav-burger {
  display: none; width: 42px; height: 42px; flex-shrink: 0;
  border: 1px solid var(--line-strong); border-radius: 10px;
  background: none; cursor: pointer; padding: 0;
  flex-direction: column; align-items: center; justify-content: center; gap: 5px;
}
.nav-burger span { display: block; width: 18px; height: 2px; background: #fff; border-radius: 2px; transition: transform var(--dur-2) var(--ease-out), opacity var(--dur-2); }
.nav-burger.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-burger.open span:nth-child(2) { opacity: 0; }
.nav-burger.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
.nav.mm-open { background-color: rgba(6, 11, 20, 0.98); box-shadow: 0 1px 0 var(--line); }

.mobile-menu {
  position: fixed; inset: 0; z-index: 890;
  padding: calc(var(--nav-h) + 14px) 6vw 44px;
  background: rgba(6, 11, 20, 0.98);
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  opacity: 0; visibility: hidden; transform: translateY(-10px);
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out), visibility 0s linear var(--dur-2);
}
.mobile-menu.open { opacity: 1; visibility: visible; transform: none; transition-delay: 0s; }
.mm-group { border-bottom: 1px solid var(--line); }
.mm-head {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  padding: 18px 2px; background: none; border: none; cursor: pointer; text-align: left;
  font-family: var(--font); font-size: 17px; font-weight: 500; color: #fff;
}
.mm-head .chev { color: var(--red-500); flex-shrink: 0; transition: transform var(--dur-2) var(--ease-out); }
.mm-group.open .mm-head .chev { transform: rotate(180deg); }
.mm-list { list-style: none; overflow: hidden; max-height: 0; transition: max-height var(--dur-3) var(--ease-out); }
.mm-group.open .mm-list { max-height: 620px; }
.mm-list a { display: block; padding: 11px 2px 11px 16px; font-size: 15px; color: var(--mist); }
.mm-list a:hover, .mm-list a:active { color: #fff; }
.mm-list li:last-child a { padding-bottom: 20px; }
.mm-cta { display: flex; justify-content: center; margin-top: 28px; }
body.mm-locked { overflow: hidden; }

/* ---------- responsive ---------- */
/* Laptop widths: tighten the bar so five items + controls never crowd. */
@media (max-width: 1200px) {
  .nav-links { gap: 22px; font-size: 13.5px; }
}
@media (max-width: 900px) {
  .nav-links { display: none; }
  .nav .btn-primary { display: none; } /* Book a Demo lives in the drawer */
  .nav-burger { display: flex; }
  .foot-grid { grid-template-columns: 1fr 1fr; }
}
/* Several pages set explicit 2/3-column counts inline on feature grids;
   those must still collapse below tablet, and inline styles outrank any
   normal rule — hence the attribute selector + !important. */
@media (max-width: 980px) {
  .sol-grid[style*="grid-template-columns"] { grid-template-columns: repeat(2, 1fr) !important; }
}
@media (max-width: 560px) {
  .sol-grid[style*="grid-template-columns"] { grid-template-columns: 1fr !important; }
  .sol-card { min-height: 340px; }
  /* Cards with background art (only the homepage Solutions section) need
     more headroom at this width: the art sits at a fixed 78%-down
     position, so a short card puts it directly behind the last lines of
     body copy instead of below it, killing contrast. Taller minimum
     pushes the art's vertical center safely past the text block. */
  .sol-card:has(.sol-art) { min-height: 460px; }
  /* Belt-and-suspenders alongside the taller min-height above: smaller and
     lower so its top edge clears the text even on the longest copy, and
     dimmer since mobile has no hover state to fade it back in behind. */
  .sol-art { background-position: center 92%; background-size: 60%; opacity: 0.28; }
  .foot-grid { grid-template-columns: 1fr; }
  .prod-item { flex: 0 0 270px; height: 120px; }
  .hero-stats { gap: 26px 40px; margin-top: 48px; }
  .stats-grid { gap: 30px; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
