/* ============================================================
   JBK — Shared design canon (site-wide)
   Edit visual tokens ONCE here → propagates to every page.
   Requires: assets/jbk-tailwind.css (Tailwind COMPILADO, cargado DESPUES de este
   archivo) + assets/fonts.css (Lato auto-hospedada). La Play CDN y
   assets/jbk-tailwind.js se eliminaron el 2026-08-14.
   ============================================================ */

/* ── Tokens de marca ──────────────────────────────────────────────────────
   Se guardan los CANALES rgb, no el hexadecimal, y el color se deriva de ellos.
   No es rebuscado: 21 utilidades del sitio llevan modificador de opacidad
   (`bg-jbk-blue/10`, `text-jbk-gray/70`) y Tailwind solo puede inyectar el alfa si
   recibe canales sueltos -- con `var(--jbk-blue)` a secas esos 21 se rompen.

   Asi hay UNA sola fuente por color: el `-rgb`. La segunda linea se deriva, y
   tailwind.config.js usa `rgb(var(--jbk-*-rgb) / <alpha-value>)`.
   Cambiar un color de marca = editar un numero, y propaga a los 1.282 usos de
   utilidades, a jbk.css y a los estilos en linea del HTML. */
:root{
  --jbk-blue-rgb:0 115 207;
  --jbk-blue-light-rgb:28 131 212;
  --jbk-cyan-rgb:0 177 204;
  --jbk-navy-rgb:2 33 53;
  --jbk-dark-rgb:27 36 42;
  --jbk-gray-rgb:94 106 113;
  --jbk-pale-rgb:198 224 244;
  --jbk-off-rgb:237 238 239;
  --jbk-yellow-rgb:255 199 44;

  --jbk-blue:rgb(var(--jbk-blue-rgb));
  --jbk-blue-light:rgb(var(--jbk-blue-light-rgb));
  --jbk-cyan:rgb(var(--jbk-cyan-rgb));
  --jbk-navy:rgb(var(--jbk-navy-rgb));
  --jbk-dark:rgb(var(--jbk-dark-rgb));
  --jbk-gray:rgb(var(--jbk-gray-rgb));
  --jbk-pale:rgb(var(--jbk-pale-rgb));
  --jbk-off:rgb(var(--jbk-off-rgb));
  --jbk-yellow:rgb(var(--jbk-yellow-rgb));
}

*, *::before, *::after { box-sizing: border-box; }
body { font-family: 'Lato', 'Lato Fallback', sans-serif; margin: 0; }

/* ── Lenis smooth scroll (classes added by Lenis when active on desktop) — Lenis owns the
   smoothing now, so native scroll-behavior:smooth is removed (it conflicts with Lenis) ── */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }
.lenis.lenis-smooth iframe { pointer-events: none; }

/* ── Hero noise texture ── */
.hero-noise::before{
  content:''; position:absolute; inset:0;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
  pointer-events:none; z-index:1;
}

/* ── Reusable radial glow + dot field (the book-a-free-consult hero treatment) ──
   Apply to any `position:relative; overflow:hidden` dark section (bg var(--jbk-navy))
   by dropping these two divs as its first children:
     <div class="glow-field-glow"></div>
     <div class="glow-field-dots"></div>
   Center is tunable per-section via CSS vars on the SECTION itself:
     style="--glow-x:70%; --glow-y:50%;"  (shown = defaults)
   Size uses `circle farthest-corner`, so the radius is PROPORTIONAL to the
   section's own box (recalculated from center to its farthest corner) — the
   same look scales correctly whether the section is short or very tall, no
   fixed-px radius to babysit per page.
   The dot sprite (color/size/gap) matches the home hero particle field at rest —
   don't touch those numbers without re-checking against jbk-globe.js.
   The dots' mask uses the SAME circle (center + farthest-corner size) as the
   glow, so dots are only visible where the glow actually reaches — pure
   dark-navy areas outside it stay dot-free on any section height. */
.glow-field-glow, .glow-field-dots{ position:absolute; inset:0; z-index:0; pointer-events:none; }
.glow-field-glow{
  background: radial-gradient(circle farthest-corner at var(--glow-x,70%) var(--glow-y,50%),
    #0d5070 0%, #063a56 16%, #031e32 50%, #000d14 100%);
}
.glow-field-dots{
  background-image: radial-gradient(circle, rgba(225,242,255,0.19) 0px, rgba(180,222,250,0.11) 0.6px, rgba(150,205,240,0.03) 1.05px, transparent 1.35px);
  background-size: 18px 18px;
  -webkit-mask-image: radial-gradient(circle farthest-corner at var(--glow-x,70%) var(--glow-y,50%), #000 0%, rgba(0,0,0,0.75) 35%, rgba(0,0,0,0.25) 65%, transparent 100%);
          mask-image: radial-gradient(circle farthest-corner at var(--glow-x,70%) var(--glow-y,50%), #000 0%, rgba(0,0,0,0.75) 35%, rgba(0,0,0,0.25) 65%, transparent 100%);
}

/* ── Nav shadow (always on — same at rest and scrolled, no hard bottom line) ── */
#nav{ transition: box-shadow 0.3s ease; box-shadow: 0 2px 20px rgba(2,33,53,0.12); }
#nav.scrolled{ box-shadow: 0 2px 20px rgba(2,33,53,0.12); }

/* ── Tarjetas de servicio (interactivas: lift en hover) ──
   La home tenia su propia version de estas dos reglas en un <style> en linea: curva mas
   larga con un retardo de entrada y una sombra de tres capas en vez de dos. Era un
   refinamiento que nunca habia subido al canon, asi que las 10 paginas de servicio
   veian una version distinta a la de la portada. Sube (Nico, 2026-08-17).
   El retardo de 0.09s solo aplica al ENTRAR: al salir se cancela con
   `transition-delay:0` para que la tarjeta no quede colgada bajo el cursor. */
.service-card{ position:relative;
  transition: transform 0.35s cubic-bezier(0.22,1,0.36,1) 0.09s,
              box-shadow 0.35s cubic-bezier(0.22,1,0.36,1) 0.09s; }
.service-card:hover{ transform: translateY(-6px); transition-delay: 0s;
  box-shadow: 0 4px 8px rgba(2,33,53,0.09), 0 16px 40px rgba(0,115,207,0.16),
              0 36px 72px rgba(2,33,53,0.11); }

/* ── Flat card (static surface — same look as the home capability cards at rest,
   but NO hover/lift; for non-interactive content blocks) ── */
.card-flat{ background:#fff; border:1px solid var(--jbk-off);
  box-shadow: 0 1px 2px rgba(2,33,53,0.07), 0 4px 16px rgba(2,33,53,0.09), 0 16px 36px rgba(2,33,53,0.06); }

/* ── CTA section background: animated diagonal stripes (fondo-rayas) ──
   Pure CSS, 0 JS, 1 pseudo-element, transform-only animation → runs on the GPU
   compositor (no layout, no repaint). Replaces the old DOM `.cta-diag` bar field. */
.cta-section{
  position:relative; overflow:hidden; isolation:isolate;
  background:#004F8F;
}
.cta-section::before{
  /* Centered square sized in vmax (not % of the section) so the rotated stripe field
     ALWAYS covers the CTA regardless of aspect ratio — no corner cut on wide/short sections.
     Clipped by the section's overflow:hidden. */
  content:""; position:absolute; top:50%; left:50%; width:120vmax; height:120vmax;
  z-index:0; pointer-events:none; will-change:transform;
  transform:translate(-50%,-50%) rotate(45deg) translate3d(0,0,0);
  animation:stripes-slide 70s linear infinite;
  background-image:repeating-linear-gradient(90deg,
    rgba(2,33,53,.50) 0px, rgba(2,33,53,.31) 56.14px, rgba(2,33,53,.11) 130.24px, rgba(2,33,53,0) 224.55px, rgba(2,33,53,0) 236.36px,
    rgba(2,33,53,.50) 236.36px, rgba(2,33,53,.31) 253.20px, rgba(2,33,53,.11) 275.43px, rgba(2,33,53,0) 303.72px, rgba(2,33,53,0) 307.27px,
    rgba(2,33,53,.50) 307.27px, rgba(2,33,53,.31) 324.11px, rgba(2,33,53,.11) 346.34px, rgba(2,33,53,0) 374.63px, rgba(2,33,53,0) 378.18px,
    rgba(2,33,53,.50) 378.18px, rgba(2,33,53,.31) 417.47px, rgba(2,33,53,.11) 469.34px, rgba(2,33,53,0) 535.36px, rgba(2,33,53,0) 543.64px,
    rgba(2,33,53,.50) 543.64px, rgba(2,33,53,.31) 568.90px, rgba(2,33,53,.11) 602.25px, rgba(2,33,53,0) 644.69px, rgba(2,33,53,0) 650.00px);
}
.cta-section > *{ position:relative; z-index:1; }   /* content sits above the ::before stripes */
@keyframes stripes-slide{ to{ transform:translate(-50%,-50%) rotate(45deg) translate3d(650px,0,0); } }
@media (prefers-reduced-motion: reduce){ .cta-section::before{ animation:none; } }

/* ── Nav link hover ── */
.nav-link{
  position:relative; color:var(--jbk-dark); font-weight:600;
  font-size:0.9rem; text-decoration:none; letter-spacing:0.01em;
  transition:color 0.18s ease;
}
.nav-link::after{
  content:''; position:absolute; bottom:-4px; left:0;
  width:0; height:2px; background:var(--jbk-blue); transition:width 0.22s ease;
}
.nav-link:hover{ color:var(--jbk-blue); }
.nav-link:hover::after{ width:100%; }
.nav-link:focus-visible{ outline:2px solid var(--jbk-blue); outline-offset:4px; border-radius:2px; }
/* Active page in nav */
.nav-link[aria-current="page"]{ color:var(--jbk-blue); }
.nav-link[aria-current="page"]::after{ width:100%; }

/* ── Section headings (canon: 48px / 900 / -0.02em — same as .t-h2) ── */
/* F-010 — homepage hero height.
   The hero was `min-h-screen`, so it grew to match the viewport while its content
   is a fixed 680px (the globe is capped at 700px square). Measured empty space:
   220px at 1440x900, 400px at 1920x1080, and 760px at 1920x1440 - more empty than
   content, which is what read as "too much space above and below the globe".
   clamp() keeps it full-height-ish on a laptop and stops the runaway on tall
   monitors. 100svh rather than 100vh so mobile browser chrome doesn't cause the
   usual jump; this is a MIN height, so the stacked mobile layout still grows past
   it freely. Adjust the 860px ceiling to taste - that is the only number that
   controls how much air the hero keeps. */
.hero-fullscreen{ min-height: min(100vh, 860px); }   /* fallback first */
.hero-fullscreen{ min-height: min(100svh, 860px); }  /* svh where supported */

/* The globe's desktop size is set as an inline style by the layout() script in
   index.html (see globePx() there), which always beats a rule here - so do NOT try
   to size #jbk-globe from this file. */

/* text-wrap:balance evens out the last line so short headings stop dropping a
   single word onto its own line ("...inside / out"). Fixes F-022 everywhere at
   once rather than hand-tuning each heading. Browsers that don't support it
   just wrap normally, so it degrades safely. It only applies to short blocks
   (browsers cap it at ~6 lines), which is exactly what a heading is. */
/* `.section-heading` (12 paginas) y `.t-h2` (5) eran dos nombres para el MISMO estilo,
   declarados por separado en dos lugares de este archivo. Van juntos para que no puedan
   volver a separarse. No se renombra el markup: cambiar 17 paginas seria mas movimiento
   que beneficio y los dos nombres se leen bien. */
.section-heading, .t-h2{ font-size:48px; font-weight:900; letter-spacing:-0.02em;
  color:var(--jbk-navy); line-height:1.05; text-wrap:balance; }
.t-h2, h1{ text-wrap:balance; }

/* ── Form inputs ── */
.form-input{
  width:100%; padding:0.75rem 1rem; border-radius:6px;
  border:2px solid rgba(255,255,255,0.3);
  background:rgba(255,255,255,0.95); color:var(--jbk-dark);
  font-family:'Lato', 'Lato Fallback', sans-serif; font-size:0.95rem;
  transition:border-color 0.18s ease, box-shadow 0.18s ease; outline:none;
}
.form-input:focus{ border-color:var(--jbk-blue-light); box-shadow:0 0 0 3px rgba(28,131,212,0.2); }
.form-input::placeholder{ color:var(--jbk-gray); }

/* ── Native contact form (shared canon) ──
   Used by the home #contact form and book-a-free-consult.html (submits to the
   HubSpot Forms API). Bold navy labels, blue required-asterisk, filled inputs,
   blue submit. Edit here once → both forms stay in sync. */
.jbk-field label {
  display:block; font-size:.8125rem; font-weight:700; color:var(--jbk-navy);
  margin-bottom:.4rem; letter-spacing:.01em;
}
.jbk-field label .req { color:var(--jbk-blue); margin-left:2px; }
.jbk-input {
  width:100%; background:#f4f7fa; border:1px solid #d7e2ec; border-radius:8px;
  padding:.7rem .85rem; font-size:.95rem; color:var(--jbk-dark); font-family:inherit;
  transition: border-color .2s ease, box-shadow .2s ease, background .2s ease;
}
.jbk-input::placeholder { color:#9aa8b2; }
.jbk-input:hover { border-color:#b9cbda; }
/* Split on purpose: a browser that does not know :focus-visible treats the WHOLE
   comma-separated selector as invalid and drops the rule, taking the plain :focus
   styling down with it. Two rules means the fallback survives. */
.jbk-input:focus {
  outline:none; border-color:var(--jbk-blue); background:#fff;
  box-shadow:0 0 0 3px rgba(0,115,207,0.15);
}
.jbk-input:focus-visible {
  outline:none; border-color:var(--jbk-blue); background:#fff;
  box-shadow:0 0 0 3px rgba(0,115,207,0.15);
}
textarea.jbk-input { min-height:2.7rem; resize:vertical; }
.jbk-field .jbk-error { display:none; color:#c0392b; font-size:.75rem; margin-top:.3rem; }
.jbk-field.invalid .jbk-input { border-color:#c0392b; }
.jbk-field.invalid .jbk-error { display:block; }
#jbk-form-status { font-size:.9rem; margin-top:.75rem; }

/* ── Tech tile ── */
.tech-tile{ position:relative; transition:transform 0.2s ease, box-shadow 0.2s ease; cursor:default; }
.tech-tile:hover{ transform:translateY(-3px); box-shadow:0 8px 20px rgba(0,115,207,0.12); z-index:50; }

/* ── Tech-stack glass tile (reusable module — matches the home #tech tiles) ── */
.tech-glass{
  background:rgba(255,255,255,0.045); border:1px solid rgba(255,255,255,0.10);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  transition: transform .25s cubic-bezier(0.22,1,0.36,1), border-color .25s ease, box-shadow .25s ease;
}
.tech-glass:hover{ transform: translateY(-3px); border-color: rgba(0,177,204,.55); box-shadow: 0 0 0 1px rgba(0,177,204,.35), 0 12px 34px rgba(0,173,208,.18); z-index:50; }

/* ── Tech tile hover tooltip (globito) ── */
.tech-tile .tt{
  position:absolute; top:calc(100% + 12px); left:50%;
  transform:translateX(-50%) translateY(-4px);
  width:230px; max-width:78vw;
  background:#0e2f47; color:#e7f1fb; border:1px solid rgba(255,255,255,0.16);
  border-radius:10px; padding:10px 12px;
  font-size:0.78rem; line-height:1.45; font-weight:400; text-align:center;
  box-shadow:0 16px 36px rgba(0,0,0,0.5);
  opacity:0; visibility:hidden; pointer-events:none;
  transition:opacity .2s ease, transform .2s ease, visibility .2s ease;
  z-index:40;
}
/* The arrow used to be the classic transparent-border triangle filled with the
   tooltip's background. That trick cannot carry a stroke, so the arrow had no
   outline while the bubble did, and the bubble's own 1px top border kept running
   straight across underneath it - which is the break you could see at the arrow's
   base. A square rotated 45deg fixes both at once: it takes the same background
   AND the same border on its two outer edges, so the stroke runs continuously up
   one side, around the point and down the other. Its lower half sits inside the
   bubble and its background covers the border segment behind it. */
.tech-tile .tt::before{
  content:''; position:absolute; top:-5px; left:50%;
  width:10px; height:10px;
  transform:translateX(-50%) rotate(45deg);
  background:#0e2f47;
  border-left:1px solid rgba(255,255,255,0.16);
  border-top:1px solid rgba(255,255,255,0.16);
  border-radius:2px 0 0 0;
}
.tech-tile:hover .tt{ opacity:1; visibility:visible; transform:translateX(-50%) translateY(0); }

/* ── Mobile menu ── */
#mobile-menu{ display:none; }
#mobile-menu.open{ display:block; }

/* ── Scrollbar ── */
::-webkit-scrollbar{ width:6px; }
::-webkit-scrollbar-track{ background:var(--jbk-off); }
::-webkit-scrollbar-thumb{ background:var(--jbk-blue); border-radius:3px; }

/* ══════════════════════════════════════════════════════════════════════════
   Subido desde el <style> en linea de index.html (2026-08-17)
   La home tenia 305 lineas de CSS en linea en 7 bloques. 47 reglas eran copias
   exactas de este archivo y se borraron; esto es lo que NO era duplicado.
   ══════════════════════════════════════════════════════════════════════════ */

/* Anillo que se expande y se desvanece una vez cuando un paso se activa. Estaba
   SOLO en el <style> de la home, pero how-we-work.html usa el mismo componente, el
   mismo markup y el mismo JS que aplica `.on`, y no tiene <style> propio: ahi el
   anillo nunca aparecio. No era una decision, era una regla atrapada en una pagina. */
.steps-point.on::after{
  content:''; position:absolute; inset:0; border-radius:50%; pointer-events:none;
  border:2px solid var(--jbk-blue); animation:steps-point-ping .7s ease-out;
}
@keyframes steps-point-ping{
  from{ transform:scale(1); opacity:.65; }
  to{ transform:scale(1.7); opacity:0; }
}
@media (prefers-reduced-motion: reduce){ .steps-point.on::after{ animation:none; } }

/* Enlace estirado: cubre toda la tarjeta para que el area clicable sea la tarjeta
   entera y no solo el texto del enlace. */
.service-card > a::after{ content:''; position:absolute; inset:0; border-radius:12px; }

.about-text{ border-left:4px solid var(--jbk-blue); padding-left:1.5rem; }

/* Seccion oscura de servicios de la home: navy mas profundo que .navy-field y con su
   propio grano. */
#services-dark{ background:#02192a; }
#services-dark::before{
  content:''; position:absolute; inset:0; pointer-events:none; opacity:.045;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.72' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size:200px 200px;
}

/* Bloque de la practica de IA de la home (orbe + tarjeta destacada). */
.ai-pill{ background:rgba(0,173,208,0.10); border:1px solid rgba(0,173,208,0.35);
  box-shadow:0 0 14px rgba(0,173,208,0.20); color:#fff; }
/* Tarjeta destacada: sombra fija, SIN lift en hover (es a proposito). */
.featured-ai{ box-shadow:0 16px 48px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.08); }
/* Enlaces de tarjeta: cian en reposo, casi blancos en hover con el mismo halo cian
   que los puntos en orbita. */
.orb-link{ color:var(--jbk-cyan); transition:color 0.2s ease, text-shadow 0.2s ease; }
.orb-link:hover{ color:#dffaff;
  text-shadow:0 0 10px rgba(46,230,255,0.85), 0 0 22px rgba(46,230,255,0.45); }
/* 420px de tamano visual, pero el margen de -30px arriba y abajo cancela los +60px de
   crecimiento para que el grafico NO estire la tarjeta: su altura la sigue mandando la
   columna de texto. */
.ai-sys{ position:relative; width:420px; height:420px; margin:-30px 0; max-width:100%; flex-shrink:0; }
#ai-orb-canvas{ position:absolute; inset:0; width:420px; height:420px; }
.ai-sys-logo{ position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); z-index:10; }

/* Fichas de tecnologia dentro de #tech: borde y halo cian propios de esa seccion. */
#tech .tech-tile{ transition:transform .25s cubic-bezier(0.22,1,0.36,1),
  border-color .25s ease, box-shadow .25s ease; }
#tech .tech-tile:hover{ transform:translateY(-3px); border-color:rgba(0,177,204,.55) !important;
  box-shadow:0 0 0 1px rgba(0,177,204,.35), 0 12px 34px rgba(0,173,208,.18); }

/* ── Escala de espaciado vertical ─────────────────────────────────────────
   De 155 secciones, NINGUNA tenia espaciado vertical responsivo: un telefono de 390px
   gastaba los mismos 96px de aire entre secciones que un monitor de 27 pulgadas.
   Y convivian 12 formas de escribirlo, entre utilidades y estilos en linea.

   96 / 64 (Nico, 2026-08-17). Tres pasos, no doce formas:

     .section-y      96px escritorio / 64px movil. El ritmo normal. En escritorio las 54
                     secciones que ya estaban en py-24 no se mueven; el cambio de verdad
                     esta en movil, donde cada seccion recupera 64px de alto.
     .section-y--sm  64 / 48. Sub-secciones y bloques densos (los py-14 y py-12).
     .hero-y        112px arriba / 64px abajo, y 88 / 48 en movil. El relleno superior
                     es mayor A PROPOSITO: la barra de navegacion mide 64px y es fija,
                     asi que 112px dejan 48px de aire real y 88px en movil dejan 24px.
                     Antes habia SEIS tratamientos distintos de hero, incluido un
                     relleno en linea de 150/80px en las 10 paginas generadas.

   Movil primero y luego el escalon: asi la pantalla chica es el caso por defecto y no
   el arreglado a ultimo momento, que es como se llego a no tener ninguno. */
.section-y{ padding-top:4rem; padding-bottom:4rem; }
.section-y--sm{ padding-top:3rem; padding-bottom:3rem; }
.hero-y{ padding-top:5.5rem; padding-bottom:3rem; }
@media (min-width:768px){
  .section-y{ padding-top:6rem; padding-bottom:6rem; }
  .section-y--sm{ padding-top:4rem; padding-bottom:4rem; }
  .hero-y{ padding-top:7rem; padding-bottom:4rem; }
}

/* ── Dos secciones claras seguidas ────────────────────────────────────────
   Cuando dos secciones tienen fondos distintos, el limite entre ellas esta DIBUJADO y
   el relleno de cada una es solo aire a su lado. Cuando comparten fondo no hay ningun
   limite, y esos dos rellenos se suman en un unico hueco del doble de alto: 96+96=192px
   en escritorio. Medido despues de pasar las 22 secciones grises a blancas (2026-08-18),
   quedaron 39 pares asi, 28 de ellos de 192px.

   El de arriba conserva su relleno inferior y el de abajo pierde el superior, asi que el
   hueco vuelve a ser UN paso de la escala -- el mismo ritmo que en el resto del sitio --
   en vez de dos pegados.

   Va por `bg-white + bg-white` y no por `.section-y + .section-y` a proposito: la regla
   tiene que depender de que compartan fondo, no de que sean secciones seguidas. Dos
   secciones seguidas con fondos distintos SI necesitan sus dos rellenos.

   Las variantes con `script` y `template` en el medio no sobran: `+` exige hermanos
   INMEDIATOS, y aunque esos dos elementos no pintan nada, cuentan como hermanos y
   rompen la adyacencia. Sin ellas quedaban 3 pares en 192px con todo correcto a la
   vista (our-company, cloud-devops y data-services-integrations), que es justo el tipo
   de fallo que no se nota leyendo el CSS. */
section.bg-white + section.bg-white,
section.bg-white + script + section.bg-white,
section.bg-white + template + section.bg-white,
section.bg-white + script + script + section.bg-white,
section.bg-white + template + script + section.bg-white{ padding-top:0; }

/* ── Glow del globo ───────────────────────────────────────────────────────
   Es la UNICA fuente de luz del hero (el fondo es un degradado lineal plano, sin foco).
   Todo lo ajustable esta aca arriba, en variables con nombre. Para cambiarlo se toca un
   numero y nada mas; no hay valores repetidos en el HTML.

     --glow-r      Tamano. Es el radio, asi que el circulo visible mide el doble.
                   Mas chico = 300px o 260px.
     --glow-op-in  Opacidad en el centro. Mas transparente = 0.16 o 0.12.
     --glow-op-mid Opacidad a media distancia. Conviene tenerla por debajo de --glow-op-in.
     --glow-mid    Donde cae esa parada intermedia. Bajarla (35%) concentra la luz en el
                   centro; subirla (65%) la reparte y aplana el degradado.
     --glow-fade   Donde empieza a apagarse del todo. Bajarla suaviza el borde, porque el
                   ultimo tramo hasta transparente se hace mas largo.

   Valores puestos por Nico el 2026-08-17. Los anteriores, por si hay que volver:
     --glow-r:380px; --glow-op-in:.22; --glow-op-mid:.15; --glow-mid:50%; --glow-fade:82%;

   Los colores salen de los tokens de marca, asi que si cambia el azul o el cian de JBK
   el glow los sigue solo.

   `mix-blend-mode: screen` es lo que hace que la luz se SUME al fondo en vez de taparlo.
   Sin eso el glow se ve como un disco pegado encima. Si se quita, hay que rebajar mucho
   las opacidades. */
#jbk-hero-glow{
  --glow-r:300px;
  --glow-op-in:.18;
  --glow-op-mid:.12;
  --glow-mid:40%;
  --glow-fade:70%;

  z-index:0;
  pointer-events:none;
  mix-blend-mode:screen;
  background:radial-gradient(circle var(--glow-r) at var(--ggX,72%) var(--ggY,50%),
    rgb(var(--jbk-blue-rgb) / var(--glow-op-in)) 0%,
    rgb(var(--jbk-cyan-rgb) / var(--glow-op-mid)) var(--glow-mid),
    rgb(var(--jbk-cyan-rgb) / calc(var(--glow-op-mid) * .35)) var(--glow-fade),
    transparent 100%);
}

/* ── Entrada del hero ─────────────────────────────────────────────────────
   El portal del splash abre en el centro de la pantalla. El globo arranca ahi, con su
   glow pegado, y viaja a su sitio; el texto entra desde la izquierda; los botones crecen
   desde cero en su lugar. Las clases las pone el guion del final de index.html, nunca
   por defecto: sin JS el hero se ve normal y completo.

   SE MUEVEN DOS COSAS Y NADA MAS: el globo y su glow. El degradado navy del fondo y el
   campo de puntos quedan quietos (Nico, 2026-08-17).

   Los dos se mueven con el MISMO mecanismo -- un `transform` alimentado por la misma
   variable y con la misma transicion -- justamente para que no puedan desincronizarse.
   En la primera version el glow movia el centro de su degradado y el globo usaba
   transform: dos mecanismos distintos, y el glow quedaba clavado en el centro mientras
   el globo ya habia llegado.

   Y es la opcion rapida: animar `radial-gradient(... at calc(...))` obliga a repintar un
   degradado a pantalla completa en cada cuadro; un `transform` se compone en GPU sin
   repintar nada. Mover la caja del glow no deja hueco porque su degradado es un circulo
   de 380px que termina en `transparent`.

   `--intro-dx` va registrada con @property: una custom property sin registrar no se
   interpola, salta de un valor al otro.

   Curva cubic-bezier(0.22,1,0.36,1): arranca a fondo y frena suave, sin el arranque
   lento de un ease-in-out. Es la misma curva de .service-card.

   Coreografia por retardos: globo y glow 0.6s (1.35s de viaje), titulo 1.25s,
   bajada 1.4s, botones 1.7s y 1.82s. */
@property --intro-dx{ syntax:'<length>'; inherits:true; initial-value:0px; }

.hero-intro #hero-globe-col,
.hero-intro #jbk-hero-glow{
  transform:translate3d(var(--intro-dx,0px),0,0);
  will-change:transform;
}
.hero-intro #hero-copy > h1 .hero-title__line,
.hero-intro #hero-copy > p{ opacity:0; transform:translateX(-44px); }
/* Los botones NO deslizan: crecen en su sitio desde tamano cero (Nico). */
.hero-intro #hero-copy .btn{ opacity:0; transform:scale(0); }

/* Arranque suave: espera un poco mas antes de salir y usa una curva con MUCHO ease-in
   (Nico, 2026-08-17). cubic-bezier(0.76,0,0.24,1) sale casi parado, acelera en el medio y
   frena igual de suave al llegar. La curva anterior (0.22,1,0.36,1) arrancaba a fondo, que
   es lo contrario de lo pedido. Tambien dura un poco mas: con un arranque tan lento, 1s
   dejaba el tramo rapido demasiado apretado. */
.hero-intro--run #hero-globe-col,
.hero-intro--run #jbk-hero-glow{
  transform:translate3d(0,0,0);
  transition:transform 1.35s cubic-bezier(0.76,0,0.24,1) 0.6s;
}
/* El titulo entra LINEA POR LINEA, no de una (Nico, 2026-08-17). 0.14s entre lineas:
   suficiente para que se lea como cascada y no tanto como para que la ultima llegue
   tarde. La bajada espera a que termine la tercera, y los botones a la bajada, para que
   la lectura sea de arriba hacia abajo sin cruces. */
.hero-intro--run #hero-copy > h1 .hero-title__line,
.hero-intro--run #hero-copy > p{
  opacity:1; transform:translateX(0);
  transition:opacity .75s cubic-bezier(0.22,1,0.36,1) 1.95s,
             transform .75s cubic-bezier(0.22,1,0.36,1) 1.95s;
}
.hero-intro--run #hero-copy > h1 .hero-title__line:nth-child(2){ transition-delay:2.09s; }
.hero-intro--run #hero-copy > h1 .hero-title__line:nth-child(3){ transition-delay:2.23s; }
.hero-intro--run #hero-copy > p{ transition-delay:2.37s; }
.hero-intro--run #hero-copy .btn{
  opacity:1; transform:scale(1);
  transition:opacity .5s cubic-bezier(0.22,1,0.36,1) 2.62s,
             transform .5s cubic-bezier(0.22,1,0.36,1) 2.62s;
}
.hero-intro--run #hero-copy .btn:nth-child(2){ transition-delay:2.74s; }

/* Quien pidio menos movimiento ve el hero entero de entrada. El guion ni siquiera arma
   las clases; esto es el cinturon de seguridad por si alguna quedara puesta. */
@media (prefers-reduced-motion: reduce){
  .hero-intro #hero-globe-col,
  .hero-intro #jbk-hero-glow,
  .hero-intro #hero-copy > h1 .hero-title__line,
  .hero-intro #hero-copy > p,
  .hero-intro #hero-copy .btn{ opacity:1; transform:none; transition:none; }
}

/* ── Titulo de hero ───────────────────────────────────────────────────────
   Habia CINCO cadenas distintas para 30 h1, y las dos principales empataban 13 contra
   12: la mayoria no decidia nada. Manda la grande (Nico, 2026-08-17).

   Dos roles, no cinco variantes:
     .hero-title       26 paginas de marketing. 48 / 60 / 72px, interlineado 1.0.
     .hero-title--sm    4 utilitarias (404, formulario enviado, privacidad, terminos).
                       36 / 48 / 48px, interlineado 1.1. Un titulo de 72px sobre un
                       texto legal denso se lee mal, asi que conservan su medida --
                       los valores salen de medir esas paginas ANTES de migrar, para
                       que la variante no mueva nada.

   El margen inferior NO va aca: es separacion respecto de lo que sigue y cambia por
   pagina (`mb-6`, `mb-5`, o ninguno en las legales). Sigue siendo una utilidad.

   El interletrado venia como estilo en linea en los 30 h1. Sube aca. */
.hero-title{ font-size:3rem; line-height:1; font-weight:900; color:#fff;
  letter-spacing:-0.03em; }
/* Solo la home divide su titulo en lineas, para poder escalonarlas al entrar. Como
   bloque, cada una fuerza el mismo corte que antes forzaba un <br/>. */
.hero-title__line{ display:block; }
@media (min-width:768px){ .hero-title{ font-size:3.75rem; } }
@media (min-width:1024px){ .hero-title{ font-size:4.5rem; } }

.hero-title--sm{ font-size:2.25rem; line-height:1.1; }
@media (min-width:768px){ .hero-title--sm{ font-size:3rem; } }
@media (min-width:1024px){ .hero-title--sm{ font-size:3rem; } }

/* ── Banda CTA ────────────────────────────────────────────────────────────
   Unifica las 25 bandas de cierre. Los numeros NO son inventados: son los valores
   calculados que ya emitia la version mayoritaria, leidos del navegador antes de
   migrar, para que unificar no mueva a las 14 que ya estaban bien.

   Antes habia 5 formas de escribir el titulo y 5 el parrafo. La mayor parte era
   ruido -- `font-black` y `font-900` son el mismo peso 900, y 4 paginas ponian las
   mismas utilidades en otro orden -- pero habia tres diferencias de verdad: 11
   bandas con 80px de relleno en vez de 96px (el hub de empleo y las 10 paginas
   generadas), un `leading-[1.1]` que daba 39.6px contra 40px en movil, y una pagina
   con el titulo un escalon entero mas chico.

   El relleno vive aca a proposito: el punto 10 de la lista define la escala de
   espaciado responsiva del sitio, y cuando eso pase se cambia aca y no en 25 paginas. */
/* Sigue la escala vertical del sitio (.section-y): 96px escritorio / 64px movil.
   Se quedo fuera de la pasada de espaciado del 2026-08-17 porque esa migro utilidades
   `py-*` en los <section>, y estas 25 bandas ya usaban el componente -- asi que eran
   las unicas secciones del sitio que seguian gastando 96px en un telefono. */
.cta-band{ padding-top:4rem; padding-bottom:4rem; }
@media (min-width:768px){ .cta-band{ padding-top:6rem; padding-bottom:6rem; } }

/* El interletrado y el equilibrado de lineas venian como estilo EN LINEA en los 25
   titulos: -0.03em en los 25, y `text-wrap: balance` en los 10 generados. Los dos
   suben al componente. Se conserva `balance` en vez de quitarlo porque es la mejor de
   las dos variantes -- reparte los saltos en lugar de dejar la ultima linea con una
   palabra sola -- y unificar hacia abajo seria empeorar 10 paginas para igualar a 15.
   En navegadores que no lo soportan no hace nada. */
/* Titulo en AMARILLO de marca sobre la banda de rayas (Nico, 2026-08-17), en las 25.
   Contraste medido, no supuesto: 5.35:1 sobre el azul liso de la banda (#004F8F) y
   7.72:1 sobre la franja mas oscura que dibujan las rayas. El umbral AA para texto
   normal es 4.5:1 y para un titulo de este tamano basta 3:1, asi que pasa en las dos
   superficies. El parrafo (.cta-band__text) sigue blanco: amarillo en un bloque de
   texto corrido cansa la vista, y ademas el contraste entre titulo y bajada es lo que
   arma la jerarquia de la banda. */
.cta-band__title{ font-size:2.25rem; line-height:2.5rem; font-weight:900;
  color:var(--jbk-yellow); margin-bottom:1.5rem; letter-spacing:-0.03em; text-wrap:balance; }
@media (min-width:768px){ .cta-band__title{ font-size:3rem; line-height:1; } }

/* El CTA de la home es mas grande a proposito: es el cierre principal del sitio y
   ademas lleva antetitulo. Es jerarquia, no deriva, asi que se conserva con un
   modificador en lugar de aplanarlo. Para igualarlo a las otras 24 alcanza con
   quitar `cta-band__title--xl` del <h2> de index.html. */
.cta-band__title--xl{ font-size:3rem; line-height:1; }
@media (min-width:768px){ .cta-band__title--xl{ font-size:3.75rem; } }
@media (min-width:1024px){ .cta-band__title--xl{ font-size:4.5rem; } }

.cta-band__text{ font-size:1.125rem; line-height:1.7; color:rgba(255,255,255,.8);
  margin-bottom:2.5rem; max-width:42rem; margin-left:auto; margin-right:auto; }

/* ── Breadcrumb ───────────────────────────────────────────────────────────
   Unifica los 22 breadcrumbs del sitio. Los 22 viven sobre el mismo navy
   (var(--jbk-navy), verificado en navegador pagina por pagina), asi que no hace falta un
   modificador por superficie: si algun dia hay uno sobre fondo claro, se agrega
   `.breadcrumb--on-light` y se cambian los dos colores, nada mas.

   `aria-current="page"` va en el ultimo <li> y es lo que le dice a un lector de
   pantalla en que pagina estas. Ninguna de las 22 lo tenia.

   El separador es SIEMPRE el chevron SVG y SIEMPRE aria-hidden. Dos paginas
   (document-forms, qa-testing) usaban una barra `/` de texto sin aria-hidden, que
   el lector de pantalla pronunciaba entre cada nivel.

   `flex-wrap` en todas: sin el, la ultima etiqueta se parte a mitad de palabra en
   pantalla angosta en vez de cortar limpio entre niveles. */
.breadcrumb{ margin-bottom:1.5rem; }
.breadcrumb ol{ display:flex; flex-wrap:wrap; align-items:center; gap:.5rem;
  font-size:.875rem; line-height:1.4; color:rgba(255,255,255,.5); }
.breadcrumb a{ color:inherit; transition:color .18s ease; }
.breadcrumb a:hover{ color:#fff; }
.breadcrumb a:focus-visible{ outline:2px solid var(--jbk-cyan); outline-offset:2px; border-radius:2px; }
.breadcrumb [aria-current="page"]{ color:#fff; font-weight:700; }
.breadcrumb__sep{ display:flex; align-items:center; }

/* ── FAQ accordion ── */
/* Se abre con grid en vez de max-height. El tope de 500px recortaba texto de verdad:
   medido a 320px de ancho, 8 de 42 respuestas pasaban el tope y la mas alta llegaba a
   595px. `0fr -> 1fr` no tiene numero magico, no puede recortar por larga que sea la
   respuesta, y anima en Chrome, Edge, Firefox y Safari 16+.
   El `min-height:0` del hijo es obligatorio: sin eso el contenido no deja que la fila
   baje de su altura natural y el acordeon no cierra.
   OJO: el relleno inferior va en el HIJO, nunca en `.faq-answer`. Cuando estaba en el
   contenedor dejaba 20-24px de espacio muerto bajo cada respuesta cerrada en 4 paginas,
   porque una fila de 0fr sigue pintando el padding de su propio contenedor. */
/* La altura la calcula toggleFaq() desde scrollHeight y la escribe inline. No hay
   numero magico, asi que no puede recortar por larga que sea la respuesta -- el tope
   fijo de 500px que habia antes cortaba texto de verdad: medido a 320px de ancho, 8
   de 42 respuestas lo superaban y la mas alta llegaba a 595px.
   Se midio contra el patron de grid (0fr -> 1fr) en los 42 acordeones a 320px: los
   dos abren completo, pero el grid deja 16-24px de espacio muerto bajo cada respuesta
   cerrada -- el relleno del hijo, que la fila de 0fr sigue pintando. Justo el defecto
   que se queria eliminar, asi que se descarto.
   OJO al medir esto en un navegador con la pestaña en segundo plano: con
   `document.hidden` el navegador congela las transiciones CSS en el fotograma 0, y
   parece que el acordeon no abre cuando en realidad abre. Medir con la pestaña visible.
   El relleno inferior va en el HIJO, nunca en .faq-answer: en el contenedor dejaba
   20-24px de espacio muerto bajo cada respuesta cerrada. */
.faq-answer{ max-height:0; overflow:hidden; transition:max-height 0.35s ease; }
.faq-toggle svg{ transition:transform 0.3s ease; }
.faq-toggle.open svg{ transform:rotate(180deg); }

/* ── Anchor offset (fixed header) ── */
[id]{ scroll-margin-top:6rem; }

/* ============================================================
   REUSABLE CONTENT MODULES (single source of truth)
   Shared across index.html, how-we-work.html and any future page.
   Edit here → propagates everywhere. Do NOT re-declare inline.
   ============================================================ */

/* ── Typography scale ── */
/* ── Antetitulo de seccion ────────────────────────────────────────────────
   Unifica 15 cadenas de clase distintas en 92 instancias. 14px es el canon: ya
   estaba decidido, hay un comentario en index.html documentando haber quitado un
   override a 11px por este mismo motivo.

   TRES superficies, y cada color esta elegido por contraste medido, no por gusto:
     claro (#fff / var(--jbk-off)) -> azul    var(--jbk-blue)
     navy  (var(--jbk-navy))        -> cian    var(--jbk-cyan)   6.42:1  pasa AA
     banda CTA (#004F8F)    -> celeste var(--jbk-pale)   6.11:1  pasa AA

   El cian NO sirve sobre la banda CTA: da 3.24:1 y no pasa. Es la razon de que
   exista el tercer modificador y no dos. */
.t-eyebrow{ font-family:'Lato','Lato Fallback',sans-serif; font-size:0.875rem;
  font-weight:700; text-transform:uppercase; letter-spacing:.12em; color:var(--jbk-blue); line-height:1; }
.t-eyebrow--on-dark{ color:var(--jbk-cyan); }
.t-eyebrow--on-cta{ color:var(--jbk-pale); }

.t-card-lg{ font-size:30px; font-weight:700; line-height:1.15; }
.t-card{ font-size:20px; font-weight:700; line-height:1.2; }
.t-step{ font-size:20px; font-weight:400; line-height:1.2; }
.t-sub{ font-size:18px; font-weight:700; line-height:1.25; }

/* ── Icon neon glow (cyan) ── */
.icon-glow{ filter: drop-shadow(0 0 5px rgba(46,230,255,.95)) drop-shadow(0 0 12px rgba(46,230,255,.6)) drop-shadow(0 0 22px rgba(46,230,255,.3)); }

/* Icon badge (canon — always a circle). Keep w- and h- utilities for size.
   Light context: pale-blue circle + blue icon. Dark context: add .icon-badge--dark. */
.icon-badge{ display:flex; align-items:center; justify-content:center; border-radius:9999px; background:var(--jbk-pale); flex-shrink:0; }
.icon-badge--dark{ background:rgba(0,177,204,0.15); border:1px solid rgba(0,177,204,0.35); }

/* ── Outcomes cards: dark gradient surface, whole-card link, hover lift + neon halo ── */
.o-card{
  background: linear-gradient(150deg, #06304d 0%, #041f32 100%);
  border: 1px solid rgba(255,255,255,0.13);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  box-shadow: 0 16px 48px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.08);
}
.o-link{ cursor:pointer; isolation:isolate; transition: transform 0.25s cubic-bezier(0.34,1.56,0.64,1), box-shadow 0.4s ease; }
.o-link:hover{ transform: translateY(-6px); box-shadow: 0 0 18px rgba(46,230,255,0.50), 0 0 38px rgba(46,230,255,0.30), inset 0 1px 0 rgba(255,255,255,0.12); }
.o-link-gold:hover{ box-shadow: 0 0 18px rgba(255,199,44,0.52), 0 0 38px rgba(255,199,44,0.32), inset 0 1px 0 rgba(255,255,255,0.12); }
.o-link::before{
  content:''; position:absolute; inset:-1px; border-radius:inherit; pointer-events:none; z-index:3;
  border:1.5px solid #eafeff;
  filter: none;
  opacity:0; transition:opacity .35s ease;
}
.o-link:hover::before{ opacity:1; }
.o-link-gold::before{
  border-color:#fff6db;
  filter: none;
}

/* ── Three Steps: time-based reveal that plays once when the section enters view ── */
.steps-track{ position: relative; }
.steps-stick{ padding: 5rem 0; }
.steps-rail{ position: relative; margin: 0 auto; }
.steps-rail-line{ position: absolute; top: 26px; left: calc((100% - 4rem) / 6); right: calc((100% - 4rem) / 6);
  height: 4px; transform: translateY(-50%); background: rgba(2,33,53,0.10); border-radius: 999px; z-index: 1; }
.steps-rail-fill{ position: absolute; top: 0; left: 0; bottom: 0; width: 0%; border-radius: 999px;
  background: linear-gradient(90deg, var(--jbk-blue), var(--jbk-cyan)); box-shadow: 0 0 12px rgba(0,177,204,0.55);
  transition: width 2.8s linear; }
.steps-points{ position: relative; z-index: 2; display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem; }
.steps-point-col{ display: flex; justify-content: center; }
.steps-point{ position: relative; width: 52px; height: 52px; border-radius: 50%; display: flex; align-items: center;
  /* El numeral inactivo era #9aa5ab sobre gris: 2.17:1, menos de la mitad del minimo
     AA de 4.5. Con el gris de marca sobre blanco da 4.79:1. El circulo tambien pasa a
     blanco con borde, por la regla de no usar fondos grises. */
  justify-content: center; font-weight: 900; font-size: 1.05rem;
  color: var(--jbk-gray); background: #fff; border: 1px solid var(--jbk-off);
  box-shadow: 0 2px 6px rgba(2,33,53,0.14);
  transition: background .4s ease, color .4s ease, box-shadow .4s ease; }
.steps-point.on{ background: var(--jbk-blue); color: #fff; box-shadow: 0 3px 10px rgba(0,115,207,0.40); }
.step-card{ opacity: 0; transform: translateY(34px);
  transition: opacity .55s ease, transform .55s cubic-bezier(.2,.7,.3,1); }
.step-card.in{ opacity: 1; transform: translateY(0); }
@media (max-width: 767px){
  .steps-track{ height: auto; }
  .steps-stick{ position: static; min-height: 0; display: block; padding: 4rem 0; }
  .steps-bar{ display: none; }
  .step-card{ opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce){
  .step-card{ opacity: 1; transform: none; transition: none; }
}

/* (Retired: the old DOM `.cta-diag` bar field. The CTA background is now the
   pure-CSS `.cta-section::before` stripes defined above.) */

/* ── Stat numbers: brand-yellow odometer that rolls up when scrolled into view ──
   Markup: <span class="stat-num" data-odometer="300" data-suffix="+"></span>
   The roll is built + triggered by assets/jbk.js. Non-numeric stats: use
   `.stat-num` with inline style="opacity:1" (they stay static, just yellow). */
.stat-num{
  font-size: 3.5rem; font-weight: 900; color: var(--jbk-yellow); /* JBK brand yellow */
  letter-spacing: -0.03em; line-height: 1;
  opacity: 0; transition: opacity 0.5s ease; /* revealed when its roll begins */
}
/* Light-background variant. The yellow above only works on navy - on white it
   fails contrast badly. Blue is the treatment the site already uses for a stat on
   a light surface (the floating "2001 / Founded" card on the home). */
.stat-num.on-light{ color: var(--jbk-blue); }
.odm      { display:inline-flex; align-items:flex-end; line-height:1; }
.odm-reel { display:inline-block; overflow:hidden; height:1.18em; padding:0 2px; vertical-align:bottom; }
.odm-track{ display:block; will-change:transform; }
.odm-d    { display:flex; align-items:center; justify-content:center; height:1.18em; opacity:0.5; }
.odm-track > .odm-d:last-child{ opacity:1; }
@media (prefers-reduced-motion: reduce){ .odm-track{ transition:none !important; transform:none !important; } }

/* ── Photo heroes ───────────────────────────────────────────────────────────
   Five pages share one hero: a right-anchored photograph with the copy on the
   left, over the navy field.

     .hero-photo__art    the photograph, faded into the navy by its own mask
     .hero-photo__veil   a navy wash over the whole section, for text contrast

   WHY THE FADE LIVES ON THE IMAGE AND NOT ON THE VEIL.
   The image is `height:100%; width:auto`, so its width is set by the section's
   HEIGHT (600px tall -> 1062px wide) while the veil's percentages are measured
   against the section's WIDTH. The two are unrelated, so the image's left edge
   walks across the veil as the viewport changes: at 1280px it lands at 17% of
   the width, at 1920px at 45%. A veil that is only ~93% opaque at 45% leaves 7%
   of the photograph showing on one side of that edge and nothing on the other,
   which renders as a hard vertical seam - and the wider the screen, the worse.
   No set of veil stops can fix it, because the seam moves and the stops do not.

   Anchoring the fade to the image solves it by construction: the mask is a
   percentage of the IMAGE's own box, so its first stop is alpha 0 exactly at
   the image's left edge, at every viewport width. There is nothing to see a
   seam at. The veil then only has to darken the copy's side for contrast.

   TUNING. Two ramps, both left-to-right:
     mask  0% -> 82% of the IMAGE   how fast the photo emerges from the navy.
                                    Lower the last stop to reveal it sooner,
                                    raise it for a longer dissolve.
     veil  0% -> 100% of the SECTION  how dark the copy's side is. The first two
                                    stops must stay solid past where the longest
                                    heading ends (currently ~62%). */
.hero-photo__art{
  position:absolute; top:0; right:0; height:100%; width:auto; max-width:none;
  z-index:0;
  -webkit-mask-image:linear-gradient(to right,
    rgba(0,0,0,0) 0%, rgba(0,0,0,0.15) 18%, rgba(0,0,0,0.45) 38%,
    rgba(0,0,0,0.75) 58%, rgba(0,0,0,1) 82%);
          mask-image:linear-gradient(to right,
    rgba(0,0,0,0) 0%, rgba(0,0,0,0.15) 18%, rgba(0,0,0,0.45) 38%,
    rgba(0,0,0,0.75) 58%, rgba(0,0,0,1) 82%);
}
.hero-photo__veil{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  background:linear-gradient(to right,
    var(--jbk-navy) 0%, var(--jbk-navy) 45%,
    rgba(2,33,53,0.86) 51%, rgba(2,33,53,0.62) 58%, rgba(2,33,53,0.34) 65%,
    rgba(2,33,53,0.12) 73%, rgba(2,33,53,0) 80%);
}

/* Below lg the photo is cropped hard against its right edge, so the copy lands
   on top of the subject rather than beside it and a horizontal ramp protects
   nothing - the h1 measured 1.74:1 against white at 375px. Drop the mask (there
   is no left edge on screen to hide) and flatten the veil. */
@media (max-width:1023px){
  .hero-photo__art{ -webkit-mask-image:none; mask-image:none; }
  .hero-photo__veil{
    background:linear-gradient(to bottom, rgba(2,33,53,0.93) 0%, rgba(2,33,53,0.88) 100%);
  }
}

/* ── Navy field ─────────────────────────────────────────────────────────────
   The single treatment for every navy section that opens or closes with a
   curve. Before this the same look was hand-rolled three different ways -
   `bg-jbk-navy`, `style="background:var(--jbk-navy)"` and `style="background:#02192a"` -
   and only the home carried the glow at all, so the curved sections on
   our-services, our-company and tech-we-work-with were flat dead navy.

     <section class="navy-field"> ... </section>

   Tunable per section, defaults shown:
     --glow-x:74%   --glow-y:38%    where the main glow sits
     --glow-op:0.22                 its strength

   Layers, painted under the content (negative z-index inside the section's own
   stacking context, so no wrapper markup is needed):
     ::before   the two radial glows
     ::after    the dither  <- load-bearing, see below

   ON THE DITHER. These glows are wide and low-contrast, which is exactly the
   condition that bands. The main one moves blue 78 -> 42 across 585px: that is
   one 8-bit step every 16px, and an 8-bit panel renders it as ~36 concentric
   rings. No number of extra colour stops fixes it - the bands ARE the steps.
   The fix is to break the quantisation boundary with noise: ~4% fractal noise
   perturbs each pixel by a couple of levels at random, so the step lands in a
   different place on every scanline and the eye integrates it as a smooth
   ramp. Same sprite as .hero-noise, kept identical on purpose.

   Colour stops end at rgba(...,0) rather than `transparent`. They are the same
   colour, but `transparent` is rgba(0,0,0,0), and any engine that interpolates
   un-premultiplied drags the midpoint toward black and greys the falloff. */
.navy-field{
  position:relative; overflow:hidden; isolation:isolate;
  background-color:var(--jbk-navy);
}
.navy-field::before,
.navy-field::after{
  content:''; position:absolute; inset:0; pointer-events:none;
}
.navy-field::before{
  z-index:-2;
  background:
    radial-gradient(ellipse 860px 680px at var(--glow-x,74%) var(--glow-y,38%),
      rgba(0,115,207,var(--glow-op,0.22)) 0%, rgba(0,115,207,0) 68%),
    radial-gradient(ellipse 600px 400px at 50% 0%,
      rgba(0,173,208,0.08) 0%, rgba(0,173,208,0) 60%);
}
.navy-field::after{
  z-index:-1;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
}

/* ── Sector gallery ─────────────────────────────────────────────────────────
   A row of tabs with a pill that glides between them, over a card whose panels
   cross-fade, auto-advancing on a countdown bar. Used by "Industries We Serve"
   on the home and "We Specialize In" on Our Services.

   Markup (the JS finds everything by data attribute, so any number of these can
   live on one page - there are no IDs to collide):

     <div class="gal" data-gallery>
       <div class="gal-tabs">
         <span class="gal-pill" aria-hidden="true"></span>
         <button class="gal-tab" data-tab="wc">Workers Compensation</button>  ...
       </div>
       <div class="gal-card">
         <div class="gal-panel" data-panel="wc"> ... </div>  ...
         <div class="gal-progress"><span class="gal-progress-fill"></span></div>
       </div>
     </div>

   The JS adds the tab/tabpanel roles, wires arrow keys and drives the rotation.
   It was previously inline on the home and bound to hard-coded element IDs, so
   a second instance was impossible. */
.gal-tabs{ position:relative; }
.gal-tab{ position:relative; z-index:1; background:transparent; color:var(--jbk-navy); transition:color .2s ease; }
.gal-tab.active,
.gal-tab.active svg{ color:#fff; }
.gal-pill{
  position:absolute; left:0; top:0; width:0; height:0; border-radius:.5rem;
  background:var(--jbk-blue); z-index:0; pointer-events:none;
  transition:transform .42s cubic-bezier(.45,0,.15,1),
             width .42s cubic-bezier(.45,0,.15,1),
             height .42s cubic-bezier(.45,0,.15,1);
}
.gal-pill.no-glide{ transition:none; }          /* first paint: place it, do not fly it in */
.gal-card{ position:relative; }
.gal-panel{ display:none; }
.gal-panel.active{ display:block; animation:gal-slide .55s cubic-bezier(.2,.7,.3,1); }
.gal-progress{ position:absolute; left:0; right:0; bottom:0; height:4px; background:rgba(2,33,53,0.06); z-index:6; }
.gal-progress-fill{
  display:block; height:100%; width:100%; transform:scaleX(0); transform-origin:left center;
  background:var(--jbk-pale);                 /* the celeste of the icon circles above the titles */
  will-change:transform;              /* own compositor layer, so the clip does not repaint */
  backface-visibility:hidden;
}
.gal-progress-fill.run{ animation:gal-progress 6s linear forwards; }
@keyframes gal-slide{ from{ opacity:0; transform:translateX(28px); } to{ opacity:1; transform:translateX(0); } }
@keyframes gal-progress{ from{ transform:scaleX(0); } to{ transform:scaleX(1); } }
@media (prefers-reduced-motion: reduce){
  .gal-pill{ transition:none; }
  .gal-panel.active{ animation:none; }
  .gal-progress{ display:none; }
}

/* ── Home hero split ────────────────────────────────────────────────────────
   The copy column needs 653px for "Technology Solutions" to hold one line at
   72px/900/-0.03em. An even lg:grid-cols-2 gave it 584px; plain 55fr/45fr gave
   it 528px, which is worse. The reason is that an `fr` track carries an implicit
   min-width:auto, so it will not shrink below its content, and the globe renders
   a fixed 680px box sized from the WINDOW HEIGHT (globePx in jbk-globe.js), not
   from this column. minmax(0,45fr) releases that floor. The globe keeps its size
   and simply overflows the track - which it already did by 48px per side.

   Selector is .grid.hero-split, not .hero-split: the element also carries
   Tailwind's grid-cols-1 for the stacked mobile layout, that rule has the same
   specificity, and the Play CDN injects its sheet after this file - so a single
   class loses the tie and the hero collapses to one column. */
@media (min-width:1024px){
  .grid.hero-split{ grid-template-columns:55fr minmax(0,45fr); column-gap:1.5rem; }
}


/* ==========================================================================
   AUTOFIX PASS - 2026-08-14
   Appended, never edited in place, so nothing above can regress. Everything
   here is either a pure addition or a same-specificity override that lands
   later in the cascade on purpose. Each block cites the audit finding it closes.
   ========================================================================== */

/* --- ios-input-zoom-on-focus (high) --------------------------------------
   Every form field computes to 15.2px. iOS Safari force-zooms the whole
   viewport whenever a focused field is under 16px, on every field of both
   live forms - i.e. across the entire conversion path. 16px stops it.
   Fixed with font-size, NOT with maximum-scale on the viewport meta: that
   would disable pinch-zoom sitewide and is a WCAG 1.4.4 failure. */
@media (max-width: 1023px) {
  .jbk-input,
  .form-input,
  select.form-input,
  textarea.jbk-input,
  textarea.form-input { font-size: 16px; }
}

/* --- M-BTN-02 (medium): hover states latch on after a tap ----------------
   The site defines zero `@media (hover: hover)` guards, so on iOS and Android
   a tapped CTA fires its hover state and stays visibly lifted until the user
   taps elsewhere - while the next page is already loading. Rather than rewrite
   the eight :hover rules above, this cancels only the MOVEMENT on devices that
   cannot really hover. Colour changes are harmless on touch and are left alone. */
@media (hover: none) {
  .service-card:hover,
  .tech-tile:hover,
  .tech-glass:hover,
  .o-link:hover { transform: none; }
}

/* --- firefox-unstyled-scrollbar (low) ------------------------------------
   Only ::-webkit-scrollbar was defined, so Firefox alone rendered the raw OS
   scrollbar against the navy panels. These are the standard properties. */
html { scrollbar-width: thin; scrollbar-color: #b9cbda transparent; }

/* --- M-BTN-06 (low): ~130 CTAs had no authored focus style ---------------
   Only 4 selectors in the whole site defined :focus-visible, so keyboard focus
   looked like four different things on one page. This gives everything else the
   same ring the buttons already use. Nothing was unreachable before - the UA
   ring still drew - this makes it consistent and visible against dark panels. */
.service-card:focus-visible,
.o-link:focus-visible,
.faq-toggle:focus-visible,
.gal-tab:focus-visible,
.tech-tile:focus-visible,
#hamburger:focus-visible {
  outline: 3px solid var(--jbk-pale);
  outline-offset: 3px;
  border-radius: 6px;
}

/* --- M-BTN-04 (medium): collapsed FAQ answers stay in the a11y tree ------
   `.faq-answer` collapses with max-height:0 + overflow:hidden and nothing else,
   so screen readers read all 42 answers on a page as if every accordion were
   open. visibility transitions cleanly and does remove the subtree from the
   accessibility tree. This is the short-term fix; the real one is the <details>
   rebuild in Phase 3, which makes it correct by construction. */
.faq-answer { visibility: hidden; transition: max-height .35s ease, visibility 0s linear .35s; }
.faq-answer.open { visibility: visible; transition: max-height .35s ease, visibility 0s; }

/* --- M-BTN-05 (low): `.legal a` had no hover, focus or active state ------
   The only interactive text on the site that did not react to the pointer at
   all. Declared here so the two legal pages can drop their private copies. */
.legal a:hover { color: #005ea6; text-decoration: underline; }
.legal a:focus-visible { outline: 3px solid var(--jbk-pale); outline-offset: 2px; border-radius: 3px; }

/* --- Honeypot + timing trap for the two HubSpot forms (2026-08-14) -------
   HubSpot's CAPTCHA had to be turned off because it blocks the API entirely, so
   spam protection lives here instead. Off-screen rather than display:none: some
   bots skip display:none inputs on purpose. aria-hidden + tabindex=-1 keep it out
   of the screen-reader and keyboard paths. */
.jbk-hp {
  position: absolute !important;
  left: -9999px !important;
  top: auto !important;
  width: 1px !important;
  height: 1px !important;
  overflow: hidden !important;
}


/* ==========================================================================
   SISTEMA DE BOTONES  ·  2026-08-17
   Reemplaza a .btn-solid-light / .btn-solid-dark / .btn-outline-light /
   .btn-outline-dark, que sumaban 25 cadenas de clase para 140 controles.

   UNA sola medida. El ancho SIEMPRE lo da el texto (inline-flex, nadie fija
   un ancho). Los "3 tamanios" de la primera propuesta no existian por diseno:
   65 de los 70 botones primarios dicen lo mismo, en el mismo tipo de lugar, a
   tres alturas distintas -- 56px (42 usos), 54px (15), 60px (8). Tres autores,
   tres numeros. 56px es la mayoritaria, asi que migrar no mueve la mayoria.

   Superficie clara y oscura comparten variante: eran identicas en reposo y solo
   diferian en la sombra del hover. Esa diferencia vive ahora en .btn--on-dark.

   NO hay variante "fantasma": no habia ni un caso real en el sitio que la
   pidiera, y el rol lo cubre .link-cta.
   ========================================================================== */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:.5rem;
  font-size:1rem; font-weight:700; line-height:1.5; padding:.875rem 2rem;
  border-radius:6px; border:2px solid transparent; letter-spacing:.01em;
  text-decoration:none; cursor:pointer; font-family:inherit;
  /* El lift y su sombra comparten duracion: son el mismo gesto. Color y fondo van
     mas lentos porque un cambio cromatico brusco se lee peor. */
  transition: transform .12s ease, box-shadow .12s ease, background .18s ease,
              border-color .18s ease, color .18s ease;
}
/* UNICA excepcion a la medida unica, y existe por una razon medible, no por gusto:
   el CTA del header vive dentro de una barra de ALTURA FIJA de 64px. Con la medida
   normal (56px) quedan 4px de aire arriba y abajo y el boton casi toca los bordes.
   Con esta quedan 7.5px, que es el aire que tenia antes de unificar.
   NO usar en ningun otro lado: los 65 botones de las bandas CTA no estan dentro de
   ningun contenedor fijo y no tienen motivo para diferir. Si aparece un segundo caso
   con un contenedor de altura fija, este es el tamanio; si no, no se toca. */
.btn--sm{ font-size:.875rem; padding:.75rem 1.5rem; }

.btn:focus-visible{ outline:3px solid var(--jbk-pale); outline-offset:3px; }
.btn:active{ transform:translateY(-1px); }
/* Estado deshabilitado: los dos formularios ponen btn.disabled = true mientras envian.
   Venia de .jbk-submit:disabled y se habria perdido al migrar la clase. */
.btn:disabled, .btn[disabled]{ opacity:.6; cursor:not-allowed; transform:none; box-shadow:none; }

.btn--primary{ background:var(--jbk-blue); color:#fff; }
.btn--primary:hover{ transform:translateY(-3px);
  box-shadow:0 6px 14px rgba(0,0,0,.12), 0 16px 30px rgba(0,0,0,.15); }

.btn--outline{ background:transparent; border-color:var(--jbk-blue); color:var(--jbk-blue); }
.btn--outline:hover{ background:rgba(0,115,207,.06); transform:translateY(-3px);
  box-shadow:0 6px 14px rgba(0,0,0,.10), 0 14px 26px rgba(0,0,0,.12); }

/* Sobre navy. En el solido, sombra proyectada: el halo de color leia como fuga de
   luz. En el de contorno, halo blanco: ahi lee como iluminacion del borde, que es
   el efecto buscado. Son dos casos distintos a proposito (Nico, 2026-08-17). */
.btn--on-dark.btn--primary:hover{
  box-shadow:0 8px 18px rgba(0,0,0,.30), 0 18px 38px rgba(0,0,0,.24); }
.btn--on-dark.btn--outline{ border-color:rgba(255,255,255,.7); color:#fff; }
.btn--on-dark.btn--outline:hover{ background:rgba(0,0,0,.25); border-color:#fff;
  box-shadow:0 0 18px rgba(255,255,255,.35), 0 0 44px rgba(255,255,255,.18); }

/* Enlace terciario con flecha. Reemplaza 4 cadenas distintas para el mismo rol. */
.link-cta{
  display:inline-flex; align-items:center; gap:.375rem;
  font-size:.875rem; font-weight:700; color:var(--jbk-blue); text-decoration:none;
  cursor:pointer; transition:gap .18s ease, color .18s ease;
}
.link-cta:hover{ gap:.75rem; color:#005ea6; }
.link-cta:focus-visible{ outline:3px solid var(--jbk-pale); outline-offset:3px; border-radius:4px; }
.link-cta--on-dark{ color:var(--jbk-cyan); }
.link-cta--on-dark:hover{ color:#7fe0ef; }

/* Pildora de indice. Las de tech-we-work-with son <a href="#uipath">: saltan a una
   seccion de la MISMA pagina. Vestian identico al CTA de conversion, dandole a un
   indice de navegacion el peso visual de "Book a Free Consult". */
.pill{
  display:inline-flex; align-items:center; gap:.4rem;
  font-size:.875rem; font-weight:700; padding:.5rem 1rem; border-radius:20px;
  /* Fondo BLANCO, no gris. Dos motivos que apuntan al mismo lado: el azul de marca
     sobre #edeeef da 4.15:1 y no llega al minimo AA de 4.5, mientras que sobre blanco
     da 4.82:1 y pasa; y ademas no puede haber fondos grises. El borde reemplaza al
     relleno como definicion de la forma. Medido, no elegido a ojo. */
  background:#fff; color:var(--jbk-blue); border:1px solid var(--jbk-off);
  text-decoration:none; cursor:pointer;
  transition:background .18s ease, border-color .18s ease, color .18s ease;
}
.pill:hover{ background:#fff; border-color:var(--jbk-blue); }
.pill:focus-visible{ outline:3px solid var(--jbk-pale); outline-offset:2px; }

@media (hover:none){
  .btn:hover, .btn--primary:hover, .btn--outline:hover{ transform:none; }
}
