/*
Theme Name:   Noah Electric
Theme URI:    https://noahelectricllc.com/
Author:       Noah Electric — Yeison George
Author URI:   https://noahelectricllc.com/
Description:  Tema enterprise a medida para Noah Electric (Austin & Manor, TX). Portal de cliente con
              login OTP, firma legal, galería de inspección, invoices y visor 3D. Sin jQuery, sin page
              builder, sin librerías de animación. Bilingüe EN/ES, PWA, WCAG 2.1 AA.
Version:      3.6.0
Requires at least: 6.4
Tested up to: 7.1
Requires PHP: 8.0
License:      GPL-2.0-or-later
License URI:  https://www.gnu.org/licenses/gpl-2.0.html
Text Domain:  noah-electric
Tags:         electrician, local-business, client-portal, pwa, accessibility-ready, translation-ready
*/

/* ============================================================================
   NOAH ELECTRIC — PARTE 1/8 · HOJA DE ESTILOS MAESTRA
   ----------------------------------------------------------------------------
   ÍNDICE
     01 · TOKENS DE DISEÑO (custom properties)
     02 · TEMA CLARO (opcional, por toggle o preferencia del SO)
     03 · RESET MODERNO
     04 · ACCESIBILIDAD (WCAG 2.1 AA)
     05 · TIPOGRAFÍA
     06 · LAYOUT · CONTENEDORES Y GRID
     07 · SUPERFICIES · GLASSMORPHISM
     08 · BOTONES
     09 · BADGES · PILLS · CHIPS
     10 · FORMULARIOS + INPUT OTP
     11 · HEADER STICKY + NAVEGACIÓN + DRAWER MÓVIL
     12 · HERO
     13 · TARJETAS DE SERVICIO / FEATURES
     14 · PORTFOLIO
     15 · RESEÑAS
     16 · BARRA DE MARCAS (marquee)
     17 · ACORDEÓN FAQ
     18 · FOOTER
     19 · BARRA CTA MÓVIL FIJA (click-to-call)
     20 · DASHBOARD · KPIs, PROGRESO, FASES
     21 · GALERÍA + LIGHTBOX TÁCTIL
     22 · VISOR 3D
     23 · MODALES
     24 · MÉTODOS DE PAGO
     25 · INVOICE
     26 · FIRMA LEGAL
     27 · BOT BILINGÜE
     28 · TOASTS · SKELETONS · SPINNERS
     29 · ANIMACIONES Y SCROLL REVEAL
     30 · UTILIDADES
     31 · PWA · SAFE AREA (notch iPhone)
     33 · LOGO CONFIGURABLE
     34 · DRAWER MÓVIL REDISEÑADO
     35 · TARJETAS DE SERVICIO CON FOTO
     36 · EFECTOS DEL DASHBOARD (loader, orbes, holograma, confeti)
     37 · REGISTRO DE CLIENTE
     38 · FORMULARIO DE CONTACTO DEL BOT
     39 · MÓVIL · AJUSTES FINOS
     40 · TARJETAS DE SERVICIO CLICABLES
     41 · MAQUETA DEL TELÉFONO
     42 · MIGAS DE PAN
     43 · REDES SOCIALES
     44 · DEMO DEL PORTAL
     45 · TELÉFONO DEL HERO · 3D Y MÓVIL
     46 · REALISMO AL PASAR EL CURSOR
     47 · ESTIMADO · CALENDARIO Y FIRMA
     48 · ALCANCE Y PRECIO ÚNICO
     49 · CURSOR PERSONALIZADO          ← nuevo en 2.5.0
     50 · RED DE PARTÍCULAS (canvas)    ← nuevo en 2.5.0
     51 · SOBRE NOSOTROS                ← nuevo en 2.5.0
     52 · PROCESO · LÍNEA DE TIEMPO     ← nuevo en 2.5.0
     53 · BANDA DE CIFRAS               ← nuevo en 2.5.0
     54 · FILTRO DEL PORTFOLIO          ← nuevo en 2.5.0
     55 · VÍDEO DE FONDO DEL HERO       ← nuevo en 2.5.0
     56 · CANDADO HORIZONTAL · MODO APP ← nuevo en 2.5.1
     57 · TAMAÑO DEL MENÚ HAMBURGUESA  ← nuevo en 2.5.1
     58 · CASILLAS DEL CÓDIGO QUE CABEN ← arreglo 2.5.2
     59 · FASES DEL PANEL · BARRA OFFLINE ← arreglo 2.5.3
     60 · INICIO v3 · COTIZACIÓN EN 30 s   ← nuevo en 2.5.5
     61 · PASADA DE PRODUCTO (auditoría)   ← nuevo en 2.6.0
     62 · ZONAS DE SERVICIO (ciudades)     ← nuevo en 2.8.0
     63 · INICIO DE ALTO IMPACTO           ← nuevo en 2.9.0
     64 · UNA IMAGEN POR MENSAJE           ← nuevo en 3.0.0
     65 · GEOMETRÍA DE MARCA · TELÉFONO    ← nuevo en 3.1.0
     66 · REPASO DE MÓVIL REAL            ← nuevo en 3.4.0
     32 · IMPRESIÓN (invoice / contrato) ← SIEMPRE LA ÚLTIMA: debe pisar todo
   ----------------------------------------------------------------------------
   DECISIONES DE ARQUITECTURA (el "por qué"):

   1) NO se usa @layer.
      Durante la migración en staging, Elementor sigue instalado para las páginas
      legacy. Los estilos SIN capa ganan SIEMPRE a los que están dentro de @layer,
      sin importar la especificidad. Usar @layer aquí haría que Elementor pisara
      todo el tema. Se usa cascada plana ordenada por secciones.

   2) NO se usa @import de Google Fonts.
      @import bloquea el render en serie y destruye el LCP. Las fuentes se
      precargan desde header.php (PARTE 3) con <link rel="preload"> + font-display:swap.

   3) Mobile-first estricto.
      Todo media query es min-width. El CSS base ES el móvil, que es el 70% del
      tráfico de "electrician near me".

   4) Todos los colores salen de tokens.
      Ni un solo hex suelto fuera de :root. Eso permite el modo claro, futuros
      rebrandings y auditorías de contraste en un solo sitio.

   5) Contraste verificado (ratio sobre el fondo de tarjeta #10141d).
      Medido con la fórmula WCAG antes de escribir un solo color, no estimado:
        --c-primary        #4B80DD  → 4,76:1  ✅ AA texto normal
        --c-secondary      #C7AC75  → 8,41:1  ✅ AAA — el bronce del logo
        --c-secondary-300  #E0CDA4  → 11,79:1 ✅ AAA
        --c-primary-300    #A3BEEE  → 9,79:1  ✅ AAA
      El morado que había antes (#9B51E0) daba 4,08:1: NO llegaba a AA, aunque
      este comentario dijera 4,49. El bronce lo arregla de paso.
        --c-success        #2ecc71  → 9,70:1  ✅
        --c-danger         #e74c3c  → 5,33:1  ✅
        --c-text-muted     #98a4b8  → 7,40:1  ✅
        --c-text-dim       #7d899e  → 5,23:1  ✅
============================================================================ */


/* ==========================================================================
   01 · TOKENS DE DISEÑO
   ========================================================================== */

:root {
  /* Le dice al navegador que pinte scrollbars, inputs y controles en oscuro. */
  color-scheme: dark;

  /* --- Fondos --------------------------------------------------------- */
  --c-bg-0: #010409;           /* fondo base, el más profundo */
  --c-bg-1: #0a0e17;           /* fondo de secciones alternas */
  --c-bg-2: #10141d;           /* fondo sólido de tarjeta (fallback sin blur) */
  --c-bg-3: #161b26;           /* elevación superior: inputs, dropdowns */

  /* --- Superficies de cristal ----------------------------------------- */
  --c-surface:      rgba(255, 255, 255, 0.030);
  --c-surface-2:    rgba(255, 255, 255, 0.060);
  --c-surface-3:    rgba(255, 255, 255, 0.090);
  --c-border:       rgba(255, 255, 255, 0.080);
  --c-border-strong:rgba(255, 255, 255, 0.160);

  /* --- Marca -----------------------------------------------------------
     SACADA DEL LOGO, NO INVENTADA (v3.0.0).

     Muestreé el PNG del logo pixel a pixel. Lo que hay dentro:

       · La X y "ELECTRIC": acero con tinte pizarra. Tono 217-229°, pero con
         una saturación de solo 9-19%. #7d8592 · #a5aabc · #9aa1ba
       · "NOAH": bronce cálido. #afa286 → hsl(41, 20%, 61%)
       · Morado: CERO. No existe en la marca.

     El azul de antes (#3081ED) estaba casi en el tono correcto —213°— pero al
     84% de saturación. Ese era el choque: el logo susurra acero y la web
     gritaba azul. Y el morado no venía de ninguna parte.

     Qué cambia:
       · El azul se queda —es lo que funciona para pulsar— pero baja al tono
         del logo (218°) y afloja la saturación al 68%.
       · El morado se va y entra el bronce del propio logo. Azul y bronce es
         además una pareja clásica, y en este oficio es cobre y acero.

     El regalo: el degradado del titular va de azul a bronce y su punto medio
     cae en #8996a9 — exactamente el acero medido en la X. El titular recorre
     el logo de izquierda a derecha sin que nadie lo note.                    */
  --c-primary:      #4B80DD;
  --c-primary-700:  #2C4F96;
  --c-primary-600:  #3A68C2;
  --c-primary-300:  #A3BEEE;
  --c-primary-rgb:  75, 128, 221;

  --c-secondary:     #C7AC75;   /* el bronce de "NOAH". 8,41:1 — pasa AAA */
  --c-secondary-300: #E0CDA4;   /* el mismo, más claro, para texto pequeño */
  --c-secondary-rgb: 199, 172, 117;

  /* --- Semánticos ------------------------------------------------------ */
  --c-success:     #2ecc71;
  --c-success-rgb: 46, 204, 113;
  --c-danger:      #e74c3c;
  --c-danger-rgb:  231, 76, 60;
  --c-warning:     #f39c12;
  --c-warning-rgb: 243, 156, 18;
  --c-info:        var(--c-primary);

  /* --- Texto ------------------------------------------------------------ */
  --c-text:       #f4f7fb;
  --c-text-muted: #98a4b8;
  --c-text-dim:   #7d899e;   /* mínimo AA; no bajar de aquí */
  --c-text-on-accent: #ffffff;

  /* --- Gradientes ------------------------------------------------------- */
  --g-brand:   linear-gradient(135deg, var(--c-primary) 0%, var(--c-secondary) 100%);
  --g-brand-h: linear-gradient(135deg, var(--c-secondary) 0%, var(--c-primary) 100%);
  --g-bg:      linear-gradient(135deg, var(--c-bg-0) 0%, var(--c-bg-1) 50%, var(--c-bg-0) 100%);
  --g-line:    linear-gradient(90deg, transparent, var(--c-primary), var(--c-secondary), transparent);
  --g-success: linear-gradient(135deg, var(--c-success) 0%, #27ae60 100%);

  /* --- Tipografía -------------------------------------------------------
     Barlow desde la v3.1.0: una grotesca de formas ligeramente cuadradas,
     dibujada para señalización. Sustituye a Poppins, que es una geométrica de
     círculos —la O es un círculo exacto— y por eso no pegaba con un logo de
     chapa con las puntas cortadas a 45°.

     El stack de respaldo replica métricas parecidas para que, si la webfont
     tarda, el salto de layout (CLS) sea mínimo.                              */
  --ff-base: "Barlow", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto,
             "Helvetica Neue", Arial, sans-serif;
  --ff-display: "Barlow Condensed", "Barlow", "Segoe UI", sans-serif;
  --ff-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;

  --fw-light: 300;
  --fw-normal: 400;
  --fw-medium: 500;
  --fw-semi: 600;
  --fw-bold: 700;
  --fw-black: 800;

  /* Escala fluida: crece con el viewport sin media queries. */
  --fs-2xs:  0.75rem;                                    /* 12px — era 11px: medido en móvil, 16 textos quedaban por debajo del mínimo cómodo de lectura */
  --fs-xs:   0.75rem;                                    /* 12px */
  --fs-sm:   0.8125rem;                                  /* 13px */
  --fs-base: 0.9375rem;                                  /* 15px */
  --fs-md:   1rem;                                       /* 16px */
  --fs-lg:   clamp(1.0625rem, 1rem + 0.30vw, 1.1875rem);
  --fs-xl:   clamp(1.25rem,  1.10rem + 0.70vw, 1.625rem);
  --fs-2xl:  clamp(1.5rem,   1.25rem + 1.20vw, 2.125rem);
  --fs-3xl:  clamp(1.875rem, 1.40rem + 2.20vw, 3rem);
  --fs-4xl:  clamp(2.25rem,  1.50rem + 3.60vw, 4.25rem);

  --lh-tight: 1.12;
  --lh-snug:  1.32;
  --lh-base:  1.62;

  --ls-tight: -0.02em;
  --ls-wide:  0.06em;
  --ls-wider: 0.16em;

  /* --- Espaciado (escala de 4px) ---------------------------------------- */
  --sp-1: 0.25rem;  --sp-2: 0.5rem;   --sp-3: 0.75rem;  --sp-4: 1rem;
  --sp-5: 1.25rem;  --sp-6: 1.5rem;   --sp-7: 1.75rem;  --sp-8: 2rem;
  --sp-10: 2.5rem;  --sp-12: 3rem;    --sp-16: 4rem;    --sp-20: 5rem;
  --sp-24: 6rem;

  /* Padding vertical de sección: fluido para no desperdiciar alto en móvil. */
  --sp-section: clamp(3.5rem, 2rem + 7vw, 7.5rem);

  /* --- Radios ------------------------------------------------------------ */
  --r-xs: 6px;   --r-sm: 10px;  --r-md: 14px;  --r-lg: 20px;
  --r-xl: 24px;  --r-2xl: 32px; --r-pill: 999px;

  /* --- Sombras ----------------------------------------------------------- */
  --sh-sm:  0 2px 8px rgba(0, 0, 0, 0.35);
  --sh-md:  0 8px 24px rgba(0, 0, 0, 0.45);
  --sh-lg:  0 20px 40px rgba(0, 0, 0, 0.50), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  --sh-xl:  0 30px 60px rgba(0, 0, 0, 0.60), inset 0 1px 0 rgba(255, 255, 255, 0.12);
  --sh-glow-primary:  0 0 28px rgba(var(--c-primary-rgb), 0.42);
  --sh-glow-secondary:0 0 28px rgba(var(--c-secondary-rgb), 0.38);
  --sh-glow-success:  0 0 28px rgba(var(--c-success-rgb), 0.35);
  --sh-glow-danger:   0 0 28px rgba(var(--c-danger-rgb), 0.35);

  /* --- Desenfoque de cristal --------------------------------------------- */
  --blur-sm: 10px;
  --blur-md: 18px;
  --blur-lg: 26px;

  /* --- Movimiento --------------------------------------------------------
     --dur-* se ponen a 0.01ms en prefers-reduced-motion (sección 04), así que
     toda animación del tema debe usar estos tokens, nunca valores literales. */
  --dur-fast: 140ms;
  --dur-base: 260ms;
  --dur-slow: 480ms;
  --dur-xslow: 900ms;
  --ease-out:  cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);

  /* --- Capas Z ------------------------------------------------------------
     Escala explícita: prohibido escribir z-index literales en el resto del CSS. */
  --z-behind: -1;
  --z-base: 1;
  --z-raised: 10;
  --z-sticky: 100;
  --z-header: 200;
  --z-drawer: 300;
  --z-modal: 400;
  --z-lightbox: 450;
  --z-bot: 500;
  --z-toast: 600;
  --z-skip: 700;

  /* --- Dimensiones -------------------------------------------------------- */
  --w-container: 1250px;
  --w-narrow: 780px;
  --w-wide: 1480px;
  --h-header: 72px;
  --h-header-scrolled: 62px;
  --h-mobile-cta: 64px;
  --tap-min: 44px;          /* objetivo táctil mínimo WCAG 2.5.5 */
  --gutter: clamp(1rem, 0.5rem + 2.5vw, 2.5rem);
}


/* ==========================================================================
   02 · TEMA — SOLO OSCURO
   --------------------------------------------------------------------------
   DECISIÓN DEL DIRECTOR: se elimina el cambiador claro/oscuro.

   El motivo es sólido: cada tema extra duplica la superficie donde algo puede
   verse mal, y un cliente que abre su factura y la ve con texto gris sobre
   fondo gris no vuelve. La marca es oscura; la web es oscura. Punto.

   El modo claro SIGUE existiendo en el único sitio donde de verdad hace falta:
   la impresión (sección 32), porque imprimir en negro gasta tinta y se lee mal.
   ========================================================================== */

/* ==========================================================================
   03 · RESET MODERNO
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;   /* iOS no debe inflar el texto al rotar */
  -moz-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Compensa el header fijo cuando se navega a un #ancla. */
  scroll-padding-top: calc(var(--h-header) + var(--sp-6));
  -webkit-tap-highlight-color: rgba(var(--c-primary-rgb), 0.18);
}

body {
  min-height: 100svh;              /* svh evita el salto de la barra de Safari iOS */
  margin: 0;
  padding: 0;
  font-family: var(--ff-base);
  font-size: var(--fs-base);
  font-weight: var(--fw-normal);
  line-height: var(--lh-base);
  color: var(--c-text);
  background-color: var(--c-bg-0);
  background-image: var(--g-bg);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;              /* red de seguridad contra desbordes horizontales */
}

/* Bloquea el scroll del fondo con modal/drawer abierto sin perder la posición.
   La clase la pone el JS junto con un top negativo calculado. */
body.ne-scroll-locked {
  position: fixed;
  inset-inline: 0;
  overflow: hidden;
  width: 100%;
}

img, picture, video, canvas, svg, iframe {
  display: block;
  max-width: 100%;
  border: 0;
}

img, video {
  height: auto;
}

/* Placeholder mientras carga: evita el "flash" blanco sobre fondo oscuro. */
img:not([src]), img[src=""] { visibility: hidden; }

input, button, textarea, select {
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
}

button { background: none; border: 0; padding: 0; cursor: pointer; }

p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }

a {
  color: var(--c-primary-300);
  text-decoration-color: rgba(var(--c-primary-rgb), 0.45);
  text-underline-offset: 0.22em;
  transition: color var(--dur-fast) var(--ease-out),
              text-decoration-color var(--dur-fast) var(--ease-out);
}
a:hover { color: var(--c-text); text-decoration-color: currentColor; }

ul, ol { padding-inline-start: 1.25em; }

hr {
  height: 1px;
  border: 0;
  background: var(--c-border);
  margin-block: var(--sp-8);
}

table { border-collapse: collapse; width: 100%; }

::selection {
  background: rgba(var(--c-primary-rgb), 0.35);
  color: var(--c-text);
}

/* Scrollbar de marca — solo en escritorio con puntero fino. */
@media (min-width: 1024px) and (pointer: fine) {
  * { scrollbar-width: thin; scrollbar-color: var(--c-border-strong) transparent; }
  ::-webkit-scrollbar { width: 10px; height: 10px; }
  ::-webkit-scrollbar-track { background: transparent; }
  ::-webkit-scrollbar-thumb {
    background: var(--c-border-strong);
    border-radius: var(--r-pill);
    border: 2px solid transparent;
    background-clip: content-box;
  }
  ::-webkit-scrollbar-thumb:hover { background: var(--c-primary); background-clip: content-box; }
}


/* ==========================================================================
   04 · ACCESIBILIDAD (WCAG 2.1 AA)
   ========================================================================== */

/* --- Skip link: primer elemento tabulable de la página (2.4.1) ----------- */
.ne-skip-link {
  position: absolute;
  top: var(--sp-2);
  left: var(--sp-2);
  z-index: var(--z-skip);
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--r-sm);
  background: var(--c-primary);
  color: var(--c-text-on-accent);
  font-weight: var(--fw-bold);
  text-decoration: none;
  transform: translateY(-160%);
  transition: transform var(--dur-fast) var(--ease-out);
}
.ne-skip-link:focus-visible { transform: translateY(0); }

/* --- Solo lector de pantalla --------------------------------------------- */
.ne-sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/* Variante que sí aparece al recibir foco (útil en enlaces de salto). */
.ne-sr-only-focusable:focus-visible {
  position: static !important;
  width: auto; height: auto;
  clip-path: none;
  white-space: normal;
  margin: 0;
}

/* --- Foco visible (2.4.7 + 2.4.11) --------------------------------------
   :focus-visible en vez de :focus para no castigar al usuario de ratón.
   Doble sombra: anillo de color + halo oscuro, legible sobre cualquier fondo. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--c-primary-300);
  outline-offset: 3px;
  border-radius: var(--r-xs);
  box-shadow: 0 0 0 5px rgba(var(--c-primary-rgb), 0.22);
}
/* Nunca eliminar el foco sin sustituirlo. Si un componente lo oculta, define el suyo. */
:where(a, button, input, select, textarea):focus { outline: none; }

/* --- Objetivo táctil mínimo 44×44 (2.5.5) -------------------------------- */
@media (pointer: coarse) {
  .ne-btn, .ne-nav__link, .ne-icon-btn, .ne-tab, .ne-pay__card, .ne-faq__q {
    min-height: var(--tap-min);
  }
}

/* --- Movimiento reducido (2.3.3) -----------------------------------------
   No se elimina la animación: se reduce a ~0. Así los callbacks de
   animationend/transitionend siguen disparando y el JS no se queda colgado. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-fast: 0.01ms; --dur-base: 0.01ms;
    --dur-slow: 0.01ms; --dur-xslow: 0.01ms;
  }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  html { scroll-behavior: auto; }
  .ne-reveal { opacity: 1 !important; transform: none !important; }
  .ne-parallax { transform: none !important; }
  .ne-marquee__track { animation: none !important; }
}

/* --- Modo alto contraste (Windows) --------------------------------------- */
@media (forced-colors: active) {
  .ne-btn, .ne-card, .ne-input { border: 1px solid ButtonBorder; }
  .ne-btn--primary { forced-color-adjust: none; }
}

/* Oculto accesible real: [hidden] debe ganar a display:flex/grid. */
[hidden] { display: none !important; }


/* ==========================================================================
   05 · TIPOGRAFÍA
   ========================================================================== */

h1, h2, h3, h4, h5, h6 {
  font-weight: var(--fw-black);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  color: var(--c-text);
  text-wrap: balance;      /* evita títulos con una palabra huérfana */
}

h1, .ne-h1 { font-size: var(--fs-4xl); }
h2, .ne-h2 { font-size: var(--fs-3xl); }
h3, .ne-h3 { font-size: var(--fs-2xl); font-weight: var(--fw-bold); }
h4, .ne-h4 { font-size: var(--fs-xl);  font-weight: var(--fw-bold); }
h5, .ne-h5 { font-size: var(--fs-lg);  font-weight: var(--fw-semi); letter-spacing: 0; }
h6, .ne-h6 { font-size: var(--fs-md);  font-weight: var(--fw-semi); letter-spacing: 0; }

p { line-height: var(--lh-base); text-wrap: pretty; }
p + p { margin-top: var(--sp-4); }

.ne-lead {
  font-size: var(--fs-lg);
  font-weight: var(--fw-light);
  color: var(--c-text-muted);
  max-width: 62ch;             /* longitud de línea legible */
}

/* Eyebrow: la etiqueta pequeña en mayúsculas sobre cada título de sección. */
.ne-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-primary-300);
  margin-bottom: var(--sp-3);
}
.ne-eyebrow::before {
  content: "";
  width: 26px; height: 2px;
  background: var(--g-brand);
  border-radius: var(--r-pill);
}

/* Texto con degradado de marca. -webkit-* es obligatorio: el estándar
   background-clip:text aún no está en todos los motores. */
.ne-gradient-text {
  background: var(--g-brand);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}
/* Si el navegador no soporta el recorte, el texto quedaría invisible. */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .ne-gradient-text { color: var(--c-primary-300); -webkit-text-fill-color: currentColor; }
}

.ne-text-muted   { color: var(--c-text-muted); }
.ne-text-dim     { color: var(--c-text-dim); }
.ne-text-success { color: var(--c-success); }
.ne-text-danger  { color: var(--c-danger); }
.ne-text-warning { color: var(--c-warning); }
.ne-mono         { font-family: var(--ff-mono); font-variant-numeric: tabular-nums; }

/* Cifras de dinero: tabular-nums alinea los dígitos en columnas. */
.ne-money {
  font-family: var(--ff-base);
  font-weight: var(--fw-black);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--ls-tight);
}


/* ==========================================================================
   06 · LAYOUT · CONTENEDORES Y GRID
   ========================================================================== */

.ne-container {
  width: 100%;
  max-width: var(--w-container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.ne-container--narrow { max-width: var(--w-narrow); }
.ne-container--wide   { max-width: var(--w-wide); }

.ne-section {
  position: relative;
  padding-block: var(--sp-section);
}
.ne-section--alt { background: var(--c-bg-1); }

/* Separador de secciones: línea con degradado que se desvanece a los lados. */
.ne-section--divided::before {
  content: "";
  position: absolute;
  inset-inline: 10%;
  top: 0;
  height: 1px;
  background: var(--g-line);
  opacity: 0.5;
}

.ne-section__head {
  max-width: var(--w-narrow);
  margin-bottom: var(--sp-10);
}
.ne-section__head--center {
  margin-inline: auto;
  text-align: center;
}
.ne-section__head--center .ne-lead { margin-inline: auto; }

/* --- Grid automática -----------------------------------------------------
   auto-fit + minmax = columnas responsivas sin una sola media query.
   min() protege de desbordes cuando el viewport es menor que --col-min.      */
.ne-grid {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--col-min, 260px), 100%), 1fr));
}
.ne-grid--2 { --col-min: 420px; }
.ne-grid--3 { --col-min: 300px; }
.ne-grid--4 { --col-min: 230px; }
.ne-grid--tight { gap: var(--sp-3); }
.ne-grid--loose { gap: var(--sp-8); }

/* Grid principal del dashboard: una columna en móvil, asimétrica en escritorio. */
.ne-grid--dash {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: 1fr;
}
@media (min-width: 1024px) {
  .ne-grid--dash { grid-template-columns: 1.8fr 1.2fr; }
  .ne-grid--dash > .ne-span-full { grid-column: 1 / -1; }
}

.ne-stack { display: flex; flex-direction: column; gap: var(--sp-4); }
.ne-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
}
.ne-row--between { justify-content: space-between; }
.ne-row--center  { justify-content: center; }
.ne-row--end     { justify-content: flex-end; }
.ne-row--top     { align-items: flex-start; }

/* Scroller horizontal con snap (galerías, reseñas, chips de filtro). */
.ne-scroller {
  display: flex;
  gap: var(--sp-4);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--gutter);
  padding-bottom: var(--sp-3);
  -webkit-overflow-scrolling: touch;
}
.ne-scroller > * { scroll-snap-align: start; flex: 0 0 auto; }
.ne-scroller--hide-bar { scrollbar-width: none; }
.ne-scroller--hide-bar::-webkit-scrollbar { display: none; }


/* ==========================================================================
   07 · SUPERFICIES · GLASSMORPHISM
   ========================================================================== */

.ne-card {
  position: relative;
  padding: var(--sp-6);
  border: 1px solid var(--c-border);
  border-radius: var(--r-xl);
  background: var(--c-bg-2);        /* fallback opaco: SIEMPRE legible */
  box-shadow: var(--sh-lg);
  overflow: hidden;
}

/* El cristal real solo se aplica si el navegador puede desenfocar.
   Sin este @supports, Firefox antiguo mostraba texto sobre casi nada. */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ne-card {
    background: var(--c-surface);
    -webkit-backdrop-filter: blur(var(--blur-lg)) saturate(160%);
    backdrop-filter: blur(var(--blur-lg)) saturate(160%);
  }
}

.ne-card--flat   { box-shadow: none; }
.ne-card--tight  { padding: var(--sp-4); }
.ne-card--roomy  { padding: var(--sp-8); }
.ne-card--solid  { background: var(--c-bg-2); backdrop-filter: none; -webkit-backdrop-filter: none; }

/* Elevación al pasar el ratón — solo con puntero fino: en móvil el :hover
   se queda "pegado" tras el toque y parece un bug. */
@media (hover: hover) and (pointer: fine) {
  .ne-card--hover {
    transition: transform var(--dur-base) var(--ease-out),
                box-shadow var(--dur-base) var(--ease-out),
                border-color var(--dur-base) var(--ease-out);
  }
  .ne-card--hover:hover {
    transform: translateY(-6px);
    box-shadow: var(--sh-xl);
    border-color: var(--c-border-strong);
  }
}

/* Borde con degradado animado. Truco: se pinta en un ::before con
   padding-box/border-box y una máscara, porque border-image no admite radios. */
.ne-card--glow::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: var(--g-brand);
  background-size: 220% 220%;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  animation: ne-border-flow 7s linear infinite;
  pointer-events: none;
  opacity: 0.85;
}

/* Brillo superior tipo "specular highlight" de cristal real. */
.ne-card--sheen::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.28), transparent);
  pointer-events: none;
}

/* Barra de acento superior (usada en invoice y tarjetas de estado). */
.ne-card--accent { padding-top: calc(var(--sp-6) + 4px); }
.ne-card--accent::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 4px;
  background: var(--g-brand);
  background-size: 300% 100%;
  animation: ne-border-flow 6s linear infinite;
}

.ne-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-5);
  flex-wrap: wrap;
}
.ne-card__title {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-text);
}
.ne-card__title i, .ne-card__title svg { color: var(--c-primary); flex: 0 0 auto; }


/* ==========================================================================
   08 · BOTONES
   ========================================================================== */

.ne-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-6);
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  overflow: hidden;              /* recorta el ripple */
  isolation: isolate;            /* el ripple no escapa del botón */
  transition: transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out),
              opacity var(--dur-fast) linear;
}

.ne-btn:active { transform: scale(0.97); }

.ne-btn[disabled], .ne-btn[aria-disabled="true"], .ne-btn.is-loading {
  opacity: 0.55;
  cursor: not-allowed;
  pointer-events: none;
}

/* --- Variantes ----------------------------------------------------------- */
.ne-btn--primary {
  background-image: var(--g-brand);
  background-size: 200% 200%;
  color: var(--c-text-on-accent);
  box-shadow: 0 6px 20px rgba(var(--c-primary-rgb), 0.35);
}
@media (hover: hover) {
  .ne-btn--primary:hover {
    background-position: 100% 0;
    box-shadow: 0 10px 30px rgba(var(--c-secondary-rgb), 0.45);
    transform: translateY(-2px);
  }
}

.ne-btn--ghost {
  background: var(--c-surface-2);
  border-color: var(--c-border-strong);
  color: var(--c-text);
}
@media (hover: hover) {
  .ne-btn--ghost:hover {
    background: var(--c-surface-3);
    border-color: var(--c-primary);
    color: var(--c-primary-300);
  }
}

.ne-btn--outline {
  background: transparent;
  border-color: var(--c-primary);
  color: var(--c-primary-300);
}
@media (hover: hover) {
  .ne-btn--outline:hover {
    background: rgba(var(--c-primary-rgb), 0.12);
    box-shadow: var(--sh-glow-primary);
  }
}

.ne-btn--success {
  background-image: var(--g-success);
  color: #04220f;                                   /* 11:1 sobre el verde */
  box-shadow: 0 6px 20px rgba(var(--c-success-rgb), 0.30);
}
.ne-btn--danger {
  background: rgba(var(--c-danger-rgb), 0.14);
  border-color: var(--c-danger);
  color: var(--c-danger);
}
@media (hover: hover) {
  .ne-btn--danger:hover { background: var(--c-danger); color: #fff; }
}

/* CTA de llamada: verde de "acción urgente", el más visible de la página. */
.ne-btn--call {
  background: var(--g-success);
  color: #04220f;
  box-shadow: 0 6px 22px rgba(var(--c-success-rgb), 0.40);
}

.ne-btn--link {
  padding: 0;
  border-radius: 0;
  background: none;
  color: var(--c-primary-300);
  text-transform: none;
  letter-spacing: 0;
}
.ne-btn--link:hover { color: var(--c-text); text-decoration: underline; }

/* --- Tamaños -------------------------------------------------------------- */
.ne-btn--sm { padding: var(--sp-2) var(--sp-4); font-size: var(--fs-xs); }
.ne-btn--lg { padding: var(--sp-4) var(--sp-8); font-size: var(--fs-md); }
.ne-btn--block { display: flex; width: 100%; }

/* --- Ripple --------------------------------------------------------------
   El JS inserta un <span class="ne-ripple"> en las coordenadas del clic y lo
   quita al terminar. CSS puro, sin librerías.                                */
.ne-ripple {
  position: absolute;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.45);
  transform: scale(0);
  animation: ne-ripple var(--dur-slow) var(--ease-out) forwards;
  pointer-events: none;
  z-index: var(--z-base);
}

/* --- Spinner de carga dentro del botón ------------------------------------ */
.ne-btn.is-loading { color: transparent !important; }
.ne-btn.is-loading::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 18px; height: 18px;
  border: 2px solid rgba(255, 255, 255, 0.30);
  border-top-color: #fff;
  border-radius: 50%;
  animation: ne-spin 700ms linear infinite;
}

/* --- Botón de icono -------------------------------------------------------- */
.ne-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-min);
  height: var(--tap-min);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface-2);
  color: var(--c-text-muted);
  cursor: pointer;
  transition: all var(--dur-fast) var(--ease-out);
}
@media (hover: hover) {
  .ne-icon-btn:hover {
    background: var(--c-surface-3);
    color: var(--c-primary-300);
    border-color: var(--c-primary);
  }
}


/* ==========================================================================
   09 · BADGES · PILLS · CHIPS
   ========================================================================== */

.ne-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-3);
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-pill);
  background: var(--c-surface-2);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-text-muted);
  white-space: nowrap;
}
.ne-badge--primary { background: rgba(var(--c-primary-rgb), 0.14); border-color: rgba(var(--c-primary-rgb), 0.45); color: var(--c-primary-300); }
.ne-badge--success { background: rgba(var(--c-success-rgb), 0.14); border-color: rgba(var(--c-success-rgb), 0.45); color: var(--c-success); }
.ne-badge--danger  { background: rgba(var(--c-danger-rgb), 0.14);  border-color: rgba(var(--c-danger-rgb), 0.45);  color: var(--c-danger); }
.ne-badge--warning { background: rgba(var(--c-warning-rgb), 0.14); border-color: rgba(var(--c-warning-rgb), 0.45); color: var(--c-warning); }

/* Punto de estado que late (pendiente / en vivo). */
.ne-badge__dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}
.ne-badge--pulse .ne-badge__dot { animation: ne-pulse-dot 1.8s ease-in-out infinite; }

/* Sello de confianza: "Licensed & Insured". */
.ne-trust {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-5);
  border: 1px solid rgba(var(--c-success-rgb), 0.35);
  border-radius: var(--r-pill);
  background: rgba(var(--c-success-rgb), 0.08);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-success);
}

/* Chips de filtro (portfolio, galería). */
.ne-chip {
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--c-border);
  border-radius: var(--r-pill);
  background: var(--c-surface);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  color: var(--c-text-muted);
  cursor: pointer;
  transition: all var(--dur-fast) var(--ease-out);
}
.ne-chip[aria-pressed="true"], .ne-chip.is-active {
  background: rgba(var(--c-primary-rgb), 0.16);
  border-color: var(--c-primary);
  color: var(--c-primary-300);
}


/* ==========================================================================
   10 · FORMULARIOS + INPUT OTP
   ========================================================================== */

.ne-field { display: flex; flex-direction: column; gap: var(--sp-2); }

.ne-label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-text-muted);
}
.ne-label .ne-req { color: var(--c-danger); margin-inline-start: 2px; }

.ne-input, .ne-textarea, .ne-select {
  width: 100%;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-bg-3);
  color: var(--c-text);
  /* 16px exactos: por debajo, iOS Safari hace zoom al enfocar el campo. */
  font-size: 16px;
  line-height: 1.45;
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
  appearance: none;
}
.ne-textarea { min-height: 120px; resize: vertical; }

.ne-input::placeholder, .ne-textarea::placeholder { color: var(--c-text-dim); opacity: 1; }

.ne-input:focus-visible, .ne-textarea:focus-visible, .ne-select:focus-visible {
  outline: none;
  border-color: var(--c-primary);
  background: var(--c-bg-2);
  box-shadow: 0 0 0 4px rgba(var(--c-primary-rgb), 0.18);
}

/* Solo marcamos en rojo tras interacción: :invalid a secas pinta en rojo
   campos vacíos que el usuario todavía no ha tocado. */
.ne-input:user-invalid, .ne-textarea:user-invalid, .ne-input.is-invalid {
  border-color: var(--c-danger);
  box-shadow: 0 0 0 4px rgba(var(--c-danger-rgb), 0.16);
}
.ne-input:user-valid, .ne-input.is-valid { border-color: rgba(var(--c-success-rgb), 0.55); }

.ne-help  { font-size: var(--fs-xs); color: var(--c-text-dim); }
.ne-error {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  color: var(--c-danger);
}

/* Flecha propia del select (appearance:none la quita). */
.ne-select {
  padding-inline-end: var(--sp-10);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2398a4b8'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--sp-4) center;
  background-size: 14px;
}

/* Campo con icono a la izquierda. */
.ne-input-group { position: relative; display: flex; align-items: center; }
.ne-input-group > .ne-input { padding-inline-start: var(--sp-10); }
.ne-input-group__icon {
  position: absolute;
  inset-inline-start: var(--sp-4);
  color: var(--c-text-dim);
  pointer-events: none;
}

/* Checkbox / radio de marca. */
.ne-check {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  cursor: pointer;
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
}
.ne-check input[type="checkbox"], .ne-check input[type="radio"] {
  flex: 0 0 auto;
  width: 20px; height: 20px;
  margin-top: 1px;
  accent-color: var(--c-primary);
  cursor: pointer;
}

/* --- Input OTP ------------------------------------------------------------
   Un campo por dígito. inputmode="numeric" abre el teclado numérico en móvil;
   autocomplete="one-time-code" permite el autorrelleno del SMS en iOS.        */
.ne-otp {
  display: flex;
  justify-content: center;
  gap: var(--sp-3);
  direction: ltr;                  /* los códigos siempre se leen izq→der */
}
.ne-otp__digit {
  width: clamp(48px, 13vw, 62px);
  height: clamp(56px, 15vw, 72px);
  padding: 0;
  border: 2px solid var(--c-border-strong);
  border-radius: var(--r-md);
  background: var(--c-bg-3);
  color: var(--c-text);
  font-size: clamp(1.5rem, 6vw, 2rem);
  font-weight: var(--fw-black);
  font-variant-numeric: tabular-nums;
  text-align: center;
  transition: all var(--dur-fast) var(--ease-out);
}
.ne-otp__digit:focus-visible {
  outline: none;
  border-color: var(--c-primary);
  box-shadow: 0 0 0 4px rgba(var(--c-primary-rgb), 0.22), var(--sh-glow-primary);
  transform: translateY(-2px);
}
.ne-otp__digit[data-filled="true"] {
  border-color: var(--c-success);
  background: rgba(var(--c-success-rgb), 0.08);
}
.ne-otp.is-error .ne-otp__digit {
  border-color: var(--c-danger);
  animation: ne-shake 420ms var(--ease-out);
}
/* Oculta los steppers que Chrome pone en input[type=number]. */
.ne-otp__digit::-webkit-outer-spin-button,
.ne-otp__digit::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Temporizador de reenvío del PIN. */
.ne-otp__timer {
  font-family: var(--ff-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm);
  color: var(--c-text-dim);
}
.ne-otp__timer.is-expiring { color: var(--c-warning); }

/* Autofill de Chrome: por defecto pinta el campo de azul claro y rompe el tema. */
.ne-input:-webkit-autofill,
.ne-input:-webkit-autofill:hover,
.ne-input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--c-text);
  -webkit-box-shadow: 0 0 0 1000px var(--c-bg-3) inset;
  caret-color: var(--c-text);
  transition: background-color 100000s ease-in-out 0s;
}

/* Honeypot antispam: invisible para humanos, visible para bots ingenuos.
   No se usa display:none porque muchos bots lo detectan. */
.ne-hp {
  position: absolute !important;
  left: -9999px;
  width: 1px; height: 1px;
  opacity: 0;
  pointer-events: none;
  /* El atributo HTML tabindex="-1" va en el markup, no aquí. */
}


/* ==========================================================================
   11 · HEADER STICKY + NAVEGACIÓN + DRAWER MÓVIL
   ========================================================================== */

.ne-header {
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: var(--z-header);
  height: var(--h-header);
  display: flex;
  align-items: center;
  /* El notch del iPhone en horizontal se come el logo sin este padding. */
  padding-inline: max(var(--gutter), env(safe-area-inset-left));
  border-bottom: 1px solid transparent;
  transition: height var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}

/* El JS añade .is-scrolled pasados ~40px. Compacta y opaca el header. */
.ne-header.is-scrolled {
  height: var(--h-header-scrolled);
  background: rgba(1, 4, 9, 0.82);
  border-bottom-color: var(--c-border);
  box-shadow: var(--sh-md);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ne-header.is-scrolled {
    background: rgba(1, 4, 9, 0.62);
    -webkit-backdrop-filter: blur(var(--blur-md)) saturate(180%);
    backdrop-filter: blur(var(--blur-md)) saturate(180%);
  }
}

/* Ocultar al bajar / mostrar al subir: devuelve altura de pantalla al leer. */
.ne-header.is-hidden { transform: translateY(-100%); transition: transform var(--dur-base) var(--ease-out); }

.ne-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  width: 100%;
  max-width: var(--w-container);
  margin-inline: auto;
}

.ne-logo {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  text-decoration: none;
  color: var(--c-text);
  flex: 0 0 auto;
}
.ne-logo img, .ne-logo svg { height: 34px; width: auto; }
.ne-logo__text {
  font-size: var(--fs-sm);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  line-height: 1;
}

/* Navegación de escritorio: oculta bajo 1024px. */
.ne-nav { display: none; }
@media (min-width: 1024px) {
  .ne-nav { display: flex; align-items: center; gap: var(--sp-1); }
}
.ne-nav__link {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-pill);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semi);
  color: var(--c-text-muted);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}
@media (hover: hover) {
  .ne-nav__link:hover { color: var(--c-text); background: var(--c-surface-2); }
}
/* aria-current="page" lo pone PHP: una sola fuente de verdad para el estado activo. */
.ne-nav__link[aria-current="page"] { color: var(--c-primary-300); }
.ne-nav__link[aria-current="page"]::after {
  content: "";
  position: absolute;
  bottom: 2px;
  left: 50%;
  transform: translateX(-50%);
  width: 18px; height: 2px;
  border-radius: var(--r-pill);
  background: var(--g-brand);
}

.ne-header__actions { display: flex; align-items: center; gap: var(--sp-2); }

/* Botón hamburguesa: tres líneas que se convierten en X. */
.ne-burger {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: var(--tap-min);
  height: var(--tap-min);
  padding: 0 10px;
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface-2);
}
@media (min-width: 1024px) { .ne-burger { display: none; } }
.ne-burger__bar {
  display: block;
  height: 2px;
  width: 100%;
  border-radius: var(--r-pill);
  background: var(--c-text);
  transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-fast) linear;
}
.ne-burger[aria-expanded="true"] .ne-burger__bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.ne-burger[aria-expanded="true"] .ne-burger__bar:nth-child(2) { opacity: 0; }
.ne-burger[aria-expanded="true"] .ne-burger__bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* --- Drawer móvil --------------------------------------------------------- */
.ne-drawer {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  display: flex;
  justify-content: flex-end;
  visibility: hidden;
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out), visibility 0s linear var(--dur-base);
}
.ne-drawer.is-open { visibility: visible; opacity: 1; transition-delay: 0s; }

.ne-drawer__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(1, 4, 9, 0.72);
  -webkit-backdrop-filter: blur(var(--blur-sm));
  backdrop-filter: blur(var(--blur-sm));
}
.ne-drawer__panel {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  width: min(88vw, 380px);
  height: 100%;
  padding: calc(var(--h-header) + var(--sp-4)) var(--sp-5)
           calc(var(--sp-8) + env(safe-area-inset-bottom));
  background: var(--c-bg-1);
  border-inline-start: 1px solid var(--c-border);
  box-shadow: var(--sh-xl);
  overflow-y: auto;
  overscroll-behavior: contain;
  transform: translateX(100%);
  transition: transform var(--dur-base) var(--ease-out);
}
.ne-drawer.is-open .ne-drawer__panel { transform: translateX(0); }

.ne-drawer__link {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--tap-min);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  font-size: var(--fs-md);
  font-weight: var(--fw-semi);
  color: var(--c-text);
  text-decoration: none;
}
.ne-drawer__link[aria-current="page"] {
  background: rgba(var(--c-primary-rgb), 0.14);
  color: var(--c-primary-300);
}
.ne-drawer__divider { height: 1px; background: var(--c-border); margin-block: var(--sp-3); }

/* Compensa el header fijo para que no tape el contenido. */
.ne-main { padding-top: var(--h-header); }
/* Excepción: el hero a pantalla completa se dibuja por debajo del header. */
.ne-main--flush { padding-top: 0; }


/* ==========================================================================
   12 · HERO
   ========================================================================== */

.ne-hero {
  position: relative;
  display: flex;
  align-items: center;
  /* 100svh, no 100vh: en móvil vh incluye la barra del navegador y corta el CTA. */
  min-height: 100svh;
  padding-block: calc(var(--h-header) + var(--sp-10)) var(--sp-12);
  overflow: hidden;
}

.ne-hero__bg {
  position: absolute;
  inset: 0;
  z-index: var(--z-behind);
  overflow: hidden;
}
.ne-hero__bg img, .ne-hero__bg video {
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0.34;
}
/* Velo oscuro: garantiza contraste del texto sobre CUALQUIER foto. */
.ne-hero__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(1, 4, 9, 0.55) 0%, rgba(1, 4, 9, 0.88) 70%, var(--c-bg-0) 100%),
    radial-gradient(circle at 20% 30%, rgba(var(--c-primary-rgb), 0.18), transparent 55%),
    radial-gradient(circle at 85% 65%, rgba(var(--c-secondary-rgb), 0.15), transparent 55%);
}

.ne-hero__inner {
  position: relative;
  z-index: var(--z-base);
  display: grid;
  gap: var(--sp-10);
  align-items: center;
  width: 100%;
  max-width: var(--w-container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
@media (min-width: 1024px) {
  .ne-hero__inner { grid-template-columns: 1.15fr 0.85fr; gap: var(--sp-16); }
}

.ne-hero__title {
  font-size: var(--fs-4xl);
  text-transform: uppercase;
  margin-bottom: var(--sp-5);
}
.ne-hero__sub {
  font-size: var(--fs-lg);
  font-weight: var(--fw-light);
  color: var(--c-text-muted);
  max-width: 54ch;
  margin-bottom: var(--sp-8);
}
.ne-hero__cta { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.ne-hero__proof {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-5);
  margin-top: var(--sp-8);
  padding-top: var(--sp-6);
  border-top: 1px solid var(--c-border);
}
.ne-hero__stat { display: flex; flex-direction: column; gap: 2px; }
.ne-hero__stat b {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-black);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.ne-hero__stat span {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-text-dim);
}

/* La maqueta del teléfono se define completa en la sección 45 (3D, reflejo,
   muesca y flotación). Aquí solo queda esta nota para no buscarla dos veces.

   CAMBIO respecto a la primera versión: ya NO se oculta en móvil. Se ocultaba
   por rendimiento, pero eso borraba el argumento de venta principal justo
   donde está el 70% del tráfico. Ahora sale más pequeña y flotando. */

/* Indicador de scroll. */
.ne-hero__scroll {
  position: absolute;
  bottom: calc(var(--sp-6) + env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-2xs);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-text-dim);
}
.ne-hero__scroll::after {
  content: "";
  width: 1px; height: 34px;
  background: linear-gradient(to bottom, var(--c-primary), transparent);
  animation: ne-scroll-hint 2.2s ease-in-out infinite;
}

/* --- Partículas de rayos ---------------------------------------------------
   Se anima transform/opacity (compositor GPU), nunca top/height, para no
   provocar layout ni paint en cada frame.                                     */
.ne-particles {
  position: absolute;
  inset: 0;
  z-index: var(--z-behind);
  overflow: hidden;
  pointer-events: none;
}
.ne-particle {
  position: absolute;
  top: -140px;
  width: 2px; height: 110px;
  background: linear-gradient(to bottom, transparent, var(--c-primary), transparent);
  opacity: 0;
  will-change: transform, opacity;
  animation: ne-bolt var(--p-dur, 4s) linear var(--p-delay, 0s) infinite;
}
.ne-particle:nth-child(even) {
  background: linear-gradient(to bottom, transparent, var(--c-secondary), transparent);
}
/* En móvil menos partículas: cada una es una capa compuesta y cuesta batería. */
@media (max-width: 767px) { .ne-particle:nth-child(n + 7) { display: none; } }
@media (prefers-reduced-motion: reduce) { .ne-particles { display: none; } }


/* ==========================================================================
   13 · TARJETAS DE SERVICIO / FEATURES
   ========================================================================== */

.ne-service {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-6);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  background: var(--c-surface);
  text-decoration: none;
  color: inherit;
  height: 100%;
  transition: transform var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .ne-service:hover {
    transform: translateY(-6px);
    border-color: rgba(var(--c-primary-rgb), 0.5);
    background: var(--c-surface-2);
  }
  .ne-service:hover .ne-service__icon { transform: scale(1.08) rotate(-6deg); }
}

.ne-service__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px; height: 52px;
  border: 1px solid rgba(var(--c-primary-rgb), 0.35);
  border-radius: var(--r-md);
  background: rgba(var(--c-primary-rgb), 0.12);
  color: var(--c-primary-300);
  font-size: 22px;
  transition: transform var(--dur-base) var(--ease-spring);
}
.ne-service__title { font-size: var(--fs-lg); font-weight: var(--fw-bold); letter-spacing: 0; }
.ne-service__text  { font-size: var(--fs-sm); color: var(--c-text-muted); }
.ne-service__link  {
  margin-top: auto;
  padding-top: var(--sp-3);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-primary-300);
}

/* Efecto tilt 3D. El JS escribe --tilt-x / --tilt-y desde la posición del ratón. */
@media (hover: hover) and (pointer: fine) {
  .ne-tilt {
    transform-style: preserve-3d;
    transform: perspective(900px)
               rotateX(var(--tilt-x, 0deg))
               rotateY(var(--tilt-y, 0deg));
    transition: transform var(--dur-fast) var(--ease-out);
  }
}


/* ==========================================================================
   14 · PORTFOLIO
   ========================================================================== */

.ne-work {
  position: relative;
  display: block;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--c-bg-2);
  aspect-ratio: 4 / 3;
  text-decoration: none;
  color: var(--c-text);
}
.ne-work img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out), filter var(--dur-base) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .ne-work:hover img { transform: scale(1.07); }
  .ne-work:hover .ne-work__body { transform: translateY(0); }
}
.ne-work__body {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  padding: var(--sp-8) var(--sp-5) var(--sp-5);
  background: linear-gradient(to top, rgba(1, 4, 9, 0.95) 25%, transparent);
}
/* En táctil el texto se muestra siempre: no hay hover que revelar. */
@media (hover: hover) and (pointer: fine) {
  .ne-work__body { transform: translateY(12px); transition: transform var(--dur-base) var(--ease-out); }
}
.ne-work__title { font-size: var(--fs-md); font-weight: var(--fw-bold); letter-spacing: 0; }
.ne-work__meta  { font-size: var(--fs-xs); color: var(--c-text-muted); }


/* ==========================================================================
   15 · RESEÑAS
   --------------------------------------------------------------------------
   NOTA IMPORTANTE: mientras las reseñas sean de ejemplo, functions.php NO
   emite schema AggregateRating (decisión tomada en la auditoría). Este bloque
   queda listo para las reseñas reales que se capturen desde el portal.
   ========================================================================== */

.ne-review {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  width: min(340px, 84vw);
  padding: var(--sp-6);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  background: var(--c-surface);
}
.ne-review__stars { display: inline-flex; gap: 2px; color: var(--c-warning); font-size: var(--fs-sm); }
.ne-review__text  { font-size: var(--fs-sm); color: var(--c-text-muted); font-style: italic; }
.ne-review__who   { display: flex; align-items: center; gap: var(--sp-3); margin-top: auto; }
.ne-review__avatar {
  display: grid;
  place-items: center;
  width: 42px; height: 42px;
  border-radius: 50%;
  background: var(--g-brand);
  color: #fff;
  font-size: var(--fs-xs);
  font-weight: var(--fw-black);
  flex: 0 0 auto;
}
.ne-review__name { font-size: var(--fs-sm); font-weight: var(--fw-bold); }
.ne-review__loc  { font-size: var(--fs-xs); color: var(--c-text-dim); }


/* ==========================================================================
   16 · BARRA DE MARCAS (marquee)
   --------------------------------------------------------------------------
   Etiquetada "Brands We Install" — NUNCA "Partners" ni "Trusted by", porque
   son fabricantes cuyo equipo se instala, no avales comerciales.
   ========================================================================== */

.ne-marquee {
  position: relative;
  overflow: hidden;
  padding-block: var(--sp-6);
  /* Desvanece los bordes para que no haya un corte seco. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.ne-marquee__track {
  display: flex;
  gap: var(--sp-12);
  width: max-content;
  animation: ne-marquee 38s linear infinite;
}
.ne-marquee:hover .ne-marquee__track { animation-play-state: paused; }
.ne-marquee__item {
  display: inline-flex;
  align-items: center;
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-text-dim);
  white-space: nowrap;
}
.ne-marquee__item img { height: 26px; width: auto; opacity: 0.6; filter: grayscale(1); }


/* ==========================================================================
   17 · ACORDEÓN FAQ
   --------------------------------------------------------------------------
   <details>/<summary> nativo: accesible por teclado y con lector de pantalla
   sin una línea de JS, y Google lo indexa para el schema FAQPage.
   ========================================================================== */

.ne-faq { display: flex; flex-direction: column; gap: var(--sp-3); }

.ne-faq__item {
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface);
  overflow: hidden;
}
.ne-faq__item[open] { border-color: rgba(var(--c-primary-rgb), 0.45); }

.ne-faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  font-size: var(--fs-md);
  font-weight: var(--fw-semi);
  cursor: pointer;
  list-style: none;
}
.ne-faq__q::-webkit-details-marker { display: none; }   /* quita el triángulo de Safari */
.ne-faq__q::after {
  content: "+";
  flex: 0 0 auto;
  font-size: var(--fs-xl);
  font-weight: var(--fw-light);
  line-height: 1;
  color: var(--c-primary);
  transition: transform var(--dur-base) var(--ease-out);
}
.ne-faq__item[open] .ne-faq__q::after { transform: rotate(45deg); }

.ne-faq__a {
  padding: 0 var(--sp-5) var(--sp-5);
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
}
.ne-faq__item[open] .ne-faq__a { animation: ne-fade-up var(--dur-base) var(--ease-out); }


/* ==========================================================================
   18 · FOOTER
   ========================================================================== */

.ne-footer {
  position: relative;
  padding-block: var(--sp-16) var(--sp-8);
  border-top: 1px solid var(--c-border);
  background: var(--c-bg-0);
  /* Deja hueco para la barra CTA fija en móvil. */
  padding-bottom: calc(var(--sp-8) + var(--h-mobile-cta) + env(safe-area-inset-bottom));
}
@media (min-width: 1024px) { .ne-footer { padding-bottom: var(--sp-8); } }

.ne-footer__grid {
  display: grid;
  gap: var(--sp-8);
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  margin-bottom: var(--sp-10);
}
.ne-footer__title {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-text);
  margin-bottom: var(--sp-4);
}
.ne-footer__list { list-style: none; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.ne-footer__list a {
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
  text-decoration: none;
}
.ne-footer__list a:hover { color: var(--c-primary-300); }

/* NAP: debe ser idéntico byte a byte al de Google Business Profile y al schema. */
.ne-footer__nap {
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
  font-style: normal;
}
.ne-footer__nap strong { color: var(--c-text); display: block; margin-bottom: var(--sp-1); }

.ne-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-top: var(--sp-6);
  border-top: 1px solid var(--c-border);
  font-size: var(--fs-xs);
  color: var(--c-text-dim);
}

/* Selector de idioma EN/ES. */
.ne-lang { display: inline-flex; border: 1px solid var(--c-border); border-radius: var(--r-pill); overflow: hidden; }
.ne-lang__btn {
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  color: var(--c-text-dim);
  transition: all var(--dur-fast) var(--ease-out);
}
.ne-lang__btn[aria-pressed="true"] { background: var(--c-primary); color: var(--c-text-on-accent); }


/* ==========================================================================
   19 · BARRA CTA MÓVIL FIJA
   --------------------------------------------------------------------------
   El 70% de "electrician near me" es móvil y urgente. El botón de llamar
   NUNCA debe quedar fuera de pantalla. Se oculta en el dashboard (ahí el
   usuario ya es cliente) y en escritorio.
   ========================================================================== */

.ne-mobile-cta {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: var(--z-sticky);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1px;
  height: calc(var(--h-mobile-cta) + env(safe-area-inset-bottom));
  padding-bottom: env(safe-area-inset-bottom);
  background: var(--c-border);
  border-top: 1px solid var(--c-border);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ne-mobile-cta { -webkit-backdrop-filter: blur(var(--blur-md)); backdrop-filter: blur(var(--blur-md)); }
}
@media (min-width: 1024px) { .ne-mobile-cta { display: none; } }
body.ne-is-dashboard .ne-mobile-cta { display: none; }

.ne-mobile-cta__btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  background: rgba(10, 14, 23, 0.94);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--c-text);
}
.ne-mobile-cta__btn i, .ne-mobile-cta__btn svg { font-size: 17px; }
.ne-mobile-cta__btn--call  { background: var(--c-success); color: #04220f; }
.ne-mobile-cta__btn--quote { color: var(--c-primary-300); }


/* ==========================================================================
   20 · DASHBOARD · KPIs, PROGRESO, FASES
   ========================================================================== */

.ne-dash { padding-block: calc(var(--h-header) + var(--sp-8)) var(--sp-16); }

.ne-dash__welcome {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-bottom: var(--sp-8);
}
.ne-dash__hello { font-size: var(--fs-2xl); font-weight: var(--fw-black); }
.ne-dash__date  { font-size: var(--fs-xs); color: var(--c-text-dim); letter-spacing: var(--ls-wide); text-transform: uppercase; }

/* --- KPI ------------------------------------------------------------------ */
.ne-kpi {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-5);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  background: var(--c-surface);
}
.ne-kpi__label {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-text-dim);
}
.ne-kpi__value {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-black);
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.ne-kpi__foot { font-size: var(--fs-xs); color: var(--c-text-muted); }
.ne-kpi--danger  .ne-kpi__value { color: var(--c-danger); }
.ne-kpi--success .ne-kpi__value { color: var(--c-success); }

/* --- Barra de progreso ----------------------------------------------------
   El ancho se pasa por --progress (inline desde PHP), no por width literal:
   así el mismo CSS sirve para 0% y para 100%.                                 */
.ne-progress {
  position: relative;
  height: 12px;
  border-radius: var(--r-pill);
  background: var(--c-surface-3);
  overflow: hidden;
}
.ne-progress__bar {
  position: relative;
  height: 100%;
  width: var(--progress, 0%);
  border-radius: inherit;
  background: var(--g-brand);
  box-shadow: var(--sh-glow-primary);
  transition: width var(--dur-xslow) var(--ease-out);
}
/* Brillo que recorre la barra: señal de "esto está vivo". */
.ne-progress__bar::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.42), transparent);
  animation: ne-shimmer 2.4s linear infinite;
}
.ne-progress__label {
  display: flex;
  justify-content: space-between;
  margin-bottom: var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-text-muted);
}
.ne-progress__pct { color: var(--c-primary-300); font-variant-numeric: tabular-nums; }

/* --- Rastreador de fases (Permits → Rough-In → Fixtures → Final) ----------- */
.ne-phases {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-2);
  margin-top: var(--sp-6);
  text-align: center;
}
/* Riel de fondo, centrado sobre los círculos de 36px (18px = radio). */
.ne-phases::before {
  content: "";
  position: absolute;
  top: 17px;
  left: 12.5%;
  right: 12.5%;
  height: 3px;
  border-radius: var(--r-pill);
  background: var(--c-surface-3);
}
/* Riel de progreso: --phase-fill lo calcula PHP a partir de fase_de_projecto. */
.ne-phases::after {
  content: "";
  position: absolute;
  top: 17px;
  left: 12.5%;
  width: calc((100% - 25%) * var(--phase-fill, 0));
  height: 3px;
  border-radius: var(--r-pill);
  background: var(--g-brand);
  box-shadow: var(--sh-glow-primary);
  transition: width var(--dur-xslow) var(--ease-out);
}
.ne-phase { position: relative; z-index: var(--z-base); }
.ne-phase__dot {
  display: grid;
  place-items: center;
  width: 36px; height: 36px;
  margin: 0 auto var(--sp-2);
  border: 2px solid var(--c-surface-3);
  border-radius: 50%;
  background: var(--c-bg-1);
  color: var(--c-text-dim);
  font-size: 13px;
  transition: all var(--dur-base) var(--ease-out);
}
.ne-phase__name {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-text-dim);
}
.ne-phase.is-done .ne-phase__dot {
  border-color: var(--c-primary);
  background: rgba(var(--c-primary-rgb), 0.16);
  color: var(--c-primary-300);
  box-shadow: var(--sh-glow-primary);
}
.ne-phase.is-done .ne-phase__name { color: var(--c-text); }
.ne-phase.is-current .ne-phase__dot { animation: ne-pulse-ring 2.2s ease-out infinite; }

/* --- Próxima visita -------------------------------------------------------- */
.ne-visit {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4);
  border: 1px solid rgba(var(--c-primary-rgb), 0.35);
  border-radius: var(--r-md);
  background: rgba(var(--c-primary-rgb), 0.08);
}
.ne-visit__date {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 56px; height: 56px;
  border-radius: var(--r-md);
  background: var(--g-brand);
  color: #fff;
  line-height: 1;
  text-align: center;
}
.ne-visit__date b { font-size: var(--fs-lg); font-weight: var(--fw-black); }
.ne-visit__date span { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: var(--ls-wide); }

/* --- Mapa incrustado -------------------------------------------------------
   El filtro invierte el mapa claro de Google a oscuro. Es un hack, pero es la
   única forma sin la API de pago con estilos personalizados.                   */
.ne-map {
  border-radius: var(--r-md);
  overflow: hidden;
  border: 1px solid var(--c-border);
  aspect-ratio: 16 / 10;
}
.ne-map iframe { width: 100%; height: 100%; border: 0; }
.ne-map iframe { filter: invert(90%) hue-rotate(180deg) brightness(0.92) contrast(1.05); }


/* ==========================================================================
   21 · GALERÍA + LIGHTBOX TÁCTIL
   ========================================================================== */

.ne-gallery {
  display: grid;
  gap: var(--sp-3);
  grid-template-columns: repeat(auto-fill, minmax(min(140px, 45%), 1fr));
}
.ne-gallery__item {
  position: relative;
  aspect-ratio: 1;
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-bg-2);
  overflow: hidden;
  cursor: zoom-in;
}
.ne-gallery__item img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-base) linear;
}
@media (hover: hover) and (pointer: fine) {
  .ne-gallery__item:hover img { transform: scale(1.09); }
}
/* Número de foto, para que el cliente pueda referirse a "la foto 12". */
.ne-gallery__n {
  position: absolute;
  top: var(--sp-2);
  inset-inline-start: var(--sp-2);
  padding: 2px 7px;
  border-radius: var(--r-xs);
  background: rgba(1, 4, 9, 0.75);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  color: var(--c-text-muted);
}

.ne-gallery__empty {
  padding: var(--sp-8);
  border: 1px dashed var(--c-border-strong);
  border-radius: var(--r-md);
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--c-text-dim);
}

/* --- Lightbox -------------------------------------------------------------- */
.ne-lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-lightbox);
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(1, 4, 9, 0.96);
  visibility: hidden;
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out), visibility 0s linear var(--dur-base);
  /* Deja que el navegador gestione el gesto de pinza para zoom. */
  touch-action: pinch-zoom;
}
.ne-lightbox.is-open { visibility: visible; opacity: 1; transition-delay: 0s; }

.ne-lightbox__stage {
  position: relative;
  width: 100%; height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: max(var(--sp-8), env(safe-area-inset-top))
           var(--sp-4)
           max(var(--sp-16), env(safe-area-inset-bottom));
}
.ne-lightbox__img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: var(--r-sm);
  /* --swipe lo escribe el JS durante el arrastre para que la foto siga al dedo. */
  transform: translateX(var(--swipe, 0px));
  transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-fast) linear;
  user-select: none;
  -webkit-user-drag: none;
}
.ne-lightbox.is-dragging .ne-lightbox__img { transition: none; }
.ne-lightbox__img.is-switching { opacity: 0; }

.ne-lightbox__close {
  position: absolute;
  top: max(var(--sp-4), env(safe-area-inset-top));
  inset-inline-end: var(--sp-4);
  z-index: var(--z-raised);
}
.ne-lightbox__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 48px; height: 48px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.10);
  border: 1px solid var(--c-border-strong);
  color: var(--c-text);
  display: grid;
  place-items: center;
}
.ne-lightbox__nav--prev { inset-inline-start: var(--sp-3); }
.ne-lightbox__nav--next { inset-inline-end: var(--sp-3); }
/* En móvil se navega deslizando; las flechas estorban sobre la foto. */
@media (max-width: 767px) { .ne-lightbox__nav { display: none; } }

.ne-lightbox__bar {
  position: absolute;
  inset-inline: 0;
  bottom: max(var(--sp-5), env(safe-area-inset-bottom));
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  font-size: var(--fs-xs);
  color: var(--c-text-muted);
}
.ne-lightbox__counter { font-variant-numeric: tabular-nums; letter-spacing: var(--ls-wide); }
.ne-lightbox__hint { font-size: var(--fs-2xs); color: var(--c-text-dim); }
@media (hover: hover) and (pointer: fine) { .ne-lightbox__hint { display: none; } }

/* Puntos de posición. */
.ne-lightbox__dots { display: flex; gap: 6px; }
.ne-lightbox__dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--c-surface-3);
  transition: all var(--dur-fast) var(--ease-out);
}
.ne-lightbox__dot.is-active { background: var(--c-primary); width: 18px; border-radius: var(--r-pill); }


/* ==========================================================================
   22 · VISOR 3D
   ========================================================================== */

.ne-viewer {
  position: relative;
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  background: var(--c-bg-2);
  overflow: hidden;
  aspect-ratio: 16 / 10;
}
.ne-viewer__img {
  width: 100%; height: 100%;
  object-fit: contain;
  background: var(--c-bg-0);
  cursor: grab;
  transition: opacity var(--dur-base) linear;
}
.ne-viewer__img:active { cursor: grabbing; }
.ne-viewer__img.is-loading { opacity: 0.25; }

.ne-viewer__bar {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: linear-gradient(to top, rgba(1, 4, 9, 0.92), transparent);
}
.ne-viewer__thumbs { display: flex; gap: var(--sp-2); overflow-x: auto; scrollbar-width: none; }
.ne-viewer__thumbs::-webkit-scrollbar { display: none; }
.ne-viewer__thumb {
  flex: 0 0 auto;
  width: 46px; height: 34px;
  border: 2px solid transparent;
  border-radius: var(--r-xs);
  overflow: hidden;
  opacity: 0.55;
  transition: all var(--dur-fast) var(--ease-out);
}
.ne-viewer__thumb img { width: 100%; height: 100%; object-fit: cover; }
.ne-viewer__thumb.is-active { border-color: var(--c-primary); opacity: 1; }

.ne-viewer__hint {
  position: absolute;
  top: var(--sp-3);
  inset-inline-start: var(--sp-3);
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-pill);
  background: rgba(1, 4, 9, 0.7);
  font-size: var(--fs-2xs);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-text-dim);
  pointer-events: none;
  transition: opacity var(--dur-base) linear;
}
.ne-viewer.is-touched .ne-viewer__hint { opacity: 0; }


/* ==========================================================================
   23 · MODALES
   ========================================================================== */

.ne-modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: flex-end;          /* móvil: hoja inferior */
  justify-content: center;
  visibility: hidden;
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out), visibility 0s linear var(--dur-base);
}
.ne-modal.is-open { visibility: visible; opacity: 1; transition-delay: 0s; }
@media (min-width: 768px) { .ne-modal { align-items: center; } }

.ne-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(1, 4, 9, 0.78);
  -webkit-backdrop-filter: blur(var(--blur-sm));
  backdrop-filter: blur(var(--blur-sm));
}

.ne-modal__panel {
  position: relative;
  width: 100%;
  max-width: 640px;
  max-height: 92svh;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  background: var(--c-bg-1);
  box-shadow: var(--sh-xl);
  transform: translateY(24px);
  transition: transform var(--dur-base) var(--ease-out);
  overflow: hidden;
}
.ne-modal.is-open .ne-modal__panel { transform: translateY(0); }
@media (min-width: 768px) {
  .ne-modal__panel { border-radius: var(--r-xl); transform: translateY(0) scale(0.96); }
  .ne-modal.is-open .ne-modal__panel { transform: translateY(0) scale(1); }
}
.ne-modal__panel--wide { max-width: 900px; }

/* Asa visual de la hoja inferior en móvil. */
.ne-modal__grip {
  width: 40px; height: 4px;
  margin: var(--sp-3) auto 0;
  border-radius: var(--r-pill);
  background: var(--c-border-strong);
  flex: 0 0 auto;
}
@media (min-width: 768px) { .ne-modal__grip { display: none; } }

.ne-modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-6);
  border-bottom: 1px solid var(--c-border);
  flex: 0 0 auto;
}
.ne-modal__title { font-size: var(--fs-lg); font-weight: var(--fw-bold); letter-spacing: 0; }
.ne-modal__body {
  padding: var(--sp-6);
  overflow-y: auto;
  overscroll-behavior: contain;   /* el scroll no "escapa" al body al llegar al final */
  flex: 1 1 auto;
}
.ne-modal__foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--sp-3);
  padding: var(--sp-5) var(--sp-6) calc(var(--sp-5) + env(safe-area-inset-bottom));
  border-top: 1px solid var(--c-border);
  flex: 0 0 auto;
}


/* ==========================================================================
   24 · MÉTODOS DE PAGO
   --------------------------------------------------------------------------
   Datos exactos, no editables sin autorización:
     Zelle®        Yeison George · (512) 430-3859
     Cash App      $YeisonGeorge
     Bank Transfer Yeison Orlando George
     Paper Check   Yeison Orlando George ($25 fee si rebota)
     Regla         50% de depósito inicial obligatorio
   ========================================================================== */

.ne-pay { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }

.ne-pay__card {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface);
  text-align: start;
  cursor: pointer;
  transition: all var(--dur-fast) var(--ease-out);
}
@media (hover: hover) {
  .ne-pay__card:hover { border-color: var(--c-primary); background: var(--c-surface-2); }
}
.ne-pay__card.is-selected {
  border-color: var(--c-success);
  background: rgba(var(--c-success-rgb), 0.08);
}
.ne-pay__icon {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 44px; height: 44px;
  border-radius: var(--r-sm);
  background: var(--c-surface-3);
  font-size: 19px;
  color: var(--c-primary-300);
}
.ne-pay__name  { font-size: var(--fs-sm); font-weight: var(--fw-bold); }
.ne-pay__value { font-size: var(--fs-xs); color: var(--c-text-muted); font-family: var(--ff-mono); word-break: break-all; }

/* Botón copiar al portapapeles. */
.ne-copy {
  margin-inline-start: auto;
  flex: 0 0 auto;
  padding: var(--sp-2);
  border-radius: var(--r-xs);
  color: var(--c-text-dim);
  transition: color var(--dur-fast) var(--ease-out);
}
.ne-copy:hover { color: var(--c-primary-300); }
.ne-copy.is-copied { color: var(--c-success); }

/* Aviso del 50% de depósito. */
.ne-pay__rule {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid rgba(var(--c-warning-rgb), 0.40);
  border-left-width: 3px;
  border-radius: var(--r-md);
  background: rgba(var(--c-warning-rgb), 0.08);
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
}
.ne-pay__rule i, .ne-pay__rule svg { color: var(--c-warning); flex: 0 0 auto; margin-top: 3px; }
.ne-pay__rule strong { color: var(--c-warning); }


/* ==========================================================================
   25 · INVOICE
   ========================================================================== */

.ne-invoice { container-type: inline-size; }

.ne-invoice__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-6);
  padding-bottom: var(--sp-6);
  border-bottom: 1px solid var(--c-border);
  margin-bottom: var(--sp-6);
}
.ne-invoice__brand h2 {
  font-size: var(--fs-xl);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  border-inline-start: 3px solid var(--c-primary);
  padding-inline-start: var(--sp-3);
}
.ne-invoice__doc {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-text-dim);
}
.ne-invoice__num { font-family: var(--ff-mono); font-size: var(--fs-sm); color: var(--c-primary-300); }

.ne-invoice__meta {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
  padding: var(--sp-5);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-bg-2);
  margin-bottom: var(--sp-6);
}
.ne-invoice__meta h3 {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-primary-300);
  margin-bottom: var(--sp-2);
}
.ne-invoice__meta p { font-size: var(--fs-sm); color: var(--c-text-muted); }
.ne-invoice__meta strong { color: var(--c-text); display: block; }

/* El scope viene de un WYSIWYG de ACF: hay que dar estilo al HTML del cliente. */
.ne-invoice__scope { font-size: var(--fs-sm); color: var(--c-text-muted); }
.ne-invoice__scope ul, .ne-invoice__scope ol { padding-inline-start: 1.3em; }
.ne-invoice__scope li { margin-bottom: var(--sp-2); }
.ne-invoice__scope li::marker { color: var(--c-primary); }
.ne-invoice__scope h1, .ne-invoice__scope h2, .ne-invoice__scope h3, .ne-invoice__scope h4 {
  font-size: var(--fs-md);
  margin-block: var(--sp-4) var(--sp-2);
  color: var(--c-text);
}
.ne-invoice__scope p + p { margin-top: var(--sp-3); }
.ne-invoice__scope strong { color: var(--c-text); }

/* Totales. */
.ne-totals { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-6); }
.ne-totals__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-3) 0;
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
}
.ne-totals__row + .ne-totals__row { border-top: 1px solid var(--c-border); }
.ne-totals__row--grand {
  margin-top: var(--sp-2);
  padding: var(--sp-4);
  border: 1px solid rgba(var(--c-primary-rgb), 0.40);
  border-radius: var(--r-md);
  background: rgba(var(--c-primary-rgb), 0.08);
  font-size: var(--fs-lg);
  font-weight: var(--fw-black);
  color: var(--c-text);
}
.ne-totals__row--paid { color: var(--c-success); }

/* Tabla de partidas: en pantallas estrechas se apila con data-label. */
.ne-items { width: 100%; font-size: var(--fs-sm); }
.ne-items th {
  padding: var(--sp-3);
  border-bottom: 1px solid var(--c-border-strong);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-text-dim);
  text-align: start;
}
.ne-items td { padding: var(--sp-3); border-bottom: 1px solid var(--c-border); color: var(--c-text-muted); }
.ne-items td:last-child, .ne-items th:last-child { text-align: end; font-variant-numeric: tabular-nums; }

@container (max-width: 560px) {
  .ne-items thead { display: none; }
  .ne-items tr { display: block; padding: var(--sp-3) 0; border-bottom: 1px solid var(--c-border); }
  .ne-items td { display: flex; justify-content: space-between; gap: var(--sp-4); border: 0; padding: var(--sp-1) 0; }
  .ne-items td::before {
    content: attr(data-label);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-wide);
    text-transform: uppercase;
    color: var(--c-text-dim);
  }
}


/* ==========================================================================
   26 · FIRMA LEGAL
   ========================================================================== */

.ne-sign__box {
  padding: var(--sp-5);
  border: 1px dashed var(--c-border-strong);
  border-radius: var(--r-md);
  background: var(--c-bg-2);
}
/* Campo de firma: tipografía cursiva para que "se sienta" una firma. */
.ne-sign__input {
  width: 100%;
  padding: var(--sp-4);
  border: 0;
  border-bottom: 2px solid var(--c-border-strong);
  border-radius: 0;
  background: transparent;
  font-family: "Segoe Script", "Brush Script MT", cursive;
  font-size: clamp(1.4rem, 5vw, 1.9rem);
  color: var(--c-text);
  text-align: center;
}
.ne-sign__input:focus-visible { outline: none; border-bottom-color: var(--c-primary); box-shadow: none; }

.ne-sign__legal {
  margin-top: var(--sp-4);
  font-size: var(--fs-xs);
  line-height: 1.65;
  color: var(--c-text-dim);
}

/* Estado firmado: sello verde con el registro legal completo. */
.ne-sign__done {
  padding: var(--sp-6);
  border: 2px solid var(--c-success);
  border-radius: var(--r-lg);
  background: rgba(var(--c-success-rgb), 0.08);
  box-shadow: var(--sh-glow-success);
  text-align: center;
}
.ne-sign__done i, .ne-sign__done svg { font-size: 38px; color: var(--c-success); }
.ne-sign__done h4 {
  margin-block: var(--sp-3) var(--sp-2);
  font-size: var(--fs-md);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-success);
}
.ne-sign__name {
  font-family: "Segoe Script", "Brush Script MT", cursive;
  font-size: var(--fs-xl);
  color: var(--c-text);
  text-transform: capitalize;
}
/* Auditoría: fecha, hora TX, IP. Letra pequeña a propósito, pero legible (AA). */
.ne-sign__audit {
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid rgba(var(--c-success-rgb), 0.28);
  font-family: var(--ff-mono);
  font-size: var(--fs-2xs);
  line-height: 1.8;
  color: var(--c-text-muted);
}


/* ==========================================================================
   27 · BOT BILINGÜE
   ========================================================================== */

.ne-bot { position: fixed; z-index: var(--z-bot); inset-inline-end: var(--sp-4); }

.ne-bot__launcher {
  position: fixed;
  z-index: var(--z-bot);
  inset-inline-end: var(--sp-4);
  /* Se sube por encima de la barra CTA móvil para no solaparse. */
  bottom: calc(var(--h-mobile-cta) + var(--sp-4) + env(safe-area-inset-bottom));
  display: grid;
  place-items: center;
  width: 56px; height: 56px;
  border-radius: 50%;
  background: var(--g-brand);
  color: #fff;
  font-size: 22px;
  box-shadow: var(--sh-lg), var(--sh-glow-primary);
  transition: transform var(--dur-base) var(--ease-spring);
}
@media (min-width: 1024px) { .ne-bot__launcher { bottom: var(--sp-6); } }
@media (hover: hover) { .ne-bot__launcher:hover { transform: scale(1.09); } }
.ne-bot__launcher[aria-expanded="true"] { transform: rotate(90deg); }

/* Globo de aviso de mensaje nuevo. */
.ne-bot__dot {
  position: absolute;
  top: 2px; inset-inline-end: 2px;
  width: 12px; height: 12px;
  border: 2px solid var(--c-bg-0);
  border-radius: 50%;
  background: var(--c-danger);
  animation: ne-pulse-dot 1.8s ease-in-out infinite;
}

.ne-bot__panel {
  position: fixed;
  z-index: var(--z-bot);
  inset-inline-end: var(--sp-4);
  bottom: calc(var(--h-mobile-cta) + 76px + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  width: min(380px, calc(100vw - var(--sp-8)));
  height: min(560px, calc(100svh - 180px));
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-lg);
  background: var(--c-bg-1);
  box-shadow: var(--sh-xl);
  overflow: hidden;
  transform-origin: bottom right;
  transform: scale(0.9) translateY(12px);
  opacity: 0;
  visibility: hidden;
  transition: transform var(--dur-base) var(--ease-out),
              opacity var(--dur-base) var(--ease-out),
              visibility 0s linear var(--dur-base);
}
@media (min-width: 1024px) { .ne-bot__panel { bottom: calc(var(--sp-6) + 70px); } }
.ne-bot__panel.is-open { transform: scale(1) translateY(0); opacity: 1; visibility: visible; transition-delay: 0s; }

.ne-bot__head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border-bottom: 1px solid var(--c-border);
  background: var(--c-bg-2);
  flex: 0 0 auto;
}
.ne-bot__avatar {
  display: grid;
  place-items: center;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: var(--g-brand);
  color: #fff;
  flex: 0 0 auto;
}
.ne-bot__name { font-size: var(--fs-sm); font-weight: var(--fw-bold); }
.ne-bot__status { display: flex; align-items: center; gap: 5px; font-size: var(--fs-2xs); color: var(--c-success); }
.ne-bot__status::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
  animation: ne-pulse-dot 2s ease-in-out infinite;
}

.ne-bot__log {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-4);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.ne-bot__msg {
  max-width: 82%;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  line-height: 1.55;
  animation: ne-fade-up var(--dur-base) var(--ease-out);
  overflow-wrap: break-word;
}
.ne-bot__msg--bot {
  align-self: flex-start;
  border: 1px solid var(--c-border);
  border-bottom-left-radius: var(--r-xs);
  background: var(--c-surface-2);
  color: var(--c-text);
}
.ne-bot__msg--user {
  align-self: flex-end;
  border-bottom-right-radius: var(--r-xs);
  background: var(--c-primary);
  color: var(--c-text-on-accent);
}
.ne-bot__msg--error {
  align-self: center;
  background: rgba(var(--c-danger-rgb), 0.12);
  color: var(--c-danger);
  font-size: var(--fs-xs);
  text-align: center;
}
.ne-bot__msg a { color: inherit; text-decoration: underline; }

/* Indicador "escribiendo…". */
.ne-bot__typing { display: inline-flex; gap: 4px; align-items: center; }
.ne-bot__typing span {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--c-text-dim);
  animation: ne-typing 1.3s ease-in-out infinite;
}
.ne-bot__typing span:nth-child(2) { animation-delay: 0.18s; }
.ne-bot__typing span:nth-child(3) { animation-delay: 0.36s; }

/* Respuestas rápidas sugeridas. */
.ne-bot__quick { display: flex; flex-wrap: wrap; gap: var(--sp-2); padding: 0 var(--sp-4) var(--sp-3); }
.ne-bot__quick button {
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid rgba(var(--c-primary-rgb), 0.40);
  border-radius: var(--r-pill);
  background: rgba(var(--c-primary-rgb), 0.10);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semi);
  color: var(--c-primary-300);
}

.ne-bot__form {
  display: flex;
  gap: var(--sp-2);
  padding: var(--sp-3);
  border-top: 1px solid var(--c-border);
  background: var(--c-bg-2);
  flex: 0 0 auto;
}
.ne-bot__input {
  flex: 1 1 auto;
  padding: var(--sp-3);
  border: 1px solid var(--c-border);
  border-radius: var(--r-pill);
  background: var(--c-bg-3);
  color: var(--c-text);
  font-size: 16px;             /* de nuevo: evita el zoom de iOS */
}
.ne-bot__send {
  display: grid;
  place-items: center;
  width: 42px; height: 42px;
  border-radius: 50%;
  background: var(--g-brand);
  color: #fff;
  flex: 0 0 auto;
}

/* Toggle de idioma dentro del bot. */
.ne-bot__lang {
  margin-inline-start: auto;
  display: flex;
  border: 1px solid var(--c-border);
  border-radius: var(--r-pill);
  overflow: hidden;
  flex: 0 0 auto;
}
.ne-bot__lang button {
  padding: 4px 9px;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  color: var(--c-text-dim);
}
.ne-bot__lang button[aria-pressed="true"] { background: var(--c-primary); color: #fff; }


/* ==========================================================================
   28 · TOASTS · SKELETONS · SPINNERS
   ========================================================================== */

.ne-toasts {
  position: fixed;
  z-index: var(--z-toast);
  inset-inline: var(--sp-4);
  top: calc(var(--h-header) + var(--sp-3) + env(safe-area-inset-top));
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  pointer-events: none;
}
@media (min-width: 768px) {
  .ne-toasts { inset-inline: auto var(--sp-6); width: min(380px, 90vw); }
}

.ne-toast {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--c-border-strong);
  border-inline-start-width: 3px;
  border-radius: var(--r-md);
  background: var(--c-bg-2);
  box-shadow: var(--sh-lg);
  font-size: var(--fs-sm);
  pointer-events: auto;
  animation: ne-toast-in var(--dur-base) var(--ease-out);
}
.ne-toast.is-leaving { animation: ne-toast-out var(--dur-base) var(--ease-out) forwards; }
.ne-toast--success { border-inline-start-color: var(--c-success); }
.ne-toast--error   { border-inline-start-color: var(--c-danger); }
.ne-toast--info    { border-inline-start-color: var(--c-primary); }
.ne-toast__icon { flex: 0 0 auto; margin-top: 2px; }
.ne-toast--success .ne-toast__icon { color: var(--c-success); }
.ne-toast--error   .ne-toast__icon { color: var(--c-danger); }
.ne-toast--info    .ne-toast__icon { color: var(--c-primary); }
.ne-toast__close { margin-inline-start: auto; color: var(--c-text-dim); flex: 0 0 auto; }

/* Skeleton: reserva el espacio exacto mientras carga → CLS = 0. */
.ne-skeleton {
  border-radius: var(--r-sm);
  background: linear-gradient(90deg, var(--c-surface) 25%, var(--c-surface-3) 50%, var(--c-surface) 75%);
  background-size: 200% 100%;
  animation: ne-skeleton 1.4s linear infinite;
}
.ne-skeleton--text { height: 0.85em; margin-block: 0.25em; }
.ne-skeleton--title { height: 1.4em; width: 60%; }
.ne-skeleton--box { aspect-ratio: 1; }

.ne-spinner {
  display: inline-block;
  width: 22px; height: 22px;
  border: 2px solid var(--c-surface-3);
  border-top-color: var(--c-primary);
  border-radius: 50%;
  animation: ne-spin 700ms linear infinite;
}


/* ==========================================================================
   29 · ANIMACIONES Y SCROLL REVEAL
   ========================================================================== */

/* --- Scroll reveal --------------------------------------------------------
   El estado inicial SOLO se aplica si el JS está vivo (html.ne-js). Sin esto,
   un fallo de JS dejaría toda la página invisible — el peor bug posible.      */
.ne-js .ne-reveal {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity var(--dur-slow) var(--ease-out),
              transform var(--dur-slow) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}
.ne-js .ne-reveal.is-visible { opacity: 1; transform: none; }
.ne-js .ne-reveal--left  { transform: translateX(-26px); }
.ne-js .ne-reveal--right { transform: translateX(26px); }
.ne-js .ne-reveal--zoom  { transform: scale(0.94); }

/* Cascada en grids: cada hijo entra un poco después que el anterior. */
.ne-js .ne-stagger > * { --reveal-delay: calc(var(--i, 0) * 70ms); }

/* Parallax suave: el JS escribe --parallax-y. translate3d fuerza el compositor. */
.ne-parallax { transform: translate3d(0, var(--parallax-y, 0), 0); will-change: transform; }

/* --- Keyframes ------------------------------------------------------------ */
@keyframes ne-spin { to { transform: rotate(360deg); } }

@keyframes ne-ripple {
  to { transform: scale(2.6); opacity: 0; }
}

@keyframes ne-shimmer {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}

@keyframes ne-skeleton {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

@keyframes ne-border-flow {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}

@keyframes ne-fade-up {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

@keyframes ne-shake {
  0%, 100%   { transform: translateX(0); }
  20%, 60%   { transform: translateX(-7px); }
  40%, 80%   { transform: translateX(7px); }
}

@keyframes ne-pulse-dot {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.35; transform: scale(0.85); }
}

@keyframes ne-pulse-ring {
  0%   { box-shadow: 0 0 0 0 rgba(var(--c-primary-rgb), 0.55); }
  70%  { box-shadow: 0 0 0 12px rgba(var(--c-primary-rgb), 0); }
  100% { box-shadow: 0 0 0 0 rgba(var(--c-primary-rgb), 0); }
}

@keyframes ne-typing {
  0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
  30%           { opacity: 1;   transform: translateY(-4px); }
}

/* Rayo: baja una altura de viewport completa y se desvanece en los extremos. */
@keyframes ne-bolt {
  0%        { opacity: 0; transform: translateY(0); }
  8%        { opacity: 0.65; }
  85%       { opacity: 0.25; }
  100%      { opacity: 0; transform: translateY(calc(100svh + 260px)); }
}

@keyframes ne-scroll-hint {
  0%, 100% { opacity: 0.25; transform: scaleY(0.4); transform-origin: top; }
  50%      { opacity: 1;    transform: scaleY(1);   transform-origin: top; }
}

/* 50%: la pista se duplica en el HTML, así el bucle es invisible. */
@keyframes ne-marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

@keyframes ne-toast-in {
  from { opacity: 0; transform: translateY(-14px) scale(0.97); }
  to   { opacity: 1; transform: none; }
}
@keyframes ne-toast-out {
  to { opacity: 0; transform: translateX(110%); }
}


/* ==========================================================================
   30 · UTILIDADES
   ========================================================================== */

.ne-hidden { display: none !important; }
@media (max-width: 767px)  { .ne-hide-mobile  { display: none !important; } }
@media (min-width: 768px)  { .ne-hide-desktop { display: none !important; } }

.ne-text-center { text-align: center; }
.ne-text-end    { text-align: end; }
.ne-flex-1      { flex: 1 1 auto; }
.ne-w-full      { width: 100%; }
.ne-nowrap      { white-space: nowrap; }
.ne-relative    { position: relative; }

.ne-mt-0 { margin-top: 0; }
.ne-mt-2 { margin-top: var(--sp-2); }
.ne-mt-4 { margin-top: var(--sp-4); }
.ne-mt-6 { margin-top: var(--sp-6); }
.ne-mt-3 { margin-top: var(--sp-3); }
.ne-mt-5 { margin-top: var(--sp-5); }
.ne-mt-8 { margin-top: var(--sp-8); }
.ne-mt-10 { margin-top: var(--sp-10); }
.ne-mt-12 { margin-top: var(--sp-12); }
.ne-mb-0 { margin-bottom: 0; }
.ne-mb-2 { margin-bottom: var(--sp-2); }
.ne-mb-4 { margin-bottom: var(--sp-4); }
.ne-mb-6 { margin-bottom: var(--sp-6); }
.ne-mb-8 { margin-bottom: var(--sp-8); }

/* Recorta a N líneas con puntos suspensivos. */
.ne-clamp-2, .ne-clamp-3 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ne-clamp-2 { -webkit-line-clamp: 2; line-clamp: 2; }
.ne-clamp-3 { -webkit-line-clamp: 3; line-clamp: 3; }

/* Ratios para reservar espacio de imagen y evitar CLS. */
.ne-ratio-1  { aspect-ratio: 1; }
.ne-ratio-43 { aspect-ratio: 4 / 3; }
.ne-ratio-169{ aspect-ratio: 16 / 9; }
.ne-cover    { width: 100%; height: 100%; object-fit: cover; }
.ne-contain  { width: 100%; height: 100%; object-fit: contain; }

/* Blur-up (LQIP): el placeholder borroso se quita al cargar la imagen real. */
.ne-lqip { filter: blur(14px); transform: scale(1.04); transition: filter var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out); }
.ne-lqip.is-loaded { filter: none; transform: none; }

.ne-divider { height: 1px; background: var(--c-border); border: 0; }
.ne-divider--glow { height: 1px; background: var(--g-line); }


/* ==========================================================================
   31 · PWA · SAFE AREA
   ========================================================================== */

/* Cuando la PWA corre instalada (sin barra de navegador), Android/iOS pintan
   la zona de estado con el color del body. Se añade padding superior para que
   el header no quede bajo el reloj. */
@media (display-mode: standalone) {
  .ne-header { padding-top: env(safe-area-inset-top); height: calc(var(--h-header) + env(safe-area-inset-top)); }
  .ne-main   { padding-top: calc(var(--h-header) + env(safe-area-inset-top)); }
  /* En la app instalada no hay barra de URL; se puede ocultar el "instalar". */
  .ne-install-prompt { display: none !important; }
}

/* Banner "Instalar app". Lo muestra el JS al capturar beforeinstallprompt. */
.ne-install-prompt {
  position: fixed;
  z-index: var(--z-sticky);
  inset-inline: var(--sp-4);
  bottom: calc(var(--h-mobile-cta) + var(--sp-4) + env(safe-area-inset-bottom));
  display: none;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-md);
  background: var(--c-bg-2);
  box-shadow: var(--sh-xl);
  font-size: var(--fs-sm);
}
.ne-install-prompt.is-visible { display: flex; animation: ne-toast-in var(--dur-base) var(--ease-out); }

/* Aviso de sin conexión (el service worker lo activa). */
.ne-offline {
  position: fixed;
  z-index: var(--z-toast);
  inset-inline: 0;
  top: 0;
  padding: var(--sp-2);
  background: var(--c-warning);
  color: #231500;
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-align: center;
  transform: translateY(-100%);
  transition: transform var(--dur-base) var(--ease-out);
}
.ne-offline.is-visible { transform: translateY(0); }



/* ==========================================================================
   33 · LOGO CONFIGURABLE
   --------------------------------------------------------------------------
   El logo se veía a 34px, demasiado pequeño. Ahora su altura sale del
   Personalizador (--logo-h / --logo-h-mobile, inyectadas por customizer.php)
   y el header crece con él para que nunca se recorte.
   ========================================================================== */

.ne-logo img,
.ne-logo svg {
  height: var(--logo-h-mobile, 40px);
  width: auto;
  max-width: min(58vw, 260px);
  object-fit: contain;
  transition: height var(--dur-base) var(--ease-out);
}
@media (min-width: 1024px) {
  .ne-logo img,
  .ne-logo svg { height: var(--logo-h, 48px); }
}
/* Al hacer scroll el header encoge: el logo lo acompaña un 15%. */
.ne-header.is-scrolled .ne-logo img,
.ne-header.is-scrolled .ne-logo svg { height: calc(var(--logo-h-mobile, 40px) * 0.85); }
@media (min-width: 1024px) {
  .ne-header.is-scrolled .ne-logo img,
  .ne-header.is-scrolled .ne-logo svg { height: calc(var(--logo-h, 48px) * 0.85); }
}

.ne-logo__text { font-size: clamp(0.75rem, 0.6rem + 0.5vw, 0.9375rem); }


/* ==========================================================================
   34 · MENÚ MÓVIL
   --------------------------------------------------------------------------
   REDISEÑO: la primera versión era una lista de cajas con borde. Se veía
   pesada y barata — cada enlace competía con el de al lado por atención.

   Ahora: tipografía grande, sin cajas, separadores finísimos y una barra de
   marca que marca la página activa. Menos elementos, más jerarquía. Es el
   patrón que usan las apps que se sienten caras.
   ========================================================================== */

.ne-drawer { justify-content: stretch; }

.ne-drawer__panel {
  /* Pantalla completa en móvil: más espacio para el dedo, cero distracción. */
  width: 100%;
  max-width: none;
  padding: 0;
  border-inline-start: 0;
  background: #06080e;
  /* Dos luces de marca muy tenues: dan profundidad sin ensuciar. */
  background-image:
    radial-gradient(ellipse 80% 50% at 50% -10%, rgba(var(--c-primary-rgb), 0.22), transparent 70%),
    radial-gradient(ellipse 60% 40% at 100% 110%, rgba(var(--c-secondary-rgb), 0.16), transparent 70%);
  gap: 0;
}
@media (min-width: 640px) {
  .ne-drawer { justify-content: flex-end; }
  .ne-drawer__panel {
    width: min(440px, 94vw);
    border-inline-start: 1px solid var(--c-border);
  }
}

/* --- Cabecera: logo + cerrar --------------------------------------------- */
.ne-drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: calc(var(--sp-5) + env(safe-area-inset-top)) var(--sp-6) var(--sp-5);
  flex: 0 0 auto;
}
.ne-drawer__head .ne-logo img,
.ne-drawer__head .ne-logo svg { height: var(--logo-h-mobile, 40px); }

/* Cerrar: círculo sobrio. 48px supera el mínimo táctil de WCAG 2.5.5. */
.ne-drawer__close {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  flex: 0 0 auto;
  border: 1px solid var(--c-border-strong);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.04);
  color: var(--c-text-muted);
  transition: transform var(--dur-base) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.ne-drawer__close:active { transform: scale(0.9); }
@media (hover: hover) {
  .ne-drawer__close:hover {
    background: rgba(var(--c-danger-rgb), 0.16);
    border-color: var(--c-danger);
    color: var(--c-danger);
    transform: rotate(90deg);
  }
}

/* --- Cuerpo --------------------------------------------------------------- */
.ne-drawer__body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  padding: var(--sp-4) var(--sp-6) var(--sp-6);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Enlaces grandes, sin caja. La jerarquía la da el tamaño, no un borde. */
.ne-drawer__link {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  min-height: 64px;
  padding: var(--sp-4) 0;
  border: 0;
  border-radius: 0;
  background: none;
  font-size: clamp(1.35rem, 1.1rem + 1.6vw, 1.75rem);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-tight);
  color: var(--c-text);
  text-decoration: none;
  /* Separador finísimo entre elementos, nunca después del último. */
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.06);
  /* Entrada escalonada. */
  opacity: 0;
  transform: translateX(20px);
  transition: color var(--dur-fast) var(--ease-out);
}
.ne-drawer__link:last-of-type { box-shadow: none; }

.ne-drawer.is-open .ne-drawer__link {
  animation: ne-drawer-in var(--dur-base) var(--ease-out) forwards;
  animation-delay: calc(var(--i, 0) * 55ms + 90ms);
}

/* Barra de marca a la izquierda: crece desde 0 al tocar o en la página activa. */
.ne-drawer__link::before {
  content: "";
  position: absolute;
  inset-inline-start: calc(var(--sp-6) * -1);
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 0;
  border-radius: var(--r-pill);
  background: var(--g-brand);
  transition: height var(--dur-base) var(--ease-out);
}
.ne-drawer__link[aria-current="page"] { color: var(--c-primary-300); }
.ne-drawer__link[aria-current="page"]::before { height: 42%; }
.ne-drawer__link:active::before { height: 42%; }
@media (hover: hover) {
  .ne-drawer__link:hover { color: var(--c-primary-300); }
  .ne-drawer__link:hover::before { height: 42%; }
}

/* La flecha se desliza al tocar: confirma que el toque se registró. */
.ne-drawer__link::after {
  content: "→";
  margin-inline-start: auto;
  font-size: var(--fs-lg);
  color: var(--c-text-dim);
  transition: transform var(--dur-base) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.ne-drawer__link:active::after,
.ne-drawer__link[aria-current="page"]::after {
  transform: translateX(5px);
  color: var(--c-primary);
}
@media (hover: hover) {
  .ne-drawer__link:hover::after { transform: translateX(5px); color: var(--c-primary); }
}

/* Numeración monoespaciada: da sensación de menú cuidado, no de lista suelta. */
.ne-drawer__n {
  font-family: var(--ff-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-normal);
  color: var(--c-text-dim);
  flex: 0 0 auto;
  min-width: 2.2em;
  letter-spacing: 0;
}

/* Variante destacada: el Área de Cliente, que es la conversión clave. */
.ne-drawer__link--cta { color: var(--c-primary-300); }
.ne-drawer__link--cta .ne-drawer__n { color: var(--c-primary); }
.ne-drawer__link--danger { color: var(--c-danger); font-size: var(--fs-lg); min-height: 52px; }
.ne-drawer__link--danger .ne-drawer__n { color: var(--c-danger); }

@keyframes ne-drawer-in {
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .ne-drawer__link { opacity: 1; transform: none; animation: none !important; }
}

/* --- Pie: llamar + contacto ----------------------------------------------- */
.ne-drawer__foot {
  flex: 0 0 auto;
  padding: var(--sp-5) var(--sp-6) calc(var(--sp-6) + env(safe-area-inset-bottom));
  border-top: 1px solid var(--c-border);
  background: rgba(0, 0, 0, 0.3);
}

/* La hamburguesa se convierte en X. */
.ne-burger[aria-expanded="true"] .ne-burger__bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.ne-burger[aria-expanded="true"] .ne-burger__bar:nth-child(2) { opacity: 0; }
.ne-burger[aria-expanded="true"] .ne-burger__bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }


/* ==========================================================================
   40 · TARJETAS DE SERVICIO CLICABLES
   --------------------------------------------------------------------------
   Cada tarjeta lleva a la página de ese servicio. La animación tiene que
   funcionar en las DOS entradas:
     · Ratón  → se eleva y la foto hace zoom al pasar por encima
     · Táctil → no hay hover, así que la señal es el hundido al tocar
                (:active) y la entrada animada al hacer scroll.
   ========================================================================== */

.ne-service--link {
  cursor: pointer;
  text-decoration: none;
  color: inherit;
  /* El navegador reserva capa de GPU: la animación no parpadea en móvil. */
  will-change: transform;
  transition: transform var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}

/* --- TÁCTIL: respuesta inmediata al tocar -------------------------------- */
.ne-service--link:active {
  transform: scale(0.975);
  border-color: var(--c-primary);
}

/* --- RATÓN: elevación + zoom de la foto ---------------------------------- */
@media (hover: hover) and (pointer: fine) {
  .ne-service--link:hover {
    transform: translateY(-8px);
    border-color: rgba(var(--c-primary-rgb), 0.6);
    background: var(--c-surface-2);
    box-shadow: 0 22px 48px rgba(0, 0, 0, 0.5), 0 0 34px rgba(var(--c-primary-rgb), 0.22);
  }
  .ne-service--link:hover .ne-service__media img { transform: scale(1.09); }
  .ne-service--link:hover .ne-service__icon {
    transform: translateY(50%) scale(1.1) rotate(-8deg);
    box-shadow: 0 0 26px rgba(var(--c-primary-rgb), 0.6);
  }
  /* El icono sin foto no está desplazado, así que rota sin el translateY. */
  .ne-service--link:not(.ne-service--photo):hover .ne-service__icon {
    transform: scale(1.1) rotate(-8deg);
  }
  .ne-service--link:hover .ne-service__arrow { transform: translateX(6px); }
  .ne-service--link:hover .ne-service__link { color: var(--c-text); }
}

/* El foco de teclado debe verse igual de claro que el hover. */
.ne-service--link:focus-visible {
  transform: translateY(-8px);
  border-color: var(--c-primary);
}

.ne-service__link {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: auto;
  padding-top: var(--sp-3);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-primary-300);
  transition: color var(--dur-fast) var(--ease-out);
}
.ne-service__arrow {
  display: inline-block;
  transition: transform var(--dur-base) var(--ease-out);
}

/* Brillo que cruza la tarjeta al entrar en pantalla. Una sola pasada: repetirlo
   sería ruido visual, y una pasada basta para que el ojo la registre. */
.ne-service--link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(105deg, transparent 35%, rgba(255, 255, 255, 0.07) 50%, transparent 65%);
  transform: translateX(-110%);
  pointer-events: none;
  opacity: 0;
}
.ne-service--link.is-visible::after {
  animation: ne-card-sheen 1.1s var(--ease-out) forwards;
  animation-delay: calc(var(--i, 0) * 70ms + 320ms);
}
@keyframes ne-card-sheen {
  0%   { transform: translateX(-110%); opacity: 0; }
  35%  { opacity: 1; }
  100% { transform: translateX(110%); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .ne-service--link::after { display: none; }
}

/* .ne-service es position:relative para que ::after y el icono se posicionen. */
.ne-service { position: relative; }


/* ==========================================================================
   41 · MAQUETA DEL TELÉFONO (respaldo sin imagen)
   --------------------------------------------------------------------------
   Si no has elegido imagen para el hero, el teléfono ya no sale vacío: se
   dibuja este resumen del portal con CSS. Pesa 0 KB y enseña justo lo que
   diferencia al negocio.
   ========================================================================== */

.ne-device-mock {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  height: 100%;
  padding: var(--sp-5) var(--sp-4);
  border-radius: 28px;
  background: linear-gradient(165deg, var(--c-bg-1), var(--c-bg-0));
  overflow: hidden;
}
.ne-device-mock__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-text-dim);
}
.ne-device-mock__bar b { color: var(--c-primary-300); font-size: var(--fs-sm); }
.ne-device-mock__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-3);
  border: 1px solid var(--c-border);
  border-radius: var(--r-sm);
  background: var(--c-surface);
  font-size: var(--fs-2xs);
  color: var(--c-text-dim);
}
.ne-device-mock__row b { color: var(--c-text); font-size: var(--fs-sm); }
.ne-device-mock__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 5px;
  margin-top: auto;
}
.ne-device-mock__grid span {
  aspect-ratio: 1;
  border-radius: 6px;
  background: linear-gradient(135deg, rgba(var(--c-primary-rgb), 0.3), rgba(var(--c-secondary-rgb), 0.22));
}
.ne-device-mock__foot {
  font-size: 9px;
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  text-align: center;
  color: var(--c-text-dim);
}


/* ==========================================================================
   42 · MIGAS DE PAN
   ========================================================================== */

.ne-crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-xs);
  color: var(--c-text-dim);
}
.ne-crumbs a { color: var(--c-text-muted); text-decoration: none; }
.ne-crumbs a:hover { color: var(--c-primary-300); }
.ne-crumbs [aria-current="page"] { color: var(--c-text); font-weight: var(--fw-semi); }


/* ==========================================================================
   35 · TARJETAS DE SERVICIO CON FOTO
   --------------------------------------------------------------------------
   Las fotos salen del Personalizador, de la biblioteca de medios existente.
   Una foto real de tu trabajo convierte mucho más que un icono.
   ========================================================================== */

.ne-service--photo { padding: 0; overflow: hidden; }

.ne-service__media {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--c-bg-2);
}
.ne-service__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .ne-service--photo:hover .ne-service__media img { transform: scale(1.08); }
}
/* Degradado inferior: garantiza que el icono flotante siempre se lea. */
.ne-service__media::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 55%;
  background: linear-gradient(to top, var(--c-bg-2), transparent);
  pointer-events: none;
}

/* Icono flotando sobre el borde de la foto. */
.ne-service--photo .ne-service__icon {
  position: absolute;
  bottom: 0;
  inset-inline-start: var(--sp-5);
  transform: translateY(50%);
  z-index: var(--z-base);
  background: var(--c-bg-2);
  border-color: var(--c-primary);
  box-shadow: var(--sh-glow-primary);
}

.ne-service__body {
  padding: calc(var(--sp-8) + var(--sp-1)) var(--sp-5) var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  flex: 1 1 auto;
}


/* ==========================================================================
   36 · EFECTOS DEL DASHBOARD
   --------------------------------------------------------------------------
   Recuperados del dashboard v21 que tenías en Elementor, pero optimizados:
   todo se anima con transform/opacity (GPU) y se apaga solo en móviles con
   poca memoria o con prefers-reduced-motion.
   ========================================================================== */

/* --- Pantalla de carga ---------------------------------------------------- */
.ne-loader {
  position: fixed;
  inset: 0;
  z-index: var(--z-toast);
  display: grid;
  place-items: center;
  gap: var(--sp-5);
  align-content: center;
  background: var(--c-bg-0);
  transition: opacity var(--dur-slow) var(--ease-out), visibility 0s linear var(--dur-slow);
}
.ne-loader.is-done { opacity: 0; visibility: hidden; pointer-events: none; }

.ne-loader__ring {
  width: 84px;
  height: 84px;
  border: 3px solid rgba(var(--c-primary-rgb), 0.22);
  border-top-color: var(--c-primary);
  border-right-color: var(--c-secondary);
  border-radius: 50%;
  animation: ne-spin 900ms linear infinite;
}
.ne-loader__text {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-text-muted);
  animation: ne-pulse-dot 1.8s ease-in-out infinite;
}

/* --- Orbes de fondo -------------------------------------------------------
   filter:blur() es caro. Se limitan a 2 orbes, solo en escritorio, y con
   will-change para que el navegador los suba a su propia capa una sola vez. */
.ne-orbs {
  position: fixed;
  inset: 0;
  z-index: var(--z-behind);
  overflow: hidden;
  pointer-events: none;
  opacity: 0.30;
}
.ne-orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  will-change: transform;
  animation: ne-orb-float 26s ease-in-out infinite;
}
.ne-orb--1 {
  width: 420px; height: 420px;
  background: radial-gradient(circle, var(--c-primary), transparent 70%);
  top: 6%; left: 4%;
}
.ne-orb--2 {
  width: 480px; height: 480px;
  background: radial-gradient(circle, var(--c-secondary), transparent 70%);
  bottom: 10%; right: 4%;
  animation-delay: -9s;
}
/* En móvil los orbes cuestan batería y apenas se ven: fuera. */
@media (max-width: 1023px) { .ne-orbs { display: none; } }
@media (prefers-reduced-motion: reduce) { .ne-orb { animation: none; } }

@keyframes ne-orb-float {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  33%      { transform: translate3d(60px, -50px, 0) scale(1.12); }
  66%      { transform: translate3d(-50px, 45px, 0) scale(0.92); }
}

/* --- Galería holograma ----------------------------------------------------- */
.ne-gallery--holo { perspective: 1000px; }

.ne-gallery--holo .ne-gallery__item {
  border-width: 2px;
  border-color: rgba(var(--c-primary-rgb), 0.30);
  transform-style: preserve-3d;
  transition: transform var(--dur-slow) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .ne-gallery--holo .ne-gallery__item:hover {
    transform: translateY(-10px) rotateX(5deg) rotateY(5deg) scale(1.05);
    border-color: var(--c-secondary);
    box-shadow:
      0 20px 50px rgba(var(--c-primary-rgb), 0.45),
      0 0 30px rgba(var(--c-secondary-rgb), 0.35);
  }
}

/* Lupa que aparece al pasar por encima. */
.ne-gallery__zoom {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: linear-gradient(135deg, rgba(var(--c-primary-rgb), 0.78), rgba(var(--c-secondary-rgb), 0.78));
  color: #fff;
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out);
  pointer-events: none;
}
@media (hover: hover) and (pointer: fine) {
  .ne-gallery__item:hover .ne-gallery__zoom { opacity: 1; }
}

/* --- Confeti (al firmar el contrato) --------------------------------------- */
.ne-confetti {
  position: fixed;
  top: -12px;
  width: 9px;
  height: 9px;
  z-index: var(--z-toast);
  pointer-events: none;
  animation: ne-confetti-fall 2.8s cubic-bezier(0.2, 0.6, 0.5, 1) forwards;
}
@keyframes ne-confetti-fall {
  to { transform: translateY(102vh) rotate(760deg); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .ne-confetti { display: none; } }


/* ==========================================================================
   37 · REGISTRO DE CLIENTE
   ========================================================================== */

.ne-tabs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-1);
  padding: var(--sp-1);
  border: 1px solid var(--c-border);
  border-radius: var(--r-pill);
  background: var(--c-surface);
  margin-bottom: var(--sp-6);
}
.ne-tab {
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-pill);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-text-dim);
  transition: all var(--dur-fast) var(--ease-out);
}
.ne-tab[aria-selected="true"] {
  background: var(--g-brand);
  color: var(--c-text-on-accent);
  box-shadow: 0 4px 14px rgba(var(--c-primary-rgb), 0.35);
}


/* ==========================================================================
   38 · FORMULARIO DE CONTACTO DEL BOT
   --------------------------------------------------------------------------
   Aparece dentro del chat cuando el bot no sabe responder. El cliente deja su
   mensaje ahí mismo, sin cambiar de página ni perder el hilo.
   ========================================================================== */

.ne-bot__contact {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin: var(--sp-2) 0;
  padding: var(--sp-4);
  border: 1px solid rgba(var(--c-primary-rgb), 0.35);
  border-radius: var(--r-md);
  background: rgba(var(--c-primary-rgb), 0.07);
  animation: ne-fade-up var(--dur-base) var(--ease-out);
}
.ne-bot__contact input,
.ne-bot__contact textarea {
  width: 100%;
  padding: var(--sp-3);
  border: 1px solid var(--c-border);
  border-radius: var(--r-sm);
  background: var(--c-bg-3);
  color: var(--c-text);
  font-size: 16px;   /* evita el zoom de iOS al enfocar */
}
.ne-bot__contact textarea { min-height: 74px; resize: vertical; }
.ne-bot__contact label {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-text-muted);
}


/* ==========================================================================
   39 · MÓVIL · AJUSTES FINOS
   --------------------------------------------------------------------------
   El 70% del tráfico es móvil. Estos ajustes no son "responsive" genérico:
   son correcciones concretas de cosas que en un teléfono real molestan.
   ========================================================================== */

@media (max-width: 767px) {

  /* Tarjetas con menos padding: en 375px cada píxel de ancho cuenta. */
  .ne-card { padding: var(--sp-5); border-radius: var(--r-lg); }
  .ne-card--roomy { padding: var(--sp-6); }

  /* El grid del dashboard a una columna, sin excepciones. */
  .ne-grid--4, .ne-grid--3, .ne-grid--2 { --col-min: 100%; }

  /* KPIs de dos en dos: cuatro tarjetas apiladas obligan a demasiado scroll. */
  .ne-grid--4 {
    grid-template-columns: 1fr 1fr;
    --col-min: 0;
  }
  .ne-kpi { padding: var(--sp-4); }
  .ne-kpi__value { font-size: var(--fs-xl); }

  /* Galería de 3 en fila: se ven más fotos sin hacer scroll. */
  .ne-gallery { grid-template-columns: repeat(3, 1fr); gap: var(--sp-2); }

  /* Fases: los nombres largos se cortaban. Tipografía menor y sin partir. */
  .ne-phase__name { font-size: 9px; letter-spacing: 0.02em; }
  .ne-phases::before, .ne-phases::after { left: 12%; right: 12%; }

  /* El invoice se desbordaba lateralmente en pantallas estrechas. */
  .ne-invoice__head { flex-direction: column; gap: var(--sp-4); }
  .ne-invoice__head .ne-text-end { text-align: start; }

  /* Modales como hoja inferior, ocupando casi toda la pantalla. */
  .ne-modal__panel { max-height: 94svh; }
  .ne-modal__body { padding: var(--sp-5); }
  .ne-modal__foot { padding: var(--sp-4) var(--sp-5) calc(var(--sp-4) + env(safe-area-inset-bottom)); }
  .ne-modal__foot .ne-btn { flex: 1 1 auto; }

  /* Secciones más compactas: menos scroll hasta el CTA. */
  .ne-section { padding-block: clamp(2.5rem, 1.5rem + 5vw, 4rem); }

  /* El chat a pantalla casi completa: escribir en 380px de alto es incómodo. */
  .ne-bot__panel {
    inset-inline: var(--sp-3);
    width: auto;
    height: min(76svh, calc(100svh - 140px));
  }

  /* Botones de ancho completo en formularios: objetivo táctil máximo. */
  .ne-modal__foot, .ne-hero__cta { flex-direction: column-reverse; }
  .ne-hero__cta .ne-btn { width: 100%; }
}

/* Pantallas muy estrechas (iPhone SE y similares). */
@media (max-width: 380px) {
  .ne-grid--4 { grid-template-columns: 1fr; }
  .ne-gallery { grid-template-columns: repeat(2, 1fr); }
  .ne-otp { gap: var(--sp-2); }
  .ne-otp__digit { width: 44px; height: 52px; font-size: 1.3rem; }
}

/* Horizontal en móvil: la altura útil es mínima, el hero no puede pedir 100svh. */
@media (max-height: 520px) and (orientation: landscape) {
  .ne-hero { min-height: auto; padding-block: calc(var(--h-header) + var(--sp-6)) var(--sp-8); }
  .ne-hero__scroll { display: none; }
  .ne-drawer__panel { overflow-y: auto; }
}

/* Dispositivos que no soportan hover: se muestra siempre el estado "activo"
   de los elementos que en escritorio solo aparecen al pasar el ratón. */
@media (hover: none) {
  .ne-work__body { transform: none; }
  .ne-gallery__zoom { display: none; }
}


/* ==========================================================================
   43 · REDES SOCIALES
   --------------------------------------------------------------------------
   Iconos SVG en línea con el color real de cada marca al pasar el cursor.
   En gris por defecto: un footer con 10 logos a todo color compite con el
   contenido y parece una feria. El color aparece solo cuando interactúas.
   ========================================================================== */

.ne-social {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  list-style: none;
  padding: 0;
  margin: 0;
}

.ne-social__link {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface);
  color: var(--c-text-dim);
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              transform var(--dur-base) var(--ease-spring),
              box-shadow var(--dur-base) var(--ease-out);
}

/* --brand lo escribe PHP con el color oficial de cada red. */
@media (hover: hover) and (pointer: fine) {
  .ne-social__link:hover {
    color: var(--brand, var(--c-primary));
    border-color: var(--brand, var(--c-primary));
    background: color-mix(in srgb, var(--brand, #4B80DD) 14%, transparent);
    transform: translateY(-4px) scale(1.06);
    box-shadow: 0 10px 24px color-mix(in srgb, var(--brand, #4B80DD) 30%, transparent);
  }
}
/* color-mix es reciente. Sin soporte, un realce neutro que funciona igual. */
@supports not (background: color-mix(in srgb, red 10%, transparent)) {
  @media (hover: hover) {
    .ne-social__link:hover { background: var(--c-surface-3); box-shadow: var(--sh-md); }
  }
}

/* Táctil: el color aparece al tocar, que es la única señal disponible. */
.ne-social__link:active {
  color: var(--brand, var(--c-primary));
  border-color: var(--brand, var(--c-primary));
  transform: scale(0.94);
}
.ne-social__link:focus-visible { color: var(--brand, var(--c-primary)); }

/* Variante pequeña para la cabecera. */
.ne-social--sm .ne-social__link { width: 34px; height: 34px; border-radius: var(--r-sm); }


/* ==========================================================================
   44 · DEMO DEL PORTAL ("What nobody else gives you")
   --------------------------------------------------------------------------
   Es la pieza que vende. Se anima al entrar en pantalla para que se sienta
   un sistema vivo y no una captura de pantalla.
   ========================================================================== */

.ne-demo { overflow: hidden; }

.ne-demo__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-5);
}

.ne-demo__phases .ne-phase__dot,
.ne-demo__phases .ne-phase__name {
  transition: all var(--dur-slow) var(--ease-out);
}

.ne-demo__section {
  margin-top: var(--sp-6);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--c-border);
}

.ne-demo__label {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-text-dim);
}
.ne-demo__label b {
  margin-inline-start: auto;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: rgba(var(--c-primary-rgb), 0.16);
  color: var(--c-primary-300);
}

/* --- El grid de fotos ---------------------------------------------------- */
.ne-demo__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
}

.ne-demo__cell {
  position: relative;
  aspect-ratio: 1;
  border-radius: var(--r-sm);
  overflow: hidden;
  background: var(--c-bg-2);
  /* Estado inicial: encogida e invisible. app.js añade .is-playing al padre. */
  opacity: 0;
  transform: scale(0.86);
  transition: opacity var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-spring);
  transition-delay: calc(var(--i, 0) * 90ms);
}
.ne-demo.is-playing .ne-demo__cell { opacity: 1; transform: none; }

.ne-demo__cell img { width: 100%; height: 100%; object-fit: cover; }

/* Marcador cuando aún no hay proyectos configurados. */
.ne-demo__cell--empty {
  background:
    linear-gradient(135deg, rgba(var(--c-primary-rgb), 0.26), rgba(var(--c-secondary-rgb), 0.18));
}
.ne-demo__cell--empty::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.14), transparent);
  animation: ne-shimmer 2.6s linear infinite;
  animation-delay: calc(var(--i, 0) * 200ms);
}

@media (prefers-reduced-motion: reduce) {
  .ne-demo__cell { opacity: 1; transform: none; transition: none; }
  .ne-demo__cell--empty::after { animation: none; }
}


/* ==========================================================================
   45 · TELÉFONO DEL HERO · 3D Y MÓVIL
   --------------------------------------------------------------------------
   CAMBIO IMPORTANTE: antes se ocultaba por debajo de 768px. Era una decisión
   de rendimiento defendible, pero se llevaba por delante el argumento de
   venta principal justo donde está el 70% del tráfico.

   Ahora se muestra en móvil, más pequeño, centrado y con flotación suave.
   El coste real es bajo: es una imagen que ya estaba descargada.
   ========================================================================== */

.ne-hero__device {
  position: relative;
  justify-self: center;
  width: min(280px, 72vw);
  aspect-ratio: 9 / 19;
  padding: 10px;
  border: 2px solid var(--c-border-strong);
  border-radius: 38px;
  background: var(--c-bg-2);
  box-shadow: var(--sh-xl), var(--sh-glow-primary);
  /* perspective en el propio elemento: la inclinación se ve volumétrica. */
  transform: perspective(1100px)
             rotateX(var(--dev-x, 0deg))
             rotateY(var(--dev-y, 0deg));
  transform-style: preserve-3d;
  transition: transform var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
  will-change: transform;
}

/* ANTES: display:none. AHORA se ve, más pequeño. */
@media (max-width: 767px) {
  .ne-hero__device {
    width: min(210px, 58vw);
    margin: var(--sp-8) auto 0;
    border-radius: 30px;
  }
}
/* En horizontal el alto es mínimo y el teléfono no cabe. Ahí sí se oculta. */
@media (max-height: 520px) and (orientation: landscape) {
  .ne-hero__device { display: none; }
}

.ne-hero__device img,
.ne-hero__device .ne-device-mock {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 28px;
}
@media (max-width: 767px) {
  .ne-hero__device img,
  .ne-hero__device .ne-device-mock { border-radius: 22px; }
}

/* --- Reflejo que se mueve con el cursor ---------------------------------
   --dev-shine lo escribe app.js según la posición del ratón. Es lo que hace
   que parezca cristal de verdad y no una foto pegada. */
.ne-hero__device::after {
  content: "";
  position: absolute;
  inset: 10px;
  border-radius: 28px;
  background: linear-gradient(
    105deg,
    transparent calc(var(--dev-shine, 50%) - 26%),
    rgba(255, 255, 255, 0.13) var(--dev-shine, 50%),
    transparent calc(var(--dev-shine, 50%) + 26%)
  );
  pointer-events: none;
  transition: background var(--dur-fast) linear;
}

/* --- Muesca del teléfono: detalle que lo hace creíble -------------------- */
.ne-hero__device::before {
  content: "";
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  width: 34%;
  height: 18px;
  border-radius: 0 0 14px 14px;
  background: var(--c-bg-0);
  z-index: var(--z-raised);
  pointer-events: none;
}

/* --- Flotación en móvil -------------------------------------------------- */
.ne-hero__device.is-floating { animation: ne-device-float 6s ease-in-out infinite; }

@keyframes ne-device-float {
  0%, 100% { transform: perspective(1100px) translateY(0) rotateZ(0deg); }
  33%      { transform: perspective(1100px) translateY(-12px) rotateZ(-1.2deg); }
  66%      { transform: perspective(1100px) translateY(6px) rotateZ(0.8deg); }
}
@media (prefers-reduced-motion: reduce) {
  .ne-hero__device.is-floating { animation: none; }
  .ne-hero__device { transform: none; }
}

/* Elevación extra al pasar el cursor: confirma que el objeto responde. */
@media (hover: hover) and (pointer: fine) {
  .ne-hero__device:hover {
    box-shadow: var(--sh-xl), 0 0 46px rgba(var(--c-primary-rgb), 0.55);
  }
}


/* ==========================================================================
   46 · REALISMO AL PASAR EL CURSOR
   --------------------------------------------------------------------------
   Ajustes finos para que el movimiento se sienta físico y no "de plantilla":
   nada se mueve en línea recta y todo tiene una inercia distinta.
   ========================================================================== */

/* Las tarjetas de proyecto ganan profundidad, no solo zoom. */
@media (hover: hover) and (pointer: fine) {
  .ne-work {
    transition: transform var(--dur-base) var(--ease-out),
                box-shadow var(--dur-base) var(--ease-out);
  }
  .ne-work:hover {
    transform: translateY(-6px) scale(1.015);
    box-shadow: 0 24px 50px rgba(0, 0, 0, 0.55);
    z-index: var(--z-raised);
  }
  .ne-work:hover img { transform: scale(1.09) rotate(-0.6deg); }

  /* Los KPI del dashboard reaccionan levemente: la web se siente viva. */
  .ne-kpi {
    transition: transform var(--dur-base) var(--ease-out),
                border-color var(--dur-base) var(--ease-out);
  }
  .ne-kpi:hover {
    transform: translateY(-3px);
    border-color: rgba(var(--c-primary-rgb), 0.45);
  }

  /* Los botones levantan la sombra, no solo cambian de color. */
  .ne-btn--call:hover {
    transform: translateY(-2px);
    box-shadow: 0 12px 30px rgba(var(--c-success-rgb), 0.5);
  }
  .ne-btn--outline:hover { transform: translateY(-2px); }
}

/* TÁCTIL: sin hover, la señal es el hundido. Se aplica a todo lo pulsable. */
@media (hover: none) {
  .ne-work:active,
  .ne-kpi:active,
  .ne-pay__card:active,
  .ne-gallery__item:active,
  .ne-faq__q:active {
    transform: scale(0.975);
    transition: transform 90ms var(--ease-out);
  }
}

/* Los iconos de servicio laten al entrar en pantalla: guían la mirada. */
.ne-service.is-visible .ne-service__icon {
  animation: ne-icon-pop var(--dur-slow) var(--ease-spring) backwards;
  animation-delay: calc(var(--i, 0) * 70ms + 200ms);
}
@keyframes ne-icon-pop {
  0%   { transform: scale(0.4) rotate(-18deg); opacity: 0; }
  100% { transform: scale(1) rotate(0); opacity: 1; }
}
/* La variante con foto ya está desplazada un 50%: su keyframe lo respeta. */
.ne-service--photo.is-visible .ne-service__icon {
  animation-name: ne-icon-pop-offset;
}
@keyframes ne-icon-pop-offset {
  0%   { transform: translateY(50%) scale(0.4) rotate(-18deg); opacity: 0; }
  100% { transform: translateY(50%) scale(1) rotate(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .ne-service.is-visible .ne-service__icon { animation: none; }
}


/* ==========================================================================
   47 · ESTIMADO · CALENDARIO Y FIRMA
   --------------------------------------------------------------------------
   Un estimado es un documento que decide miles de dólares desde el teléfono
   de una obra, con guantes puestos y a pleno sol. Por eso aquí los objetivos
   táctiles son más grandes de lo normal y el contraste, más alto.
   ========================================================================== */

/* --- Fechas: scroller horizontal ----------------------------------------
   Una rejilla de calendario completa obliga a leer 30 casillas para
   encontrar las 4 que están libres. Un carrusel con SOLO las disponibles
   se entiende de un vistazo. */
.ne-est__dates {
  display: flex;
  gap: var(--sp-3);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  padding: var(--sp-1) var(--sp-1) var(--sp-4);
  scrollbar-width: none;
}
.ne-est__dates::-webkit-scrollbar { display: none; }

.ne-est__date {
  flex: 0 0 auto;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  width: 86px;
  padding: var(--sp-4) var(--sp-2);
  border: 2px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface);
  color: var(--c-text-muted);
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}
.ne-est__date:active { transform: scale(.95); }
@media (hover: hover) and (pointer: fine) {
  .ne-est__date:hover {
    border-color: var(--c-primary);
    background: var(--c-surface-2);
    transform: translateY(-3px);
  }
}

.ne-est__date-day {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-text-dim);
}
.ne-est__date-num {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-black);
  line-height: 1;
  color: var(--c-text);
  font-variant-numeric: tabular-nums;
}
.ne-est__date-mon {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semi);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-text-dim);
}

/* Seleccionada: verde, no azul. El verde dice "confirmado" en un flujo de
   reserva; el azul es el color de todo lo demás y no destacaría. */
.ne-est__date.is-active {
  border-color: var(--c-success);
  background: rgba(var(--c-success-rgb), .12);
  box-shadow: var(--sh-glow-success);
}
.ne-est__date.is-active .ne-est__date-num,
.ne-est__date.is-active .ne-est__date-day,
.ne-est__date.is-active .ne-est__date-mon { color: var(--c-success); }

/* --- Horas ---------------------------------------------------------------- */
.ne-est__times { animation: ne-fade-up var(--dur-base) var(--ease-out); }

.ne-est__timegrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(104px, 100%), 1fr));
  gap: var(--sp-2);
}

.ne-est__time {
  min-height: var(--tap-min);
  padding: var(--sp-3) var(--sp-2);
  border: 1px solid var(--c-border);
  border-radius: var(--r-sm);
  background: var(--c-surface);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semi);
  font-variant-numeric: tabular-nums;
  color: var(--c-text-muted);
  cursor: pointer;
  transition: all var(--dur-fast) var(--ease-out);
}
.ne-est__time:active { transform: scale(.96); }
@media (hover: hover) and (pointer: fine) {
  .ne-est__time:hover { border-color: var(--c-primary); background: var(--c-surface-2); color: var(--c-text); }
}
.ne-est__time.is-active {
  border-color: var(--c-success);
  background: rgba(var(--c-success-rgb), .14);
  color: var(--c-success);
  font-weight: var(--fw-bold);
}

/* --- Resumen antes de firmar ---------------------------------------------
   Lo que se va a firmar, repetido justo encima de la firma. Nadie debería
   tener que subir a comprobar la cifra antes de comprometerse. */
.ne-est__summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
  gap: var(--sp-4);
  padding: var(--sp-4);
  border: 1px solid rgba(var(--c-success-rgb), .32);
  border-radius: var(--r-md);
  background: rgba(var(--c-success-rgb), .07);
}
.ne-est__summary strong {
  display: block;
  margin-top: 3px;
  font-size: var(--fs-lg);
  color: var(--c-text);
}

/* --- Términos ------------------------------------------------------------- */
.ne-est__terms {
  list-style: none;
  padding: 0;
  margin: var(--sp-4) 0 0;
}
.ne-est__terms li {
  position: relative;
  padding: 0 0 0 var(--sp-5);
  margin-bottom: var(--sp-3);
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--c-text-muted);
}
.ne-est__terms li::before {
  content: "▸";
  position: absolute;
  inset-inline-start: 0;
  color: var(--c-primary);
  font-weight: var(--fw-bold);
}
.ne-est__terms strong { color: var(--c-text); }

/* --- Pad de firma ---------------------------------------------------------
   touch-action:none es OBLIGATORIO. Sin él, el navegador interpreta el
   arrastre del dedo como scroll y el cliente literalmente no puede firmar
   en el teléfono. Es el fallo más común de un canvas de firma. */
.ne-sigpad {
  position: relative;
  height: 190px;
  border: 2px dashed var(--c-border-strong);
  border-radius: var(--r-md);
  background: var(--c-bg-3);
  overflow: hidden;
  transition: border-color var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out);
}
.ne-sigpad.is-signed {
  border-style: solid;
  border-color: var(--c-success);
  background: rgba(var(--c-success-rgb), .05);
}

.ne-sigpad canvas {
  position: relative;
  z-index: 2;
  display: block;
  width: 100%;
  height: 100%;
  cursor: crosshair;
  touch-action: none;          /* ← sin esto no se puede firmar en móvil */
  -webkit-user-select: none;
  user-select: none;
}

/* Línea de firma, como en un documento de papel. */
.ne-sigpad__line {
  position: absolute;
  z-index: 1;
  inset-inline: 10%;
  bottom: 34%;
  height: 1px;
  background: var(--c-border-strong);
  pointer-events: none;
}

.ne-sigpad__hint {
  position: absolute;
  z-index: 1;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: var(--fs-lg);
  font-weight: var(--fw-semi);
  letter-spacing: var(--ls-wide);
  color: var(--c-text-dim);
  pointer-events: none;
}

@media (max-width: 560px) {
  .ne-sigpad { height: 165px; }
  .ne-est__date { width: 76px; padding: var(--sp-3) var(--sp-2); }
  .ne-est__date-num { font-size: var(--fs-xl); }
}

/* --- Impresión ------------------------------------------------------------
   El estimado impreso es un documento contractual. Fuera todo lo interactivo;
   la firma registrada SÍ se imprime, porque es la prueba. */
@media print {
  .ne-est__dates, .ne-est__times, .ne-sigpad,
  #ne-est-book, #ne-est-sign { display: none !important; }
  .ne-est__terms li { color: #000 !important; break-inside: avoid; }
  .ne-est__summary { border: 1px solid #999 !important; background: #fff !important; }
}


/* ==========================================================================
   48 · ALCANCE Y PRECIO ÚNICO
   --------------------------------------------------------------------------
   Un contratista NO enseña el desglose de precios: en cuanto el cliente ve
   qué cuesta cada parte, empieza a quitar partes. Lo que sí conviene enseñar
   —y con detalle— es el ALCANCE: qué entra. Eso protege el margen por el otro
   lado, porque todo lo que no está en la lista es trabajo extra facturable.
   ========================================================================== */

/* --- Lista de alcance ---------------------------------------------------- */
.ne-scope {
  list-style: none;
  padding: 0;
  margin: var(--sp-5) 0 0;
  border-top: 1px solid var(--c-border);
}

.ne-scope__item {
  display: flex;
  gap: var(--sp-4);
  align-items: flex-start;
  padding: var(--sp-5) var(--sp-1);
  border-bottom: 1px solid var(--c-border);
}

/* Palomita verde: cada línea se lee como "esto está incluido", no como
   "esto es una partida que te van a cobrar". El matiz importa. */
.ne-scope__check {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  margin-top: 2px;
  border-radius: 50%;
  background: rgba(var(--c-success-rgb), .14);
  color: var(--c-success);
}

.ne-scope__body { min-width: 0; }

.ne-scope__title {
  display: block;
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  color: var(--c-text);
  margin-bottom: var(--sp-1);
}

.ne-scope__desc {
  display: block;
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--c-text-muted);
  max-width: 68ch;
}

@media (max-width: 560px) {
  .ne-scope__item { gap: var(--sp-3); padding: var(--sp-4) 0; }
  .ne-scope__check { width: 22px; height: 22px; }
  .ne-scope__title { font-size: var(--fs-sm); }
}


/* --- El precio ------------------------------------------------------------
   Una sola cifra, grande, sin nada al lado que invite a compararla o a
   descomponerla. Debajo, y en mucho menor jerarquía, cómo se reparte el pago
   — que es información que el cliente necesita, no una negociación. */
.ne-price {
  margin-top: var(--sp-8);
  border: 1px solid rgba(var(--c-primary-rgb), .35);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--c-bg-2);
}

.ne-price__main {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-8) var(--sp-5);
  text-align: center;
  background:
    radial-gradient(ellipse 70% 100% at 50% 0%, rgba(var(--c-primary-rgb), .16), transparent 70%);
  border-bottom: 1px solid var(--c-border);
}

.ne-price__label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-text-muted);
}

.ne-price__figure {
  font-size: clamp(2.6rem, 1.6rem + 4.4vw, 4.2rem);
  font-weight: var(--fw-black);
  line-height: 1;
  letter-spacing: var(--ls-tight);
  color: var(--c-text);
  /* Un resplandor muy contenido: llama la atención sin parecer un anuncio. */
  text-shadow: 0 0 34px rgba(var(--c-primary-rgb), .45);
}

.ne-price__note {
  font-size: var(--fs-xs);
  color: var(--c-text-dim);
  max-width: 42ch;
}

/* --- Reparto del pago ----------------------------------------------------- */
.ne-price__split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
}

.ne-price__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
}
/* Separadores solo entre columnas, nunca colgando en el borde. */
.ne-price__row + .ne-price__row { border-inline-start: 1px solid var(--c-border); }
@media (max-width: 620px) {
  .ne-price__row + .ne-price__row {
    border-inline-start: 0;
    border-top: 1px solid var(--c-border);
  }
}

.ne-price__row strong { font-size: var(--fs-lg); }


/* --- Impresión ------------------------------------------------------------
   El precio es lo primero que busca quien recibe el PDF. Tiene que leerse
   perfecto en negro sobre blanco. */
@media print {
  .ne-price {
    border: 2px solid #000 !important;
    background: #fff !important;
    break-inside: avoid;
  }
  .ne-price__main { background: #fff !important; }
  .ne-price__figure { color: #000 !important; text-shadow: none !important; }
  .ne-scope__check { background: transparent !important; color: #000 !important; }
  .ne-scope__item { break-inside: avoid; }
}

/* ==========================================================================
   49 · CURSOR PERSONALIZADO
   --------------------------------------------------------------------------
   Un punto sólido que sigue al ratón al instante + un anillo que llega con
   retraso. El retraso es lo que da la sensación "cara": el ojo lee el desfase
   como peso físico.

   POR QUÉ SOLO EN ESCRITORIO CON RATÓN:
   En un móvil no hay cursor, y en una tablet híbrida el puntero "coarse" haría
   aparecer un punto pegado al último toque, que se queda ahí flotando. La
   consulta (hover:hover) and (pointer:fine) es la única forma fiable de decir
   "hay un ratón de verdad". Fuera de ahí el marcado ni siquiera se inyecta
   (app.js lo comprueba antes), pero se deja el display:none como segunda red.

   POR QUÉ NO SE OCULTA EL CURSOR DEL SISTEMA:
   Se mantiene visible a propósito. Ocultarlo (cursor:none) rompe el arrastre,
   los campos de texto y la accesibilidad para quien tiene poca visión. Aquí el
   cursor real sigue estando; esto es un adorno que lo acompaña.
   ========================================================================== */

.ne-cursor,
.ne-cursor-ring {
  display: none;
}

@media (hover: hover) and (pointer: fine) {

  .ne-cursor,
  .ne-cursor-ring {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    border-radius: 50%;
    /* pointer-events:none es OBLIGATORIO: sin esto el adorno se comería todos
       los clics de la web, porque está encima de todo. */
    pointer-events: none;
    z-index: var(--z-toast);
    /* will-change avisa a la GPU: promociona la capa una vez en lugar de
       repintar el documento entero en cada frame. */
    will-change: transform;
  }

  .ne-cursor {
    width: 7px;
    height: 7px;
    margin: -3.5px 0 0 -3.5px;
    background: var(--c-primary);
    box-shadow: 0 0 10px rgba(var(--c-primary-rgb), 0.9);
  }

  .ne-cursor-ring {
    width: 34px;
    height: 34px;
    margin: -17px 0 0 -17px;
    border: 1.5px solid rgba(var(--c-primary-rgb), 0.55);
    transition:
      width var(--dur-base) var(--ease-out),
      height var(--dur-base) var(--ease-out),
      margin var(--dur-base) var(--ease-out),
      border-color var(--dur-base) var(--ease-out),
      background-color var(--dur-base) var(--ease-out);
  }

  /* Sobre un enlace o botón el anillo crece y se tiñe: confirma "esto se pulsa"
     antes incluso de que aparezca el subrayado. */
  .ne-cursor-ring.is-hot {
    width: 56px;
    height: 56px;
    margin: -28px 0 0 -28px;
    border-color: var(--c-primary);
    background: rgba(var(--c-primary-rgb), 0.10);
  }

  /* Al pulsar, el anillo se encoge: retroalimentación visual del clic. */
  .ne-cursor-ring.is-down {
    width: 24px;
    height: 24px;
    margin: -12px 0 0 -12px;
    background: rgba(var(--c-primary-rgb), 0.22);
  }
}

/* Quien pide menos movimiento no ve el adorno: es 100% decorativo. */
@media (prefers-reduced-motion: reduce) {
  .ne-cursor,
  .ne-cursor-ring { display: none !important; }
}


/* ==========================================================================
   50 · RED DE PARTÍCULAS (canvas)
   --------------------------------------------------------------------------
   Puntos que flotan y se unen con líneas cuando están cerca: el efecto "red
   eléctrica" que encaja con el negocio.

   POR QUÉ UN CANVAS PROPIO Y NO particles.js:
   particles.js son 27 KB minificados + una petición a un CDN ajeno, pinta con
   requestAnimationFrame sin pausarse nunca (gasta batería con la pestaña en
   segundo plano) y arrastra un bug conocido de fugas de memoria al redimensionar.
   El equivalente propio son ~90 líneas, se pausa fuera de pantalla y respeta
   prefers-reduced-motion. Cero peticiones externas.

   El canvas va DETRÁS de todo y sin eventos: jamás puede robar un clic.
   ========================================================================== */

.ne-net {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: var(--z-behind);
  pointer-events: none;
  /* Baja a propósito: tiene que leerse como textura, no competir con el texto.
     Con opacidad alta, el contraste del cuerpo bajaría de AA. */
  opacity: 0.42;
  /* Aparece suave para que no "salte" al terminar de arrancar. */
  animation: ne-net-in var(--dur-xslow) var(--ease-out) both;
}

@keyframes ne-net-in {
  from { opacity: 0; }
  to   { opacity: 0.42; }
}

/* En el portal del cliente no se pinta: ahí lo que importa son los datos,
   y el portal se abre muchas veces al día desde el móvil en la obra. */
.ne-is-dashboard .ne-net { display: none; }


/* ==========================================================================
   51 · SOBRE NOSOTROS
   --------------------------------------------------------------------------
   Dos fotos superpuestas con una placa de años encima. Es el patrón que usan
   las webs de constructoras caras porque resuelve dos cosas a la vez: enseña
   obra real y da profundidad sin necesitar una foto de estudio.

   Mobile-first: en móvil las fotos se apilan y la segunda se oculta (una foto
   grande convence más que dos pequeñas en 360 px de ancho).
   ========================================================================== */

.ne-about__media {
  position: relative;
  margin-bottom: var(--sp-10);
}

.ne-about__shot {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-xl);
  box-shadow: var(--sh-lg);
  border: 1px solid var(--c-border);
}

.ne-about__shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out);
}

.ne-about__shot--1 { aspect-ratio: 4 / 3; }

/* La segunda foto solo existe cuando hay sitio para que aporte algo. */
.ne-about__shot--2 { display: none; }

/* Placa de años. Es un dato real que sale de Ajustes → Noah Electric; si vale
   0, la plantilla no la pinta. Nunca un número inventado. */
.ne-about__badge {
  position: absolute;
  left: var(--sp-4);
  bottom: calc(var(--sp-4) * -1);
  z-index: var(--z-raised);
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--r-md);
  background: var(--g-brand);
  color: var(--c-text-on-accent);
  box-shadow: var(--sh-glow-primary), var(--sh-md);
  text-align: center;
  line-height: 1.05;
}

.ne-about__badge b {
  display: block;
  font-size: var(--fs-2xl);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-tight);
}

.ne-about__badge span {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semi);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  opacity: 0.9;
}

/* Los cuatro argumentos cortos bajo el texto. */
.ne-about__points {
  display: grid;
  gap: var(--sp-5);
  margin-top: var(--sp-8);
  padding: 0;
  list-style: none;
}

.ne-about__point {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
}

.ne-about__point-ico {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--r-pill);
  background: rgba(var(--c-primary-rgb), 0.14);
  border: 1px solid rgba(var(--c-primary-rgb), 0.28);
  color: var(--c-primary-300);
}

.ne-about__point h3 {
  margin: 0;
  font-size: var(--fs-md);
  font-weight: var(--fw-semi);
  line-height: var(--lh-snug);
}

.ne-about__point p {
  margin: 2px 0 0;
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
  line-height: var(--lh-snug);
}

@media (min-width: 640px) {
  .ne-about__points { grid-template-columns: 1fr 1fr; }
}

@media (min-width: 992px) {

  .ne-about__media {
    height: 520px;
    margin-bottom: 0;
  }

  .ne-about__shot--1 {
    position: absolute;
    inset: 0 auto auto 0;
    width: 72%;
    height: 400px;
    aspect-ratio: auto;
    z-index: var(--z-raised);
  }

  .ne-about__shot--2 {
    display: block;
    position: absolute;
    inset: auto 0 0 auto;
    width: 56%;
    height: 310px;
    /* El borde del color del fondo es lo que "recorta" la foto de arriba y crea
       la sensación de capas. Sin él las dos fotos se leen como una mancha. */
    border: 9px solid var(--c-bg-1);
    z-index: var(--z-base);
  }

  /* En escritorio la placa se coloca a caballo del borde inferior de la foto
     grande. Puesta en una esquina vacía se lee como un adorno suelto; montada
     sobre la foto, se lee como un sello.
     El 86px sale de la geometría de aquí al lado: el contenedor mide 520 px y
     la foto grande 400, asi que su borde inferior cae a 120 px del suelo. */
  .ne-about__badge {
    left: 5%;
    right: auto;
    top: auto;
    bottom: 86px;
  }
}

/* El zoom solo con ratón: en táctil el :hover se queda "pegado" tras el toque. */
@media (hover: hover) {
  .ne-about__shot:hover img { transform: scale(1.06); }
}


/* ==========================================================================
   52 · PROCESO · LÍNEA DE TIEMPO
   --------------------------------------------------------------------------
   "¿Qué pasa después de que te llame?" es la duda que más presupuestos mata en
   servicios del hogar. Contestarla por escrito, paso a paso, sube la conversión
   más que cualquier animación — y de paso es contenido indexable con las
   palabras clave del proceso (permiso, inspección, garantía).

   Mobile-first: un raíl a la izquierda con los números. En escritorio se abre
   en zigzag a los dos lados de una línea central.
   ========================================================================== */

.ne-steps {
  position: relative;
  margin-top: var(--sp-12);
  /* Es un <ol>: sin esto el navegador pinta su propia numeración junto a los
     círculos, y se verían los números dos veces. */
  list-style: none;
  padding-left: 52px;
}

/* La línea vertical. Se degrada hacia abajo para que no parezca que se corta. */
.ne-steps::before {
  content: "";
  position: absolute;
  top: 8px;
  bottom: 8px;
  left: 21px;
  width: 2px;
  background: linear-gradient(to bottom,
              var(--c-primary) 0%,
              var(--c-secondary) 45%,
              rgba(var(--c-primary-rgb), 0.08) 100%);
}

.ne-step {
  position: relative;
  margin-bottom: var(--sp-8);
}

.ne-step:last-child { margin-bottom: 0; }

.ne-step__num {
  position: absolute;
  left: -52px;
  top: 0;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--g-brand);
  color: var(--c-text-on-accent);
  font-size: var(--fs-md);
  font-weight: var(--fw-black);
  /* El borde del color del fondo tapa la línea justo detrás del número, para
     que la línea no se vea cruzando el círculo. */
  border: 4px solid var(--c-bg-0);
  box-shadow: var(--sh-glow-primary);
  z-index: var(--z-base);
}

.ne-section--alt .ne-step__num { border-color: var(--c-bg-1); }

.ne-step__card {
  padding: var(--sp-5) var(--sp-6);
  border-radius: var(--r-lg);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  transition: border-color var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}

.ne-step__card h3 {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-lg);
  color: var(--c-primary-300);
}

.ne-step__card p {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
  line-height: var(--lh-base);
}

/* El plazo de cada paso. Concretar ("24-48 h") vale mucho más que "rápido". */
.ne-step__when {
  display: inline-block;
  margin-top: var(--sp-3);
  padding: 3px var(--sp-3);
  border-radius: var(--r-pill);
  background: rgba(var(--c-success-rgb), 0.12);
  border: 1px solid rgba(var(--c-success-rgb), 0.25);
  color: var(--c-success);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semi);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
}

@media (hover: hover) {
  .ne-step__card:hover {
    border-color: rgba(var(--c-primary-rgb), 0.42);
    transform: translateX(4px);
  }
}

@media (min-width: 900px) {

  .ne-steps {
    padding-left: 0;
    max-width: 1060px;
    margin-inline: auto;
  }

  .ne-steps::before {
    left: 50%;
    transform: translateX(-50%);
  }

  .ne-step {
    display: grid;
    grid-template-columns: 1fr 92px 1fr;
    align-items: center;
    margin-bottom: var(--sp-10);
  }

  .ne-step__num {
    position: static;
    grid-column: 2;
    grid-row: 1;
    justify-self: center;
    width: 54px;
    height: 54px;
    font-size: var(--fs-lg);
  }

  /* Impares a la izquierda, pares a la derecha. */
  .ne-step__card {
    grid-row: 1;
    grid-column: 1;
  }

  .ne-step:nth-child(even) .ne-step__card {
    grid-column: 3;
  }

  .ne-step:nth-child(odd) .ne-step__card {
    text-align: right;
  }
}

@media (min-width: 900px) and (hover: hover) {
  .ne-step:nth-child(odd) .ne-step__card:hover { transform: translateX(-4px); }
}


/* ==========================================================================
   53 · BANDA DE CIFRAS
   --------------------------------------------------------------------------
   Franja de ancho completo con los números del negocio. Rompe el ritmo de
   secciones oscuras y funciona como respiro visual entre bloques largos.

   Los números salen de Ajustes → Noah Electric (proyectos, años, licencia).
   La plantilla no pinta la banda si están a cero: una banda que dice "0
   proyectos" hace más daño que no tenerla.
   ========================================================================== */

.ne-band {
  position: relative;
  overflow: hidden;
  padding: var(--sp-12) 0;
  background: var(--g-brand);
  color: var(--c-text-on-accent);
}

/* Dos halos suaves para que el degradado no se vea plano. */
.ne-band::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 18% 30%, rgba(255,255,255,0.16) 0%, transparent 52%),
    radial-gradient(circle at 82% 78%, rgba(255,255,255,0.10) 0%, transparent 52%);
  pointer-events: none;
}

.ne-band__grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-8) var(--sp-4);
  text-align: center;
}

.ne-band__item b {
  display: block;
  font-size: var(--fs-3xl);
  font-weight: var(--fw-black);
  line-height: 1;
  letter-spacing: var(--ls-tight);
  /* Sombra dura: el blanco sobre azul claro pierde definición sin ella. */
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.28);
}

.ne-band__item span {
  display: block;
  margin-top: var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  opacity: 0.92;
}

@media (min-width: 860px) {
  .ne-band__grid { grid-template-columns: repeat(4, 1fr); }
}


/* ==========================================================================
   54 · FILTRO DEL PORTFOLIO
   --------------------------------------------------------------------------
   Botonera que filtra los proyectos por tipo de trabajo.

   Detalle importante: son <button> reales con aria-pressed, no <div> con
   onclick. Así funcionan con teclado y un lector de pantalla anuncia cuál está
   activo. Y el filtrado usa el atributo [hidden], para que el estado quede en
   el DOM y sea legible por la tecnología de asistencia.

   Si solo hay una categoría, la plantilla no pinta la botonera: un filtro con
   un único botón es ruido.
   ========================================================================== */

.ne-filter {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-8);
}

.ne-filter__btn {
  min-height: var(--tap-min);
  padding: var(--sp-2) var(--sp-5);
  border-radius: var(--r-pill);
  border: 1px solid var(--c-border);
  background: transparent;
  color: var(--c-text-muted);
  font-family: inherit;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}

@media (hover: hover) {
  .ne-filter__btn:hover {
    color: var(--c-text);
    border-color: var(--c-border-strong);
  }
}

.ne-filter__btn[aria-pressed="true"] {
  background: var(--c-primary);
  border-color: var(--c-primary);
  color: var(--c-text-on-accent);
}

/* La tarjeta que vuelve a entrar hace una entrada corta, para que el cambio de
   la rejilla se lea como movimiento y no como un parpadeo. */
.ne-work.is-in {
  animation: ne-work-in var(--dur-base) var(--ease-out) both;
}

@keyframes ne-work-in {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: none; }
}

/* Mensaje cuando un filtro no tiene proyectos todavía. */
.ne-filter__empty {
  padding: var(--sp-10) var(--sp-4);
  text-align: center;
  color: var(--c-text-dim);
  font-size: var(--fs-sm);
}


/* ==========================================================================
   55 · VÍDEO DE FONDO DEL HERO
   --------------------------------------------------------------------------
   Opcional, se activa desde el Personalizador.

   AVISO HONESTO SOBRE RENDIMIENTO:
   un vídeo de fondo es lo más caro que se puede poner en una portada. Por eso
   aquí va con cuatro frenos, todos en app.js:
     1. No se carga en móvil (datos del cliente y batería).
     2. No se carga con Ahorro de datos activado ni en redes 2G/3G.
     3. Se pausa cuando el hero sale de pantalla.
     4. Se pausa con prefers-reduced-motion (un vídeo en bucle puede provocar
        mareo a personas con trastornos vestibulares).
   La imagen del hero sigue siendo el LCP y se ve siempre; el vídeo entra
   encima cuando ya está listo, con una transición suave.
   ========================================================================== */

.ne-hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity var(--dur-xslow) var(--ease-out);
  pointer-events: none;
}

/* app.js pone esta clase solo cuando hay fotogramas listos (readyState >= 3).
   Sin ella se vería un rectángulo negro durante la descarga. */
.ne-hero__video.is-ready { opacity: 1; }

/* Velo sobre el vídeo: sin él, el titular blanco es ilegible en los fotogramas
   claros y el contraste cae por debajo de AA en mitad del bucle. */
.ne-hero__veil {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(1,4,9,0.78) 0%, rgba(1,4,9,0.62) 45%, rgba(1,4,9,0.92) 100%),
    linear-gradient(90deg, rgba(1,4,9,0.85) 0%, rgba(1,4,9,0.35) 60%, transparent 100%);
}

/* ==========================================================================
   56 · CANDADO HORIZONTAL · MODO APP · TAMAÑO DEL MENÚ
   --------------------------------------------------------------------------
   EL BUG QUE ARREGLA ESTA SECCIÓN (v2.5.1)
   En un iPhone de 375 px la página se movía 26 px hacia los lados. La causa
   medida en el navegador, no adivinada: el botón "Empieza por el paso 1 —
   llámanos" de la sección Proceso mide 384 px por su texto largo + el
   white-space:nowrap de .ne-btn, y el hueco disponible era de 341 px.

   Un solo elemento ancho estira el documento entero, y a partir de ahí TODO
   lo que mide 100 % pasa a medir de más: header, drawer, barra de llamada...
   Por eso parecía que "toda la web" se movía.

   Se arregla en tres capas, de dentro hacia fuera:
     1. Ningún botón puede pasar del ancho de su contenedor (max-width:100%).
     2. En pantalla estrecha, el texto largo se parte en dos líneas.
     3. Candado en la raíz del documento por si algún día se cuela otra cosa.
   ========================================================================== */

/* --- 1. CANDADO EN LA RAÍZ ------------------------------------------------
   Antes solo el <body> tenía overflow-x:hidden. Eso no basta: cuando <html>
   es overflow:visible, el navegador PROPAGA el valor del body al viewport y
   el body vuelve a comportarse como visible. En iOS Safari ese detalle deja
   pasar desbordes igualmente. Poniéndolo en los dos, no hay escapatoria.

   Se prefiere 'clip' a 'hidden': hidden en la raíz crea un contenedor de
   scroll que rompe position:sticky en toda la página. clip recorta sin crear
   ese contenedor. 'hidden' queda de respaldo para navegadores anteriores a
   Safari 16 / Chrome 90.                                                     */
html,
body {
  overflow-x: hidden;
}

@supports (overflow: clip) {
  html,
  body { overflow-x: clip; }
}

/* Nada de rebote lateral. En iPhone, ese "tirón" horizontal es justo lo que
   delata que estás en una web y no en una app. */
html {
  overscroll-behavior-x: none;
}

/* --- 2. MODO APP ----------------------------------------------------------
   Interruptor del Personalizador → Cabecera. Con él activado:
     · no hay "tirar hacia abajo para recargar";
     · el scroll no se encadena a la ventana del navegador al llegar al final.
   El atributo lo pone header.php en el <body>; overscroll-behavior del body
   se propaga al viewport igual que overflow, así que con esto basta.          */
body[data-ne-app] {
  overscroll-behavior-y: contain;
}

/* --- 3. BOTONES A PRUEBA DE TEXTOS LARGOS --------------------------------
   ESTA es la regla importante. Tú escribes los textos de los botones desde el
   Personalizador; sin este tope, un texto largo en español (que casi siempre
   ocupa un 20-25 % más que en inglés) vuelve a romper la web en móvil.        */
.ne-btn {
  max-width: 100%;
}

@media (max-width: 560px) {
  /* El texto largo se parte en dos líneas en lugar de desbordar. Un botón de
     dos líneas se ve perfectamente; una web que se mueve para los lados, no. */
  .ne-btn {
    white-space: normal;
    text-wrap: balance;      /* reparte las dos líneas de forma pareja */
  }
  .ne-btn--lg {
    padding-inline: var(--sp-5);
  }
}

/* --- 4. GESTOS DE iOS -----------------------------------------------------
   Long-press sobre un botón abre el menú "Copiar / Buscar / Compartir" de
   Safari, que en una interfaz de app no pinta nada. Se quita SOLO en los
   controles: el texto del contenido sigue siendo seleccionable, que es lo
   correcto y lo accesible.                                                    */
.ne-btn,
.ne-burger,
.ne-filter__btn,
.ne-drawer__link,
.ne-mobile-cta a {
  -webkit-touch-callout: none;
}


/* ==========================================================================
   57 · TAMAÑO DEL MENÚ HAMBURGUESA (configurable)
   --------------------------------------------------------------------------
   --burger-size lo inyecta inc/customizer.php desde el Personalizador.
   Todo lo demás se calcula a partir de ese número: el grosor de las barras,
   su separación y el desplazamiento de la animación a X.

   POR QUÉ TODO CALCULADO Y NO VALORES SUELTOS:
   la versión anterior tenía el translateY(7px) de la X escrito a mano. Al
   agrandar el botón, las dos barras cruzaban descentradas y la X salía torcida.
   Con calc() la X sale perfecta a cualquier tamaño.
   ========================================================================== */

.ne-burger {
  /* Separación entre barras y grosor, proporcionales al botón. */
  --bg-gap: calc(var(--burger-size, 44px) * 0.115);
  --bg-bar: max(2px, calc(var(--burger-size, 44px) * 0.045));

  width: var(--burger-size, 44px);
  height: var(--burger-size, 44px);
  gap: var(--bg-gap);
  padding-inline: calc(var(--burger-size, 44px) * 0.2);
}

.ne-burger__bar {
  height: var(--bg-bar);
}

/* La X. El desplazamiento es exactamente lo que separa el centro de una barra
   del centro de la del medio: gap + grosor. */
.ne-burger[aria-expanded="true"] .ne-burger__bar:nth-child(1) {
  transform: translateY(calc(var(--bg-gap) + var(--bg-bar))) rotate(45deg);
}
.ne-burger[aria-expanded="true"] .ne-burger__bar:nth-child(2) {
  opacity: 0;
}
.ne-burger[aria-expanded="true"] .ne-burger__bar:nth-child(3) {
  transform: translateY(calc((var(--bg-gap) + var(--bg-bar)) * -1)) rotate(-45deg);
}

/* --- Tamaño de los enlaces del menú abierto ------------------------------
   --drawer-fs y --drawer-row salen del mismo sitio. El valor por defecto es
   el de siempre, así que si no tocas nada el menú se ve exactamente igual.   */
.ne-drawer__link {
  font-size: var(--drawer-fs, clamp(1.35rem, 1.1rem + 1.6vw, 1.75rem));
  min-height: var(--drawer-row, 64px);
}


/* ==========================================================================
   58 · CASILLAS DEL CÓDIGO QUE CABEN SIEMPRE
   --------------------------------------------------------------------------
   EL BUG (v2.5.2): el ancho de cada casilla dependía del ancho de la PANTALLA
   (clamp(48px, 13vw, 62px)), pero las casillas viven dentro de una tarjeta de
   ancho fijo. En un ordenador cada una crecía hasta 62 px: 6 × 62 + 5 huecos
   = 432 px dentro de una tarjeta de ~276 px útiles. La primera y la última
   salían cortadas por el borde.

   AHORA: las casillas se reparten el ancho de la TARJETA. Una rejilla con
   minmax(0, 1fr) no puede desbordar por definición — cada columna es una
   fracción del hueco disponible, y el 0 le permite encogerse por debajo del
   ancho de su contenido.

   grid-auto-flow:column en lugar de repeat(6): si algún día el código pasa a
   tener 4 u 8 dígitos (NOAH_OTP_LENGTH), la rejilla se adapta sola.
   ========================================================================== */

.ne-otp {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: var(--sp-2);
  width: 100%;
  max-width: 360px;
  margin-inline: auto;
  /* Convierte la fila en contenedor: las casillas pueden medir su letra en
     función del ancho de la FILA (cqi), no de la pantalla. */
  container-type: inline-size;
}

.ne-otp__digit {
  width: 100%;
  min-width: 0;
  height: auto;
  aspect-ratio: 5 / 6;
  /* Primera línea: respaldo para Safari anterior a 16. La segunda la pisa
     donde se entiende cqi, y escala la cifra con el ancho real. */
  font-size: 1.5rem;
  font-size: clamp(1.15rem, 9cqi, 2rem);
}

/* iPhone SE y similares: la tarjeta deja ~238 px útiles. Con 8 px entre
   casillas quedaban de 33 px; con 4 px suben a ~36 × 44, la altura mínima
   recomendada para tocar con el dedo. */
@media (max-width: 380px) {
  .ne-otp { gap: 4px; }
}

/* ==========================================================================
   59 · FASES DEL PANEL · BARRA "SIN CONEXIÓN"
   ========================================================================== */

/* .ne-phases es un <ol>: sin esto el navegador pinta "1." "2." "3." "4." al
   lado de cada círculo, que es lo que se veía a la izquierda de las fases. */
.ne-phases {
  list-style: none;
  padding: 0;
}

/* Fase actual: resaltada pero NO marcada como terminada. Antes compartía el
   estilo de .is-done y llevaba una ✓, como si ya estuviera acabada. */
.ne-phase.is-current .ne-phase__dot {
  border-color: var(--c-primary);
  background: rgba(var(--c-primary-rgb), 0.10);
  color: var(--c-primary-300);
}
.ne-phase.is-current .ne-phase__name { color: var(--c-text); }

.ne-phase__num {
  font-size: 13px;
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* La barra "Sin conexión" estaba escondida SOLO visualmente (subida por encima
   del borde de la pantalla). Un lector de pantalla la seguía leyendo aunque
   hubiera internet, y las capturas de página completa la sacaban a la vista.
   visibility:hidden la quita de verdad. El retardo en la transición deja que
   termine de deslizarse hacia arriba antes de desaparecer. */
.ne-offline {
  visibility: hidden;
  transition: transform var(--dur-base) var(--ease-out),
              visibility 0s linear var(--dur-base);
}
.ne-offline.is-visible {
  visibility: visible;
  transition: transform var(--dur-base) var(--ease-out),
              visibility 0s;
}

/* ==========================================================================
   60 · INICIO v3 · CONFIANZA + COTIZACIÓN EN 30 SEGUNDOS
   --------------------------------------------------------------------------
   El inicio anterior hablaba de NOSOTROS ("constructores y proveedores nos
   eligen…"). El cliente que llega desde Google no busca eso: busca que alguien
   de fiar le arregle SU problema, sin sorpresas en el precio. Por eso el orden
   de lectura ahora es:

     1. Confianza, antes del titular: licencia y "hablamos español".
     2. Titular con la palabra clave (Electricista + Austin/Manor) y la promesa.
     3. Su problema, en sus palabras ("se me bota el breaker").
     4. Llamar — o, si no quiere llamar, el formulario de al lado.
     5. Tres garantías concretas.

   El formulario ocupa el sitio del teléfono de muestra. El teléfono no
   desaparece: sigue en su sección propia justo debajo ("Lo que nadie más te
   da"), y en el Personalizador se puede volver a ponerlo aquí.
   ========================================================================== */

/* --- Franja de confianza ------------------------------------------------- */
.ne-hero__kicker {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-5);
  padding: 0;
  list-style: none;
}

.ne-hero__pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px var(--sp-3);
  border-radius: var(--r-pill);
  border: 1px solid rgba(var(--c-success-rgb), 0.35);
  background: rgba(var(--c-success-rgb), 0.10);
  color: var(--c-success);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
}

/* El idioma va en azul: es un argumento distinto, no una credencial. */
.ne-hero__pill--lang {
  border-color: rgba(var(--c-primary-rgb), 0.40);
  background: rgba(var(--c-primary-rgb), 0.12);
  color: var(--c-primary-300);
}

/* --- La promesa, segunda línea del H1 ----------------------------------
   Sigue dentro del <h1> (Google la lee como parte del titular), pero más
   pequeña y sin mayúsculas: con las dos líneas al mismo tamaño el titular
   ocupaba cinco líneas de mayúsculas y costaba leerlo. */
.ne-hero__promise {
  display: block;
  margin-top: var(--sp-3);
  font-size: 0.5em;
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
  text-transform: none;
  color: var(--c-text);
}

/* --- Tres garantías bajo los botones ------------------------------------ */
.ne-hero__checks {
  display: grid;
  gap: var(--sp-2);
  margin: var(--sp-6) 0 0;
  padding: 0;
  list-style: none;
}

.ne-hero__checks li {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
  line-height: var(--lh-snug);
}

.ne-hero__checks svg {
  flex: 0 0 auto;
  margin-top: 1px;
  color: var(--c-success);
}

/* --- Tarjeta de cotización ----------------------------------------------- */
.ne-quote {
  position: relative;
  padding: var(--sp-6);
  border-radius: var(--r-xl);
  border: 1px solid var(--c-border-strong);
  background: rgba(10, 14, 23, 0.82);
  box-shadow: var(--sh-xl), 0 0 0 1px rgba(var(--c-primary-rgb), 0.10);
  /* Cristal: se lee el fondo del hero detrás, pero el formulario manda. */
  -webkit-backdrop-filter: blur(var(--blur-md));
  backdrop-filter: blur(var(--blur-md));
  scroll-margin-top: calc(var(--h-header) + var(--sp-4));
}

/* Línea de marca arriba: la misma firma visual que las tarjetas del portal. */
.ne-quote::before {
  content: "";
  position: absolute;
  inset: 0 var(--sp-6) auto;
  height: 3px;
  border-radius: 0 0 var(--r-pill) var(--r-pill);
  background: var(--g-brand);
}

.ne-quote__eyebrow {
  margin: 0 0 var(--sp-1);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-success);
}

.ne-quote__title {
  margin: 0 0 var(--sp-4);
  font-size: var(--fs-xl);
  line-height: var(--lh-tight);
}

/* --- Botones de tipo de trabajo (radios con forma de ficha) -------------
   Son <input type="radio"> reales dentro de un <label>: se eligen con el
   teclado, un lector de pantalla los anuncia como grupo, y sin JavaScript
   el formulario sigue funcionando.                                         */
.ne-quote__chips {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-2);
  margin: 0 0 var(--sp-4);
  padding: 0;
  border: 0;
}

.ne-chip {
  position: relative;
  display: block;
  cursor: pointer;
}

/* El radio real queda invisible pero ENFOCABLE (no display:none, que lo
   sacaría del teclado y de los lectores de pantalla). */
.ne-chip input {
  position: absolute;
  inset: 0;
  opacity: 0;
  margin: 0;
  cursor: pointer;
}

.ne-chip span {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-min);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-md);
  border: 1px solid var(--c-border-strong);
  background: var(--c-surface-2);
  color: var(--c-text-muted);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semi);
  text-align: center;
  line-height: 1.2;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}

@media (hover: hover) {
  .ne-chip:hover span { border-color: rgba(var(--c-primary-rgb), 0.55); color: var(--c-text); }
}

.ne-chip input:checked + span {
  border-color: var(--c-primary);
  background: rgba(var(--c-primary-rgb), 0.16);
  color: var(--c-text);
  box-shadow: inset 0 0 0 1px var(--c-primary);
}

.ne-chip input:focus-visible + span {
  outline: 2px solid var(--c-primary-300);
  outline-offset: 2px;
}

/* La emergencia, en rojo: se distingue sin tener que leerla. */
.ne-chip--emergency input:checked + span {
  border-color: var(--c-danger);
  background: rgba(var(--c-danger-rgb), 0.16);
  box-shadow: inset 0 0 0 1px var(--c-danger);
}

/* Aviso de emergencia: llamar es más rápido que esperar a que te llamen. */
.ne-quote__urgent {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin: 0 0 var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  border: 1px solid rgba(var(--c-danger-rgb), 0.45);
  background: rgba(var(--c-danger-rgb), 0.12);
  color: var(--c-text);
  font-size: var(--fs-sm);
}

.ne-quote__urgent .ne-btn { flex: 0 0 auto; }

/* --- Campos ------------------------------------------------------------- */
.ne-quote__fields {
  display: grid;
  gap: var(--sp-3);
}

@media (min-width: 480px) {
  .ne-quote__fields { grid-template-columns: 1fr 1fr; }
  .ne-quote__fields .ne-quote__field--wide { grid-column: 1 / -1; }
}

.ne-quote__field .ne-label { margin-bottom: 4px; }

/* 16px en los campos: con menos, iOS hace zoom al tocar y descoloca todo. */
.ne-quote .ne-input { font-size: 16px; }

.ne-quote__submit { margin-top: var(--sp-4); }

.ne-quote__fine {
  margin: var(--sp-3) 0 0;
  text-align: center;
  font-size: var(--fs-2xs);
  color: var(--c-text-dim);
}

.ne-quote__error {
  margin: var(--sp-3) 0 0;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm);
  background: rgba(var(--c-danger-rgb), 0.12);
  color: #ffb4aa;
  font-size: var(--fs-sm);
}

/* Trampa para bots: fuera de la vista y del teclado, pero en el formulario.
   No se usa display:none porque algunos bots se saltan los campos ocultos así. */
.ne-hp {
  position: absolute !important;
  left: -9999px !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --- Confirmación ------------------------------------------------------- */
.ne-quote__done {
  padding: var(--sp-6) 0 var(--sp-2);
  text-align: center;
}

.ne-quote__done-icon {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  margin: 0 auto var(--sp-4);
  border-radius: 50%;
  background: rgba(var(--c-success-rgb), 0.14);
  border: 2px solid var(--c-success);
  color: var(--c-success);
  box-shadow: var(--sh-glow-success);
  animation: ne-quote-pop var(--dur-slow) var(--ease-spring) both;
}

@keyframes ne-quote-pop {
  from { transform: scale(0.6); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

.ne-quote__done h3 {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-xl);
}

.ne-quote__done p {
  margin: 0 auto;
  max-width: 34ch;
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
}

/* --- Móvil ----------------------------------------------------------------
   En móvil la tarjeta va DEBAJO del texto y a todo el ancho. Los botones de
   llamar se quedan arriba: en el móvil, llamar es un toque y convierte más
   que cualquier formulario.                                                */
@media (max-width: 1023px) {
  .ne-quote { margin-top: var(--sp-8); padding: var(--sp-5); }
}

/* ==========================================================================
   61 · PASADA DE PRODUCTO (auditoría 3-oct-2026)
   --------------------------------------------------------------------------
   Cinco arreglos de uso y dos piezas nuevas. Todo lo de aquí abajo es aditivo:
   no reescribe ninguna sección anterior, solo la afina.
   ========================================================================== */

/* --- 1 · ÁREA DE TOQUE EN EL PIE -----------------------------------------
   Medido en un iPhone de 375 px: el teléfono del pie ocupaba 98 × 19 px y los
   iconos de redes 42 px. La guía de accesibilidad pide 44 px, y un electricista
   con guantes necesita más. Se agranda el ÁREA, no la letra: el diseño del pie
   no cambia a la vista.                                                      */
.ne-footer__list a,
.ne-footer__nap a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  /* El margen negativo compensa el relleno: el texto sigue donde estaba. */
  padding-block: calc((var(--tap-min) - 1.4em) / 2);
  margin-block: calc((var(--tap-min) - 1.4em) / -2);
}

.ne-social__link {
  width: var(--tap-min);
  height: var(--tap-min);
}

/* --- 2 · TARJETA DE SERVICIO SIN FOTO -------------------------------------
   Ocho servicios tienen foto y uno no, así que ese rompía la rejilla y se leía
   como un hueco. Ahora la tarjeta sin foto tiene su propia portada dibujada:
   degradado de marca, el icono grande y una trama de líneas. La rejilla queda
   pareja aunque falte material.                                              */
.ne-service--link:not(.ne-service--photo) {
  position: relative;
  padding-top: 0;
  overflow: hidden;
}

.ne-service--link:not(.ne-service--photo)::before {
  content: "";
  display: block;
  height: 120px;
  margin: 0 0 var(--sp-5);
  background:
    linear-gradient(135deg, rgba(var(--c-primary-rgb), 0.28), rgba(var(--c-secondary-rgb), 0.18)),
    repeating-linear-gradient(135deg, rgba(255,255,255,.05) 0 2px, transparent 2px 10px);
  border-bottom: 1px solid var(--c-border);
}

.ne-service--link:not(.ne-service--photo) > .ne-service__icon {
  position: absolute;
  top: 38px;
  inset-inline-start: var(--sp-6);
  width: 46px;
  height: 46px;
}

.ne-service--link:not(.ne-service--photo) > .ne-service__title,
.ne-service--link:not(.ne-service--photo) > .ne-service__text,
.ne-service--link:not(.ne-service--photo) > .ne-service__link {
  padding-inline: var(--sp-6);
}

.ne-service--link:not(.ne-service--photo) > .ne-service__link {
  padding-bottom: var(--sp-6);
}

/* --- 3 · BARRA DE AVANCE DE LECTURA ---------------------------------------
   La portada mide 19 pantallas en móvil. Esta línea dice cuánto queda.

   Va SIN JavaScript: animation-timeline deja que el navegador la mueva con el
   scroll, en el hilo de composición. Un listener de scroll en JS haría el mismo
   efecto costando trabajo en cada fotograma. Donde no se entienda (Safari
   antiguo), la barra simplemente no aparece: nunca se queda a medias.          */
.ne-readbar {
  display: none;
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  height: 3px;
  z-index: calc(var(--z-header) + 1);
  transform-origin: 0 50%;
  background: var(--g-brand);
  pointer-events: none;
}

@supports (animation-timeline: scroll()) {
  .ne-readbar {
    display: block;
    transform: scaleX(0);
    animation: ne-readbar-grow linear both;
    animation-timeline: scroll(root block);
  }
}

@keyframes ne-readbar-grow {
  to { transform: scaleX(1); }
}

@media (prefers-reduced-motion: reduce) {
  .ne-readbar { display: none; }
}

/* --- 4 · TRANSICIÓN AL CAMBIAR DE PÁGINA ----------------------------------
   Al tocar un servicio, la página se funde con la siguiente en lugar de
   parpadear en blanco. Es la diferencia entre "web" y "app", y lo hace el
   navegador: cero JavaScript, cero peso.                                      */
@view-transition {
  navigation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 220ms;
  animation-timing-function: var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* --- 5 · ANTES Y DESPUÉS ---------------------------------------------------
   Dos fotos del mismo sitio y una barra que se arrastra. En oficios visuales
   es el formato que más retiene, y tú ya tienes las fotos de cada obra.

   El control es un <input type="range"> de verdad, no un div con eventos de
   ratón: funciona con el teclado (flechas), lo anuncia un lector de pantalla
   y en el móvil se arrastra con el dedo sin una línea de código extra.         */
.ne-ba {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-lg);
  border: 1px solid var(--c-border);
  background: var(--c-bg-2);
  box-shadow: var(--sh-lg);
  aspect-ratio: 4 / 3;
  isolation: isolate;
}

.ne-ba img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
  -webkit-user-drag: none;
}

/* La capa "antes" se recorta hasta la posición de la barra. clip-path no
   repinta la imagen: solo cambia qué parte se ve. Por eso va fluido en móvil. */
.ne-ba__before {
  position: absolute;
  inset: 0;
  clip-path: inset(0 calc(100% - var(--ba-pos, 50%)) 0 0);
}

.ne-ba__line {
  position: absolute;
  inset-block: 0;
  inset-inline-start: var(--ba-pos, 50%);
  width: 2px;
  margin-inline-start: -1px;
  background: #fff;
  box-shadow: 0 0 12px rgba(0, 0, 0, 0.55);
  pointer-events: none;
}

.ne-ba__grip {
  position: absolute;
  top: 50%;
  inset-inline-start: var(--ba-pos, 50%);
  width: 46px;
  height: 46px;
  margin: -23px 0 0 -23px;
  border-radius: 50%;
  background: var(--c-bg-1);
  border: 2px solid #fff;
  box-shadow: var(--sh-md);
  display: grid;
  place-items: center;
  color: #fff;
  font-size: 15px;
  letter-spacing: -2px;
  pointer-events: none;
}

/* El range ocupa toda la foto, invisible: el dedo puede arrastrar desde
   cualquier punto, no solo desde el tirador. */
.ne-ba__range {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: ew-resize;
  -webkit-appearance: none;
  appearance: none;
  background: none;
}
.ne-ba__range::-webkit-slider-thumb { -webkit-appearance: none; width: 60px; height: 100%; }
.ne-ba__range::-moz-range-thumb { width: 60px; height: 100%; border: 0; background: transparent; }

.ne-ba__range:focus-visible ~ .ne-ba__grip {
  outline: 3px solid var(--c-primary);
  outline-offset: 3px;
}

.ne-ba__tag {
  position: absolute;
  top: var(--sp-3);
  padding: 4px var(--sp-3);
  border-radius: var(--r-pill);
  background: rgba(1, 4, 9, 0.72);
  -webkit-backdrop-filter: blur(var(--blur-sm));
  backdrop-filter: blur(var(--blur-sm));
  color: var(--c-text);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  pointer-events: none;
}
.ne-ba__tag--before { inset-inline-start: var(--sp-3); }
.ne-ba__tag--after  { inset-inline-end: var(--sp-3); }

.ne-ba__caption {
  margin-top: var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
  text-align: center;
}

/* ==========================================================================
   62 · ZONAS DE SERVICIO (páginas de ciudad)   ← nuevo en 2.8.0
   --------------------------------------------------------------------------
   Casi todo lo que necesitan estas páginas ya existía: .ne-service para las
   tarjetas, .ne-card para los bloques, .ne-faq para las preguntas. Aquí solo
   van las cuatro cosas que faltaban, y ni una más — cada regla nueva es una
   regla que alguien tiene que mantener.
   ========================================================================== */

/* La foto de la ciudad. El <figure> lleva la proporción y el recorte; la <img>
   que genera WordPress se adapta dentro. Sin aspect-ratio en el contenedor, la
   página salta cuando la foto termina de cargar. */
.ne-area__photo {
  margin: 0;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  background: var(--c-bg-2);
}
.ne-area__photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Tarjeta del índice SIN foto. .ne-service es un flex con gap, así que su
   contenido envuelto en un <div> perdería la separación entre líneas: el div
   sería un único hijo y dentro todo quedaría pegado. */
.ne-area-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

/* Los barrios NO son botones. .ne-chip trae cursor:pointer porque nació para
   el filtro del portfolio; una etiqueta que parece pulsable y no lo es es una
   promesa rota, y el usuario la pulsa dos veces antes de rendirse. */
.ne-chip--static {
  cursor: default;
}

/* Un .ne-badge que de verdad lleva a otra ciudad. El subrayado global de los
   enlaces dentro de una pastilla queda mal, pero quitarlo sin dar NINGUNA
   señal de que se puede pulsar es peor: de ahí el borde que se enciende y el
   desplazamiento de un píxel al pasar por encima. */
a.ne-badge {
  text-decoration: none;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  a.ne-badge:hover {
    border-color: var(--c-primary);
    background: rgba(var(--c-primary-rgb), 0.18);
    transform: translateY(-1px);
  }
}
a.ne-badge:focus-visible {
  outline: 2px solid var(--c-primary);
  outline-offset: 2px;
}


/* ==========================================================================
   63 · INICIO DE ALTO IMPACTO   ← nuevo en 2.9.0
   --------------------------------------------------------------------------
   DIAGNÓSTICO, porque el rediseño sin diagnóstico es decoración:

   El inicio no se veía "pobre" por el reparto de la pantalla — eso estaba
   bien. Se veía pobre por tres cosas medibles:

     1 · El fondo era plano. Un degradado liso sobre negro no tiene ni luz ni
         material, así que el ojo no encuentra profundidad en ninguna parte.
     2 · Nada se movía por sí solo. Todo aparecía ya colocado, como una
         diapositiva.
     3 · Un solo mensaje fijo. El visitante que no se reconoce en esa frase
         concreta no tiene una segunda oportunidad.

   Lo que de verdad hace que una web oscura se sienta cara no son más efectos:
   es LUZ (una fuente que se nota), MATERIAL (grano y bordes finos que
   capturan esa luz) y MOVIMIENTO CON INTENCIÓN (lo importante entra primero).
   Eso es lo que hay aquí abajo, en ese orden.

   Todo respeta prefers-reduced-motion, todo sigue siendo legible sin JS, y
   nada de esto toca el formulario de cotización ni su envío.
   ========================================================================== */


/* --- 1 · LUZ: un foco que sigue al puntero --------------------------------
   Una fuente de luz que reacciona convierte un fondo en un espacio. El JS solo
   escribe dos variables (--ne-mx / --ne-my) y el navegador repinta un
   degradado: cero layout, cero reflow, se mueve en el compositor.
   Sin JS, sin ratón o con movimiento reducido se queda centrado arriba, que es
   exactamente donde conviene que esté la luz de todas formas.               */
.ne-hero {
  --ne-mx: 50%;
  --ne-my: 34%;
  isolation: isolate;
}

.ne-hero__spot {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background:
    radial-gradient(620px circle at var(--ne-mx) var(--ne-my),
      rgba(var(--c-primary-rgb), 0.20), transparent 62%),
    radial-gradient(420px circle at var(--ne-mx) var(--ne-my),
      rgba(var(--c-secondary-rgb), 0.12), transparent 55%);
  transition: opacity var(--dur-slow) var(--ease-out);
}

/* --- 2 · MATERIAL: grano -------------------------------------------------
   El detalle más barato y más eficaz de toda esta sección. Sobre fondos
   oscuros, un degradado liso produce bandas visibles (banding) y se lee como
   "plantilla". Una capa de ruido finísimo las rompe y le da textura de
   fotografía. Es un SVG en línea: cero peticiones, cero kilobytes de red.    */
.ne-hero__grain {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  opacity: 0.055;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* --- 3 · PROFUNDIDAD: rejilla de conduit ---------------------------------
   Una retícula muy tenue, como el replanteo de una instalación. Da una escala
   de referencia al fondo —sin ella el ojo no sabe si el espacio es grande o
   pequeño— y es la única parte que dice "esto es eléctrico" sin dibujar un
   rayo. Se desvanece con una máscara para que nunca llegue al texto.        */
.ne-hero__bg::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background-image:
    linear-gradient(to right,  rgba(var(--c-primary-rgb), 0.085) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(var(--c-primary-rgb), 0.055) 1px, transparent 1px);
  background-size: 74px 74px;
  -webkit-mask-image: radial-gradient(ellipse 85% 70% at 50% 42%, #000 18%, transparent 72%);
          mask-image: radial-gradient(ellipse 85% 70% at 50% 42%, #000 18%, transparent 72%);
  animation: ne-grid-drift 26s linear infinite;
}
@keyframes ne-grid-drift {
  to { background-position: 74px 74px, 74px 74px; }
}

/* El velo pasa por encima de la rejilla y el foco, nunca por debajo: el texto
   manda siempre sobre la decoración. */
.ne-hero__bg::after { z-index: 2; }

/* --- 4 · VIDA: la foto respira -------------------------------------------
   Un movimiento de 40 segundos que casi no se percibe conscientemente, pero
   que separa "web viva" de "captura de pantalla". Escala mínima para que no se
   pierda nitidez.                                                           */
.ne-hero__bg > img {
  transform-origin: 55% 45%;
  animation: ne-hero-breathe 44s var(--ease-out) infinite alternate;
}
@keyframes ne-hero-breathe {
  from { transform: scale(1.015) translate3d(0, 0, 0); }
  to   { transform: scale(1.085) translate3d(-1.2%, -1.6%, 0); }
}


/* --- 5 · LOS MENSAJES QUE ROTAN ------------------------------------------
   Las diapositivas se apilan en LA MISMA celda de un grid. El contenedor toma
   la altura de la más alta, así que al cambiar de mensaje no se mueve ni un
   píxel de lo que hay debajo: ni los botones ni el formulario saltan.

   Sin JS se ve la primera y ya está: is-active viene puesto desde el servidor.
   El <h1> real es siempre el de la primera — Google indexa un solo titular. */
.ne-hero__stage {
  display: grid;
  margin-bottom: var(--sp-5);
}
/* La que SALE se va deprisa. La que ENTRA espera a que la anterior se haya
   ido del todo.

   Un fundido cruzado normal —las dos a la vez durante medio segundo— deja los
   dos titulares legibles superpuestos en la misma celda del grid. Se ve en la
   previsualización y es horrible: parecen dos textos impresos uno encima del
   otro. Con la salida en 240 ms y la entrada retrasada 220 ms no coinciden
   nunca, y el cambio se sigue sintiendo suave. */
.ne-hero__slide {
  grid-area: 1 / 1;
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity 240ms var(--ease-out),
              transform 240ms var(--ease-out),
              visibility 0s linear 240ms;
}
.ne-hero__slide.is-active {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity 420ms var(--ease-out) 220ms,
              transform 520ms var(--ease-out) 220ms,
              visibility 0s linear 0s;
}
/* El <p> que hace de titular en las diapositivas 2 y 3 tiene que verse
   exactamente igual que el <h1>, sin heredar el estilo de párrafo. */
p.ne-hero__title {
  margin: 0 0 var(--sp-5);
  font-weight: var(--fw-black);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  color: var(--c-text);
  text-wrap: balance;
}
.ne-hero__stage .ne-hero__title { margin-bottom: var(--sp-5); }
.ne-hero__stage .ne-hero__sub   { margin-bottom: 0; }

/* Los puntos. Área de toque de 44 px aunque el punto mida 8: el área se
   agranda con el relleno, no el dibujo. */
.ne-hero__dots {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  margin-bottom: var(--sp-8);
}
.ne-hero__dot {
  --dot: 8px;
  position: relative;
  width: var(--tap-min);
  height: var(--tap-min);
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  border-radius: var(--r-pill);
}
.ne-hero__dot::after {
  content: "";
  position: absolute;
  inset: 50% auto auto 50%;
  width: var(--dot);
  height: var(--dot);
  transform: translate(-50%, -50%);
  border-radius: var(--r-pill);
  background: var(--c-text-dim);
  opacity: 0.45;
  transition: width var(--dur-base) var(--ease-spring),
              opacity var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out);
}
.ne-hero__dot.is-active::after {
  width: 28px;
  opacity: 1;
  background: var(--c-primary);
  box-shadow: 0 0 14px rgba(var(--c-primary-rgb), 0.8);
}
.ne-hero__dot:focus-visible { outline: 2px solid var(--c-primary); outline-offset: -8px; }
@media (hover: hover) and (pointer: fine) {
  .ne-hero__dot:hover::after { opacity: 0.9; }
}


/* --- 6 · CORRIENTE EN LOS BOTONES ----------------------------------------
   SOLO en los dos botones del inicio. Los demás botones de la web —portal,
   estimados, firma— no se tocan: ahí un borde que gira distrae de una acción
   seria, y además es donde no se puede romper nada.

   La corriente es un degradado cónico que gira alrededor del borde. Se anima
   un ángulo registrado con @property porque un conic-gradient normal no es
   interpolable: sin esto el giro iría a saltos.                             */
@property --ne-volt {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.ne-hero__cta .ne-btn {
  position: relative;
  isolation: isolate;
}
.ne-hero__cta .ne-btn::after {
  content: "";
  position: absolute;
  inset: -2px;
  z-index: -1;
  border-radius: inherit;
  padding: 2px;
  background: conic-gradient(from var(--ne-volt),
    transparent 0deg,
    rgba(var(--c-primary-rgb), 0.95) 32deg,
    rgba(var(--c-secondary-rgb), 0.85) 58deg,
    transparent 92deg,
    transparent 360deg);
  /* Deja ver solo el borde: se pinta el relleno y se recorta el interior. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .ne-hero__cta .ne-btn:hover::after { opacity: 1; animation: ne-volt-spin 2.6s linear infinite; }
}
.ne-hero__cta .ne-btn:focus-visible::after { opacity: 1; animation: ne-volt-spin 2.6s linear infinite; }
@keyframes ne-volt-spin { to { --ne-volt: 360deg; } }

/* Respaldo donde @property no existe (Firefox antiguo): el borde se enciende
   igual, simplemente no gira. Mejor un brillo quieto que nada. */
@supports not (background: conic-gradient(from 0deg, red, blue)) {
  .ne-hero__cta .ne-btn::after {
    background: linear-gradient(135deg,
      rgba(var(--c-primary-rgb), 0.9), rgba(var(--c-secondary-rgb), 0.7));
  }
}


/* --- 7 · LA ENTRADA ------------------------------------------------------
   Por orden de importancia, no todo a la vez: sellos → titular → subtítulo →
   botones → garantías. 70 ms entre cada uno; menos no se percibe, más se hace
   esperar.

   Va bajo .ne-js: si el JavaScript no llega, el contenido se ve desde el
   primer milisegundo. Un titular invisible porque falló un script es la peor
   manera posible de perder un cliente.                                      */
.ne-js .ne-hero__copy > * {
  /* 'backwards', NO 'both', y SIN opacity:0 de base.

     Con opacity:0 de base el elemento depende de que la animación llegue a
     ejecutarse para volver a ser visible. Si algo la impide —una extensión que
     desactiva animaciones, un navegador que no entiende el @keyframes, una
     hoja de estilos que llega tarde— el titular del inicio se queda invisible
     para siempre. Lo vi en la previsualización antes de entregarlo.

     Con 'backwards' el elemento solo está oculto DURANTE el retardo; en cuanto
     la animación termina vuelve a su estilo propio, y si la animación nunca
     corre, nunca se oculta. El efecto es idéntico y el fallo es imposible. */
  animation: ne-hero-rise 760ms var(--ease-out) backwards;
}
.ne-js .ne-hero__copy > *:nth-child(1) { animation-delay:  60ms; }
.ne-js .ne-hero__copy > *:nth-child(2) { animation-delay: 130ms; }
.ne-js .ne-hero__copy > *:nth-child(3) { animation-delay: 200ms; }
.ne-js .ne-hero__copy > *:nth-child(4) { animation-delay: 270ms; }
.ne-js .ne-hero__copy > *:nth-child(5) { animation-delay: 340ms; }
.ne-js .ne-hero__copy > *:nth-child(6) { animation-delay: 410ms; }
.ne-js .ne-hero__copy > *:nth-child(7) { animation-delay: 480ms; }

@keyframes ne-hero-rise {
  from { opacity: 0; transform: translate3d(0, 18px, 0); filter: blur(6px); }
  to   { opacity: 1; transform: none;                    filter: none; }
}


/* --- 8 · EL CRISTAL DEL FORMULARIO ---------------------------------------
   Solo estilo, y solo dentro del inicio: ni una línea del HTML del formulario
   ni de su envío se toca aquí.

   El borde superior más claro imita la luz cayendo desde arriba, que es lo que
   hace que un cristal parezca cristal y no un rectángulo translúcido.        */
.ne-hero .ne-quote {
  border-color: rgba(255, 255, 255, 0.10);
  background: rgba(10, 14, 23, 0.58);
  -webkit-backdrop-filter: blur(var(--blur-lg)) saturate(150%);
          backdrop-filter: blur(var(--blur-lg)) saturate(150%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.10),
    0 32px 70px -28px rgba(0, 0, 0, 0.9),
    0 0 0 1px rgba(var(--c-primary-rgb), 0.12);
}
/* Donde no hay backdrop-filter, el cristal transparente dejaría el formulario
   ilegible sobre la foto. Ahí se vuelve opaco, sin discusión. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ne-hero .ne-quote { background: rgba(10, 14, 23, 0.95); }
}


/* --- 9 · MOVIMIENTO REDUCIDO ---------------------------------------------
   Quien lo pide, lo pide por mareo o por migraña. Se apaga TODO lo de arriba;
   la web queda idéntica en contenido y en legibilidad.                      */
@media (prefers-reduced-motion: reduce) {
  .ne-hero__bg > img,
  .ne-hero__bg::before { animation: none; }
  .ne-hero__slide,
  .ne-hero__slide.is-active { transition: opacity 1ms linear; transform: none; }
  .ne-js .ne-hero__copy > * { opacity: 1; animation: none; }
  .ne-hero__cta .ne-btn::after { animation: none; }
}

/* En móvil la rejilla y el grano se notan menos y cuestan pintura en pantallas
   grandes de poca potencia. El foco queda fijo: sin ratón no aporta nada. */
@media (max-width: 767px) {
  .ne-hero__bg::before { opacity: 0.6; animation: none; }
  .ne-hero__grain { opacity: 0.04; }
}


/* ==========================================================================
   64 · UNA IMAGEN POR MENSAJE   ← nuevo en 3.0.0
   --------------------------------------------------------------------------
   Cada mensaje del inicio puede llevar SU foto de fondo y SU foto al lado. Un
   carrusel en el que solo cambia el texto se queda a medias: el fondo es el
   90% de la pantalla, y si no acompaña al mensaje el cambio casi no se nota.

   Las fotos se apilan todas desde el principio y solo se cruza la opacidad.
   Nada aparece ni desaparece del flujo, así que el navegador no recalcula la
   maquetación en ningún momento del cambio.
   ========================================================================== */

/* --- Fondos por mensaje ---------------------------------------------------
   Gana en especificidad a `.ne-hero__bg img` (0,2,1 contra 0,1,1), que es
   quien fija el 0.34 de opacidad para que el texto siempre se lea encima.    */
.ne-hero__bg img.ne-hero__bgshot {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 700ms var(--ease-out);
}
.ne-hero__bg img.ne-hero__bgshot.is-active {
  opacity: 0.34;
}

/* --- Foto al lado, por mensaje -------------------------------------------
   Altura fija por proporción y no por contenido: así la columna no cambia de
   tamaño al pasar de una foto vertical a una horizontal, y el formulario o el
   texto de al lado no se mueven.                                             */
.ne-hero__shots {
  position: relative;
  display: grid;
  aspect-ratio: 4 / 5;
  width: 100%;
  max-width: 520px;
  margin-inline: auto;
}
@media (min-width: 1024px) {
  .ne-hero__shots { aspect-ratio: 4 / 4.4; }
}

.ne-hero__shot {
  grid-area: 1 / 1;
  position: relative;
  margin: 0;
  overflow: hidden;
  border-radius: var(--r-xl);
  border: 1px solid var(--c-border-strong);
  background: var(--c-bg-2);
  box-shadow: var(--sh-xl), 0 0 0 1px rgba(var(--c-primary-rgb), 0.10);
  opacity: 0;
  visibility: hidden;
  transform: scale(1.02);
  transition: opacity 240ms var(--ease-out),
              transform 240ms var(--ease-out),
              visibility 0s linear 240ms;
}
.ne-hero__shot.is-active {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity 420ms var(--ease-out) 200ms,
              transform 620ms var(--ease-out) 200ms,
              visibility 0s linear 0s;
}
.ne-hero__shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Luz de arriba sobre el marco, igual que en el cristal del formulario: es lo
   que hace que la foto parezca montada en la página y no pegada encima. */
.ne-hero__shot::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12),
              inset 0 -80px 90px -60px rgba(1, 4, 9, 0.9);
}

/* Pie de foto opcional, dentro del marco y sobre el degradado de abajo. */
.ne-hero__shot figcaption {
  position: absolute;
  inset: auto 0 0;
  z-index: var(--z-base);
  padding: var(--sp-4) var(--sp-5);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semi);
  color: var(--c-text);
  text-shadow: 0 1px 3px rgba(1, 4, 9, 0.9);
}

@media (prefers-reduced-motion: reduce) {
  .ne-hero__bg img.ne-hero__bgshot { transition: opacity 1ms linear; }
  .ne-hero__shot,
  .ne-hero__shot.is-active { transition: opacity 1ms linear; transform: none; }
}


/* ==========================================================================
   65 · GEOMETRÍA DE MARCA + TELÉFONO REAL   ← nuevo en 3.1.0
   --------------------------------------------------------------------------
   POR QUÉ: "la web no combina con el logo" seguía siendo cierto DESPUÉS de
   igualar los colores. Medí por qué, y el color no era el problema que
   quedaba. Era la FORMA.

     El logo:  diagonales a 45°, puntas cortadas, cero curvas, macizo, opaco.
     La web:   31 elementos con radio 999px (pastilla), tarjetas a 24-32px,
               23 superficies de cristal, y Poppins — de las geométricas, la
               más redonda que existe (la O es un círculo perfecto).

   Una pieza de acero mecanizada al lado de una app de cristal. Los colores ya
   eran los mismos; las formas se contradecían.

   Lo de aquí abajo no añade efectos. Quita redondez.
   ========================================================================== */


/* --- 1 · RADIOS MECANIZADOS ----------------------------------------------
   Se redefinen los tokens, no las reglas: un solo bloque y cambia toda la web
   —inicio, login, panel del cliente, estimados— sin tocar ni una regla suelta.
   Y se vuelve atrás borrando este bloque.

   No se va a cero: el logo es angular, no brutalista. 8-12px lee a "pieza
   mecanizada"; 0px leería a "sin terminar".                                  */
:root {
  --r-xs:  3px;
  --r-sm:  4px;
  --r-md:  6px;
  --r-lg:  8px;
  --r-xl:  10px;
  --r-2xl: 12px;

  /* Para lo que antes era pastilla y ahora es ficha. */
  --r-chip: 4px;
}

/* Lo que era pastilla y NO debería serlo: botones, etiquetas, fichas, enlaces
   del menú, pestañas. Una pastilla es la forma más contraria que existe a una
   X de puntas cortadas.

   Lo que SÍ sigue siendo redondo no está en esta lista y no se toca: los
   puntos, la barra de progreso, las rayas del menú hamburguesa, los iconos
   circulares. Esos son redondos porque son redondos, no por decoración.     */
.ne-btn,
.ne-badge,
.ne-chip,
.ne-trust,
.ne-nav__link,
.ne-lang,
.ne-lang__btn,
.ne-tabs,
.ne-tab,
.ne-filter__btn,
.ne-hero__pill,
.ne-ba__tag,
.ne-step__when,
.ne-viewer__hint,
.ne-bot__input,
.ne-bot__lang,
.ne-bot__quick button {
  border-radius: var(--r-chip);
}


/* --- 2 · TIPOGRAFÍA QUE SE PARECE AL LOGO --------------------------------
   Poppins es una geométrica de círculos: la O es un círculo exacto y la A una
   punta perfecta. Es amable y redonda — lo contrario de un logo de chapa.

   Barlow es una grotesca de formas LIGERAMENTE CUADRADAS, dibujada para
   señalización y transporte. Es la misma familia de sensación que las letras
   de tu logo, que tienen la C casi cuadrada y los remates planos.

   Barlow Condensed se reserva para los titulares grandes, donde el logo pone
   "NOAH ELECTRIC" en mayúsculas estrechas y espaciadas.

   Barlow es además MÁS ESTRECHA que Poppins, así que al cambiar el texto ocupa
   menos, nunca más: ninguna caja del panel del cliente se puede desbordar.    */
/* Los tokens --ff-base y --ff-display se definen UNA sola vez, arriba con el
   resto. Aquí solo se dice QUÉ usa la display. */
h1, h2,
.ne-hero__title,
.ne-h1, .ne-h2 {
  font-family: var(--ff-display);
  letter-spacing: 0.012em;   /* la condensada pide algo de aire; la normal no */
}

/* Las etiquetas pequeñas en mayúsculas son donde más se nota el parecido con
   el logo: espaciadas y estrechas, como "MANOR · AUSTIN · TX". */
.ne-eyebrow,
.ne-label,
.ne-hero__stat span,
.ne-kpi__label {
  font-family: var(--ff-display);
  font-weight: var(--fw-semi);
}


/* ==========================================================================
   3 · EL TELÉFONO, DE VERDAD
   --------------------------------------------------------------------------
   Lo que había ya tenía muesca y reflejo que sigue al ratón — más de lo que
   suele tener un mockup de CSS. Lo que fallaba era concreto:

     · Era una MUESCA, no una Dynamic Island. Una muesca pegada al borde
       superior es un iPhone X a 14. Del 15 en adelante es una píldora suelta,
       separada del borde. Una muesca hoy se lee como teléfono viejo.
     · Ningún botón lateral. Es el detalle que más delata a un dibujo.
     · Un halo azul alrededor. Los teléfonos no brillan.
     · El bisel medía 10px sobre 280 de ancho: el doble de lo real.
     · Proporción 9/19 en vez de 9/19.5.

   Todo lo de abajo son proporciones reales: la isla ocupa el 31,8% del ancho
   porque en un iPhone mide 125 pt sobre una pantalla de 393 pt.
   ========================================================================== */

.ne-hero__device {
  aspect-ratio: 9 / 19.5;
  padding: 6px;                      /* bisel, ~2,3 mm reales a esta escala */
  border: 0;
  border-radius: 42px;
  background: #04060a;               /* el bisel es negro, no gris de tema */
  box-shadow:
    /* el canto interior del titanio, en sombra */
    inset 0 0 0 1px rgba(255, 255, 255, 0.07),
    /* una sombra de objeto real: larga, baja y sin color */
    0 38px 64px -28px rgba(0, 0, 0, 0.95),
    0 12px 24px -12px rgba(0, 0, 0, 0.8);
}

/* El marco de titanio: va DETRÁS y asoma 2px. El degradado es lo que hace que
   el canto parezca captar la luz en lugar de ser una línea pintada. */
.ne-hero__frame {
  position: absolute;
  inset: -2px;
  z-index: -1;
  border-radius: 44px;
  background: linear-gradient(145deg,
    #9aa3b2 0%,
    #4a515c 22%,
    #2e343d 48%,
    #434a55 62%,
    #8e97a6 100%);
  pointer-events: none;
}

/* La isla. Suelta, separada del borde, completamente redondeada. */
.ne-hero__device::before {
  content: "";
  position: absolute;
  top: 2.2%;
  left: 50%;
  transform: translateX(-50%);
  width: 31.8%;
  aspect-ratio: 3.38 / 1;            /* 125 × 37 pt, la medida real */
  height: auto;
  border-radius: 999px;
  background: #000;
  z-index: var(--z-raised);
  pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05);
}

/* El reflejo se queda, pero ajustado al bisel nuevo. */
.ne-hero__device::after {
  inset: 6px;
  border-radius: 36px;
}

/* Botones laterales. Tres a la izquierda —acción, subir, bajar— y el de
   encendido a la derecha, en las alturas en que están de verdad. */
.ne-hero__keys {
  position: absolute;
  width: 3px;
  background: linear-gradient(90deg, #7b8492, #3d434c);
  pointer-events: none;
  z-index: var(--z-base);
}
.ne-hero__keys::before,
.ne-hero__keys::after {
  content: "";
  position: absolute;
  left: 0;
  width: 3px;
  background: inherit;
  border-radius: inherit;
}

.ne-hero__keys--l {
  left: -3px;
  top: 13%;
  height: 4.2%;                       /* botón de acción */
  border-radius: 2px 0 0 2px;
}
.ne-hero__keys--l::before { top: 155%; height: 142%; }   /* subir volumen */
.ne-hero__keys--l::after  { top: 330%; height: 142%; }   /* bajar volumen */

.ne-hero__keys--r {
  right: -3px;
  top: 21%;
  height: 9%;                         /* encendido */
  border-radius: 0 2px 2px 0;
  background: linear-gradient(270deg, #7b8492, #3d434c);
}

/* La pantalla, con el radio concéntrico correcto: 42 de fuera menos 6 de
   bisel = 36. Si no son concéntricos, el ojo lo nota aunque no sepa por qué. */
.ne-hero__device img,
.ne-hero__device .ne-device-mock {
  border-radius: 36px;
}

@media (max-width: 1023px) {
  .ne-hero__device { border-radius: 34px; padding: 5px; }
  .ne-hero__frame  { border-radius: 36px; }
  .ne-hero__device::after { inset: 5px; border-radius: 29px; }
  .ne-hero__device img,
  .ne-hero__device .ne-device-mock { border-radius: 29px; }
}

/* En una pantalla de verdad la hora y la batería están SIEMPRE. Su ausencia es
   de las cosas que hacen que un mockup se vea como un dibujo. */
.ne-device-mock {
  position: relative;                 /* la barra de estado se ancla aquí */
  padding-top: calc(var(--sp-5) + 20px);
}

.ne-device-mock__status {
  position: absolute;
  top: 10px;
  left: 0;
  right: 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 var(--sp-5);
  font-size: 10px;
  font-weight: var(--fw-bold);
  color: #fff;
  letter-spacing: 0.02em;
  z-index: var(--z-raised);
  pointer-events: none;
}
.ne-device-mock__status svg { display: block; opacity: 0.95; }


/* ==========================================================================
   66 · REPASO DE MÓVIL REAL   ← nuevo en 3.4.0
   --------------------------------------------------------------------------
   Medido el 7-oct-2026 en un iPhone de 375 px, sobre el CSS de verdad.

   Lo que NO era un fallo, y conviene dejar escrito para no "arreglarlo" otro
   día: varios bloques salían a L-9 y a R384, fuera del viewport. Son los
   elementos de .ne-reveal en pleno vuelo — translateX(±26px) con opacity:0,
   esperando al observador. Invisibles y sin barra horizontal. No se tocan.

   Lo que SÍ lo era está aquí abajo.
   ========================================================================== */


/* --- 1 · LETRA DE 9 PX EN EL PANEL DEL CLIENTE ---------------------------
   Los nombres de las fases se habían encogido a 9 px para que los largos no
   se cortaran. 9 px no se lee: en un iPhone son menos de 2 mm de altura de x,
   y esto lo mira un cliente en la obra, con el móvil al sol y guantes.

   La solución correcta no es achicar la letra, es dejar que el nombre ocupe
   dos líneas. El texto vuelve a 11 px y se permite el salto.                */
@media (max-width: 767px) {
  .ne-phase__name {
    font-size: 11px;
    letter-spacing: 0.01em;
    line-height: 1.2;
    white-space: normal;        /* antes se forzaba a una línea */
    overflow-wrap: anywhere;    /* "Inspección" no rompe la columna */
    hyphens: auto;
  }
}


/* --- 2 · ÁREAS DE TOQUE DE 44 PX ------------------------------------------
   Medidos en vivo: el selector de idioma a 35 px de alto, y las etiquetas de
   ciudad —que AHORA son enlaces de verdad— a 27 px. La guía de accesibilidad
   pide 44, y un electricista con guantes necesita 44 de verdad.

   Se agranda el ÁREA, no el dibujo: relleno hacia fuera y margen negativo que
   lo compensa, así no se mueve nada de lo que ya estaba colocado.            */
.ne-lang__btn {
  min-height: var(--tap-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Una etiqueta que NO es enlace es una etiqueta: no necesita área de toque.
   Una que sí lo es, sí. De ahí que esto sea a.ne-badge y no .ne-badge. */
a.ne-badge {
  min-height: var(--tap-min);
  padding-block: calc((var(--tap-min) - 1.5em) / 2);
  margin-block: calc((var(--tap-min) - 1.5em) / -2);
}

/* Los enlaces sueltos del pie de abajo (aviso legal, el crédito) se habían
   quedado fuera del repaso de la 2.6.0, que solo cubrió .ne-footer__list y
   .ne-footer__nap. */
.ne-footer__bottom a,
.ne-footer a:not(.ne-btn):not(.ne-social__link):not(.ne-logo) {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding-block: calc((var(--tap-min) - 1.4em) / 2);
  margin-block: calc((var(--tap-min) - 1.4em) / -2);
}

/* Los iconos de redes eran 42 px. Dos píxeles, pero son los dos que separan
   "cumple" de "no cumple", y cuestan cero. */
.ne-social__link {
  min-width: var(--tap-min);
  min-height: var(--tap-min);
}


/* --- 3 · EL BOTÓN DE IDIOMA NO SE PUEDE PERDER ---------------------------
   Es lo que pediste expresamente: que esté siempre. En móvil vive dentro del
   cajón, y ahí es donde más se busca. Se le da el mismo peso visual que a un
   enlace del cajón para que no parezca un adorno.                            */
@media (max-width: 1023px) {
  .ne-drawer .ne-lang {
    width: 100%;
    justify-content: center;
  }
  .ne-drawer .ne-lang__btn {
    flex: 1;
    font-size: var(--fs-sm);
  }
}


/* ==========================================================================
   32 · IMPRESIÓN (invoice / contrato)
   --------------------------------------------------------------------------
   El cliente imprime o guarda en PDF su invoice y su contrato firmado. La
   versión impresa debe ser NEGRO SOBRE BLANCO: tinta oscura es ilegible,
   cara y poco profesional.
   ========================================================================== */

@page { margin: 14mm; }

@media print {
  :root {
    --c-text: #000;
    --c-text-muted: #333;
    --c-text-dim: #555;
    --c-border: #ccc;
    --c-border-strong: #999;
    --c-surface: transparent;
    --c-surface-2: transparent;
    --c-surface-3: transparent;
    --c-bg-0: #fff; --c-bg-1: #fff; --c-bg-2: #fff; --c-bg-3: #fff;
  }

  body {
    background: #fff !important;
    color: #000 !important;
    font-size: 11pt;
  }

  /* Todo lo interactivo o decorativo desaparece. */
  .ne-header, .ne-footer, .ne-drawer, .ne-bot, .ne-bot__launcher, .ne-bot__panel,
  .ne-mobile-cta, .ne-toasts, .ne-particles, .ne-hero__scroll, .ne-skip-link,
  .ne-install-prompt, .ne-offline, .ne-lightbox, .ne-modal, .ne-marquee,
  .no-print, .ne-btn, .ne-icon-btn, .ne-copy, .ne-lang {
    display: none !important;
  }

  .ne-main, .ne-dash { padding-top: 0 !important; }

  .ne-card, .ne-invoice__meta {
    border: 1px solid #ccc !important;
    background: #fff !important;
    box-shadow: none !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    break-inside: avoid;
    page-break-inside: avoid;
  }

  .ne-gradient-text {
    -webkit-text-fill-color: #000 !important;
    color: #000 !important;
    background: none !important;
  }

  /* El registro de firma DEBE imprimirse íntegro: es la prueba legal. */
  .ne-sign__done { border: 2px solid #000 !important; break-inside: avoid; }
  .ne-sign__audit { color: #000 !important; }

  h1, h2, h3, h4 { break-after: avoid; page-break-after: avoid; }
  table, tr, .ne-totals__row { break-inside: avoid; }

  /* Muestra el destino de los enlaces, que en papel se pierden. */
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; color: #555; }
  a[href^="#"]::after, a[href^="tel:"]::after, a[href^="mailto:"]::after { content: ""; }

  img { max-width: 100% !important; break-inside: avoid; }
}

/* ============================================================================
   FIN PARTE 1/8 · style.css
   Siguiente: PARTE 2 · functions.php
============================================================================ */
