
/* =========================================================
   0. VARIABLES & RESET
   ========================================================= */
:root{
  --black:#040508;
  --navy-950:#060a14;
  --navy-900:#0a0f1e;
  --navy-800:#111a2e;
  --navy-700:#1a2740;
  /* Real Aphelyx brand palette (sampled from the production site's CSS) */
  --teal-deep:#0b7f8c;
  --teal-bright:#30bfbf;
  --white:#f6f8fc;
  --gray-300:#c3ccdb;
  --gray-500:#8b96ab;
  --gray-700:#5b6579;
  --glass-bg:rgba(255,255,255,.055);
  --glass-bg-strong:rgba(255,255,255,.09);
  --glass-border:rgba(255,255,255,.12);
  --radius-lg:28px;
  --radius-md:18px;
  --radius-sm:10px;
  --ease:cubic-bezier(.16,.8,.24,1);
  --container:1220px;
}

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
/* Keyboard-only focus ring (mouse/touch focus never triggers :focus-visible) —
   form fields keep their existing border-color treatment instead. */
:focus-visible{outline:2px solid var(--teal-bright);outline-offset:3px;border-radius:4px;}
.form-field input:focus-visible,.form-field select:focus-visible,.form-field textarea:focus-visible{outline:none;}
html{scroll-behavior:smooth;background:var(--black);}
body{
  background:var(--black);
  color:var(--white);
  font-family:'Inter',system-ui,sans-serif;
  line-height:1.5;
  overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
}
h1,h2,h3,h4{font-family:'Outfit',sans-serif;font-weight:600;letter-spacing:-0.02em;line-height:1.08;text-wrap:balance;}
p{text-wrap:pretty;}
/* SplitText measures line breaks itself — text-wrap:balance/pretty fights that
   measurement and can cause a visible reflow right as the split runs. */
[data-split]{text-wrap:wrap;}
img,video{max-width:100%;display:block;}
a{color:inherit;text-decoration:none;}
ul{list-style:none;}
button{font:inherit;background:none;border:none;color:inherit;cursor:pointer;}
::selection{background:var(--teal-deep);color:var(--white);}

/* Custom scrollbar */
::-webkit-scrollbar{width:10px;}
::-webkit-scrollbar-track{background:var(--black);}
::-webkit-scrollbar-thumb{background:linear-gradient(var(--teal-bright),var(--teal-deep));border-radius:10px;border:2px solid var(--black);}
html{scrollbar-width:thin;scrollbar-color:var(--teal-deep) var(--black);}

.container{max-width:var(--container);margin:0 auto;padding:0 32px;}
.eyebrow{
  display:inline-flex;align-items:center;gap:8px;
  font-size:14.5px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;
  color:var(--teal-bright);margin-bottom:18px;
}
.eyebrow::before{content:'';width:22px;height:1px;background:var(--teal-bright);}
/* Time-of-day greeting prepended to the hero eyebrow — see initHeroGreeting.
   Lighter weight/color than the brand label so "APHELYX Tech Solutions" still
   reads as the primary text; empty text node if JS/Date ever fails, which just
   leaves the eyebrow exactly as it always was (its built-in fallback). */
.hero-greeting{color:var(--gray-300);font-weight:500;}

.btn{
  display:inline-flex;align-items:center;gap:10px;
  padding:15px 30px;border-radius:100px;will-change:transform;
  font-size:15px;font-weight:600;position:relative;overflow:hidden;
  transition:transform .35s var(--ease),box-shadow .35s var(--ease),background .35s var(--ease);
  white-space:nowrap;
}
.btn-primary{background:linear-gradient(135deg,var(--teal-deep),var(--teal-bright));color:var(--white);box-shadow:0 8px 30px -8px rgba(11,127,140,.55);}
.btn-primary:hover{transform:translateY(-2px);box-shadow:0 14px 36px -8px rgba(11,127,140,.7);}
.btn-ghost{background:var(--glass-bg);border:1px solid var(--glass-border);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);color:var(--white);}
/* Shine sweep: a soft diagonal highlight that crosses the button once on
   hover — pure CSS, GPU-only (transform), no JS. */
.btn::after{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(115deg,transparent 40%,rgba(255,255,255,.35) 50%,transparent 60%);
  transform:translateX(-120%);
}
.btn:hover::after{transition:transform .75s var(--ease);transform:translateX(120%);}

/* =========================================================
   PREMIUM HOVER SYSTEM (cursor spotlight + 3D tilt + magnetic)
   Applied via JS (initPremiumHover/initMagnetic) to elements carrying
   these classes; --mx/--my are set on pointermove, tilt reads MOTION.tilt.
   Every rule here is transform/opacity/background only (GPU, no layout).
   ========================================================= */
.tilt-card{--mx:50%;--my:50%;transform-style:preserve-3d;will-change:transform;}
.tilt-card::before{
  content:'';position:absolute;inset:0;z-index:0;border-radius:inherit;pointer-events:none;
  background:radial-gradient(var(--spotlight-size,320px) circle at var(--mx) var(--my),rgba(48,191,191,.16),transparent 70%);
  opacity:0;transition:opacity .4s var(--ease);
}
.tilt-card:hover::before{opacity:1;}
.tilt-card > *{position:relative;z-index:1;}

/* Global cursor glow: a soft light that follows the pointer everywhere on the
   page, shifting color over text/interactive elements vs empty space — see
   initCursorGlow. screen blend only lightens (never darkens) so it reads
   correctly over both the black background and the video reels. */
#cursorGlow{
  position:fixed;top:0;left:0;width:520px;height:520px;margin:-260px;
  border-radius:50%;pointer-events:none;z-index:9999;
  background:radial-gradient(circle,rgba(48,191,191,.16),transparent 68%);
  mix-blend-mode:screen;transition:background .35s ease;will-change:transform;
}
#cursorGlow.is-text{background:radial-gradient(circle,rgba(255,158,110,.22),transparent 68%);}

/* Custom cursor: small dot (tight tracking) + looser ring that expands over
   interactive elements. Desktop-with-a-mouse only — initCursorGlow() never
   creates these on touch devices, so .has-custom-cursor is never added there. */
#cursorDot{
  position:fixed;top:0;left:0;width:6px;height:6px;margin:-3px;
  border-radius:50%;background:var(--teal-bright);pointer-events:none;z-index:9999;
}
#cursorRing{
  position:fixed;top:0;left:0;width:34px;height:34px;margin:-17px;
  border-radius:50%;border:1.5px solid rgba(48,191,191,.55);pointer-events:none;z-index:9999;
  transition:width .3s var(--ease),height .3s var(--ease),margin .3s var(--ease),
             border-color .3s var(--ease),background .3s var(--ease);
}
#cursorRing.is-active{width:54px;height:54px;margin:-27px;background:rgba(48,191,191,.1);border-color:rgba(48,191,191,.85);}
html.has-custom-cursor,html.has-custom-cursor *{cursor:none;}
html.has-custom-cursor input,html.has-custom-cursor textarea{cursor:text;}

/* Floating WhatsApp button — sits above .service-next/.service-prev (bottom:36px/18px)
   so it never overlaps the service-page navigation, and below the Nosotros overlay's
   z-index:2000 so it's automatically covered while that overlay is open. */
.whatsapp-fab{
  position:fixed;right:28px;bottom:112px;z-index:820;
  width:58px;height:58px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  background:#25D366;color:#fff;
  box-shadow:0 12px 30px -8px rgba(37,211,102,.55),0 0 0 1px rgba(255,255,255,.08) inset;
  opacity:0;transform:translateY(16px) scale(.8);
  transition:transform .3s var(--ease),box-shadow .3s var(--ease),background .3s var(--ease);
}
.whatsapp-fab.is-visible{opacity:1;transform:translateY(0) scale(1);transition:opacity .5s var(--ease),transform .5s var(--ease);}
.whatsapp-fab:hover{background:#22c15e;box-shadow:0 16px 36px -6px rgba(37,211,102,.7),0 0 0 1px rgba(255,255,255,.12) inset;}
.whatsapp-fab svg{width:29px;height:29px;position:relative;z-index:1;}
.whatsapp-fab::before{
  content:'';position:absolute;inset:0;border-radius:50%;
  box-shadow:0 0 0 0 rgba(37,211,102,.55);
  animation:whatsappPulse 2.6s ease-out infinite;
}
@keyframes whatsappPulse{
  0%{box-shadow:0 0 0 0 rgba(37,211,102,.5);}
  70%{box-shadow:0 0 0 16px rgba(37,211,102,0);}
  100%{box-shadow:0 0 0 0 rgba(37,211,102,0);}
}
.whatsapp-fab-label{
  position:absolute;right:70px;top:50%;transform:translateY(-50%) translateX(6px);
  white-space:nowrap;padding:9px 16px;border-radius:10px;font-size:13.5px;font-weight:600;
  background:var(--navy-900);border:1px solid var(--glass-border);color:var(--white);
  opacity:0;pointer-events:none;transition:opacity .25s var(--ease),transform .25s var(--ease);
}
.whatsapp-fab:hover .whatsapp-fab-label{opacity:1;transform:translateY(-50%) translateX(0);}
@media (prefers-reduced-motion: reduce){
  .whatsapp-fab::before{animation:none;}
}
/* perspective is scoped to these specific grids (not body/html) — putting it
   on a shared ancestor of position:fixed elements (nav, overlays) would make
   that ancestor a containing block for them and break their fixed positioning,
   exactly like the backdrop-filter/nav-mobile issue elsewhere in this file. */
.solutions-grid,.capability-grid,.problem-grid,.partners-layout,
.reel-content--split .container,.portfolio-cta .container,
.feature-row,.steps-grid,.why-values,.why-mission-grid{perspective:1200px;}
.btn-ghost:hover{background:var(--glass-bg-strong);transform:translateY(-2px);}

/* Glass card */
.glass{
  background:var(--glass-bg);
  border:1px solid var(--glass-border);
  backdrop-filter:blur(18px);
  -webkit-backdrop-filter:blur(18px);
  border-radius:var(--radius-md);
}

/* Scroll progress bar */
#scroll-progress{position:fixed;top:0;left:0;height:3px;width:0%;background:linear-gradient(90deg,var(--teal-deep),var(--teal-bright));z-index:999;}

/* Skip-to-content: invisible until keyboard-focused, then jumps above everything */
.skip-link{
  position:fixed;top:-60px;left:16px;z-index:10000;
  background:var(--teal-bright);color:var(--navy-950);
  padding:12px 20px;border-radius:8px;font-weight:700;font-size:14px;
  transition:top .25s var(--ease);
}
.skip-link:focus{top:16px;}

/* Cinematic preloader — shown ~1.1-1.5s (or until the hero video can play,
   whichever is later, capped at 2.6s) so the hero video never pops in mid-load. */
#preloader{
  position:fixed;inset:0;z-index:10050;background:var(--navy-950);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:22px;
  transition:opacity .5s var(--ease),visibility .5s var(--ease);
}
#preloader.is-hidden{opacity:0;visibility:hidden;pointer-events:none;}
.preloader-mark{width:88px;opacity:0;transform:scale(.85);animation:preloaderIn .9s var(--ease) .1s forwards;}
.preloader-mark img{width:100%;height:auto;display:block;filter:drop-shadow(0 0 26px rgba(48,191,191,.45));}
@keyframes preloaderIn{to{opacity:1;transform:scale(1);}}
.preloader-bar{width:120px;height:2px;border-radius:2px;background:rgba(255,255,255,.12);overflow:hidden;}
.preloader-bar span{display:block;height:100%;width:0%;background:linear-gradient(90deg,var(--teal-deep),var(--teal-bright));border-radius:2px;transition:width .25s ease-out;}
.preloader-pct{font-family:'Outfit';font-size:12px;color:var(--gray-500);letter-spacing:.06em;min-width:34px;text-align:center;}
@media (prefers-reduced-motion: reduce){
  .preloader-mark{animation:none;opacity:1;transform:none;}
  .preloader-bar span{transition:none;}
  #preloader{transition:opacity .2s linear;}
}

/* Film-grain overlay — a static noise tile whose position steps every frame,
   which reads as flickering grain without ever swapping the image itself.
   No mix-blend-mode: it sits fixed over every playing background video, and a
   blend mode forces the browser to recomposite this full-viewport layer against
   whatever's changed underneath on every single video frame — measured close to
   half the section's frame budget on non-GPU-accelerated video decode. Plain
   opacity reads almost identically at 4.5% strength and composites independently
   (own GPU layer, no per-frame reblend), which is what actually keeps it cheap. */
#filmGrain{
  position:fixed;inset:-4% -4% -4% -4%;z-index:9998;pointer-events:none;
  background-image:url("../images/grain.png");
  background-image:image-set(url("../images/grain.webp") type("image/webp"), url("../images/grain.png") type("image/png"));
  background-size:180px 180px;
  opacity:.045;
  animation:filmGrain .6s steps(3) infinite;
}
@keyframes filmGrain{
  0%{transform:translate(0,0);}
  33%{transform:translate(-1.5%,1%);}
  66%{transform:translate(1%,-1.5%);}
  100%{transform:translate(0,0);}
}
@media (prefers-reduced-motion: reduce){ #filmGrain{animation:none;} }

/* =========================================================
   1. NAV
   ========================================================= */
.nav{
  position:fixed;top:0;left:0;width:100%;z-index:900;
  padding:32px 0;transition:padding .4s var(--ease),background .4s var(--ease),border-color .4s var(--ease),transform .45s var(--ease),box-shadow .4s var(--ease);
  border-bottom:1px solid transparent;
  /* Always-on scrim, even before scrolling — otherwise nav links sit fully
     transparent over the hero and visually collide with its text on short viewports. */
  background:linear-gradient(180deg, rgba(3,4,7,.65) 0%, rgba(3,4,7,.35) 70%, transparent 100%);
}
.nav::after{
  content:'';position:absolute;left:0;right:0;bottom:0;height:1px;
  background:linear-gradient(90deg,transparent,rgba(48,191,191,.5) 50%,transparent);
  opacity:.6;
}
.nav.is-scrolled{
  padding:18px 0;background:rgba(6,10,20,.76);
  backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);
  border-bottom-color:var(--glass-border);
  box-shadow:0 12px 40px -20px rgba(0,0,0,.6);
}
.nav.is-hidden{transform:translateY(-100%);}
.nav .container{display:flex;align-items:center;justify-content:space-between;}
.nav-logo{display:flex;align-items:center;}
/* Height stays fixed in CSS — opacity/scale are driven continuously by scroll
   position in JS (see initNav) so the logo shrinks and fades away smoothly
   instead of snapping between two sizes at a single scroll threshold. */
.nav-logo img{height:60px;width:auto;transform-origin:left center;}
.nav-links{display:flex;align-items:center;gap:40px;}
.nav-links a{font-size:15.5px;font-weight:500;letter-spacing:.01em;color:var(--gray-300);transition:color .25s,letter-spacing .25s var(--ease);position:relative;}
.nav-links a:hover{letter-spacing:.03em;}
.nav-links a:hover{color:var(--white);}
.nav-links a::after{content:'';position:absolute;left:0;bottom:-6px;width:0;height:1px;background:var(--teal-bright);transition:width .3s var(--ease);}
.nav-links a:hover::after{width:100%;}
.nav-cta{display:flex;align-items:center;gap:16px;}
.nav-cta .btn{padding:13px 25px;font-size:14.5px;}
.sound-toggle{
  width:43px;height:43px;border-radius:11px;background:var(--glass-bg);border:1px solid var(--glass-border);
  display:flex;align-items:center;justify-content:center;color:var(--gray-300);flex:0 0 auto;
  transition:background .25s,color .25s;
}
.sound-toggle:hover{background:var(--glass-bg-strong);color:var(--white);}
.sound-toggle svg{width:19px;height:19px;}
.sound-toggle .icon-off{display:none;}
.sound-toggle.is-muted .icon-on{display:none;}
/* Language toggle: only the elements tagged [data-i18n] switch text (nav, a
   handful of buttons/labels) — paragraphs and headings stay in Spanish until
   someone reviews and adds real translations for them, see initI18n. */
.lang-toggle{
  width:43px;height:43px;border-radius:11px;background:var(--glass-bg);border:1px solid var(--glass-border);
  display:flex;align-items:center;justify-content:center;color:var(--gray-300);flex:0 0 auto;
  font-family:'Outfit';font-weight:700;font-size:12px;letter-spacing:.03em;
  transition:background .25s,color .25s;
}
.lang-toggle:hover{background:var(--glass-bg-strong);color:var(--white);}
.sound-toggle.is-muted .icon-off{display:block;}
.nav-toggle{display:none;width:38px;height:38px;border-radius:10px;background:var(--glass-bg);border:1px solid var(--glass-border);align-items:center;justify-content:center;}
.nav-toggle span,.nav-toggle span::before,.nav-toggle span::after{content:'';display:block;width:16px;height:2px;background:var(--white);position:relative;transition:.3s;}
.nav-toggle span::before{position:absolute;top:-5px;}
.nav-toggle span::after{position:absolute;top:5px;}
.nav-toggle.active span{background:transparent;}
.nav-toggle.active span::before{top:0;transform:rotate(45deg);}
.nav-toggle.active span::after{top:0;transform:rotate(-45deg);}
.nav-mobile{display:none;}

/* "Nosotros" is deliberately NOT part of the normal scroll flow — the nav link
   opens it as its own full-screen overlay instead, so it never appears while
   scrolling down the page. */
body.no-scroll{overflow:hidden;}
.nosotros-overlay{
  position:fixed;inset:0;z-index:2000;
  background:var(--navy-950);overflow-y:auto;
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .45s var(--ease);
}
.nosotros-overlay.is-open{opacity:1;visibility:visible;pointer-events:auto;}
/* Single continuous parallax background (.section-parallax) spans the hero
   band AND the content below it (.nosotros-body), instead of two separately
   -colored sections — .why no longer carries its own solid background. */
.nosotros-hero,.nosotros-body .why{position:relative;z-index:1;}
.nosotros-overlay .why{padding-top:70px;padding-bottom:110px;min-height:auto;}
.nosotros-close{
  position:fixed;top:28px;right:32px;z-index:2001;
  width:46px;height:46px;border-radius:50%;
  background:var(--glass-bg);border:1px solid var(--glass-border);
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  display:flex;align-items:center;justify-content:center;color:var(--white);
  transition:background .25s,transform .35s var(--ease);
}
.nosotros-close svg{width:18px;height:18px;}
.nosotros-close:hover{background:var(--glass-bg-strong);transform:rotate(90deg);}
.nosotros-home{
  position:fixed;top:26px;left:32px;z-index:2001;
  display:flex;align-items:center;gap:8px;
  padding:11px 20px 11px 15px;border-radius:999px;
  background:var(--glass-bg);border:1px solid var(--glass-border);
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  color:var(--white);font-size:13.5px;font-weight:600;font-family:'Outfit';
  transition:background .25s,transform .3s var(--ease);
}
.nosotros-home svg{width:16px;height:16px;flex:0 0 auto;transition:transform .3s var(--ease);}
.nosotros-home:hover{background:var(--glass-bg-strong);}
.nosotros-home:hover svg{transform:translateX(-3px);}

/* Nosotros hero band — parallax image background, mirrors aphelyx.com/nosotros */
.nosotros-hero{padding:150px 0 74px;}
.nosotros-hero h1{font-family:'Outfit';font-weight:800;font-size:clamp(34px,5.6vw,64px);line-height:1.05;margin-bottom:20px;}
.nosotros-hero-sub{color:var(--gray-300);font-size:clamp(16px,1.6vw,19px);max-width:640px;}

/* Nosotros — Misión / Visión, in the same "cuadrito" language as the values grid below */
.why-mission-grid{display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-bottom:32px;}
.why-block{
  padding:32px 28px;position:relative;overflow:hidden;opacity:0;
  transition:transform .35s var(--ease),border-color .35s var(--ease),background .35s var(--ease);
}
.why-block:hover{transform:translateY(-4px);border-color:rgba(48,191,191,.4);background:var(--glass-bg-strong);}
.why-block-ic{
  width:48px;height:48px;border-radius:13px;margin-bottom:18px;
  background:rgba(48,191,191,.14);border:1px solid rgba(48,191,191,.32);color:var(--teal-bright);
  display:flex;align-items:center;justify-content:center;font-size:21px;
}
.why-block h3{font-size:clamp(20px,2.2vw,24px);margin-bottom:12px;}

/* A slow-drifting ambient glow behind the Misión/Visión/Valores cuadros — purely
   decorative GPU transform animation, keeps the screen feeling alive at rest. */
.why{position:relative;overflow:hidden;}
.why-glow{
  position:absolute;top:-10%;right:-6%;width:520px;height:520px;z-index:0;
  background:radial-gradient(circle,rgba(48,191,191,.24),transparent 70%);
  border-radius:50%;filter:blur(20px);pointer-events:none;
  animation:whyGlowFloat 16s ease-in-out infinite;
}
@keyframes whyGlowFloat{
  0%,100%{transform:translate(0,0) scale(1);}
  50%{transform:translate(-60px,50px) scale(1.18);}
}
.why .container{position:relative;z-index:1;}
.why-item{
  display:flex;flex-direction:column;gap:12px;position:relative;overflow:hidden;opacity:0;
  padding:24px 22px;border-radius:var(--radius-sm);
  background:rgba(255,255,255,.075);border:1px solid rgba(255,255,255,.16);
  backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
  transition:transform .35s var(--ease),border-color .35s var(--ease),background .35s var(--ease);
}
.why-item:hover{transform:translateY(-4px);border-color:rgba(255,255,255,.4);background:rgba(255,255,255,.12);}
.why-item .ic{flex:0 0 auto;width:42px;height:42px;border-radius:11px;background:rgba(255,255,255,.16);display:flex;align-items:center;justify-content:center;font-size:18px;}
.why-item h4{font-size:16px;margin-bottom:4px;}
.why-item p{color:rgba(255,255,255,.78);font-size:13.5px;line-height:1.5;}

/* =========================================================
   2. REEL SECTIONS (video-driven cinematic blocks)
   shared by: hero, analitica, inteligencia, desarrollo, cloud, proceso
   ========================================================= */
/* .reel-media and .reel-content are stacked in the same grid cell (grid-area:1/1)
   so both stick independently to the viewport top without one pushing the other
   down the normal document flow. */
.reel{position:relative;height:180vh;display:grid;}
.reel--hero{height:170vh;}
.reel-media{
  grid-area:1/1;
  position:sticky;top:0;height:100vh;width:100%;
  overflow:hidden;background:var(--navy-950);
}
.reel-video{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;will-change:transform;
  transform:scale(1.02);
  filter:saturate(1.05) contrast(1.02);
}
/* Shifts a raw neon-green source (e.g. Matrix-style code rain) toward the site's
   teal palette instead of clashing with it, and mutes it so it reads as a mood
   backdrop rather than a green screensaver. */
.reel-video--desaturate{filter:hue-rotate(50deg) saturate(.65) brightness(.72) contrast(1.15);}
/* Same idea as .reel-video--desaturate above, without the hue-rotate: for
   backgrounds (circuit-board.mp4, particle-network.mp4) whose own color is
   fine, just too bright/saturated to sit behind a transparent luma-keyed
   frame sequence — at full brightness the frame's own semi-transparent edges
   (its keyed backdrop is never 100% clean, see #desarrollo-web's crop
   comments in initFrameSequence) pick up the video's color/brightness and
   read as a washed-out card instead of a crisp floating object. Dimming the
   video behind it, not the frame in front, is what Desarrollo/matrix.mp4
   already does for the same reason. */
.reel-video--dim{filter:saturate(.55) brightness(.55) contrast(1.05);}
/* object-fit:cover crops from dead-center by default, which cut off the top of
   her head on the Desarrollo Web clip on wide/short viewports — shifting the
   crop window up keeps her fully in frame instead of cropping mid-face. */
.reel-video--top{object-position:center 18%;}
/* Publicidad: Apple AirPods-style scroll-scrubbed image sequence, drawn to a
   canvas instead of a <video> (see initFrameSequence). Sits in the same slot a
   .reel-video would, cropped/centered the same way (cover-fit, computed in JS
   since canvas has no object-fit). */
.frame-seq{position:absolute;inset:0;width:100%;height:100%;background:var(--navy-950);}
/* The source frames are shot on a light studio-gray backdrop, which reads as a
   jarring paper-white rectangle against the site's dark navy/teal palette.
   brightness/contrast pulls it down toward the page's own darkness, and
   .frame-seq-tint (mix-blend-mode:color, sitting on top) recolors it toward teal
   while preserving the metal's own highlights/shadows — same idea as a color
   grade, tuned by eye against the other sections' imagery. */
.frame-seq-canvas{position:absolute;inset:0;width:100%;height:100%;display:block;opacity:0;transition:opacity .7s ease;filter:brightness(.75) contrast(1.1);}
.frame-seq.is-ready .frame-seq-canvas{opacity:1;}
.frame-seq-tint{position:absolute;inset:0;background:linear-gradient(135deg,var(--teal-deep),var(--navy-950));mix-blend-mode:color;opacity:.85;pointer-events:none;}
.frame-seq-fallback{
  position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;
  background:radial-gradient(circle at 50% 45%,rgba(14,110,120,.22),var(--navy-950) 70%);
  transition:opacity .5s ease;pointer-events:none;
}
.frame-seq.is-ready .frame-seq-fallback{opacity:0;}
.frame-seq-loader{width:180px;height:3px;border-radius:2px;background:rgba(255,255,255,.12);overflow:hidden;}
.frame-seq-loader-bar{display:block;height:100%;width:0%;background:linear-gradient(90deg,var(--teal-deep),var(--teal-bright));transition:width .25s ease-out;}
.frame-seq-fallback span{color:var(--gray-500);font-size:12.5px;letter-spacing:.06em;text-transform:uppercase;}
.reel-overlay{
  position:absolute;inset:0;
  background:
    linear-gradient(180deg, rgba(3,4,7,.6) 0%, rgba(4,6,10,.25) 30%, rgba(4,6,10,.4) 70%, rgba(3,4,7,.92) 100%),
    linear-gradient(90deg, rgba(3,4,7,.94) 0%, rgba(3,4,7,.8) 32%, rgba(3,4,7,.4) 55%, transparent 75%, rgba(3,4,7,.45) 100%);
}
.reel-mask{
  position:absolute;inset:0;background:var(--black);opacity:1;pointer-events:none;
}
/* Hero-only ambient glow that drifts toward the cursor — a quiet Framer-style
   touch, not a spotlight: kept faint (mix-blend:screen + low alpha) so it reads
   as depth behind the title rather than competing with the video underneath it.
   Desktop pointer only (see initHeroGlow) — no mouse to react to on touch, and
   reduced-motion users get the fixed centered position with no drift at all. */
.hero-glow{
  position:absolute;inset:0;z-index:2;pointer-events:none;opacity:0;
  background:radial-gradient(640px circle at var(--gx,50%) var(--gy,30%),rgba(48,191,191,.22),rgba(48,191,191,.05) 45%,transparent 70%);
  mix-blend-mode:screen;
  transition:opacity 1.4s var(--ease),background .5s ease-out;
}
.reel--hero.is-glow-ready .hero-glow{opacity:1;}
@media (max-width:900px){.hero-glow{display:none;}}
@media (prefers-reduced-motion:reduce){.hero-glow{transition:opacity 1.4s var(--ease);background:radial-gradient(640px circle at 50% 30%,rgba(48,191,191,.18),transparent 65%);}}
.reel-content{
  grid-area:1/1;
  position:sticky;top:0;height:100vh;width:100%;
  display:flex;flex-direction:column;align-items:flex-start;justify-content:center;
  padding-top:88px;/* keeps content clear of the fixed nav on short viewports */
  z-index:3;pointer-events:none;
}
.reel-content .container{width:100%;pointer-events:none;}
.reel-content .container > *{pointer-events:auto;}

.reel-eyebrow-num{color:var(--gray-500);font-family:'JetBrains Mono',monospace;font-size:13px;}

/* Hero specifics */
.hero-title{font-size:clamp(38px,6.2vw,86px);max-width:920px;margin-bottom:22px;letter-spacing:-0.035em;text-shadow:0 4px 28px rgba(0,0,0,.4);}
/* Idle "breathing": a near-imperceptible scale/tracking drift once the hero
   has fully revealed — reads as alive without ever looking like a loop. */
@keyframes titleBreathe{0%,100%{letter-spacing:-0.035em;transform:scale(1);}50%{letter-spacing:-0.025em;transform:scale(1.006);}}
.hero-title.is-breathing{animation:titleBreathe 7s ease-in-out infinite;}
.accent{background:linear-gradient(120deg,var(--teal-bright),var(--teal-deep) 60%,var(--teal-bright));-webkit-background-clip:text;background-clip:text;color:transparent;}
.hero-sub{font-size:clamp(16px,1.6vw,20px);color:var(--gray-300);max-width:560px;margin-bottom:38px;font-weight:400;text-shadow:0 2px 16px rgba(0,0,0,.4);}
.hero-actions{display:flex;gap:16px;flex-wrap:wrap;}
.hero-stats{display:flex;gap:36px;flex-wrap:wrap;margin-top:40px;}
.hero-stat-num{display:block;font-family:'Outfit';font-size:clamp(26px,2.6vw,34px);font-weight:700;color:var(--white);font-variant-numeric:tabular-nums;}
.hero-stat-label{display:block;color:var(--gray-500);font-size:12.5px;text-transform:uppercase;letter-spacing:.06em;margin-top:3px;}
.hero-scroll-cue{
  position:absolute;bottom:38px;left:50%;transform:translateX(-50%);
  display:flex;flex-direction:column;align-items:center;gap:8px;
  color:var(--gray-500);font-size:12px;letter-spacing:.1em;text-transform:uppercase;
  z-index:3;
}
.hero-scroll-cue .line{width:1px;height:34px;background:linear-gradient(var(--teal-bright),transparent);animation:cueMove 1.8s ease-in-out infinite;}
@keyframes cueMove{0%,100%{opacity:.2;transform:scaleY(.6);transform-origin:top;}50%{opacity:1;transform:scaleY(1);transform-origin:top;}}

/* Generic reel section content block */
.reel-block{max-width:820px;position:relative;z-index:1;}
/* SplitText line/word masks: the mask wrapper (auto-created, overflow:clip) sits
   around each unit; will-change keeps the stagger smooth on longer headings. */
.split-line,.split-word{will-change:transform,opacity;}
.reel-block::before{
  content:'';position:absolute;inset:-24px -28px;z-index:-1;border-radius:28px;
  background:rgba(3,5,9,.58);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);
}
/* Proceso only: the new process-network.mp4 background (28 jul 2026) is worth
   actually seeing, not just implying — text still stays legible on its own
   text-shadow layer above (see .reel-block h2/.lead), so this card can afford
   to sit much lighter than the rgba(3,5,9,.58) default every other reel uses. */
#proceso .reel-block::before{background:rgba(3,5,9,.3);backdrop-filter:blur(9px);-webkit-backdrop-filter:blur(9px);}
#proceso .step-item{background:rgba(255,255,255,.035);border-color:rgba(255,255,255,.1);}
#proceso .step-item:hover{background:rgba(255,255,255,.065);}
/* A soft drop shadow on the text itself is a second, background-independent
   layer of separation — busier/brighter video frames (charts, office photos)
   still read clearly even where the blur card underneath is thinnest. */
.reel-block h2{font-size:clamp(30px,3.6vw,50px);margin-bottom:16px;text-shadow:0 4px 24px rgba(0,0,0,.45);}
.reel-block .lead{color:var(--gray-300);font-size:18px;margin-bottom:6px;max-width:560px;text-shadow:0 2px 14px rgba(0,0,0,.4);}
.reel-tagline{
  display:inline-block;margin-top:22px;color:var(--teal-bright);font-weight:600;font-size:14.5px;
  padding:8px 17px;border:1px solid rgba(48,191,191,.35);border-radius:100px;background:rgba(48,191,191,.08);
}

.feature-row{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:28px;}
.feature-item{
  display:flex;gap:13px;align-items:flex-start;position:relative;overflow:hidden;
  padding:16px 15px;border-radius:var(--radius-sm);
  background:var(--glass-bg);border:1px solid var(--glass-border);
  transition:transform .35s var(--ease),border-color .35s var(--ease),background .35s var(--ease);
}
.feature-item:hover{transform:translateY(-4px);border-color:rgba(48,191,191,.4);background:var(--glass-bg-strong);}
.feature-item .dot{flex:0 0 auto;width:34px;height:34px;border-radius:9px;display:flex;align-items:center;justify-content:center;background:var(--glass-bg);border:1px solid var(--glass-border);color:var(--teal-bright);font-family:'Outfit';font-weight:600;font-size:14px;transition:transform .4s var(--ease);}
.feature-item h4{font-size:16px;margin-bottom:4px;font-weight:600;}
.feature-item p{color:var(--gray-300);font-size:14px;line-height:1.5;}

/* Desarrollo: code typing */
.code-window{
  margin-top:18px;width:min(400px,90vw);border-radius:var(--radius-sm);overflow:hidden;
  box-shadow:0 30px 60px -20px rgba(0,0,0,.6);
}
.code-titlebar{display:flex;align-items:center;gap:7px;padding:8px 14px;background:rgba(255,255,255,.06);}
.code-titlebar span{width:9px;height:9px;border-radius:50%;background:var(--gray-700);}
.code-titlebar span:nth-child(1){background:#ff5f57;}
.code-titlebar span:nth-child(2){background:#febc2e;}
.code-titlebar span:nth-child(3){background:#28c840;}
.code-body{padding:16px;font-family:'JetBrains Mono',monospace;font-size:13px;color:var(--teal-bright);min-height:92px;white-space:pre-wrap;line-height:1.55;}

/* Cloud: connections canvas sits inside media */
.reel-canvas{position:absolute;inset:0;z-index:1;pointer-events:none;opacity:.85;}

/* =========================================================
   SERVICE PAGES: Analítica / Inteligencia / Software a la Medida /
   Cloud / Desarrollo Web live OUTSIDE the normal scroll flow. Each
   opens as its own full "page" (own scroll, from its own top) when
   the user clicks a bento card or nav link — never encountered by
   scrolling down the homepage. Inactive pages sit far off the
   document (top:-100000px) rather than display:none, so SplitText
   can still measure real line widths and GSAP ScrollTrigger never
   sees them overlapping the homepage's own scroll range; becoming
   .is-active switches them back into normal flow and a
   ScrollTrigger.refresh() re-measures everything correctly. */
#servicePages{display:contents;}
#servicePages .reel{position:absolute;top:-100000px;left:0;width:100%;visibility:hidden;pointer-events:none;}
#servicePages .reel.is-active{position:relative;top:auto;visibility:visible;pointer-events:auto;}
body.service-open .home-section{display:none;}
.service-back{
  position:fixed;top:126px;left:32px;z-index:890;
  display:none;align-items:center;gap:9px;
  padding:12px 22px 12px 18px;border-radius:100px;
  background:var(--glass-bg);border:1px solid var(--glass-border);
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  color:var(--white);font-size:14.5px;font-weight:600;
  transition:background .25s,transform .25s;
}
.service-back svg{width:16px;height:16px;transition:transform .25s;}
.service-back:hover{background:var(--glass-bg-strong);}
.service-back:hover svg{transform:translateX(-3px);}
body.service-open .service-back{display:flex;}

/* 1/5..5/5 progress indicator across the service pages — updated in
   initServicePages()'s open(id) via SERVICE_IDS.indexOf(id). */
.service-progress{
  position:fixed;top:138px;left:50%;transform:translateX(-50%);z-index:890;
  display:none;align-items:center;gap:9px;
}
.service-progress .dot{
  width:8px;height:8px;border-radius:50%;flex:0 0 auto;
  background:rgba(255,255,255,.22);border:1px solid rgba(255,255,255,.32);
  transition:background .35s var(--ease),width .35s var(--ease),border-color .35s var(--ease);
}
.service-progress .dot.is-active{
  width:24px;border-radius:5px;border-color:transparent;
  background:linear-gradient(90deg,var(--teal-deep),var(--teal-bright));
}
body.service-open .service-progress{display:flex;}

/* Bottom-of-screen control on every service page so it never feels like a dead
   end — pinned to the sticky 100vh content box, always visible without
   scrolling, linking straight to the next service in the lineup. */
.service-next{
  position:absolute;bottom:36px;right:0;z-index:3;pointer-events:auto;
  display:inline-flex;align-items:center;gap:11px;
  padding:13px 24px;border-radius:100px;
  background:var(--glass-bg);border:1px solid var(--glass-border);
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  color:var(--white);font-size:14px;font-weight:600;
  transition:background .25s,transform .25s;
}
.service-next:hover{background:var(--glass-bg-strong);transform:translateX(4px);}
.service-next svg{width:16px;height:16px;}
.service-next .label{display:block;color:var(--gray-500);font-weight:500;font-size:11px;text-transform:uppercase;letter-spacing:.07em;}
/* Mirror of .service-next on the opposite corner — every service page can
   go forward AND back to the one before it, never just a dead end. */
.service-prev{
  position:absolute;bottom:36px;left:0;z-index:3;pointer-events:auto;
  display:inline-flex;align-items:center;gap:11px;
  padding:13px 24px;border-radius:100px;
  background:var(--glass-bg);border:1px solid var(--glass-border);
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  color:var(--white);font-size:14px;font-weight:600;
  transition:background .25s,transform .25s;
}
.service-prev:hover{background:var(--glass-bg-strong);transform:translateX(-4px);}
.service-prev svg{width:16px;height:16px;}
.service-prev .label{display:block;color:var(--gray-500);font-weight:500;font-size:11px;text-transform:uppercase;letter-spacing:.07em;}

/* Arquitectura de Soluciones: bento teaser that replaces the 4-5 full
   cinematic reels in the main scroll — each card opens its service
   as its own page instead of continuing the scroll. */
/* No overflow:hidden here on purpose: it would clip the card-grid entrance
   effect's off-screen starting position (see initStaticReveals) before it's
   ever visible, making cards read as a plain fade instead of flying in from
   outside the section. The only element that would otherwise overflow
   (.section-video's zoom) already clips itself. */
.solutions{padding:130px 0;position:relative;}
.solutions > .container{position:relative;z-index:1;}
.solutions-head{text-align:center;margin-bottom:64px;}
.solutions-head h2{font-size:clamp(42px,6vw,76px);letter-spacing:-0.03em;}
.solutions-head .sub{color:var(--gray-300);font-size:18.5px;max-width:620px;margin:18px auto 0;}
/* minmax, not a flat 220px: a fixed row height clipped the "Conocer más" button
   in whichever narrow (non-wide) card happened to have enough copy to wrap to
   3 lines instead of 2 — auto lets that row grow to fit its tallest card. */
.solutions-grid{display:grid;grid-template-columns:repeat(3,1fr);grid-auto-rows:minmax(220px,auto);gap:20px;}
.solution-card{
  padding:32px;border-radius:var(--radius-lg);
  background:var(--glass-bg);border:1px solid var(--glass-border);
  display:flex;flex-direction:column;justify-content:space-between;
  position:relative;overflow:hidden;
  transition:border-color .4s var(--ease),background .4s var(--ease),transform .4s var(--ease);
}
.solution-card:hover{transform:translateY(-4px);border-color:rgba(48,191,191,.4);}
.solution-card--wide{grid-column:span 2;}
.solution-card--tall{grid-row:span 2;background:linear-gradient(180deg,rgba(11,127,140,.28),rgba(11,127,140,.04));border-color:rgba(48,191,191,.3);}
.solution-card--tall:hover{border-color:rgba(48,191,191,.55);}
.solution-icon{width:44px;height:44px;color:var(--teal-bright);margin-bottom:18px;transition:transform .4s var(--ease);}
.solution-card--tall .solution-icon{color:var(--white);}
.solution-card h3{font-size:22px;margin-bottom:10px;}
.solution-card--wide h3,.solution-card--tall h3{font-size:27px;}
.solution-card p{color:var(--gray-300);font-size:14.5px;line-height:1.55;}
/* Made into an actual pill button (not just colored text) with a soft resting
   pulse — explicitly asked for something that reads as clickable on its own,
   without needing a hover to notice it. */
.solution-link{
  display:inline-flex;align-items:center;gap:8px;
  /* .solution-card is display:flex;flex-direction:column with the default
     align-items:stretch, so every child (including this one) stretches to the
     full cross-axis width unless told otherwise — invisible with the old plain
     text+arrow (nothing to see at that width), but very visible once this got
     a background/border/pill shape. align-self:flex-start opts back out. */
  align-self:flex-start;
  color:var(--teal-bright);font-weight:600;font-size:13.5px;margin-top:18px;
  padding:9px 16px;border-radius:100px;
  background:rgba(48,191,191,.12);border:1px solid rgba(48,191,191,.4);
  animation:solutionLinkPulse 2.6s ease-in-out infinite;
  transition:background .3s var(--ease),border-color .3s var(--ease),transform .25s var(--ease);
}
.solution-link svg{width:15px;height:15px;transition:transform .25s;}
.solution-card:hover .solution-link{background:rgba(48,191,191,.22);border-color:rgba(48,191,191,.7);transform:translateY(-2px);}
.solution-card:hover .solution-link svg{transform:translateX(3px);}
.solution-card--tall .solution-link{color:var(--white);background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.4);}
.solution-card--tall:hover .solution-link{background:rgba(255,255,255,.22);border-color:rgba(255,255,255,.7);}
@keyframes solutionLinkPulse{
  0%,100%{box-shadow:0 0 0 0 rgba(48,191,191,.3);}
  50%{box-shadow:0 0 0 7px rgba(48,191,191,0);}
}
@media (prefers-reduced-motion:reduce){.solution-link{animation:none;}}

/* Proceso */
.steps-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:28px;max-width:680px;position:relative;}
.step-item{
  position:relative;z-index:1;overflow:hidden;padding:18px 18px 20px;border-radius:var(--radius-sm);
  background:var(--glass-bg);border:1px solid var(--glass-border);
  transition:transform .35s var(--ease),border-color .35s var(--ease),background .35s var(--ease);
}
/* Connecting line threaded through the grid's gutter, behind the cards — drawn
   progressively via stroke-dashoffset as the Proceso section scrolls (see
   initStepsPath). Purely decorative: it doesn't need to touch each card exactly,
   it just needs to read as "these steps are connected", so a simple viewBox with
   preserveAspectRatio="none" stretching to the grid's actual size is enough and
   stays robust across the 2-col/1-col responsive breakpoint.
   z-index sits below .step-item (1) so cards visually sit above the line. */
.steps-path{position:absolute;inset:0;z-index:0;overflow:visible;pointer-events:none;}
.steps-path path{fill:none;stroke:url(#stepsPathGradient);stroke-width:2;stroke-linecap:round;}
.steps-path circle{fill:var(--teal-bright);}
.step-item:hover{transform:translateY(-4px);border-color:rgba(48,191,191,.4);background:var(--glass-bg-strong);}
.step-item .num{font-family:'Outfit';font-size:26px;font-weight:700;color:var(--teal-bright);display:inline-block;transition:transform .4s var(--ease);}
.step-item h4{font-size:16px;margin:5px 0 4px;}
.step-item p{color:var(--gray-300);font-size:13.5px;line-height:1.5;}

/* Proceso: text column + a small inset video card beside it, same idea as the
   static sections' side visuals (Problema, Nosotros, Partners) but inside a
   cinematic reel. */
.reel-content--split .container{display:grid;grid-template-columns:1.15fr .85fr;gap:50px;align-items:center;}
.reel-visual{
  position:relative;aspect-ratio:4/5;border-radius:var(--radius-lg);overflow:hidden;
  background:rgba(6,10,20,.4);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  border:1px solid var(--glass-border);box-shadow:0 30px 70px -20px rgba(0,0,0,.6);
}
/* contain, not cover: this card is taller/narrower than the source clip, and
   cover would crop it hard — contain shows the whole frame, letterboxed into
   the card's own gradient background instead of cutting anyone off. The mask
   fades the video's own top/bottom edge into that background too, so the
   letterbox reads as a soft vignette instead of a hard pasted-on rectangle. */
.reel-visual .reel-video{
  position:absolute;inset:0;object-fit:contain;
  -webkit-mask-image:linear-gradient(180deg,transparent,#000 14%,#000 86%,transparent);
  mask-image:linear-gradient(180deg,transparent,#000 14%,#000 86%,transparent);
}

/* Publicidad: aspect-ratio 16/9 matches the new source frames' own native
   ratio (1280x720) exactly, on purpose: the sequence's "open" poses spread
   much wider than tall, so the 4/5 portrait ratio every other .reel-visual
   uses would crop its sides off. Matching the ratio means the cover-fit draw
   in initFrameSequence never needs to crop at all — the complete brain is
   always in frame, at every pose.
   Same #lumaKey treatment as Desarrollo/Analítica/Inteligencia now (this
   frame set replaced an earlier one whose light-gray studio backdrop
   couldn't be keyed — see initFrameSequence's watermark-patch comment for
   the swap's other half). Pixel-sampled: background is a true (0,0,0) black
   corner-to-corner, brain chrome highlights sample ~225-235 — a wide-enough
   gap that #lumaKey's existing threshold (tuned for Desarrollo's near-black
   backdrop) keys it cleanly without eating any of the brain. No card chrome
   for the same reason as Desarrollo: a transparent card just reads as the
   brain floating over the section's own background (see #publicidad
   .reel-media below), never as "image in a box". */
.publicidad-visual{
  aspect-ratio:16/9;
  background:none;border:none;box-shadow:none;backdrop-filter:none;border-radius:0;
}
#publicidad .frame-seq{background:transparent;}
#publicidad .frame-seq-tint{display:none;}
#publicidad .frame-seq-canvas{filter:url(#lumaKey);}

/* Software a la Medida: same frame-sequence technique, same 16/9 match to the
   source frames' own ratio (avoids cropping here too), sitting in the
   standard .reel-content--split narrower visual column instead of a custom
   full-width grid — this page's left column doesn't need the extra room
   Publicidad's did. No card chrome here on purpose (unlike every other
   .reel-visual): the source frames render on a true near-black backdrop
   (sampled: luminance 0-26 at the edges vs. 68+ everywhere the device
   actually is — a clean gap, unlike Publicidad's light backdrop above), so
   #lumaKey (see the end of body, and the comment on .analitica-visual) keys
   it to genuinely transparent instead of just a dark box. A mix-blend-mode
   trick was tried here first and rejected — WebP compression meant "black"
   wasn't perfectly pure, and blend modes amplified that noise into a visible
   washed-out rectangle. #lumaKey doesn't have that failure mode: it's a hard
   (feathered) threshold on alpha, not a cumulative color blend, so compression
   noise well below the threshold just resolves to fully transparent instead
   of getting brightened. */
.desarrollo-visual{
  aspect-ratio:16/9;
  background:none;border:none;box-shadow:none;backdrop-filter:none;border-radius:0;
}
#desarrollo .frame-seq{background:transparent;}
#desarrollo .frame-seq-tint{display:none;}
#desarrollo .frame-seq-canvas{filter:url(#lumaKey);}

/* Publicidad's own grid: text left, wide image right (its native ratio needs
   more width than the shared .reel-content--split's narrow visual column
   gives it), features spanning full width below both — rather than the generic
   layout's "features squeezed into the text column" every other split page
   uses. #publicidad-scoped (not just a class) so it reliably outranks
   .reel-content--split .container's own grid-template-columns regardless of
   source order. */
#publicidad .container{
  grid-template-columns:1fr 1.3fr;
  grid-template-areas:"text visual" "features features";
  gap:32px 56px;
}
#publicidad .reel-block{grid-area:text;}
#publicidad .publicidad-visual{grid-area:visual;}
#publicidad .publicidad-features{grid-area:features;margin-top:4px;}

/* Publicidad is GSAP-pinned on desktop (see setupFrameScrub) instead of using the
   sticky-inside-a-180vh-section trick every other reel uses — the pin genuinely
   locks scroll on this one section until the full frame sequence has scrubbed
   through, which is the point here (the rest of the reels are fine easing past).
   GSAP's own pin spacer supplies the scroll runway, so this section only needs to
   be exactly one viewport tall, and its media/content must NOT also be
   position:sticky — sticky and a GSAP-pinned ancestor fight each other. Scoped to
   desktop only: mobile never gets the pin (see initFrameSequence), and the
   text+visual stack taller than 100vh once the visual is forced visible there
   (see .publicidad-visual override below) — the default 180vh/sticky behavior
   already handles content taller than one viewport correctly, so mobile keeps it. */
@media (min-width:901px){
  .reel--pinned{height:100vh;}
  .reel--pinned .reel-media,.reel--pinned .reel-content{position:relative;}
}
.reel-visual::after{content:'';position:absolute;inset:0;background:linear-gradient(180deg,transparent 55%,rgba(3,4,7,.55));}

/* Analítica: Rio data-pipeline frame sequence (assets/images/rio-frames/, see
   initFrameSequence) — same technique as Publicidad/Software a la Medida, and
   now the same treatment on both counts they were asked for: Software a la
   Medida's transparent card (no background/border/shadow — the frames render
   on a near-black backdrop already, so the box just reads as floating over
   the chart video behind it) plus Publicidad's own wide grid (visual gets
   more width than the default split layout's narrow column, features row
   spans full width below both, instead of being squeezed into the text
   column). Pinned + scroll-scrubbed like Software a la Medida, not locked
   like Publicidad — see the .analitica-grid rules below for the layout. */
.analitica-visual{
  aspect-ratio:16/9;
  background:none;border:none;box-shadow:none;backdrop-filter:none;border-radius:0;
}
#analitica .frame-seq{background:transparent;}
#analitica .frame-seq-tint{display:none;}
/* #lumaKey (defined once, right after <body>) makes the frame's own baked-in
   black backdrop genuinely transparent instead of just visually dark — the
   section's real chart video shows straight through wherever the frame is
   black, not just at the card's edges. Superseded the mask-image edge-feather
   this used before: that was a geometric approximation (fade the card's own
   edges) that couldn't touch black in the middle of the frame; this operates
   on the actual pixels, so it's exact everywhere, not just at the boundary. */
#analitica .frame-seq-canvas{filter:url(#lumaKey);}
#analitica .container{
  grid-template-columns:1fr 1.3fr;
  grid-template-areas:"text visual" "features features";
  gap:32px 56px;
}
#analitica .reel-block{grid-area:text;}
#analitica .analitica-visual{grid-area:visual;}
#analitica .analitica-features{grid-area:features;margin-top:4px;}

/* Inteligencia: Núcleo frame sequence (assets/images/nucleo-frames/) — same
   transparent-card + luma-key + Publicidad-style wide grid treatment as
   .analitica-visual above, for the same reasons (see the comments there). The
   background here stays a real video (blue-technology.mp4) plus the existing
   particle-glow canvas, both untouched by this — the frame sequence is an
   addition on top of the split layout, not a replacement for them. */
.inteligencia-visual{
  aspect-ratio:16/9;
  background:none;border:none;box-shadow:none;backdrop-filter:none;border-radius:0;
}
#inteligencia .frame-seq{background:transparent;}
#inteligencia .frame-seq-tint{display:none;}
#inteligencia .frame-seq-canvas{filter:url(#lumaKey);}
#inteligencia .container{
  grid-template-columns:1fr 1.3fr;
  grid-template-areas:"text visual" "features features";
  gap:32px 56px;
}
#inteligencia .reel-block{grid-area:text;}
#inteligencia .inteligencia-visual{grid-area:visual;}
#inteligencia .inteligencia-features{grid-area:features;margin-top:4px;}

/* Cloud: Anillo frame sequence (assets/images/anillo-frames/) — same
   transparent-card + luma-key + wide-grid treatment as .analitica-visual/
   .inteligencia-visual above, for the same reasons (see the comments there).
   Cloud wasn't a split layout before this (just a single centered text
   column over cloud-computing.mp4 + the existing particle-glow canvas, both
   untouched here) — .reel-content--split and the grid overrides below are
   new on this section specifically to make room for the visual, the same
   way Inteligencia's were. Pinned + scroll-scrubbed, not locked — matches
   every frame-seq section except Publicidad. */
.cloud-visual{
  aspect-ratio:16/9;
  background:none;border:none;box-shadow:none;backdrop-filter:none;border-radius:0;
}
#cloud .frame-seq{background:transparent;}
#cloud .frame-seq-tint{display:none;}
#cloud .frame-seq-canvas{filter:url(#lumaKey);}
#cloud .container{
  grid-template-columns:1fr 1.3fr;
  grid-template-areas:"text visual" "features features";
  gap:32px 56px;
}
#cloud .reel-block{grid-area:text;}
#cloud .cloud-visual{grid-area:visual;}
#cloud .cloud-features{grid-area:features;margin-top:4px;}

/* Desarrollo Web: Tablero frame sequence (assets/images/tablero-frames/) —
   same transparent-card + luma-key + wide-grid treatment as .cloud-visual
   above, for the same reasons. Desarrollo Web wasn't a split layout before
   this (single centered text column over the section's own background video,
   untouched here) — .reel-content--split and the grid overrides below are new on this
   section specifically, the same way Cloud's were. Pinned + scroll-scrubbed,
   not locked — matches every frame-seq section except Publicidad. */
.desarrollo-web-visual{
  aspect-ratio:16/9;
  background:none;border:none;box-shadow:none;backdrop-filter:none;border-radius:0;
}
#desarrollo-web .frame-seq{background:transparent;}
#desarrollo-web .frame-seq-tint{display:none;}
#desarrollo-web .frame-seq-canvas{filter:url(#lumaKey);}
#desarrollo-web .container{
  /* Wider than the 1fr 1.3fr every other wide-grid section uses — asked to
     give the tablet more relative presence, on top of the extra shrink
     already applied to the drawn frame itself (see the JS comment). */
  grid-template-columns:0.85fr 1.45fr;
  grid-template-areas:"text visual" "features features";
  gap:32px 56px;
}
#desarrollo-web .reel-block{grid-area:text;}
#desarrollo-web .desarrollo-web-visual{grid-area:visual;}
#desarrollo-web .desarrollo-web-features{grid-area:features;margin-top:4px;}
/* Compacted on request — smaller dot/text/padding than the shared
   .feature-item default, so the row reads lighter and leaves the tablet as
   the section's visual focus instead of competing with it. Scoped to this
   section only; every other feature-row on the page keeps the shared size. */
#desarrollo-web .feature-row{gap:10px;margin-top:20px;}
#desarrollo-web .feature-item{padding:12px 13px;gap:10px;}
#desarrollo-web .feature-item .dot{width:26px;height:26px;border-radius:7px;font-size:12px;}
#desarrollo-web .feature-item h4{font-size:14px;margin-bottom:2px;}
#desarrollo-web .feature-item p{font-size:12.5px;line-height:1.42;}

/* =========================================================
   3. STATIC SECTIONS (no video)
   ========================================================= */
.section{padding:130px 0;position:relative;}
/* Curtain wipe: covers the section, then slides away (clip-path) once on first
   entrance — see initWipeTransitions. Purely an additive overlay layer, doesn't
   touch the section's own layout/content underneath. */
.wipe-curtain{
  position:absolute;inset:0;z-index:5;pointer-events:none;
  background:linear-gradient(120deg,var(--teal-deep) 0%,var(--navy-950) 65%);
  clip-path:inset(0 0 0 0%);
}
/* Same left-to-right wipe, but viewport-fixed and reusable — plays every time a
   service page opens (from a bento card OR the Siguiente/Anterior links, which
   both go through initServicePages' open()), not just once on scroll-entrance. */
.service-wipe-curtain{
  position:fixed;inset:0;z-index:895;pointer-events:none;
  background:linear-gradient(120deg,var(--teal-deep) 0%,var(--navy-950) 65%);
  clip-path:inset(0 0 0 100%);
}
.section-tight{padding:100px 0;}
.reveal{
  opacity:0;pointer-events:none;
  transform:translate(var(--from-x,0),var(--from-y,24px)) scale(.98);filter:blur(6px);
  transition:opacity 1.2s var(--ease),transform 1.2s var(--ease),filter 1.2s var(--ease),pointer-events 0s linear 1.2s;
}
.reveal.is-visible{opacity:1;pointer-events:auto;transform:translate(0,0) scale(1);filter:blur(0);}

/* Card-grid entrances: --from-x/--from-y are set per element from JS
   (initStaticReveals) to a random point genuinely outside the viewport, so each
   card in a grid flies in from its own random direction/distance instead of every
   card sliding up the same way. Elements that don't get a random offset (plain
   headings, single visuals) fall back to the plain translateY(24px) above via the
   var() defaults. */

/* Full-bleed cinematic video background for static sections (Partners, Contacto) */
.section-video{position:absolute;inset:0;z-index:0;overflow:hidden;}
.section-video .reel-video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;transform:scale(1.02);will-change:transform;}
.section-video-overlay{
  position:absolute;inset:0;
  background:linear-gradient(180deg, rgba(3,4,7,.82) 0%, rgba(6,10,20,.55) 40%, rgba(3,4,7,.9) 100%);
}

/* Full-bleed parallax IMAGE background for static sections with no cinematic
   video of their own (Problema, Portafolio CTA, Nosotros). The image is sized
   taller than its container so initParallaxBg() can translateY it on scroll
   without ever exposing an edge. */
/* No overflow:hidden at the section level, same reasoning as .solutions above —
   .parallax-bg already clips its own oversized image, so the section itself
   doesn't need to, and leaving it off lets the card-grid entrance effect (e.g.
   Problema) actually fly in from outside instead of being clipped tight to
   this box. */
.section-parallax{position:relative;}
.parallax-bg{position:absolute;inset:-12% 0;z-index:0;overflow:hidden;pointer-events:none;}
.parallax-bg img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;will-change:transform;}
.parallax-bg-overlay{
  position:absolute;inset:0;
  background:linear-gradient(180deg, rgba(3,4,7,.88) 0%, rgba(6,10,20,.62) 45%, rgba(3,4,7,.92) 100%);
}
.section-parallax > .container{position:relative;z-index:1;}

/* Problema */
.problema{background:radial-gradient(ellipse at 20% 0%,rgba(48,191,191,.1),transparent 55%),var(--navy-950);}
.problema h2{font-size:clamp(28px,3.8vw,46px);max-width:780px;margin-bottom:44px;}
.problema-layout{display:grid;grid-template-columns:1.25fr 1fr;gap:50px;align-items:start;}
.problem-grid{display:flex;flex-direction:column;gap:20px;}
.problem-card{padding:28px 26px;position:relative;overflow:hidden;}
.problem-card .idx{font-family:'Outfit';font-size:34px;font-weight:700;color:rgba(48,191,191,.35);margin-bottom:14px;}
.problem-card h3{font-size:17px;margin-bottom:8px;}
.problem-card p{color:var(--gray-500);font-size:14.5px;}
.problema-closing{margin-top:32px;color:var(--gray-300);font-size:15.5px;max-width:600px;border-left:2px solid var(--teal-deep);padding-left:20px;}
.problema-visual{
  aspect-ratio:3/4;border-radius:var(--radius-lg);overflow:hidden;position:relative;
  border:1px solid var(--glass-border);box-shadow:0 30px 60px -25px rgba(0,0,0,.55);
}
.problema-visual video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}
.problema-visual::after{content:'';position:absolute;inset:0;background:linear-gradient(180deg,rgba(3,4,7,.1) 0%,rgba(3,4,7,.55) 100%);}

/* Por qué APHELYX (Nosotros) — background now comes from the shared .parallax-bg on .nosotros-body */
.why-mission{color:rgba(255,255,255,.78);font-size:14.5px;line-height:1.65;}
.why-values{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;}

/* Portafolio */
.portfolio-cta{background:var(--black);}
.portfolio-card{
  background:linear-gradient(120deg,var(--teal-deep),var(--teal-bright));
  border-radius:var(--radius-lg);padding:56px 64px;position:relative;overflow:hidden;
  display:flex;align-items:center;justify-content:space-between;gap:40px;
  box-shadow:0 30px 70px -30px rgba(11,127,140,.5);
}
.portfolio-text h2{font-size:clamp(28px,3.2vw,40px);margin-bottom:16px;}
.portfolio-text p{color:rgba(255,255,255,.88);font-size:15.5px;max-width:480px;margin-bottom:28px;}
.portfolio-actions{display:flex;gap:14px;flex-wrap:wrap;}
.btn-white{background:var(--white);color:var(--teal-deep);}
.btn-white:hover{transform:translateY(-2px);}
.btn-outline-white{border:1px solid rgba(255,255,255,.5);color:var(--white);}
.btn-outline-white:hover{background:rgba(255,255,255,.12);transform:translateY(-2px);}
.portfolio-icon{flex:0 0 auto;width:96px;height:96px;border-radius:22px;background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.3);display:flex;align-items:center;justify-content:center;}
.portfolio-icon svg{width:44px;height:44px;}

/* Partners */
/* No overflow:hidden here either — same reasoning as .solutions/.section-parallax
   above: .section-video already clips itself, and this section's own clip was only
   ever cutting off the capability-card entrance effect's off-screen start. */
.partners{position:relative;background:var(--navy-950);}
.partners > .container{position:relative;z-index:1;}
.partners h2{font-size:clamp(26px,3.4vw,40px);max-width:640px;margin-bottom:16px;}
.partners .sub{color:var(--gray-500);margin-bottom:56px;}
.partners-layout{display:grid;grid-template-columns:1.6fr 1fr;gap:44px;align-items:stretch;}
/* align-self:start + its own aspect-ratio (rather than the grid's default
   align-items:stretch) is deliberate: the capability-grid beside it is 3 rows tall,
   and stretching this card to match that full height — while object-fit:contain
   keeps the video's own proportions — left most of the card as empty black
   letterbox bars. Sizing it to its own aspect ratio and sticking it as the (much
   taller) card grid scrolls past reads as intentional and keeps it fully visible
   instead. */
.partners-visual{
  border-radius:var(--radius-lg);overflow:hidden;position:sticky;top:110px;align-self:start;
  aspect-ratio:16/10;
  background:rgba(6,10,20,.4);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  border:1px solid var(--glass-border);box-shadow:0 30px 60px -25px rgba(0,0,0,.55);
}
/* object-fit:contain (not cover) so the whole clip shows — this card sits
   flush with the capability grid's height, not the video's own aspect ratio,
   so cover would crop it hard; contain letterboxes into the card's own
   gradient background instead, which reads as intentional, not empty. */
.partners-visual video{
  position:absolute;inset:0;width:100%;height:100%;object-fit:contain;
  -webkit-mask-image:linear-gradient(180deg,transparent,#000 14%,#000 86%,transparent);
  mask-image:linear-gradient(180deg,transparent,#000 14%,#000 86%,transparent);
}
.partners-visual::after{content:'';position:absolute;inset:0;background:linear-gradient(180deg,rgba(3,4,7,.1) 0%,rgba(3,4,7,.55) 100%);}

/* Capability grid: a handful of polished capability cards instead of an exhaustive
   tech-name chip cloud — reads as curated expertise rather than a checklist, and the
   closing card explicitly signals "not on this list? we still cover you". */
.capability-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px;}
.capability-card{
  padding:26px 24px;border-radius:var(--radius-md);
  background:var(--glass-bg);border:1px solid var(--glass-border);
  transition:transform .3s var(--ease),border-color .3s var(--ease),background .3s var(--ease);
}
.capability-card:hover{transform:translateY(-4px);border-color:rgba(48,191,191,.4);background:var(--glass-bg-strong);}
.capability-icon{
  width:44px;height:44px;border-radius:12px;margin-bottom:16px;
  display:flex;align-items:center;justify-content:center;
  background:linear-gradient(135deg,rgba(48,191,191,.22),rgba(11,127,140,.14));
  border:1px solid rgba(48,191,191,.3);color:var(--teal-bright);
  transition:transform .4s var(--ease),box-shadow .4s var(--ease);
}
.capability-card:hover .capability-icon,
.solution-card:hover .solution-icon,
.feature-item:hover .dot,
.step-item:hover .num{transform:scale(1.12);}
.capability-card:hover .capability-icon{box-shadow:0 0 24px -4px rgba(48,191,191,.6);}
.capability-icon svg{width:22px;height:22px;}
.capability-card h4{font-size:16.5px;margin-bottom:8px;}
.capability-card p{color:var(--gray-300);font-size:13.5px;line-height:1.55;margin-bottom:16px;}
.capability-tags{display:flex;flex-wrap:wrap;gap:6px;}
.capability-tags span{
  font-size:11px;font-weight:500;padding:4px 11px;border-radius:100px;
  background:rgba(255,255,255,.04);border:1px solid var(--glass-border);color:var(--gray-500);
}
.capability-card--open{background:linear-gradient(135deg,rgba(48,191,191,.16),rgba(11,127,140,.08));border-color:rgba(48,191,191,.35);}
.capability-card--open .capability-icon{background:rgba(48,191,191,.28);}
.capability-link{display:inline-block;color:var(--teal-bright);font-weight:600;font-size:13.5px;}
.capability-link:hover{text-decoration:underline;}

/* Contacto */
/* No overflow:hidden here either, same reasoning as .solutions/.partners above. */
.contacto{position:relative;background:linear-gradient(180deg,var(--navy-950),var(--black));}
.contacto > .container{position:relative;z-index:1;}
.contacto-grid{display:grid;grid-template-columns:.85fr 1.15fr;gap:50px;align-items:start;}
.contacto h2{font-size:clamp(30px,4vw,52px);margin-bottom:22px;}
.contacto p.lead{color:var(--gray-300);font-size:16.5px;max-width:480px;margin-bottom:34px;}
.contact-list{display:flex;flex-direction:column;gap:16px;margin-top:34px;}
.contact-row{display:flex;align-items:center;gap:16px;padding:18px 20px;}
.contact-row .ic{flex:0 0 auto;width:38px;height:38px;border-radius:10px;background:rgba(48,191,191,.14);color:var(--teal-bright);display:flex;align-items:center;justify-content:center;}
.contact-row .lbl{display:block;font-size:11.5px;color:var(--gray-500);text-transform:uppercase;letter-spacing:.08em;margin-bottom:3px;}
.contact-row .val{font-size:15px;font-weight:600;}
.contact-row a.val:hover{color:var(--teal-bright);}

/* Contact form card */
.contact-form-card{padding:36px 38px;}
.contact-form-card h3{font-size:22px;margin-bottom:8px;}
.form-sub{color:var(--gray-500);font-size:14px;margin-bottom:26px;}
.contact-form{display:flex;flex-direction:column;gap:18px;}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
.form-field{display:flex;flex-direction:column;gap:7px;}
.form-field label{font-size:12.5px;font-weight:600;color:var(--gray-300);}
.form-field input,.form-field select,.form-field textarea{
  background:rgba(255,255,255,.04);border:1px solid var(--glass-border);border-radius:var(--radius-sm);
  padding:12px 14px;color:var(--white);font-family:'Inter';font-size:14.5px;transition:border-color .25s,background .25s;
}
.form-field input::placeholder,.form-field textarea::placeholder{color:var(--gray-700);}
.form-field input:focus,.form-field select:focus,.form-field textarea:focus{
  outline:none;border-color:var(--teal-bright);background:rgba(255,255,255,.06);
}
.form-field select{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%238b96ab'%3E%3Cpath d='M5.5 7.5l4.5 4.5 4.5-4.5' stroke='%238b96ab' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 14px center;}
.form-field select option{background:var(--navy-900);color:var(--white);}
.form-field textarea{resize:vertical;font-family:'Inter';}
.form-submit{justify-content:center;width:100%;}
.form-status{font-size:13.5px;min-height:18px;text-align:center;}
.form-status.is-success{color:var(--teal-bright);}
.form-status.is-error{color:#ff8a80;}

/* Success celebration: checkmark draws in (same stroke-dashoffset technique as
   the Proceso path and Analítica line), confetti is a handful of GSAP-driven
   DOM particles — see initContactForm's celebrate(). Both stay inert/hidden
   until a real successful submission; the form's own behavior is unchanged. */
.form-success-icon{width:44px;height:44px;margin:0 auto;display:none;}
.form-success-icon.is-visible{display:block;}
.form-success-icon svg{width:100%;height:100%;}
.form-success-circle{fill:none;stroke:var(--teal-bright);stroke-width:2;}
.form-success-check{fill:none;stroke:var(--teal-bright);stroke-width:3;stroke-linecap:round;stroke-linejoin:round;}
.confetti-piece{position:fixed;top:0;left:0;width:7px;height:7px;pointer-events:none;z-index:9997;will-change:transform;}

/* FAQ — no video background on purpose (see the section's own HTML comment):
   a quiet, editorial-paced beat between Partners and Contacto, built entirely
   from existing tokens (.glass, --teal-bright, --ease) so it reads as part of
   the same system, not a bolted-on component. Native <details>/<summary> —
   accessible and keyboard-operable with zero extra JS. */
.faq{background:linear-gradient(180deg,var(--black) 0%,var(--navy-950) 100%);}
.faq-head{max-width:640px;margin:0 auto 48px;text-align:center;}
.faq-head .eyebrow{justify-content:center;}
.faq-head h2{font-size:clamp(30px,4vw,48px);}
.faq-list{max-width:760px;margin:0 auto;display:flex;flex-direction:column;gap:14px;}
.faq-item{padding:6px 26px;transition:border-color .3s var(--ease),background .3s var(--ease);}
.faq-item:hover{border-color:rgba(48,191,191,.35);}
.faq-item summary{
  display:flex;align-items:center;justify-content:space-between;gap:20px;
  padding:20px 0;cursor:pointer;list-style:none;
  font-family:'Outfit',sans-serif;font-weight:600;font-size:16.5px;color:var(--white);
}
.faq-item summary::-webkit-details-marker{display:none;}
.faq-item summary::marker{content:'';}
.faq-chevron{flex:0 0 auto;width:20px;height:20px;color:var(--teal-bright);transition:transform .35s var(--ease);}
.faq-item[open] .faq-chevron{transform:rotate(180deg);}
.faq-answer{color:var(--gray-300);font-size:14.5px;line-height:1.65;padding:0 0 22px;max-width:62ch;}

/* Soluciones que implementamos — reuses .capability-grid/.capability-card verbatim
   (already defined above for Partners) so the cards need zero new styling; only the
   section wrapper and closing note below the grid are new. */
.soluciones-impl{background:var(--navy-950);}
.soluciones-impl > .container{position:relative;z-index:1;}
.soluciones-impl-note{max-width:620px;margin:36px auto 0;text-align:center;color:var(--gray-500);font-size:13.5px;line-height:1.6;}
.faq > .container{position:relative;z-index:1;}
/* Same #frame-seq-tint technique (mix-blend-mode:color over the raw footage) applied to
   a full section-video instead of a canvas — grayscale/monochrome source in, brand-teal
   out, motion and contrast untouched. Not partners/contacto's plain .section-video-overlay
   alone: that only darkens, it can't add color a black-and-white clip never had. */
.section-video-tint{position:absolute;inset:0;pointer-events:none;}
.section-video-tint--teal{background:linear-gradient(135deg,var(--teal-deep),var(--teal-bright) 45%,var(--navy-950));mix-blend-mode:color;opacity:.9;}

/* Diagnóstico Rápido — deliberately no background video (this section sits between
   two already-heavy reels; the whole point of this addition was to improve
   Performance, not spend more of the budget). A radial glow does the "premium"
   work instead, for free. */
.diagnostico{background:radial-gradient(circle at 50% 15%,rgba(48,191,191,.14),transparent 55%),var(--navy-950);}
.diag-head{max-width:640px;margin:0 auto 40px;text-align:center;}
.diag-head h2{font-size:clamp(28px,3.6vw,44px);}
.diag-card{max-width:760px;margin:0 auto;background:var(--glass-bg);border:1px solid var(--glass-border);border-radius:var(--radius-lg);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);padding:clamp(28px,4vw,44px);}
.diag-progress{display:flex;justify-content:center;gap:8px;margin-bottom:30px;}
.diag-progress span{width:7px;height:7px;border-radius:50%;background:var(--glass-border);transition:background .3s var(--ease),transform .3s var(--ease);}
.diag-progress span.is-active{background:var(--teal-bright);transform:scale(1.35);}
.diag-progress span.is-done{background:var(--teal-deep);}
.diag-step{display:none;}
.diag-step.is-active{display:block;animation:diagFade .4s var(--ease);}
.diag-result{display:none;}
.diag-result.is-active{display:block;animation:diagFade .5s var(--ease);}
@keyframes diagFade{from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:translateY(0);}}
.diag-q{font-family:'Outfit';font-weight:600;font-size:clamp(18px,2.2vw,22px);color:var(--white);margin-bottom:24px;text-align:center;}
.diag-options{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
.diag-options--single{grid-template-columns:1fr;max-width:440px;margin:0 auto;}
.diag-opt{
  display:flex;align-items:center;gap:12px;text-align:left;width:100%;
  padding:15px 18px;border-radius:var(--radius-md);
  background:rgba(255,255,255,.03);border:1px solid var(--glass-border);
  color:var(--white);font-size:13.5px;font-weight:600;line-height:1.35;
  transition:border-color .25s var(--ease),background .25s var(--ease),transform .25s var(--ease);
}
.diag-opt:hover{border-color:rgba(48,191,191,.5);background:var(--glass-bg-strong);transform:translateY(-2px);}
.diag-opt svg{width:20px;height:20px;flex:none;color:var(--teal-bright);}
.diag-back{display:inline-flex;align-items:center;gap:6px;margin-top:22px;color:var(--gray-500);font-size:12.5px;font-weight:600;}
.diag-back:hover{color:var(--white);}
.diag-result-icon{width:52px;height:52px;border-radius:14px;display:flex;align-items:center;justify-content:center;background:rgba(48,191,191,.16);border:1px solid rgba(48,191,191,.35);color:var(--teal-bright);margin:0 auto 18px;}
.diag-result-icon svg{width:24px;height:24px;}
.diag-result-title{text-align:center;font-size:clamp(22px,2.8vw,28px);margin-bottom:10px;}
.diag-result-desc{text-align:center;color:var(--gray-300);font-size:14px;max-width:460px;margin:0 auto;}
.diag-result-note{text-align:center;color:var(--teal-bright);font-size:12.5px;font-weight:600;margin-top:14px;margin-bottom:26px;max-width:460px;margin-left:auto;margin-right:auto;}
.diag-result-actions{display:flex;gap:14px;justify-content:center;flex-wrap:wrap;}
.diag-restart{display:block;margin:22px auto 0;text-align:center;color:var(--gray-500);font-size:12px;width:fit-content;}
.diag-restart:hover{color:var(--white);}
@media (max-width:640px){.diag-options{grid-template-columns:1fr;}}

.footer{padding:70px 0 0;background:var(--navy-950);border-top:1px solid var(--glass-border);}
.footer-top{display:grid;grid-template-columns:1.6fr 1fr 1fr 1fr;gap:40px;padding-bottom:48px;}
.footer-brand img{height:64px;width:auto;margin-bottom:18px;}
.footer-brand p{color:var(--gray-500);font-size:13.5px;line-height:1.6;max-width:320px;margin-bottom:20px;}
.footer-social{display:flex;gap:10px;}
.footer-social a,.footer-social span{
  width:36px;height:36px;border-radius:50%;background:var(--glass-bg);border:1px solid var(--glass-border);
  display:flex;align-items:center;justify-content:center;color:var(--gray-300);font-size:13px;transition:background .25s,color .25s;
}
.footer-social a:hover{background:var(--glass-bg-strong);color:var(--white);}
.footer-col h5{font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--teal-bright);margin-bottom:18px;}
.footer-col{display:flex;flex-direction:column;gap:12px;}
.footer-col a{color:var(--gray-300);font-size:14px;transition:color .2s;}
.footer-col a:hover{color:var(--white);}
.footer-bottom{padding:24px 0;border-top:1px solid var(--glass-border);}
.footer-bottom p{color:var(--gray-500);font-size:13px;}

/* =========================================================
   4. RESPONSIVE
   ========================================================= */
/* Large / ultra-wide monitors: the 1220px container reads as mostly dead
   space on 1920px+ screens, so it widens in steps and the nav/reel content
   get a bit more presence instead of floating small in a vast dark frame. */
@media (min-width:1600px){
  :root{--container:1360px;}
  .nav{padding:38px 0;}
  .nav.is-scrolled{padding:22px 0;}
  .nav-logo img{height:66px;}
  .nav-links{gap:46px;}
  .nav-links a{font-size:16.5px;}
  .nav-cta{gap:20px;}
  .nav-cta .btn{padding:14px 28px;font-size:15.5px;}
  .sound-toggle{width:46px;height:46px;}
  .sound-toggle svg{width:20px;height:20px;}
  .reel-block{max-width:980px;}
  .hero-title{max-width:1100px;}
}
@media (min-width:2200px){
  :root{--container:1560px;}
  .reel-block{max-width:1120px;}
  .hero-title{max-width:1260px;}
}

@media (max-width:900px){
  .footer-top{grid-template-columns:1fr 1fr;gap:32px;}
  .footer-brand{grid-column:1 / -1;}
  .nav-links,.nav-cta .btn-ghost{display:none;}
  .nav-toggle{display:flex;}
  .nav-mobile{
    position:fixed;inset:0;top:0;background:rgba(6,10,20,.98);backdrop-filter:blur(20px);
    z-index:850;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:28px;
    opacity:0;pointer-events:none;transition:opacity .35s var(--ease);
  }
  .nav-mobile.active{opacity:1;pointer-events:auto;}
  .nav-mobile a{font-size:22px;font-weight:600;font-family:'Outfit';}
  .nav-mobile-logo img{height:64px;width:auto;}
  /* Avoid showing the small header logo AND this larger centered one at once. */
  .nav.menu-open .nav-logo{opacity:0;pointer-events:none;}

  .reel,.reel--hero{height:auto;min-height:100vh;display:block;}
  .reel-media{grid-area:auto;position:relative;height:70vh;}
  .reel-content{grid-area:auto;position:relative;height:auto;padding:52px 0 64px;}
  .reel-content .container{width:100%;}
  .hero-scroll-cue{display:none;}

  .portfolio-card{flex-direction:column;text-align:center;padding:40px 28px;}
  .portfolio-actions{justify-content:center;}
  .portfolio-icon{display:none;}
  .problema-layout{grid-template-columns:1fr;gap:32px;}
  .problema-visual{aspect-ratio:16/10;order:-1;}
  .why-mission-grid{grid-template-columns:1fr;gap:18px;margin-bottom:24px;}
  .why-values{grid-template-columns:1fr 1fr;gap:16px;}
  .nosotros-hero{padding:118px 0 50px;}
  .nosotros-home{top:20px;left:18px;padding:9px 16px 9px 12px;font-size:12.5px;}
  .nosotros-home span{display:none;}
  .nosotros-close{top:20px;right:18px;}
  .whatsapp-fab{right:16px;bottom:92px;width:52px;height:52px;}
  .whatsapp-fab svg{width:26px;height:26px;}
  .whatsapp-fab-label{display:none;}
  .partners-layout{grid-template-columns:1fr;gap:32px;}
  /* Stacked single-column layout here — nothing for it to stick alongside anymore. */
  .partners-visual{position:static;}
  .capability-grid{grid-template-columns:1fr;}
  .solutions-grid{grid-template-columns:1fr;grid-auto-rows:auto;}
  .solution-card--wide,.solution-card--tall{grid-column:span 1;grid-row:span 1;}
  .service-back{top:86px;left:18px;padding:10px 18px 10px 14px;font-size:13.5px;}
  .service-progress{top:98px;gap:7px;}
  .service-progress .dot{width:6px;height:6px;}
  .service-progress .dot.is-active{width:18px;}
  .service-next{bottom:18px;right:0;padding:10px 18px;font-size:13px;}
  .service-next .label{display:none;}
  .service-prev{bottom:18px;left:0;padding:10px 18px;font-size:13px;}
  .service-prev .label{display:none;}
  .contacto-grid{grid-template-columns:1fr;gap:44px;}
  .form-row{grid-template-columns:1fr;}
  .contact-form-card{padding:28px 24px;}
  .steps-grid{grid-template-columns:1fr;}
  .steps-path{display:none;}
  .feature-row{grid-template-columns:1fr;gap:18px;}
  .counter-row{gap:30px;}
  .reel-content--split .container{grid-template-columns:1fr;}
  .reel-visual{display:none;}
  /* Publicidad's visual IS the feature (the frame-sequence animation), unlike the
     decorative dashboard/video visuals elsewhere that are safe to drop on mobile
     — this one stays, sized to sit under the text instead of beside it. 16/9
     (not 4/5 like the rest) matches the source frames' own ratio here too, so
     nothing crops on mobile either. */
  .publicidad-visual{display:block;width:100%;aspect-ratio:16/9;max-width:none;margin:24px 0 0;}
  #publicidad .container{grid-template-areas:"text" "visual" "features";}
  /* particle-network.mp4 stays on mobile too, same reasoning as Analítica/
     Inteligencia/Cloud below (this used to be a flat gradient with nothing
     to preserve, back when Publicidad had no real .reel-media video —
     it does now, so it gets the same override they do). */
  #publicidad.reel--pinned .reel-media{height:70vh;min-height:0;}
  /* Same reasoning as .publicidad-visual above — this page's frame sequence
     stays visible on mobile too instead of being dropped. */
  .desarrollo-visual{display:block;width:100%;aspect-ratio:16/9;max-width:none;margin:24px 0 0;}
  /* .reel-media only ever held Software a la Medida's background video, which
     frame-seq pages don't have (the visual is .reel-visual now, not
     .reel-media) — the generic mobile rule above still reserves it 70vh of
     real space regardless, which read as a broken empty black gap with
     nothing in it. Collapsing it to 0 for .reel--pinned specifically removes
     that dead space; the frame-seq visual still shows up fine further down,
     in its normal DOM position. Every other .reel--pinned page overrides
     this back to 70vh below because, unlike this one, they do have a real
     video to show. */
  .reel--pinned .reel-media{height:0;min-height:0;}
  /* Removing .reel-media's height above also removed the clearance it
     accidentally provided under the fixed nav + the service-page back button/
     progress dots that float above the content on these pages — add it back
     directly on the content instead of leaving a background block do it. */
  .reel--pinned .reel-content{padding-top:150px;}
  /* Same reasoning as .publicidad-visual/.desarrollo-visual above — Analítica's
     frame sequence stays visible on mobile too instead of being dropped. */
  .analitica-visual{display:block;width:100%;aspect-ratio:16/9;max-width:none;margin:24px 0 0;}
  #analitica .container{grid-template-areas:"text" "visual" "features";}
  /* Unlike Publicidad/Software a la Medida, Analítica's .reel-media isn't
     empty — it holds the chart background video, which stays on mobile too
     (explicitly asked for), so the generic .reel--pinned collapse right above
     doesn't apply here: restore the default mobile .reel-media height instead
     of the 0 every other frame-seq page gets. */
  #analitica.reel--pinned .reel-media{height:70vh;min-height:0;}
  /* Same reasoning as .analitica-visual above — Inteligencia's frame sequence
     stays visible on mobile too, and its .reel-media (blue-technology.mp4 +
     the particle-glow canvas) stays too instead of collapsing to 0. */
  .inteligencia-visual{display:block;width:100%;aspect-ratio:16/9;max-width:none;margin:24px 0 0;}
  #inteligencia .container{grid-template-areas:"text" "visual" "features";}
  #inteligencia.reel--pinned .reel-media{height:70vh;min-height:0;}
  /* Same reasoning as .analitica-visual/.inteligencia-visual above — Cloud's
     frame sequence stays visible on mobile too, and its real video background
     (cloud-computing.mp4 + the particle-glow canvas) stays too instead of
     collapsing to 0 like the generic .reel--pinned rule does for Software a
     la Medida, the only frame-seq page left with no .reel-media content of
     its own. */
  .cloud-visual{display:block;width:100%;aspect-ratio:16/9;max-width:none;margin:24px 0 0;}
  #cloud .container{grid-template-areas:"text" "visual" "features";}
  #cloud.reel--pinned .reel-media{height:70vh;min-height:0;}
  /* Same reasoning as .cloud-visual above — Desarrollo Web's frame sequence
     stays visible on mobile too, and its real video background
     (circuit-board.mp4) stays too instead of collapsing to 0. */
  .desarrollo-web-visual{display:block;width:100%;aspect-ratio:16/9;max-width:none;margin:24px 0 0;}
  #desarrollo-web .container{grid-template-areas:"text" "visual" "features";}
  #desarrollo-web.reel--pinned .reel-media{height:70vh;min-height:0;}
}

/* Laptop-width screens (1366x768, 1440x900-class — shorter viewports than the
   desktop monitors the 130px section padding and generous heading margins were
   tuned against): the static sections (Problema, Soluciones, Partners, Contacto,
   Nosotros, Portfolio CTA) all share .section's padding, so tightening it here
   once cuts the extra scrolling all of them needed on a shorter screen, without
   touching the 100vh-pinned .reel sections (already viewport-relative, not
   affected by this). */
@media (min-width:901px) and (max-width:1400px){
  .section{padding:90px 0;}
  .solutions{padding:90px 0;}
  .solutions-head{margin-bottom:44px;}
  .partners .sub{margin-bottom:32px;}
  .capability-card{padding:20px 20px;}
}

@media (max-width:560px){
  .container{padding:0 20px;}
  .section{padding:90px 0;}
  .footer-top{grid-template-columns:1fr;}
}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto;}
  .reel-video{transform:none !important;}
  .hero-scroll-cue{display:none;}
}


/* =========================================================
   SERVICE LANDING PAGES ONLY (the servicios subpages)
   Additive rules for the standalone SEO pages -- index.html never
   loads this stylesheet, so nothing here can affect the homepage.
   ========================================================= */
.crumb{font-size:12.5px;color:var(--gray-500);padding:110px 0 0;}
.crumb a{color:var(--gray-500);transition:color .2s;}
.crumb a:hover{color:var(--teal-bright);}
.crumb .sep{margin:0 8px;opacity:.5;}
.service-hero{position:relative;padding:36px 0 90px;overflow:hidden;}
.service-hero-bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;}
/* Pages with no clean licensed still to show (see the 'poster:null' comment in
   this file) get this brand gradient instead of an image -- same radial-teal-on-navy
   language as .frame-seq-fallback elsewhere on the site, so it still reads as
   APHELYX, not as a placeholder. */
.service-hero--plain{background:radial-gradient(circle at 75% 30%,rgba(48,191,191,.16),transparent 55%),linear-gradient(160deg,var(--navy-900),var(--black) 70%);}
.service-hero--plain .section-video-overlay{display:none;}
.service-hero > .container{position:relative;z-index:1;}
.service-hero h1{font-size:clamp(34px,5vw,58px);max-width:820px;margin-bottom:20px;letter-spacing:-0.03em;}
.service-hero .lead{color:var(--gray-300);font-size:17px;max-width:620px;margin-bottom:30px;}
.service-hero .hero-actions{display:flex;gap:16px;flex-wrap:wrap;}
.service-tags-label{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--gray-500);margin-bottom:14px;}
.service-related{padding:60px 0 40px;position:relative;overflow:hidden;}
.service-related > .container{position:relative;z-index:1;}
.service-related h3{font-size:22px;margin-bottom:22px;color:var(--white);}
.service-related-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;}
.service-related-grid a{
  display:block;padding:18px 20px;border-radius:var(--radius-md);
  background:var(--glass-bg);border:1px solid var(--glass-border);
  font-weight:600;font-size:14.5px;transition:border-color .25s,background .25s,transform .25s var(--ease);
}
.service-related-grid a:hover{border-color:rgba(48,191,191,.4);background:var(--glass-bg-strong);transform:translateY(-3px);}
.service-pager{display:flex;justify-content:space-between;gap:16px;padding:40px 0 0;border-top:1px solid var(--glass-border);margin-top:50px;flex-wrap:wrap;}
.service-pager a{display:flex;align-items:center;gap:8px;font-size:14px;color:var(--gray-300);font-weight:600;}
.service-pager a:hover{color:var(--teal-bright);}
.service-pager svg{width:16px;height:16px;}
.service-cta{padding:70px 0 110px;}
.service-cta-card{
  background:linear-gradient(135deg,var(--teal-deep),var(--navy-900));
  border-radius:var(--radius-lg);padding:52px 48px;text-align:center;
}
.service-cta-card h2{font-size:clamp(26px,3.4vw,38px);margin-bottom:14px;color:var(--white);}
.service-cta-card p{color:rgba(246,248,252,.82);font-size:15.5px;margin-bottom:28px;max-width:520px;margin-left:auto;margin-right:auto;}
.service-cta-card .hero-actions{justify-content:center;}
