/* ═══════════════════════════════════════════════════════════════
   SITE.CSS — Estilos del sitio generado (bloques, animaciones)
   Cargado en: editor + preview + futuro sitio público
   ═══════════════════════════════════════════════════════════════ */

:root {
    --blur: 16px;
    --radius: 24px;
    --transition: 0.8s cubic-bezier(0.16, 1, 0.3, 1);
}

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

*:focus-visible {
    /* El anillo de foco era indigo FIJO (#6366f1) en todas las webs generadas.
       --accent lo declara inline cada <section> y lo heredan sus descendientes
       → el foco pasa a ser de la marca; el literal queda solo de fallback. */
    outline: 2px solid var(--accent, #6366f1);
    outline-offset: 3px;
    border-radius: 4px;
}

/* Fuerza la transparencia en los contenedores raíz */
html,
body {
    scrollbar-gutter: stable;
    /* Evita saltos de layout al ocultar el fondo */
}

/* OCULTAR EL TRACK EN DESKTOP */
::-webkit-scrollbar {
    width: 12px;
    background-color: rgba(0, 0, 0, 0) !important;
    /* Transparencia total */
}

::-webkit-scrollbar-track {
    background: rgba(0, 0, 0, 0) !important;
    -webkit-appearance: none;
    /* Quita el aspecto por defecto de sistema */
    margin: 5px 0;
    /* Separa un poco la barra de los bordes */
}

::-webkit-scrollbar-thumb {
    background-color: rgba(0, 0, 0, 0.3);
    border-radius: 10px;
    /* TRUCO FINAL: El borde del mismo color que el fondo simula transparencia real */
    /* Como tu fondo es un gradiente complejo, usamos 'transparent' y 'background-clip' */
    border: 3px solid rgba(0, 0, 0, 0);
    background-clip: padding-box;
}

::-webkit-scrollbar-thumb:hover {
    background-color: #EDB55E;
}

/* Fallback para Splide en modo editor: Splide oculta los contenedores por defecto sin JS */
/* En editor mode Splide está destruido, así que no hay clones — se puede forzar visible */
.editor-active .splide { visibility: visible !important; }

/* Mismo caso para el hero SPLIT (v3): su media va envuelta en un clip-path SVG
   (`hero.js`: clip-path:url(#hero-reveal-clip)) cuyo path nace DEGENERADO — todos los puntos en
   "0.5 0.5", es decir área CERO. Quien lo abre es la coreografía `heroV3` (animations/variants/
   hero.js), que en el editor NO corre: `initGlobalAnimations` toma su fast-path de editorMode y
   sale antes de ejecutar ninguna variante. Resultado: la imagen existía y estaba opaca, pero
   recortada a un punto = invisible al editar, y visible en preview/publicado (donde sí anima).
   `!important` es necesario: el clip-path viaja INLINE en el elemento. */
.editor-active .hero-media-wrapper { clip-path: none !important; }

body {
    font-family: var(--font-body, 'Plus Jakarta Sans', sans-serif);
    /* El fondo/color del body estaban CABLEADOS a negro: en una web clara, el
       rebote de scroll del móvil (y cualquier zona fuera de las secciones)
       enseñaba una franja negra. Ahora derivan del sitio: compileSiteHtml
       publica --hero-bg/--hero-text en :root del HTML compilado, y render.js
       inyecta --hero-bg en el preview. Sin esas variables (editor, creador) el
       literal sigue mandando → el chrome oscuro del editor no cambia. */
    background: var(--hero-bg, #0a0a0c);
    color: var(--hero-text, #fff);
    line-height: 1.7; /* F0: Interlineado amplio y lujoso */
    font-weight: 300; /* F0: Peso fino para textos base */
    overflow-x: hidden;
}

/* En preview, el body adopta el bg del hero para evitar flash negro
   entre el header fixed y el hero al hacer scroll.
   render.js inyecta --hero-bg como CSS variable en #resultado. */
.preview-mode {
    background: var(--hero-bg, #0a0a0c);
}




/* --- SISTEMA DE ANIMACIONES POR IA (Anime.js) --- */
/* CSS oculta CONTENIDO (section > *), no el wrapper → fondo siempre visible.
   Aplica a TODOS los bloques con data-anim — el JS decide cuáles animar.
   Excluye: bg-layer, style, ghost-text/number (decorativos con opacity propia). */
.preview-mode [data-anim]:not(.anime-init) > section,
.preview-mode [data-anim]:not(.anime-init) > footer {
    opacity: 0;
}

.preview-mode [data-anim].anime-init > section,
.preview-mode [data-anim].anime-init > footer {
    opacity: 1;
}

/* Fallback de seguridad → se gestiona via JS (setTimeout en anime-orchestrador.js) */


/* --- TYPED EFFECT: CURSOR PARPADEANTE --- */
.typed-cursor {
    display: inline-block;
    color: inherit;
    font-weight: 100;
    animation: typedBlink 0.53s step-end infinite;
    margin-left: 2px;
    opacity: 1;
}

@keyframes typedBlink {

    0%,
    100% {
        opacity: 1;
    }

    50% {
        opacity: 0;
    }
}

@keyframes typedFadeOut {
    to {
        opacity: 0;
    }
}

/* --- TEXT GRADIENT: TÍTULOS CON GRADIENTE --- */
.text-gradient {
    background-clip: text;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
    display: inline-block;
    /* Necesario para que background-clip funcione */
}

/* --- PARALLAX: ASEGURAR QUE EL FONDO CUBRA SIEMPRE --- */
.unsplash-bg-target {
    background-size: cover !important;
    background-repeat: no-repeat !important;
    will-change: background-position-y;
    /* Hint al GPU para parallax suave */
}

/* Gradient overlay fijo — no se mueve con parallax.
   unsplash.js inyecta --bg-overlay como CSS variable. */
.unsplash-bg-target::after {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--bg-overlay, none);
    z-index: 1;
    pointer-events: none;
}

/* --- BLOQUES BASE --- */
/* EL PIE, AL FONDO — Y SIN HUECO DEBAJO (preview y publicado).
   `#resultado` mide 100vh como mínimo, así que en una página CORTA (header + una sección
   pequeña + pie, o directamente header + pie) el pie quedaba donde acabara el contenido y
   por debajo se veía una franja en blanco enorme hasta completar la pantalla. Con el
   contenedor en columna y el <main> creciendo (`flex: 1 0 auto`), ese sobrante se lo queda
   el <main> y el pie aterriza en el borde inferior de la ventana. Cuando la página es larga
   no cambia nada (`0 auto` = no encoge, el contenido manda).
   Dos árboles distintos, misma regla:
     · preview/SPA  → #resultado > main#n3i-main + wrapper del <footer>   (render.js)
     · publicado    → #resultado > #nube3i_content_block > main#contenido + <footer>
                      (templates/sites/page_detail.html + compileSiteHtml)
   Solo en `.preview-mode`: el canvas del editor apila wrappers y su layout no se toca.
   OJO: NO se resta `--header-h` del `min-height`. El header va `position: fixed` en preview
   y en publicado (ver `.preview-mode .nav-pro`), así que NO ocupa flujo: restarlo dejaría el
   contenedor MÁS CORTO que el viewport y reaparecería una franja, ahora del color del body. */
#resultado.preview-mode {
    display: flex;
    flex-direction: column;
}

#resultado.preview-mode > main,
#resultado.preview-mode > #nube3i_content_block {
    flex: 1 0 auto;
}

#resultado.preview-mode > #nube3i_content_block {
    display: flex;
    flex-direction: column;
}

#resultado.preview-mode > #nube3i_content_block > main {
    flex: 1 0 auto;
}

/* CINTURÓN del sticky-footer: el pie se empuja al fondo AUNQUE nadie más crezca.
   `> main { flex: 1 0 auto }` basta mientras exista un <main>, y ahora existe siempre
   (render.js::applyPublicLandmarks y viewer.js::_envolverEnMain lo crean vacío). Esta regla
   cubre el caso de que no lo haya —HTML publicado ANTES de ese arreglo, que rehidrata con este
   mismo CSS sin republicar— y no cambia nada cuando sí lo hay: `margin-top: auto` solo consume
   espacio LIBRE por encima, y con un main flexible ese espacio ya es cero.
   Las cuatro ramas son los dos árboles posibles (ver la nota de #resultado.preview-mode):
   wrapper del bloque en preview/SPA, y <footer> pelado en el publicado. */
#resultado.preview-mode > [data-type="footer"],
#resultado.preview-mode > footer,
#resultado.preview-mode > #nube3i_content_block > [data-type="footer"],
#resultado.preview-mode > #nube3i_content_block > footer {
    margin-top: auto;
}

/* Salto a un ancla (#seccion) sin quedar tapado por el header fijo. `--header-h` la escribe
   `render.js::applyFirstSectionHeaderOffset` con la altura REAL medida; sin ella, 0 = como antes. */
html {
    scroll-padding-top: var(--header-h, 0px);
}

#resultado {
    min-height: 100vh;
    background: white;
    color: #1a1a1a; /* Corte de herencia: NO heredar body color de Bootstrap (#212529) */
    position: relative;
    width: 100%;
    margin: 0 auto;
    transition: width 0.5s cubic-bezier(0.16, 1, 0.3, 1), margin 0.5s cubic-bezier(0.16, 1, 0.3, 1), border-radius 0.5s ease, box-shadow 0.5s ease;
    container-type: inline-size;
}

section {
    position: relative;
    padding: 140px 8%;
    width: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    color: var(--text, inherit);
}

/* --- FONDOS ANIMADOS: EL CONTRATO DE CAPAS (P2 del motor de fondos, 23-09-2026) ---
   La decoración de una sección —el fondo animado (`.bg-layer`, lo crea
   animations/backgrounds.js como primer hijo) y la forma decorativa (`.canvas-shape-decor`)—
   va por DEBAJO de todo su contenido SIN tocar la maqueta del bloque: la sección se AÍSLA
   (contexto de apilamiento propio) y la capa baja a `z-index:-1`. Resultado, de abajo arriba:
   color/foto de la sección → fondo animado → forma → contenido en flujo → posicionados del
   bloque (el velo `::after` a z 1, el glow, los fantasmas, `.banner-overlay`, `.journey-pin`…)
   → lo que lleve z-index. Es el orden del bloque SIN fondo, con el fondo encajado justo encima
   de su color o su foto: ningún hijo cambia de `position` ni de tamaño.

   Lo que había antes era un «lift»: `position:relative; z-index:1` a TODO hijo directo de una
   sección con fondo, con una lista de excepciones a mano, y apagaba el velo `::after`. Rompía
   todo hijo absoluto o sticky que no estuviera en la lista, medido en el arnés A del plan
   (`CLAUDE_RESUMEN_PLANES/2026-09-22_plan-motor-fondos.md`): journey v2 dejaba de fijarse, el
   `.banner-overlay` del banner v2 colapsaba a 0 px (titular blanco sobre blanco, 4,74 → 1,00),
   el hero con foto perdía el velo (12-13 → 1,9-3,9) y el glow ambiental desaparecía. No se
   vuelve a poner: el aislamiento no necesita saber qué hijos posicionados tiene cada bloque.

   `!important` en la capa, a propósito: la blinda contra cualquier `> div` de variante
   presente o futura (content v4 la dejaba en 0 px de alto; faq v4, en 780 px de ancho) y contra
   el `transform`/`display` en línea que escribe una coreografía que recorra los hijos de la
   sección (la timeline v4 la escondía con `display:none`).
   Riesgo que se comprobó: el aislamiento confina los z-index de DENTRO de la sección. Los
   paneles del editor van en <body> y el bloque bajo el cursor o seleccionado sube entero
   (editor.css, `.block-editor-wrapper:hover`), así que no afecta a lo que se edita. */
section:has(> .bg-layer),
section:has(> .canvas-shape-decor) {
    isolation: isolate;
}

/* `:not(#_)` ×3 = especificidad de TRES ids (el truco del repo: un id que no existe y siempre
   casa). Hace falta: entre dos `!important` gana el más específico, y las reglas de variante van
   con id (`#bloque-faq.variant-v4 > div { max-width: 780px !important }`, 1-1-1), así que con
   `.bg-layer` a pelo (0-1-0) la capa seguía encogida aunque llevara `!important`. */
.bg-layer:not(#_):not(#_):not(#_) {
    position: absolute !important;
    inset: 0 !important;
    z-index: -1 !important;
    width: auto !important;
    height: auto !important;
    min-width: 0 !important;
    min-height: 0 !important;
    max-width: none !important;
    max-height: none !important;
    margin: 0 !important;
    padding: 0 !important;
    transform: none !important;
    display: block !important;
    overflow: hidden;
    pointer-events: none;
}

/* Capa CSS de overlay (efecto CSS combinado con un canvas) — dentro de la capa, debajo del canvas
   (que lleva `z-index:0` en línea). Que vaya DEBAJO es el E9 del plan, pendiente del P3. */
.bg-overlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 0;
}

/* El velo de legibilidad NO se apaga cuando hay fondo animado: es el `::after` de la sección
   (`.unsplash-bg-target::after`, z 1) y el contrato de arriba lo deja POR ENCIMA del fondo. Aquí
   había una regla que lo apagaba y una `.bg-gradient-layer` dentro del fondo que solo lo reponía
   si había `bg_gradient`: un hero con foto puesta a mano se quedaba sin velo. La regla de qué
   degradado lleva, en `js/utils/velo.js`. */

/* Capa de atmósfera Next-Gen que respira lentamente en el fondo */
.ambient-glow-layer {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    z-index: 0;
}

.ambient-glow-layer::before,
.ambient-glow-layer::after {
    content: "";
    position: absolute;
    width: 60cqw;
    height: 60cqw;
    border-radius: 50%;
    filter: blur(120px);
    opacity: 0.12;
    mix-blend-mode: screen;
    pointer-events: none;
    animation: breathingGlow 18s ease-in-out infinite alternate;
}

.ambient-glow-layer::before {
    background: radial-gradient(circle, var(--accent) 0%, transparent 70%);
    top: -10%;
    left: -10%;
}

.ambient-glow-layer::after {
    background: radial-gradient(circle, var(--text) 0%, transparent 70%);
    bottom: -10%;
    right: -10%;
    animation-delay: -9s;
}

@keyframes breathingGlow {
    0% {
        transform: translate(0, 0) scale(1) rotate(0deg);
    }
    50% {
        transform: translate(5%, 8%) scale(1.12) rotate(90deg);
    }
    100% {
        transform: translate(-3%, -5%) scale(0.95) rotate(180deg);
    }
}

@media (prefers-reduced-motion: reduce) {
    [class*="bg-"] { animation: none !important; }
    .ambient-glow-layer::before,
    .ambient-glow-layer::after { animation: none !important; }
}

.hero-section {
    min-height: 100vh;
    padding-top: 180px;
}

/* ── ESCALA TIPOGRÁFICA COMO TOKEN ──────────────────────────────────────────
   Tokens NUEVOS (`--type-scale`, `--display-weight`, `--display-tracking`,
   `--kicker-case`, `--title-fit`) con el VALOR ACTUAL como fallback: mientras
   nadie los declare, el resultado computado es EXACTAMENTE el de hoy
   (`calc(X * 1)` === `X`). Los emite render.js / compileSiteHtml desde
   `site_tokens` (paridad gotcha #12). Ver la tabla de presets al final del
   archivo para saber qué valor propone cada estilo.
   --------------------------------------------------------------------------- */
h1,
h2,
h3 {
    line-height: 1.08; /* F0: Interlineado muy ajustado */
    letter-spacing: var(--display-tracking, -0.03em); /* F0: Espaciado negativo agresivo */
    text-wrap: balance;
    font-family: var(--font-display, inherit);
}

h1 {
    font-size: calc(clamp(3rem, 7cqi, 6.5rem) * var(--type-scale, 1));
    font-weight: var(--display-weight, 900);
    margin-bottom: 20px;
}

h2 {
    font-size: calc(clamp(2.5rem, 5cqi, 4rem) * var(--type-scale, 1));
    font-weight: var(--display-weight, 800);
}

/* ── UN SOLO <h1> POR PÁGINA — marcadores de la jerarquía decidida en el render ──
   render.js / compileSiteHtml deciden a nivel de PÁGINA quién es el titular
   (antes lo hardcodeaba cada bloque: hero Y video emitían <h1>, y una subpágina
   sin ninguno de los dos se quedaba sin <h1>). El cambio es SEMÁNTICO: estas dos
   clases devuelven la tipografía que el elemento tenía ANTES de cambiar de etiqueta.

   · .n3i-h1-as-h2 → un <h2> de sección PROMOVIDO a <h1> (página sin hero/video).
     Selector de UNA SOLA CLASE (nada de `h1.n3i-…`) y colocado aquí ARRIBA del todo,
     los dos a propósito: así CUALQUIER regla de título de bloque posterior —tanto las
     descendentes (`.menu-head :is(h1,h2)`, 0-1-1) como las de clase suelta
     (`.form-title`, `.rv-title`, 0-1-0)— le gana por especificidad o por orden, igual
     que le ganaban al `h2` de base cuando el elemento era un <h2>. Con `h1.n3i-…`
     (0-1-1) se habría comido el tamaño de .form-title/.rv-title.
   · .n3i-h2-as-h1 → el <h1> de un hero/video DEGRADADO a <h2> por no ser el
     primer titular. Su font-size ya viaja INLINE, así que solo hay que exceptuarlo
     del reescalado móvil de los h2 de sección (ver `:not(.n3i-h2-as-h1)` más abajo). */
.n3i-h1-as-h2 {
    font-size: calc(clamp(2.5rem, 5cqi, 4rem) * var(--type-scale, 1));
    font-weight: var(--display-weight, 800);
    margin-bottom: 0;
}

/* ── EL TITULAR SE DIMENSIONA POR LO QUE MIDE ───────────────────────────────
   `--title-len` (nº de caracteres del titular) lo emite el render INLINE en cada
   <section> (paridad render.js ↔ compileSiteHtml). `--title-fit` (0..1) gradúa
   cuánto pesa esa longitud; **por defecto 0 → el factor vale exactamente 1** y
   ningún sitio ya generado cambia de tamaño. Con 1, un titular de 14 palabras
   (~90 chars) encoge ~25% y uno de 3 palabras crece hasta +12% — justo lo que
   hace falta al traducir al alemán, donde el mismo titular crece 20-35%.
   Lo consumen los <h1> de hero/video (multiplicador extra en su font-size inline). */
#resultado section,
#resultado header,
#resultado footer,
header.nav-pro {
    --title-adj: clamp(0.72, calc(1 - var(--title-fit, 0) * (var(--title-len, 40) - 40) / 200), 1.12);
}

[contenteditable="true"] {
    transition: background 0.3s;
    border-radius: 8px;
}

[contenteditable="true"]:focus {
    background: rgba(128, 128, 128, 0.2);
    padding: 5px;
    cursor: text;
}

/* --- GRIDS Y TARJETAS PREMIUM --- */
/* `--grid-gap` / `--card-pad`: tokens NUEVOS de la DENSIDAD (ver `[data-density]`
   al final del archivo), con el valor de hoy como fallback → sin densidad elegida,
   nada cambia. Molde ya probado con `--section-pad-y`. */
.grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: var(--grid-gap, 40px);
    width: 100%;
}

.card {
    background: rgba(255, 255, 255, 0.02);
    backdrop-filter: blur(10px);
    padding: var(--card-pad, 50px 40px);
    border-radius: var(--radius);
    transition: box-shadow 0.4s cubic-bezier(0.16, 1, 0.3, 1),
                background 0.4s cubic-bezier(0.16, 1, 0.3, 1);
    position: relative;
    overflow: hidden;
}

.card.featured {
    transform: scale(1.05);
    z-index: 10;
    box-shadow: 0 40px 80px rgba(0, 0, 0, 0.4);
}

.card.featured:hover {
    transform: scale(1.05) translateY(-10px);
}

/* --- BOTONES Y NAVEGACIÓN --- */
/* Tokenizado para el ESTILO DE BOTÓN GLOBAL (`[data-btn-style]`, al final del
   archivo): cada declaración lleva su valor de hoy como fallback → sin estilo
   elegido el botón es idéntico. Los estilos se aplican por HERENCIA de custom
   properties desde la <section>, NUNCA tocando los renderers de bloque. */
.btn-action {
    padding: var(--btn-pad, 18px 45px);
    border-radius: 50px;
    font-weight: var(--btn-weight, 800); /* F0: Peso extremo para labels */
    font-size: calc(0.95rem * var(--type-scale, 1));
    text-decoration: none;
    display: inline-block;
    transition: box-shadow 0.3s ease, background 0.3s ease;
    border: none;
    cursor: pointer;
    text-transform: var(--kicker-case, uppercase); /* F0: Transformación */
    letter-spacing: var(--kicker-tracking, 0.15em); /* F0: Espaciado positivo para badges/labels */
    font-family: var(--font-label, inherit);
}

/* F0: Clase de utilidad para etiquetas/badges (ej. precios, tags, números de paso) */
.sys-label {
    font-family: var(--font-label, inherit);
    font-weight: 700;
    text-transform: var(--kicker-case, uppercase);
    letter-spacing: var(--kicker-tracking, 0.15em);
    font-size: calc(0.85rem * var(--type-scale, 1));
}

.btn-action:hover {
    transform: translateY(-3px) scale(1.02);
    filter: brightness(1.15);
}

.btn-action.outline {
    background: transparent !important;
    border: 2px solid;
}

.nav-pro {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* <- CLAVE: Empuja el logo a la izq y la nav a la der */
    padding: 25px 8%;
    backdrop-filter: blur(20px);
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
    transition: all 0.4s;
}

/* Solo en preview/público — fixed en viewport real (al estar hoisted) */
.preview-mode .nav-pro:not(.pill),
body.preview-mode .nav-pro:not(.pill) {
    position: fixed !important;
    top: 0;
    left: 0;
    width: 100%;
    z-index: 1000 !important;
    animation: headerReveal 0.8s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes headerReveal {
    from {
        opacity: 0;
        transform: translateY(-10px);
        backdrop-filter: blur(0px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
        backdrop-filter: blur(20px);
    }
}

/* Solo en preview/público — stagger de links del nav */
.preview-mode .nav-pro .editable-link,
body.preview-mode .nav-pro .editable-link {
    animation: navLinkReveal 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(0.3s + var(--link-i, 0) * 0.08s);
}

@keyframes navLinkReveal {
    from {
        opacity: 0;
        transform: translateY(-8px);
    }
    to {
        opacity: 0.8; /* Mantiene la opacidad original de header.js */
        transform: translateY(0);
    }
}

.nav-pro.pill {
    border-radius: 50px !important;
    border-bottom: none !important;
    padding: 15px 40px !important;
}

/* En preview/público: pill flota de forma fija sobre el hero */
.preview-mode .nav-pro.pill,
body.preview-mode .nav-pro.pill {
    position: fixed !important;
    top: 20px;
    left: 0;
    right: 0;
    margin: 0 auto;
    z-index: 1000 !important;
    animation: headerRevealPill 0.8s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes headerRevealPill {
    from {
        opacity: 0;
        transform: translateY(-10px);
        backdrop-filter: blur(0px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
        backdrop-filter: blur(20px);
    }
}

/* Wrapper del pill en preview: posiciona sin ocupar espacio */
.preview-mode [data-type="header"]:has(.nav-pro.pill) {
    position: absolute !important;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    background: transparent !important;
    min-height: 0 !important;
    pointer-events: none;
}
.preview-mode [data-type="header"]:has(.nav-pro.pill) > * {
    pointer-events: auto;
}

/* En editor: la cabecera vuelve a flujo normal para poder seleccionarla y evitar solapamientos */
.editor-active .nav-pro {
    position: relative !important;
}
.editor-active .nav-pro.pill {
    margin: 20px auto;
}

/* --- SISTEMA DE CARRUSEL HORIZONTAL (SCROLL-SNAP) --- */
.horizontal-scroll {
    display: flex;
    gap: 30px;
    overflow-x: auto;
    padding: 20px 10px 50px 10px;
    /* Padding extra abajo para no cortar la sombra al hacer hover */
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    width: 100%;

    /* Ocultar scrollbar visualmente, pero mantener la funcionalidad táctil/mouse */
    scrollbar-width: none;
    /* Firefox */
    -ms-overflow-style: none;
    /* IE and Edge */
}

.horizontal-scroll::-webkit-scrollbar {
    display: none;
    /* Chrome, Safari, Opera */
}

/* Forzamos a que las tarjetas dentro del carrusel mantengan su tamaño */
.horizontal-scroll>.card {
    flex: 0 0 auto;
    width: min(85cqi, 380px);
    /* En móviles ocupa 85%, en PC max 380px */
    scroll-snap-align: center;
    /* Efecto magnético al centro */
}



#btn-guardar-cambios svg,
#btn-guardar-sitio svg,
#toggle-editor-mode svg {
    width: 22px !important;
    height: 22px !important;
    display: block !important;
}

#toggle-editor-mode {
    left: 30px;
    padding: 0 24px !important;
    height: 50px !important;
    border-radius: 30px !important;
    gap: 12px;
    font-weight: 700;
    font-size: 0.95rem;
}

#toggle-editor-mode .icon-wrap {
    display: flex;
    align-items: center;
    justify-content: center;
}


/* --- ANIMACIONES CSS (SPINNER Y PULSO) --- */
@keyframes pulse {
    0% {
        opacity: 0.6;
    }

    50% {
        opacity: 1;
    }

    100% {
        opacity: 0.6;
    }
}

.loader {
    width: 60px;
    height: 60px;
    border-radius: 50%;
    display: inline-block;
    border-top: 4px solid #fff;
    border-right: 4px solid transparent;
    box-sizing: border-box;
    animation: rotation 1s linear infinite;
}

@keyframes rotation {
    0% {
        transform: rotate(0deg);
    }

    100% {
        transform: rotate(360deg);
    }
}

/* --- SKELETON LOADER (ESTADO DE CARGA) --- */
.skeleton-wrapper {
    width: 100%;
    height: 100vh;
    background: #ffffff;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    position: relative;
}

.skeleton-header {
    height: 90px;
    padding: 0 8%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-bottom: 1px solid #f1f5f9;
}

.skeleton-hero {
    padding: 100px 8%;
    display: flex;
    gap: 60px;
    align-items: center;
}

.skeleton-box {
    background: linear-gradient(90deg, #f1f5f9 25%, #e2e8f0 50%, #f1f5f9 75%);
    background-size: 200% 100%;
    animation: shimmer 1.5s infinite linear;
    border-radius: 8px;
}

@keyframes shimmer {
    0% {
        background-position: 200% 0;
    }

    100% {
        background-position: -200% 0;
    }
}

/* ===================================================
   FASE 2 — CRO: WHATSAPP, CTA BAR, COUNTDOWN, TOASTS
   =================================================== */

/* --- WHATSAPP FLOAT BUTTON --- */
.wa-float-btn {
    position: fixed;
    bottom: 90px;
    right: 28px;
    z-index: 9999;
    width: 58px;
    height: 58px;
    background: #25D366;
    color: #fff;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 6px 20px rgba(37, 211, 102, 0.45);
    text-decoration: none;
    transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275), box-shadow 0.3s;
    animation: waPulse 2.5s ease-in-out infinite;
}

.wa-float-btn:hover {
    transform: scale(1.12);
    box-shadow: 0 10px 30px rgba(37, 211, 102, 0.6);
    animation: none;
}

/* En editor con número: badge de lápiz encima del botón WA */
.wa-edit-badge {
    position: absolute;
    top: -4px;
    right: -4px;
    background: #1e293b;
    border-radius: 50%;
    width: 20px;
    height: 20px;
    font-size: 0.65rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid rgba(255, 255, 255, 0.15);
}

@keyframes waPulse {
    0% {
        box-shadow: 0 0 0 0 rgba(37, 211, 102, 0.5);
    }

    60% {
        box-shadow: 0 0 0 14px rgba(37, 211, 102, 0);
    }

    100% {
        box-shadow: 0 0 0 0 rgba(37, 211, 102, 0);
    }
}

/* Placeholder editor — botón para configurar WA cuando no hay número */
.wa-config-btn {
    position: fixed;
    bottom: 90px;
    right: 28px;
    z-index: 9999;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 16px;
    background: #25D366;
    color: #fff;
    border: none;
    border-radius: 30px;
    font-family: inherit;
    font-size: 0.8rem;
    font-weight: 700;
    cursor: pointer;
    box-shadow: 0 4px 15px rgba(37, 211, 102, 0.4);
    transition: transform 0.2s, box-shadow 0.2s;
}

.wa-config-btn:hover {
    transform: scale(1.05);
    box-shadow: 0 8px 20px rgba(37, 211, 102, 0.5);
}

/* Solo visible en modo editor */
.preview-mode .wa-config-btn {
    display: none !important;
}

/* Ocultar WA y CTA bar en editor (preview-only = visible solo en preview) */
.editor-active .preview-only,
.editor-active .wa-float-btn {
    display: none !important;
}

/* --- STICKY CTA BAR --- */
.cta-sticky-bar {
    position: fixed;
    bottom: 0;
    left: 0;
    width: 100%;
    z-index: 9998;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 20px;
    padding: 14px 30px;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    animation: ctaSlideUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: 1.5s;
}

@keyframes ctaSlideUp {
    from {
        transform: translateY(100%);
        opacity: 0;
    }

    to {
        transform: translateY(0);
        opacity: 1;
    }
}

.cta-bar-btn {
    padding: 10px 24px;
    border-radius: 30px;
    font-weight: 800;
    font-size: 0.9rem;
    text-decoration: none;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    transition: 0.2s;
    flex-shrink: 0;
}

.cta-bar-btn:hover {
    filter: brightness(1.15);
    transform: scale(1.03);
}

.cta-bar-close {
    background: transparent;
    border: none;
    color: inherit;
    opacity: 0.5;
    cursor: pointer;
    font-size: 1rem;
    padding: 4px 8px;
    transition: 0.2s;
    flex-shrink: 0;
}

.cta-bar-close:hover {
    opacity: 1;
}

/* --- COUNTDOWN TIMER --- */
.countdown-section {
    overflow: visible;
}

.countdown-display {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    flex-wrap: wrap;
}

.cd-unit {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
}

.cd-num {
    font-size: clamp(3rem, 8cqi, 5.5rem);
    font-weight: 900;
    line-height: 1;
    letter-spacing: -3px;
    font-variant-numeric: tabular-nums;
    min-width: 1.6ch;
    text-align: center;
    display: inline-block;
    transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
                opacity 0.2s ease;
}

.cd-num.cd-flip {
    transform: translateY(-6px) scale(1.05);
    opacity: 0.7;
}

.cd-label {
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 2px;
    opacity: 0.5;
    font-family: var(--font-label, inherit);
}

.cd-sep {
    font-size: clamp(2.5rem, 6cqi, 4rem);
    font-weight: 900;
    opacity: 0.3;
    margin-bottom: 20px;
    line-height: 1;
}

@container (max-width: 480px) {
    .countdown-display {
        gap: 8px;
    }

    .cd-sep {
        display: none;
    }
}

/* ===================================================
   FASE 3 — NUEVOS BLOQUES DE CONTENIDO
   =================================================== */

/* ── LOGO CAROUSEL ─────────────────────────────────── */
.logos-track-outer {
    overflow: hidden;
    width: 100%;
    mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
}

.logos-track {
    display: flex;
    align-items: center;
    gap: 0;
    width: max-content;
    animation: logoScroll 30s linear infinite;
    will-change: transform;
}

@keyframes logoScroll {
    0% {
        transform: translateX(0);
    }

    100% {
        transform: translateX(-50%);
    }
}

.logo-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
    opacity: 0.85;
    flex-shrink: 0;
    transition: opacity 0.3s, transform 0.3s;
}
[data-anim].anime-init .logo-item {
    transition: opacity 0.3s, transform 0.3s;
}

.logo-item img {
    height: 56px;
    width: auto;
    max-width: 160px;
    object-fit: contain;
}

.logo-item:hover {
    opacity: 1;
}

/* Marca enlazada a su página de tienda */
.logo-link {
    text-decoration: none;
    color: inherit;
    cursor: pointer;
}
.logo-link:hover {
    transform: translateY(-3px);
}

.logo-name {
    font-size: 1rem;
    font-weight: 800;
    letter-spacing: 1px;
    text-transform: uppercase;
    white-space: nowrap;
    font-family: var(--font-label, inherit);
}

/* ── TAMAÑO GRADUABLE (stepper − / + en Marcas/Catálogo) ────────────
   El cliente gradúa el tamaño de cada marca/categoría en 5 niveles: Normal (56px,
   sin clase) · Grande (80) · Enorme (110) · Gigante (150) · Máximo (200); la escala es
   `LOGO_SIZE_LABELS` de js/blocks/logos.js (un nivel ahí = su `.logo-size-N` aquí). La altura es
   la de la CAJA: con recuadro, el relleno sale de ella, así que se mantiene CORTO (22-09-2026:
   con 12-28 px la foto de «Grande» medía lo mismo que la de «Normal»). .logo-featured = BASE de
   realce (acento de marca) común a todos los niveles > 0; el escalón concreto
   (dimensiones de la imagen + cuántas columnas ocupa en el grid) lo fija .logo-size-N.
   El realce (fondo/anillo/sombra) se aplica al chip de la imagen, no al <div>/<a>
   contenedor, para no chocar con el padding horizontal inline del carrusel v1
   (buildDisplayItem le inyecta padding:0 Npx). Funciona idéntico en v1 (carrusel) y
   v2 (grid). Es 100% edición del cliente (la IA no lo genera). Retrocompat: los items
   viejos con featured:true se pintan en nivel 1 (logoSizeOf → .logo-featured.logo-size-1). */
.logo-item.logo-featured {
    opacity: 1;
}
.logo-item.logo-featured img {
    max-width: 220px;
    padding: 6px 8px;
    border-radius: 16px;
    background: color-mix(in srgb, var(--accent) 9%, transparent);
    box-shadow: 0 10px 28px color-mix(in srgb, var(--accent) 20%, transparent),
                inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 40%, transparent);
}
.logo-item.logo-featured .logo-name {
    letter-spacing: 1.5px;
    color: var(--accent);
}
/* Escalón por nivel: dimensiones de la imagen (y del nombre) crecen con el nivel.
   Estas reglas van DESPUÉS de .logo-featured img (misma especificidad) para ganar. */
.logo-item.logo-size-1 img { height: 80px; }
.logo-item.logo-size-2 img { height: 110px; max-width: 260px; padding: 8px 10px; }
.logo-item.logo-size-3 img { height: 150px; max-width: 320px; padding: 10px 12px; }
.logo-item.logo-size-4 img { height: 200px; max-width: 420px; padding: 12px 14px; }
.logo-item.logo-size-1 .logo-name { font-size: 1.1rem; }
.logo-item.logo-size-2 .logo-name { font-size: 1.2rem; }
.logo-item.logo-size-3 .logo-name { font-size: 1.35rem; }
.logo-item.logo-size-4 .logo-name { font-size: 1.5rem; }
/* SIN RECUADRO (props.frame = 'none' → `.logos-sin-marco` en la sección; interruptor de la
   barra del editor): la imagen ocupa TODA la altura de su nivel, sin relleno, fondo ni anillo.
   (0,3,1) gana a `.logo-item.logo-size-N img` (0,2,1) sin !important. El nombre conserva el
   acento: solo se quita la caja. */
.logos-sin-marco .logo-item.logo-featured img {
    padding: 0;
    background: none;
    box-shadow: none;
    border-radius: 12px;
}
/* v2 (.logos-grid) es FLEX-WRAP centrado (no grid): así los items de distinto tamaño
   fluyen y se envuelven sin descuadrar filas — un item mayor ocupa más ancho por su
   propia imagen (max-width del nivel) y los vecinos se recolocan solos. (Antes era un
   grid auto-fit con grid-column:span N, que rompía la maquetación al agrandar.) El
   carrusel v1 ya era flex; ahora ambas variantes comparten el mismo modelo robusto. */

/* ── CHROME EDITOR: MODO DE TAMAÑO (Predefinido / Manual) ──────────
   Cabecera .editor-only encima de las píldoras de Marcas/Catálogo. NO viaja al publicado
   (compileSiteHtml elimina .editor-only). Chrome OSCURO coherente con la píldora
   (rgba(15,23,42,…)) + acento indigo (#6366f1, el mismo del hover de la píldora) en el
   control activo. Predefinido → un tamaño global (props.uniform_size) para TODAS las marcas;
   Manual → stepper por item (props.logos[i].size). Es 100% edición del cliente. */
.logos-size-toolbar {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    margin-bottom: 18px;
}
/* Primera fila: [ Predefinido | Manual ]  [ Con recuadro | Sin recuadro ] */
.logos-toolbar-fila {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 10px;
}
/* Segmentado [ Predefinido | Manual ] (y el del recuadro, mismo aspecto) */
.logo-mode-switch {
    display: inline-flex;
    gap: 2px;
    background: rgba(15, 23, 42, 0.92);
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: 24px;
    padding: 3px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.28);
}
.logo-mode-btn,
.logo-frame-btn {
    appearance: none;
    border: none;
    background: transparent;
    color: rgba(226, 232, 240, 0.75);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.3px;
    padding: 6px 16px;
    border-radius: 20px;
    cursor: pointer;
    transition: background 0.2s, color 0.2s;
}
.logo-mode-btn:hover,
.logo-frame-btn:hover {
    color: #ffffff;
    background: rgba(255, 255, 255, 0.08);
}
.logo-mode-btn.is-active,
.logo-frame-btn.is-active {
    color: #ffffff;
    background: #6366f1;
    box-shadow: 0 2px 8px rgba(99, 102, 241, 0.45);
}
/* Selector global de tamaño (solo visible en modo Predefinido): 4 niveles */
.logo-usize-row {
    display: inline-flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 4px;
    background: rgba(15, 23, 42, 0.92);
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: 22px;
    padding: 4px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.28);
}
.logo-usize-btn {
    appearance: none;
    border: none;
    background: transparent;
    color: rgba(226, 232, 240, 0.75);
    font-size: 0.68rem;
    font-weight: 700;
    padding: 5px 12px;
    border-radius: 16px;
    cursor: pointer;
    transition: background 0.2s, color 0.2s;
}
.logo-usize-btn:hover {
    color: #ffffff;
    background: rgba(255, 255, 255, 0.08);
}
.logo-usize-btn.is-active {
    color: #ffffff;
    background: #6366f1;
    box-shadow: 0 2px 8px rgba(99, 102, 241, 0.45);
}

/* ── TEAM GRID ─────────────────────────────────────── */
.team-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 280px));
    justify-content: center;
    gap: 28px;
    max-width: 1200px;
    margin: 0 auto;
}

.team-card {
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 24px;
    overflow: hidden;
    transition: box-shadow 0.4s ease,
        border-color 0.4s;
    cursor: default;
}

.team-card:hover {
    transform: translateY(-8px);
    box-shadow: 0 30px 60px rgba(0, 0, 0, 0.3);
    border-color: rgba(255, 255, 255, 0.18);
}

.team-avatar {
    width: 100%;
    aspect-ratio: 1;
    overflow: hidden;
}

.team-initials {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 3rem;
    font-weight: 900;
    letter-spacing: -1px;
}

.team-info {
    padding: 20px 22px 24px;
}

.team-name {
    font-size: 1.05rem;
    font-weight: 800;
    margin: 0 0 6px;
    outline: none;
}

.team-role {
    font-size: 0.82rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1px;
    margin: 0;
    opacity: 0.9;
    outline: none;
}

/* ── TIMELINE V1: Sticky Scroll Storytelling ───────── */
/* En editor: layout estático normal para poder editar */
/* ── Timeline v1: el ZIGZAG. Layout BASE, no "layout del editor" ──────────────────────────
   Estas ocho reglas llevaban el prefijo `.editor-active`, y eso las apagaba justo donde más
   falta hacen: en la previsualización del creador y en la WEB PUBLICADA. Allí el bloque
   dependía por completo de que corriera `animations/variants/timeline.js`, que sustituye el
   zigzag por una experiencia sticky… salvo que hace `return null` con el viewport a 768px o
   menos. Traducción: **en móvil la timeline v1 no tenía layout ninguno** — la línea central
   caía en el flujo como una barrita, los puntos eran divs sin tamaño y las tarjetas se
   apilaban sin eje. Y en escritorio pasaba lo mismo si la variante fallaba a mitad.
   Sin el prefijo, el zigzag es lo que el bloque ES, y el sticky pasa a ser lo que siempre
   debió ser: un REALCE que se pone encima (oculta estos hijos con `display:none`) y que, si
   no puede, no se lleva el bloque por delante.
   Las demás reglas `.tl-*` del archivo (centrado en contenedor estrecho, topes de ancho) van
   con `!important` o con `#resultado`, así que bajar la especificidad aquí no las descoloca. */
.timeline-container {
    position: relative;
    max-width: 900px;
    margin: 0 auto;
}

.tl-line {
    position: absolute;
    left: 50%;
    top: 0;
    bottom: 0;
    width: 2px;
    transform: translateX(-50%);
}

.tl-item {
    display: flex;
    width: 100%;
    margin-bottom: 50px;
    position: relative;
    align-items: flex-start;
}

.tl-left  { flex-direction: row; }
.tl-right { flex-direction: row-reverse; }

.tl-left .tl-card  { margin-right: calc(50% + 36px); }
.tl-right .tl-card { margin-left:  calc(50% + 36px); }

.tl-dot {
    position: absolute;
    left: 50%;
    top: 24px;
    /* El centrado va por MARGEN, NO por `transform`, y es a propósito: la coreografía de esta
       variante (`animations/variants/timeline.js::timelineV2`) siembra `d.style.transform =
       'scale(0)'` INLINE sobre cada punto y luego anime.js lo anima hasta `scale(1)`. Un
       `translate(-50%,-50%)` aquí lo BORRA esa primera línea, así que el punto perdía su
       corrección de centrado y se iba 8px a la derecha y 8px abajo (la mitad de sus 16x16):
       quedaba descolgado de la línea central y más bajo que la tarjeta. Y solo se veía en
       preview y en el publicado, porque el orquestador de animaciones es `previewOnly` — en el
       editor el punto conservaba su transform y salía bien. Con margen, `transform` queda LIBRE
       para animar y el centrado no depende de nadie. Los margenes son -width/2 y -height/2: si
       cambias el tamaño del punto, cambialos. (Pariente del gotcha nº18, por la otra cara: alli
       anime.js congelaba un tween, aqui destruia un transform usado como LAYOUT.)
       El punto del sticky (`.tl-ss-dot`) no tiene este problema: compone `translateX(-50%)
       scale(...)` en las dos clases y lo conmuta por CSS, sin inline. */
    margin-left: -8px;
    margin-top: -8px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    z-index: 2;
}

.tl-card { flex: 1; }

/* En pantalla estrecha el eje central no cabe: la timeline pasa a una sola columna. Antes esto
   no hacía falta porque en móvil no había layout que deshacer — ahora sí lo hay, y el zigzag a
   400px dejaría las tarjetas en un canal de la mitad de ancho. */
@media (max-width: 768px) {
    .tl-line { left: 12px; }
    .tl-item,
    .tl-left,
    .tl-right { flex-direction: row; }
    .tl-left .tl-card,
    .tl-right .tl-card { margin-left: 34px; margin-right: 0; }
    .tl-dot { left: 12px; top: 28px; }
}

/* ── Preview: Sticky wrapper ── */
.tl-ss-outer {
    position: relative;
    background: inherit;
}

/* `top`/`height` cuentan con el header FIJO: `--header-h` la publica
   `render.js::applyFirstSectionHeaderOffset` con la altura real medida (0px si no hay header).
   Con `top: 0` el menú tapaba el título y el subtítulo del panel, y ahí
   `applyFirstSectionHeaderOffset` no puede hacer nada: un hijo `position: sticky` se ancla al
   VIEWPORT, no a su sección, así que darle padding a la sección no lo mueve.
   ESPEJO en JS: `animations/variants/timeline.js` mide lo mismo para calcular el recorrido
   (`stickyH`); si cambias uno, cambia el otro o el último hito se revela fuera de sitio. */
.tl-ss-sticky {
    position: sticky;
    top: var(--header-h, 0px);
    height: calc(100vh - var(--header-h, 0px));
    width: 100%;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    align-items: center;
    background: var(--bg, #09131e);
}

/* Con FONDO ANIMADO el panel pegado y su contenedor dejan verlo: el panel pinta `--bg` y el
   contenedor HEREDA el fondo de la sección (`background: inherit`), así que los dos tapaban la
   capa y el fondo se veía en el editor y nunca en «Ver sitio» ni en el publicado (L5 del plan
   del motor de fondos; medido en el arnés A: con solo el panel transparente seguía tapado). El
   color de la sección ya lo pintan la propia sección o su envoltorio, por debajo del fondo. */
section:has(> .bg-layer) .tl-ss-outer,
section:has(> .bg-layer) .tl-ss-sticky {
    background: transparent;
}

/* Canvas central donde vive la timeline */
.tl-ss-canvas {
    position: relative;
    width: 100%;
    max-width: 720px;
    flex: 1;
    margin: 0 auto;
}

/* Header dentro del sticky */
.tl-ss-header {
    text-align: center;
    padding: 48px 0 0;
    flex-shrink: 0;
    z-index: 2;
}

.tl-ss-header :is(h1, h2) {
    font-size: clamp(1.8rem, 4cqw, 3rem);
    font-weight: 900;
    margin-bottom: 12px;
    color: var(--text, #e8f0f7);
}

.tl-ss-header p {
    font-size: 1rem;
    opacity: 0.55;
    color: var(--text, #e8f0f7);
}

/* Spine (línea vertical que crece) */
.tl-ss-spine {
    position: absolute;
    left: 50%;
    top: 0;
    transform: translateX(-50%);
    width: 2px;
    height: 0;            /* JS la aumenta */
    background: var(--accent, #6366f1);
    box-shadow: 0 0 12px color-mix(in srgb, var(--accent, #6366f1) 50%, transparent);
    pointer-events: none;
    z-index: 1;
    transition: height 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Dots del sticky */
.tl-ss-dot {
    position: absolute;
    left: 50%;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--bg, #09131e);
    border: 2px solid var(--accent, #6366f1);
    box-shadow: 0 0 14px color-mix(in srgb, var(--accent, #6366f1) 60%, transparent);
    transform: translateX(-50%) scale(0);
    opacity: 0;
    z-index: 3;
    transition: opacity 0.35s ease, transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.tl-ss-dot.on {
    opacity: 1;
    transform: translateX(-50%) scale(1);
}

/* Cards del sticky */
.tl-ss-card {
    position: absolute;
    max-width: 340px;
    background: color-mix(in srgb, var(--bg, #09131e) 85%, var(--text, #fff) 5%);
    border: 1px solid color-mix(in srgb, var(--accent, #6366f1) 15%, transparent);
    border-radius: 18px;
    padding: 24px 26px 20px;
    opacity: 0;
    transform: translateY(16px);
    transition: opacity 0.5s ease, transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
    backdrop-filter: blur(12px);
    z-index: 2;
    overflow: hidden;
}

.tl-ss-card.on {
    opacity: 1;
    transform: translateY(0);
}

.tl-ss-card .tl-ss-year {
    font-size: 0.7rem;
    letter-spacing: 0.14em;
    color: var(--accent, #6366f1);
    font-weight: 700;
    margin-bottom: 8px;
    text-transform: uppercase;
    display: block;
}

.tl-ss-card .tl-ss-title {
    font-size: 1.2rem;
    font-weight: 800;
    margin-bottom: 8px;
    line-height: 1.2;
    color: var(--text, #e8f0f7);
}

.tl-ss-card .tl-ss-desc {
    font-size: 0.88rem;
    line-height: 1.65;
    opacity: 0.65;
    color: var(--text, #e8f0f7);
}

.tl-ss-card .tl-ss-ghost {
    position: absolute;
    bottom: -12px;
    right: -4px;
    font-size: clamp(3.5rem, 6cqw, 5rem);
    font-weight: 900;
    color: var(--accent, #6366f1);
    opacity: 0.06;
    line-height: 1;
    pointer-events: none;
    user-select: none;
}

/* Progress dots en la parte inferior */
.tl-ss-progress {
    position: absolute;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 8px;
    z-index: 10;
}

.tl-ss-pd {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--accent, #6366f1) 20%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent, #6366f1) 35%, transparent);
    transition: background 0.3s, transform 0.3s;
}

.tl-ss-pd.on {
    background: var(--accent, #6366f1);
    transform: scale(1.4);
}

/* Scroll hint */
.tl-ss-hint {
    position: absolute;
    bottom: 56px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 7px;
    opacity: 1;
    transition: opacity 0.4s;
    pointer-events: none;
    z-index: 10;
}

.tl-ss-hint.hidden { opacity: 0; }

.tl-ss-hint span {
    font-size: 0.65rem;
    letter-spacing: 0.14em;
    color: var(--text, #e8f0f7);
    opacity: 0.4;
    text-transform: uppercase;
}

/* Contador "2 / 5": dice cuánto queda de historia. Va un punto más presente que la palabra
   'scroll' porque es el dato, no la instrucción. */
.tl-ss-hint span.tl-ss-hint-count {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    opacity: 0.65;
    font-variant-numeric: tabular-nums;
}

.tl-ss-hint-arrow {
    width: 14px;
    height: 14px;
    border-right: 1.5px solid var(--accent, #6366f1);
    border-bottom: 1.5px solid var(--accent, #6366f1);
    transform: rotate(45deg);
    animation: tlHintBounce 1.5s ease-in-out infinite;
}

@keyframes tlHintBounce {
    0%, 100% { transform: rotate(45deg) translateY(0); }
    50%       { transform: rotate(45deg) translateY(5px); }
}

/* Responsive */
@container (max-width: 640px) {
    .tl-ss-card { width: 80%; left: 10% !important; right: auto !important; }
}



/* ── TIMELINE V3 (HORIZONTAL) ─────────────────────── */
.tl-v3-track {
    display: flex;
    gap: 32px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    padding: 60px 20px 40px;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    scrollbar-color: color-mix(in srgb, var(--accent) 50%, transparent) transparent;
}

/* Magia para centrar cuando hay pocos items pero permitir scroll libre si desbordan */
.tl-v3-track::before,
.tl-v3-track::after {
    content: '';
    margin: auto;
}

/* Ocultar barra de scroll en Chrome/Safari o hacerla sutil */
.tl-v3-track::-webkit-scrollbar {
    height: 6px;
}
.tl-v3-track::-webkit-scrollbar-track {
    background: transparent;
}
.tl-v3-track::-webkit-scrollbar-thumb {
    background: color-mix(in srgb, var(--accent) 30%, transparent);
    border-radius: 10px;
}
.tl-v3-track:hover::-webkit-scrollbar-thumb {
    background: color-mix(in srgb, var(--accent) 70%, transparent);
}

.tl-v3-item {
    scroll-snap-align: center;
    flex: 0 0 min(340px, 85cqw);
    position: relative;
    cursor: default;
}

.tl-v3-item:hover .tl-v3-card {
    transform: translateY(-8px);
    box-shadow: 0 20px 40px color-mix(in srgb, var(--text) 5%, transparent);
    border-color: color-mix(in srgb, var(--accent) 30%, transparent) !important;
}

.tl-v3-item:hover > div[style*="position: absolute"] {
    transform: translateX(-50%) scale(1.3) !important;
    box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent) 30%, transparent) !important;
}

/* ── VIDEO HERO ─────────────────────────────────────── */
.video-hero-section {
    color: #fff;
}

.video-play-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 72px;
    height: 72px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.15);
    backdrop-filter: blur(10px);
    border: 2px solid rgba(255, 255, 255, 0.3);
    color: #fff;
    cursor: pointer;
    margin: 0 auto 32px;
    transition: transform 0.3s, background 0.3s;
}

.video-play-btn:hover {
    transform: scale(1.1);
    background: rgba(255, 255, 255, 0.25);
}

.video-hero-bg {
    pointer-events: none;
}

/* ── BEFORE / AFTER ─────────────────────────────────── */
.ba-container {
    position: relative;
    width: 100%;
    max-width: 900px;
    margin: 0 auto;
    border-radius: 24px;
    overflow: hidden;
    aspect-ratio: 16/9;
    user-select: none;
    box-shadow: 0 30px 80px rgba(0, 0, 0, 0.25);
}

.ba-after,
.ba-before {
    position: absolute;
    inset: 0;
    background-size: cover !important;
    background-position: center !important;
}

.ba-before {
    z-index: 1;
}

.ba-label {
    position: absolute;
    bottom: 20px;
    font-size: 0.75rem;
    font-weight: 800;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: #fff;
    padding: 6px 14px;
    border-radius: 20px;
    pointer-events: none;
    z-index: 10;
}

.ba-label-left {
    left: 16px;
    background: rgba(0, 0, 0, 0.5);
}

.ba-label-right {
    right: 16px;
}

.ba-slider-line {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 2px;
    background: rgba(255, 255, 255, 0.8);
    z-index: 5;
    transform: translateX(-50%);
    pointer-events: none;
}

.ba-handle {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 44px;
    height: 44px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
    cursor: col-resize;
    pointer-events: auto;
    z-index: 10;
}

@container (max-width: 600px) {
    .ba-container {
        aspect-ratio: 4/3;
        border-radius: 16px;
    }

    .ba-label {
        font-size: 0.65rem;
        padding: 4px 10px;
        bottom: 12px;
        letter-spacing: 1px;
    }
    .ba-label-left { left: 8px; }
    .ba-label-right { right: 8px; }

    .ba-handle {
        width: 36px;
        height: 36px;
    }
    .ba-handle svg {
        width: 16px;
        height: 16px;
    }

    .team-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: 16px;
    }
}

/* ===================================================
   VISTA MÓVIL SIMULADA (PHONE MOCKUP)
   =================================================== */
body.mobile-view {
    background-color: #cbd5e1 !important;
    /* Fondo gris-azulado para resaltar el móvil */
}

body.mobile-view #resultado {
    width: 375px !important;
    margin: 80px auto 120px auto !important;
    border-radius: 40px !important;
    box-shadow: 0 0 0 14px #0f172a, 0 40px 80px rgba(0, 0, 0, 0.3) !important;
    transform: translateZ(0) !important;
    /* MAGIA CSS: Contiene los elementos 'fixed' al ancho exacto del teléfono */
    overflow: hidden !important;
    /* Corta los bordes rectos de las secciones para adaptarse a las curvas */
    min-height: 812px;
}

/* Notch (Cámara frontal) del móvil simulado */
body.mobile-view #resultado::before {
    content: '';
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 130px;
    height: 25px;
    background: #0f172a;
    border-bottom-left-radius: 16px;
    border-bottom-right-radius: 16px;
    z-index: 9999999;
    pointer-events: none;
}

/* Solo se conserva la altura (encaja el hero en el alto del teléfono simulado ≈ svh). El
   padding y las tipografías (h1/p) se PODARON: eran números inventados (80px 6% / clamp
   1.8-3rem / 1rem) distintos del publicado. Ahora la base los da igual en ambas superficies:
   padding inline 100px 8% (hero.js) y h1 clamp(2.5rem, 6cqw, 5rem) → cqw resuelve al ancho de
   #resultado (=375 en el frame Y ≈375 en móvil real) → paridad editor↔publicado. */
body.mobile-view .hero-section {
    min-height: 90svh !important;
}

/* Reposicionar elementos flotantes globales para que encajen en el teléfono simulado */
body.mobile-view .wa-float-btn,
body.mobile-view .wa-config-btn {
    right: auto !important;
    left: calc(50% + 105px) !important; /* Calcula exactamente el borde derecho del móvil */
    bottom: 30px !important;
}

body.mobile-view #wa-config-panel {
    right: auto !important;
    left: calc(50% - 140px) !important; /* Centrado en los 375px */
    bottom: 100px !important;
}

body.mobile-view .cta-sticky-bar {
    width: 375px !important;
    left: 50% !important;
    transform: translateX(-50%) !important;
    border-radius: 20px 20px 0 0 !important;
}

/* ===================================================
   CENTRAMIENTO AUTOMÁTICO EN MÓVILES (CONTAINER QUERIES)
   =================================================== */
@container (max-width: 768px) {

    .card,
    .team-info,
    .tl-card,
    .faq-card,
    .feature-card,
    .pricing-card {
        text-align: center !important;
        align-items: center !important;
    }

    .card>div[style*="display: flex"],
    .card>div[style*="display:flex"] {
        justify-content: center !important;
    }

    /* Forzar centrado en textos con alineación inline (Precios y Características) */
    .desc-scroll,
    .pricing-card h3,
    .feature-card h3,
    .feature-card p {
        text-align: center !important;
    }

    .pricing-card>div[style*="align-items: baseline"] {
        justify-content: center !important;
    }

    /* Mantener identidad en Características v2 (borde izquierdo y alineación) */
    .feature-card[style*="border-left"] {
        text-align: left !important;
        align-items: flex-start !important;
    }

    .feature-card[style*="border-left"] h3,
    .feature-card[style*="border-left"] p {
        text-align: left !important;
    }

    .faq-card h3 {
        padding-right: 0 !important;
    }

    .testimonial-avatar {
        position: relative !important;
        bottom: auto !important;
        left: auto !important;
        margin-top: 20px !important;
        justify-content: center !important;
    }

    /* Sobrescribir alineaciones manuales previas en móvil */
    .card span[data-align],
    .team-info span[data-align],
    .faq-card span[data-align],
    .tl-card span[data-align] {
        text-align: center !important;
    }
}

/* ================================================
   BLOCK DESIGN SYSTEM — CSS Custom Properties
   Fase 5.1.1 — Tokens de color, tipografía y efectos.
   Cada bloque JS inyecta --bg, --text, --accent, etc.
   en el <section> raíz via style="". Estas reglas los
   consumen de forma genérica. SIN efecto hasta Fase 5.2.
   Scoped a #resultado para no afectar el UI del editor.
   ================================================ */

/* --- COLORES BASE DE BLOQUE --- */
/* Aplica a section, header Y footer (los 3 tipos de contenedor de bloque) */
#resultado section,
#resultado header,
#resultado footer {
    background: var(--bg, #ffffff);
    color: var(--text, #1a1a1a);
}

/* --- SECTION GAP — padding vertical global controlado por tokens ---
   Gobierna las secciones SIN padding inline propio (pricing/timeline/maps/gallery/stats/
   journey/hero…). Las que SÍ traen padding inline lo enhebran ellas mismas via
   render.js/compileSiteHtml (`padding: var(--section-pad-y, <original>)`, el inline gana).

   RETROCOMPAT (crítico): el fallback ENCADENADO `var(--section-pad-y, var(--section-gap, 100px))`
   es obligatorio. Esta regla existía en main como `var(--section-gap, 100px)` y SÍ gobernaba el
   layout de esos bloques; los sitios YA PUBLICADOS llevan `--section-gap:140/160px` horneado en
   Site.head_html. Renombrar la var a secas (o excluir el hero) los re-tramaba de golpe al
   desplegar este CSS (se sirve por <link>, sin republicar): 160px→100px, hero descentrado. Con
   el encadenado, un sitio viejo resuelve al `--section-gap` legado (idéntico a main) y el slider
   nuevo, que escribe `--section-pad-y`, gana por la var anidada. El hero se incluye igual que en
   main (allí `#resultado section` [1-0-1] ya pisaba `.hero-section`). */
#resultado section {
    padding-block: var(--section-pad-y, var(--section-gap, 100px));
}

/* --- TIPOGRAFÍA DINÁMICA POR SITIO --- */
/* LA FUENTE ES DEL SITIO (typography.js): --font-body es la pila YA RESUELTA que viaja al :root
   del publicado y al #resultado del editor. Va PRIMERO. El --font que cada bloque inyecta inline
   es legado: hoy lleva el ID del preset ('industrial', 'warm'…), no una familia, y como var()
   solo usa el fallback cuando la variable NO existe, `font-family: 'industrial'` era una familia
   inválida → el navegador caía a Times en TODO el texto de cuerpo (visto en producción, 18-09-2026).
   Se conserva como último recurso para sitios antiguos donde sí era una familia real. */
#resultado section,
#resultado header,
#resultado footer {
    font-family: var(--font-body, var(--font, 'Inter', sans-serif));
}

/* --- BOTONES CTA --- */
/* Reemplaza los style="background:..." inline de los botones.
   El scope #resultado existe para NO pintar los botones del chrome del editor. Pero el
   <header> se HOISTEA fuera de #resultado (render.js en preview, public-init.js en publicado:
   #resultado tiene container-type:inline-size y atraparía su position:fixed) → su CTA se
   quedaba sin estas reglas y caía al botón NATIVO del navegador (gris #efefef, texto negro,
   sin sombra) en preview y publicado, mientras en el editor se veía bien.
   `header.nav-pro` cubre las dos superficies: en preview lleva id="hoisted-header" y en
   publicado queda como hermano previo a #resultado, pero la clase la emite header.js en ambas.
   Al ser una lista de selectores (no reglas duplicadas), cualquier cambio de estilo del CTA
   sigue aplicándose a las dos a la vez. */
/* Cada propiedad pasa por un token con SU VALOR DE HOY como fallback: así el
   estilo de botón global (`[data-btn-style]`) puede redefinirlas por HERENCIA
   —sin pelear con la especificidad de ID de este selector ni tocar un solo
   renderer— y, mientras nadie elija estilo, el botón renderiza idéntico.
   INVARIANTE nº19: la etiqueta del sólido sigue siendo `--accent-contrast`
   (p.accentContrast, luminancia del accent REAL), NUNCA el bg/text de la sección. */
#resultado .btn-action,
header.nav-pro .btn-action {
    background: var(--btn-bg, var(--accent, #6366f1));
    color: var(--btn-fg, var(--accent-contrast, #ffffff));
    box-shadow: var(--btn-shadow, 0 10px 25px color-mix(in srgb, var(--accent, #6366f1) 30%, transparent));
    border: var(--btn-border, 1px solid color-mix(in srgb, var(--accent, #6366f1) 20%, transparent));
    border-radius: var(--btn-radius, 50px);
}

/* --- TESTIMONIALS FLEX GRID (pocos items, centrados) --- */
.testimonials-flex-grid {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: stretch;
    gap: 30px;
}
@container (max-width: 768px) {
    .testimonials-flex-grid { flex-direction: column; }
    .testimonials-flex-grid .testimonial-card { max-width: 100% !important; }
}

/* --- CARDS (features, pricing, faq, stats, testimonials) --- */
/* color: inherit rompe Bootstrap .card { color: var(--bs-body-color) } */
#resultado .card {
    background: var(--card-bg, color-mix(in srgb, var(--text, #1a1a1a) 5%, transparent));
    border: var(--card-border, 1px solid color-mix(in srgb, var(--text, #1a1a1a) 12%, transparent));
    border-radius: var(--card-radius, 24px);
    /* `--card-shadow` es la sombra de IDENTIDAD del preset (hoy solo la usa `bold`:
       offset DURA sin blur). Va por delante de `--shadow-card` —el token de ELEVACIÓN
       que gradúa el slider de Sombras— para que bajar/apagar ese slider no borre el
       rasgo del preset. Sin preset que la declare, la cadena cae a `var(--shadow-card,
       none)`: exactamente el comportamiento anterior. */
    box-shadow: var(--card-shadow, var(--shadow-card, none));
    color: inherit;
}

/* Tratamiento de imagen del preset. `filter: none` es el valor inicial → inerte
   mientras ningún preset declare `--img-filter`. Selector deliberadamente estrecho
   (.unsplash-target: las fotos de contenido de hero/content/features/gallery); no
   se toca `img` a secas para no pisar filtros propios de otros bloques. */
#resultado .unsplash-target {
    filter: var(--img-filter, none);
}

/* --- LINKS DE HEADER Y FOOTER --- */
#resultado .editable-link {
    color: var(--text, #1a1a1a);
}

/* --- TEXTO SECUNDARIO (subtítulos, descripciones) --- */
#resultado [data-field="subtitle"],
#resultado [data-field="desc"],
#resultado [data-field="text"] {
    opacity: 0.8;
}

/* --- GRADIENTE EN TÍTULOS --- */
/* Clase .title-gradient se añade por JS cuando accentColor ≠ textColor */
#resultado .title-gradient {
    /* El gradient va de --text (garantizado a contrastar con el fondo) a una MEZCLA de --accent con
       --text, no a --accent puro: si el accent es del mismo tono que el bg (o muy oscuro/claro), su
       extremo se fundía con el fondo y el título perdía contraste. La mezcla conserva el matiz del
       accent pero sube su legibilidad → el título entero resalta sobre el hero oscuro.
       DUOTONO: el extremo cromático usa el SECUNDARIO de marca si existe → el titular recorre los
       DOS colores del cliente en vez de uno. `var(--secondary, var(--accent))` es lo que hace que
       un sitio sin secundario se pinte exactamente igual que antes. */
    background: linear-gradient(120deg, var(--text, #1a1a1a), color-mix(in srgb, var(--secondary, var(--accent, #6366f1)) 62%, var(--text, #1a1a1a)));
    background-clip: text;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    display: inline;
}

/* --- ICONOS SVG (Lucide — Fase 5.4) --- */
/* Los iconos usan currentColor → heredan --accent automáticamente */
#resultado .block-icon {
    color: var(--accent, #6366f1);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

/* --- ACCESIBILIDAD: línea de foco coherente con el accent del bloque --- */
#resultado *:focus-visible {
    outline: 2px solid var(--accent, #6366f1);
    outline-offset: 3px;
    border-radius: 4px;
}

/* ================================================
   SCROLL-DRIVEN ANIMATIONS — Fase 5.1.2
   Animaciones de entrada al scroll, nativas del navegador (0KB JS).
   Solo activas en .preview-mode (no en el editor).
   Fallback: el sistema .reveal existente (líneas 68-108) sigue
   activo para Firefox y navegadores sin soporte.
   ================================================ */

@supports (animation-timeline: view()) {

    /* --- REVEAL DE SECCIÓN AL ENTRAR EN VIEWPORT --- */
    /* Reveal nativo ligero para los bloques que el orquestador Anime.js NO anima:
       sin data-anim, o con data-anim="" (= sin props.animation, el defecto que emite
       render.js; antes ese defecto era "none", de ahí que "none" estuviera aquí).
       data-anim="none" queda FUERA a propósito: ahora solo lo escribe el cliente al
       elegir "Ninguna" en Ajustes de sección → "Ninguna" debe ser SIN animación de
       verdad, también la scroll-driven. El resto los gestiona anime-orchestrador.js. */
    .preview-mode .block-editor-wrapper:not([data-anim]),
    .preview-mode .block-editor-wrapper[data-anim=""] {
        /* Anula los valores del sistema .reveal JS-based para evitar conflicto */
        opacity: 1 !important;
        filter: none !important;
        transform: none !important;
        transition: none !important;

        /* Animación nativa vinculada al scroll */
        animation: scrollReveal linear both;
        animation-timeline: view();
        animation-range: entry 0% entry 35%;
    }

    /* --- STAGGER DE ITEMS DENTRO DE UN BLOQUE --- */
    /* .stagger-item se aplica en features, stats, pricing, testimonials, etc. */
    /* --stagger-i se inyecta desde JS (ya existe en el codebase) */
    /* Mismo criterio que el reveal de sección: data-anim="" (sin props.animation)
       conserva el stagger scroll-driven; data-anim="none" NO (es "Ninguna" explícita). */
    .preview-mode .block-editor-wrapper:not([data-anim]) .stagger-item,
    .preview-mode .block-editor-wrapper[data-anim=""] .stagger-item {
        animation: scrollStagger linear forwards;
        animation-timeline: view();
        animation-range: entry 0% entry 30%;
        animation-delay: calc(var(--stagger-i, 0) * 60ms);
    }
    /* Fallback: items ya visibles en viewport al cargar la página */
    .preview-mode .block-editor-wrapper:not([data-anim]) .stagger-item.in-view,
    .preview-mode .block-editor-wrapper[data-anim=""] .stagger-item.in-view {
        opacity: 1 !important;
        transform: none !important;
    }
}

/* --- KEYFRAMES DE SCROLL-DRIVEN --- */
/* Separados del @supports para que estén disponibles aunque no haya support */
@keyframes scrollReveal {
    from {
        opacity: 0;
        transform: translateY(50px) scale(0.97);
        filter: blur(4px);
    }
    to {
        opacity: 1;
        transform: translateY(0) scale(1);
        filter: blur(0);
    }
}

@keyframes scrollStagger {
    from {
        opacity: 0;
        transform: translateY(28px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* --- ACCESIBILIDAD: respetar preferencias de movimiento reducido --- */
/* Esto sobreescribe TANTO el sistema .reveal existente COMO las nuevas animaciones */
@media (prefers-reduced-motion: reduce) {
    .block-editor-wrapper,
    .stagger-item,
    .reveal {
        animation: none !important;
        opacity: 1 !important;
        filter: none !important;
        transform: none !important;
        transition: none !important;
    }
}

/* ================================================
   MICRO-INTERACCIONES — Fase 5.1.3
   Hover effects en cards y botones usando las CSS vars
   del bloque (--bg, --accent). Solo activos en .preview-mode
   para no interferir con la edición de contenido.
   Nota: La regla .card existente (línea ~568) tiene hover con
   valores hardcoded. Estas reglas con #resultado son más
   específicas y las sobreescriben correctamente.
   ================================================ */

/* --- CARDS: transición suave y elevación al hover --- */
#resultado .card {
    transition: transform var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1)),
                box-shadow var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1)),
                background var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1)),
                border-color var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1));
}

.preview-mode .card:hover {
    box-shadow:
        var(--shadow-hover, 0 25px 50px color-mix(in srgb, var(--accent, #6366f1) 15%, rgba(0,0,0,0.2)));
}

/* --- BOTONES CTA: elevación + brillo al hover --- */
/* Mismo par de selectores que arriba: el header hoisteado vive fuera de #resultado. */
#resultado .btn-action,
header.nav-pro .btn-action {
    transition: box-shadow 0.3s ease;
}

.preview-mode .btn-action:hover {
    transform: translateY(-3px) scale(1.02);
    filter: brightness(1.1);
    box-shadow:
        0 15px 35px color-mix(in srgb, var(--accent, #6366f1) 40%, transparent),
        0 5px 15px rgba(0,0,0,0.1);
}

.preview-mode .btn-action:active {
    transform: translateY(0) scale(0.98);
    filter: brightness(0.95);
}

/* --- ICONOS SVG: escala suave al hover del card padre con parallax 3D --- */
.preview-mode .card:hover .block-icon svg,
.preview-mode .card:hover .block-icon-svg svg {
    transform: translateZ(30px) scale(1.12);
    transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
    transform-origin: center center;
}

.preview-mode .card:hover h3 {
    transform: translateZ(15px);
    transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}

.preview-mode .card:hover p {
    transform: translateZ(8px);
    transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}

#resultado .block-icon svg,
#resultado .block-icon-svg svg,
#resultado .card h3,
#resultado .card p {
    transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}

/* --- LINKS DEL HEADER: subrayado animado al hover --- */
.preview-mode .editable-link {
    position: relative;
    text-decoration: none;
}

.preview-mode .editable-link::after {
    content: '';
    position: absolute;
    bottom: -2px;
    left: 0;
    width: 100%;
    height: 2px;
    background: var(--accent, #6366f1);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
    border-radius: 2px;
}

.preview-mode .editable-link:hover::after {
    transform: scaleX(1);
}

/* --- FEATURE #8: MODO VOZ --- */
.btn-voice-mic {
    position: absolute;
    bottom: 10px;
    right: 10px;
    background: rgba(99, 102, 241, 0.15);
    border: 1px solid rgba(99, 102, 241, 0.3);
    color: #818cf8;
    border-radius: 50%;
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.2s;
    z-index: 10;
}
.btn-voice-mic:hover {
    background: rgba(99, 102, 241, 0.25);
    transform: scale(1.08);
}
.btn-voice-mic.recording {
    background: rgba(239, 68, 68, 0.2);
    border-color: rgba(239, 68, 68, 0.5);
    color: #ef4444;
    animation: voicePulse 1.2s infinite;
}
@keyframes voicePulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.3); }
    50% { box-shadow: 0 0 0 8px rgba(239, 68, 68, 0); }
}

/* ================================================
   GALLERY BLOCK
   ================================================ */

/* --- v1: Masonry (CSS columns) --- */
.gallery-masonry {
    columns: 3;
    column-gap: 16px;
}

.gallery-masonry .gallery-item {
    break-inside: avoid;
    margin-bottom: 16px;
    border-radius: var(--card-radius, 16px);
    overflow: hidden;
}

.gallery-masonry .gallery-item img {
    width: 100%;
    height: auto;
    display: block;
}

/* --- v2: Grid uniforme --- */
.gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 16px;
}

.gallery-grid .gallery-item {
    aspect-ratio: 4 / 3;
    border-radius: var(--card-radius, 16px);
    overflow: hidden;
}

/* --- Overlay caption (compartido) --- */
.gallery-item {
    position: relative;
}

.gallery-item .caption-overlay {
    position: absolute;
    inset: 0;
    background: linear-gradient(transparent 40%, rgba(0, 0, 0, 0.65));
    display: flex;
    align-items: flex-end;
    padding: 20px;
    opacity: 0;
    transition: opacity 0.3s ease;
}

.gallery-item:hover .caption-overlay {
    opacity: 1;
}

/* --- Celda SIN foto: aviso del EDITOR (.editor-only → compileSiteHtml lo elimina) ---
   Un hueco gris es indistinguible de una foto que no cargó, así que nadie descubría que se
   podía rellenar (el caso del cliente que apaga las fotos de archivo para subir las suyas).
   Es un `.image-picker-btn` real: abre el selector por la delegación de `editor/catalog.js`.
   Chrome oscuro, el mismo lenguaje que el resto de avisos del editor. */
.gallery-empty-notice {
    position: absolute;
    inset: 0;
    z-index: 4;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    border: 1px dashed rgba(255, 255, 255, 0.35);
    border-radius: inherit;
    background: rgba(15, 23, 42, 0.55);
    backdrop-filter: blur(4px);
    color: #fff;
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 0.2px;
    text-align: center;
    padding: 12px;
    cursor: pointer;
    transition: background 0.25s ease, border-color 0.25s ease;
}

.gallery-empty-notice:hover {
    background: rgba(15, 23, 42, 0.72);
    border-color: rgba(255, 255, 255, 0.6);
}

.gallery-empty-notice svg {
    width: 26px;
    height: 26px;
    opacity: 0.9;
}

/* --- MAPA SIN DIRECCIÓN (PDR-0f) ---
   Antes el bloque `maps` caía a 'Madrid, España' y pintaba el mapa REAL de una ciudad donde el
   negocio puede no estar. Hoy, sin dirección, no hay ni texto inventado ni mapa: solo un panel
   neutro y (en el editor) el aviso de que ahí va la dirección. */
.maps-empty-notice {
    display: block;
    margin-top: 6px;
    font-size: 0.8rem;
    font-weight: 600;
    opacity: 0.55;
    font-style: italic;
}

.maps-sin-mapa {
    background:
        repeating-linear-gradient(45deg,
            color-mix(in srgb, var(--text) 4%, transparent) 0 12px,
            color-mix(in srgb, var(--text) 7%, transparent) 12px 24px);
}

/* El aviso cubre la celda ENTERA (inset:0, z-index sobre el pie), y una celda sin foto es
   justo la que SÍ suele traer pie escrito: sin esto, el clic para editarlo caía en el aviso y
   abría el selector de imagen — el cliente perdía el acceso a su propio texto. El pie sube por
   encima y solo el TEXTO recibe el clic; el resto de la celda sigue abriendo el selector.
   Neutro fuera del editor: el aviso es `.editor-only` (no viaja al publicado) y el fondo del
   pie nunca fue interactivo. Mismo recurso que `#bloque-gallery.variant-v4 .caption-overlay`. */
.gallery-item .caption-overlay,
.gallery-slide-caption {
    z-index: 5;
    pointer-events: none;
}

.gallery-item .caption-overlay [data-field="caption"],
.gallery-slide-caption [data-field="caption"] {
    pointer-events: auto;
}

/* --- Tag badge --- (DUOTONO: rol "tag" → color secundario de marca si lo hay) */
.gallery-tag {
    position: absolute;
    top: 12px;
    left: 12px;
    background: var(--secondary, var(--accent, #6366f1));
    color: #fff;
    border-radius: 20px;
    padding: 4px 12px;
    font-size: 0.7rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    z-index: 3;
}

/* --- v2: Slider / Carousel --- */
.gallery-slider {
    position: relative;
    overflow: hidden;
    border-radius: var(--card-radius, 16px);
}

/* Botón + para añadir slides (fuera del slider) */
.gallery-add-slide {
    position: relative !important;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    margin: 16px auto 0 !important;
    left: auto !important;
    top: auto !important;
    right: auto !important;
    transform: none !important;
    opacity: 1 !important;
    visibility: visible !important;
    line-height: 1 !important;
}

.gallery-slider-track {
    display: flex;
    transition: transform 0.5s cubic-bezier(0.25, 0.1, 0.25, 1);
    will-change: transform;
}

.gallery-slide {
    flex: 0 0 100%;
    position: relative;
    aspect-ratio: 16 / 9;
    max-height: 70vh;
    overflow: hidden;
}

.gallery-slide img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.gallery-slide-caption {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 40px 32px 28px;
    background: linear-gradient(transparent, rgba(0, 0, 0, 0.55));
    color: #fff;
    font-size: 1.1rem;
    font-weight: 700;
    letter-spacing: 0.2px;
}

/* Gallery v2: Splide sizing - en site.css para que sea leído ANTES de que Splide inicialice */
.gallery-v2-splide {
    margin: 0 auto;
    width: 100%;
    will-change: transform;
    /* opacity:0 oculta los clones de loop HASTA que Splide termina de posicionarlos */
    opacity: 0;
    transition: opacity 0.4s ease;
}
.gallery-v2-splide.splide-ready {
    opacity: 1;
    transition: opacity 0.4s ease;
}
/* En editor (Splide destruido, no hay is-initialized) forzar visible */
.editor-active .gallery-v2-splide {
    opacity: 1 !important;
}
/* Los clones de loop solo sirven para posicionamiento interno de Splide — nunca visibles */
.gallery-v2-splide .splide__slide--clone {
    opacity: 0 !important;
    pointer-events: none !important;
}
@container (min-width: 1024px) {
    .gallery-v2-splide { max-width: 92%; }
}

/* Flechas */
.gallery-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: none;
    background: rgba(255, 255, 255, 0.15);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    color: #fff;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.2s, transform 0.2s;
    z-index: 4;
    opacity: 0;
}

.gallery-slider:hover .gallery-arrow {
    opacity: 1;
}

/* En editor, flechas siempre visibles */
.editor-active .gallery-arrow {
    opacity: 0.7;
}

.gallery-arrow:hover {
    background: rgba(255, 255, 255, 0.3);
    transform: translateY(-50%) scale(1.08);
}

.gallery-arrow-left { left: 16px; }
.gallery-arrow-right { right: 16px; }

/* Dots indicadores */
.gallery-dots {
    position: absolute;
    bottom: 14px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 8px;
    z-index: 4;
}

.gallery-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    border: none;
    background: rgba(255, 255, 255, 0.4);
    cursor: pointer;
    padding: 0;
    transition: background 0.2s, transform 0.2s;
}

.gallery-dot.active {
    background: #fff;
    transform: scale(1.3);
}

.gallery-dot:hover {
    background: rgba(255, 255, 255, 0.8);
}

/* --- Responsive --- */
@container (max-width: 768px) {
    .gallery-masonry { columns: 2; }
}

@container (max-width: 480px) {
    .gallery-masonry { columns: 1; }
    .gallery-grid { grid-template-columns: 1fr; }
}

/* ================================================
   PROCESS BLOCK — Pasos / Cómo Funciona
   ================================================ */

/* --- v1: Horizontal Steps --- */
.process-steps-h {
    display: flex;
    align-items: flex-start;
    gap: 0;
    text-align: center;
}

.process-steps-h .process-step {
    flex: 1;
    position: relative;
    padding: 0 20px;
}

.step-number {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    background: var(--accent, #6366f1);
    color: var(--bg, #fff);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 900;
    font-size: 1.1rem;
    margin: 0 auto 0;
    position: relative;
    z-index: 2;
    box-shadow: 0 4px 15px color-mix(in srgb, var(--accent, #6366f1) 30%, transparent);
}

.step-number svg {
    width: 22px;
    height: 22px;
    stroke: currentColor;
    fill: none;
    stroke-width: 2;
}

.step-connector {
    position: absolute;
    top: 24px;
    left: calc(50% + 24px);
    width: calc(100% - 48px);
    height: 2px;
    background: repeating-linear-gradient(
        90deg,
        color-mix(in srgb, var(--accent, #6366f1) 35%, transparent) 0px,
        color-mix(in srgb, var(--accent, #6366f1) 35%, transparent) 6px,
        transparent 6px,
        transparent 12px
    );
    z-index: 1;
}

@container (max-width: 768px) {
    .process-steps-h {
        flex-direction: column;
        align-items: stretch;
        gap: 48px;
        text-align: left;
        max-width: 500px;
        margin: 0 auto;
    }
    .process-steps-h .process-step {
        position: relative;
        padding-left: 68px;
        text-align: left;
    }
    .process-steps-h .step-number {
        position: absolute;
        top: 0;
        left: 0;
        margin: 0;
    }
    .process-steps-h .step-connector {
        position: absolute;
        top: 48px;
        left: 23px;
        width: 2px;
        bottom: -48px;
        background: repeating-linear-gradient(
            180deg,
            color-mix(in srgb, var(--accent, #6366f1) 35%, transparent) 0px,
            color-mix(in srgb, var(--accent, #6366f1) 35%, transparent) 6px,
            transparent 6px,
            transparent 12px
        );
        z-index: 1;
    }
}

/* PODADO: el bloque `body.mobile-view .process-steps-h ...` era un duplicado EXACTO del
   `@container (max-width:768px)` de arriba. #resultado es el container query también en el
   editor (width:375px en mobile-view), así que el @container 768 ya colapsa el zig-zag en
   ambas superficies → el override por clase sobraba (misma fuente de verdad). */

/* --- v2: Zigzag con conectores SVG --- */
.process-zigzag {
    display: flex;
    flex-direction: column;
    gap: 0;
}

.pz-item {
    display: grid;
    align-items: center;
    position: relative;
}

.pz-item.left  { grid-template-columns: 1fr 80px 1fr; }
.pz-item.right { grid-template-columns: 1fr 80px 1fr; }

/* Card premium */
.pz-card {
    padding: 36px 32px;
    border-radius: var(--card-radius, 20px);
    background: color-mix(in srgb, var(--text, #1a1a1a) 4%, transparent);
    border: 1px solid color-mix(in srgb, var(--text, #1a1a1a) 8%, transparent);
    position: relative;
    transition: box-shadow 0.4s ease;
}

.pz-card:hover {
    box-shadow: 0 12px 40px color-mix(in srgb, var(--accent, #6366f1) 10%, rgba(0,0,0,0.08));
}

.pz-card h3 {
    font-size: 1.15rem;
    font-weight: 700;
    margin-bottom: 8px;
}

.pz-card p {
    opacity: 0.6;
    font-size: 0.9rem;
    line-height: 1.6;
}

/* Badge numerado.
   DUOTONO (--secondary): los badges/tags son EXACTAMENTE el rol que el prompt describe para el
   color secundario de marca. Con `var(--secondary, var(--accent))` el badge lo estrena cuando el
   cliente tiene dos colores, y cae al acento (idéntico a antes) cuando solo tiene uno. */
.pz-badge {
    width: 32px;
    height: 32px;
    border-radius: 10px;
    background: color-mix(in srgb, var(--secondary, var(--accent, #6366f1)) 15%, transparent);
    color: var(--secondary, var(--accent, #6366f1));
    font-weight: 900;
    font-size: 0.8rem;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 16px;
    border: 1px solid color-mix(in srgb, var(--secondary, var(--accent, #6366f1)) 25%, transparent);
    flex-shrink: 0;
}

/* Icono debajo del badge */
.pz-icon {
    color: var(--accent, #6366f1);
    margin-bottom: 14px;
    opacity: 0.8;
    display: flex;
}

.pz-icon svg {
    width: 22px;
    height: 22px;
    stroke: currentColor;
    fill: none;
    stroke-width: 2;
}

/* SVG wrapper */
.pz-svg-wrap {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100%;
    color: color-mix(in srgb, var(--text, #1a1a1a) 20%, transparent);
}

.pz-empty {
    min-height: 1px;
}

/* Animaciones de entrada — solo en preview, antes de que JS las active */
.preview-mode [data-anim]:not(.anime-init) .pz-item.left .pz-card {
    opacity: 0;
    transform: translateX(-30px);
}

.preview-mode [data-anim]:not(.anime-init) .pz-item.right .pz-card {
    opacity: 0;
    transform: translateX(30px);
}

.pz-item.active .pz-card {
    opacity: 1;
    transform: translateX(0);
}

/* Fallback: si anime-init ya pasó pero JS no puso .active, forzar visibilidad */
.preview-mode [data-anim].anime-init .pz-card {
    opacity: 1;
    transform: none;
}

/* En editor: todo visible sin animación */
.editor-active .pz-item .pz-card {
    opacity: 1 !important;
    transform: none !important;
}

.editor-active .pz-item .pz-svg-wrap svg {
    clip-path: none !important;
}

.editor-active .pz-dot-outer {
    stroke: var(--accent, #6366f1);
}

.editor-active .pz-dot-inner {
    fill: var(--accent, #6366f1);
    opacity: 0.6;
}

/* ── Animación SVG: clip-path reveal ── */
.preview-mode [data-anim]:not(.anime-init) .pz-item .pz-svg-wrap svg {
    clip-path: inset(0 0 100% 0);
    transition: clip-path 1s cubic-bezier(0.65, 0, 0.35, 1) 0.3s;
}

.pz-item.active .pz-svg-wrap svg {
    clip-path: inset(0 0 0 0);
}

/* Fallback SVG: anime-init pasó → forzar reveal */
.preview-mode [data-anim].anime-init .pz-svg-wrap svg {
    clip-path: inset(0 0 0 0);
}

/* ── Cambio de color líneas al activar ── */
.pz-line-main, .pz-line-sub {
    transition: stroke 0.6s ease 0.4s, opacity 0.6s ease 0.4s;
}

.pz-item.active .pz-line-main {
    stroke: var(--accent, #6366f1);
}

.pz-item.active .pz-line-sub {
    stroke: var(--accent, #6366f1);
    opacity: 0.25;
}

/* ── Iluminación del dot final ── */
.pz-dot-outer, .pz-dot-inner {
    transition: all 0.5s ease 0.8s;
}

.pz-item.active .pz-dot-outer {
    stroke: var(--accent, #6366f1);
}

.pz-item.active .pz-dot-inner {
    fill: var(--accent, #6366f1);
    opacity: 1;
    filter: drop-shadow(0 0 6px var(--accent, #6366f1));
}

/* Responsive v2 */
@container (max-width: 768px) {
    .pz-item.left, .pz-item.right {
        grid-template-columns: 1fr;
        gap: 0;
    }
    .pz-svg-wrap, .pz-empty {
        display: none;
    }
    .process-zigzag {
        gap: 24px;
    }
    /* Reset entrance animation for mobile (no zigzag = no slide) */
    .pz-item.left .pz-card,
    .pz-item.right .pz-card {
        opacity: 1;
        transform: none;
    }
}

/* --- v3: Cards numeradas + barra progreso --- */
.process-progress {
    width: 100%;
    height: 3px;
    background: color-mix(in srgb, var(--text, #1a1a1a) 10%, transparent);
    margin-bottom: 60px;
    position: relative;
    border-radius: 3px;
    overflow: hidden;
}

.process-progress::after {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    height: 100%;
    width: 100%;
    background: linear-gradient(90deg, var(--accent, #6366f1), color-mix(in srgb, var(--accent, #6366f1) 30%, transparent));
    border-radius: 3px;
}

.process-cards {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 30px;
}

.process-cards .process-step {
    flex: 1 1 280px;
    max-width: 380px;
}

.pcard-num {
    position: absolute;
    top: 12px;
    right: 20px;
    font-size: 3.5rem;
    font-weight: 900;
    color: var(--accent, #6366f1);
    opacity: 0.12;
    line-height: 1;
    pointer-events: none;
    user-select: none;
}

@container (max-width: 768px) {
    .process-cards {
        flex-direction: column;
        align-items: center;
    }
    .process-cards .process-step {
        max-width: 100%;
    }
}

/* ═══════════════════════════════════════════════════════════════
   BANNER BLOCK — Separador Visual Full-Width
   ═══════════════════════════════════════════════════════════════ */

.banner-overlay {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    z-index: 1;
}

.banner-content {
    position: relative;
    z-index: 2;
}

/* ═══════════════════════════════════════════════════════════════
   COMPARISON BLOCK — CSS Grid (Premium)
   ═══════════════════════════════════════════════════════════════ */

.comparison-container {
    width: 100%;
}

/* Sangría del texto en la columna de características: el cuerpo la construye sumando el
   padding lateral de `.comp-cell` (16px) y el `padding-left` inline del div de
   `comparison.js` (20px). La cabecera de esa columna no tiene div interior, así que la
   necesita ENTERA de una vez o el rótulo no cae sobre su propia columna. */
.comp-grid {
    --comp-sangria: 36px;
    background: var(--card-bg, rgba(255, 255, 255, 0.02));
    border: var(--card-border, 1px solid rgba(255, 255, 255, 0.08));
    border-radius: var(--card-radius, 16px);
    box-shadow: var(--shadow-card, 0 10px 30px rgba(0,0,0,0.1));
    overflow: visible;
    position: relative;
    z-index: 1;
}

/* En móvil, las celdas se hacen compactas para evitar scroll horizontal */
@container (max-width: 768px) {
    .comp-grid {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }
    .comp-cell {
        padding: 16px 10px;
        font-size: 0.9rem;
    }
    .comp-header {
        font-size: 0.85rem;
        padding-top: 20px;
    }
    .comp-feature-name {
        font-size: 0.9rem;
    }
    .comp-feature-name div {
        padding-left: 8px !important;
    }
    .comp-grid {
        --comp-sangria: 18px;      /* 10 de .comp-cell + 8 del div */
    }
    .comp-row.comp-thead .comp-header:first-child {
        background: color-mix(in srgb, var(--bg) 95%, transparent);
        backdrop-filter: blur(8px);
    }
    .comparison-container {
        padding: 0 10px !important;
    }
}

.comp-row {
    border-bottom: 1px solid color-mix(in srgb, var(--text) 10%, transparent);
}
.comp-row.comp-thead {
    border-top-left-radius: var(--card-radius, 16px);
    border-top-right-radius: var(--card-radius, 16px);
}
.comp-tbody .comp-row:last-child {
    border-bottom-left-radius: var(--card-radius, 16px);
    border-bottom-right-radius: var(--card-radius, 16px);
}

.comp-row:last-child {
    border-bottom: none;
}

.comp-cell {
    padding: 24px 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
}

.comp-header {
    font-size: 1.1rem;
    font-weight: 800;
    font-family: var(--font-label, inherit);
    opacity: 0.9;
    padding-top: 32px;
}

.comp-feature-name {
    justify-content: flex-start;
    text-align: left;
    font-weight: 600;
    font-size: 1.05rem;
    position: sticky;
    left: 0;
    /* Fondo opaco para que las celdas debajo no se vean al hacer scroll en movil */
    background: inherit; /* Hereda del padre o variante */
    z-index: 2;
}

@container (max-width: 768px) {
    .comp-feature-name {
        background: color-mix(in srgb, var(--bg) 95%, transparent);
        backdrop-filter: blur(8px);
    }
}

/* Columna destacada */
.comp-cell.col-highlight {
    background: color-mix(in srgb, var(--accent) 8%, transparent);
}

.comp-header.col-highlight {
    color: var(--accent);
    font-size: 1.25rem;
}

/* La cabecera de la PRIMERA columna es la única que rotula una columna alineada a la
   izquierda: `.comp-cell` centra todas las celdas y `.comp-feature-name` reinvierte esa
   decisión, pero solo en el CUERPO (esa clase no la lleva ninguna cabecera). El resultado
   era un rótulo flotando en mitad de una columna cuyo contenido está pegado al borde, que
   se lee como "la columna está vacía". Mismo motivo para el sticky: la columna de
   características se queda fija al hacer scroll horizontal en móvil y su cabecera se iba. */
.comp-row.comp-thead .comp-header:first-child {
    justify-content: flex-start;
    text-align: left;
    padding-left: var(--comp-sangria);
    position: sticky;
    left: 0;
    background: inherit;
    z-index: 2;
}

/* Íconos booleanos */
.comp-content svg {
    width: 24px;
    height: 24px;
}

.state-check {
    color: var(--accent);
}

.state-minus {
    color: color-mix(in srgb, var(--text) 30%, transparent);
}

.state-text .comp-content {
    font-family: var(--font-label, inherit);
    font-size: 0.9rem;
    font-weight: 700;
    letter-spacing: 0.5px;
    opacity: 0.8;
}

/* Mostrar botón de ciclar en editor */
.editor-active .comp-value:hover .comp-cycle-btn {
    display: flex !important;
}

/* Ocultar elementos de UI si NO estamos en editor */
body:not(.editor-active) .editor-only {
    display: none !important;
}

/* ═══════════════════════════════════════════════════════════════
   DISEÑO BASE (VARIABLES GENERALES Y TIPOGRAFÍA)
   ═══════════════════════════════════════════════════════════════ */

/* --- CORE LENIS SMOOTH SCROLL --- */
html.lenis, html.lenis body {
    height: auto;
}
.lenis.lenis-smooth {
    scroll-behavior: auto !important;
}
.lenis.lenis-smooth [data-lenis-prevent] {
    overscroll-behavior: contain;
}
.lenis.lenis-stopped {
    overflow: hidden;
}
.lenis.lenis-smooth iframe {
    pointer-events: none;
}

/* ═══════════════════════════════════════════════════════════════
   VARIANTES DEL BLOQUE COMPARISON
   ═══════════════════════════════════════════════════════════════ */

/* VARIANTE v1: Minimal/Líneas limpias (Default + ajustes) */
.comparison-section.variant-v1 .comp-grid {
    background: transparent;
    border: none;
    border-top: 1px solid color-mix(in srgb, var(--text) 10%, transparent);
    border-radius: 0;
    box-shadow: none;
}
.comparison-section.variant-v1 .comp-row {
    border-bottom: 1px solid color-mix(in srgb, var(--text) 10%, transparent);
}
.comparison-section.variant-v1 .comp-header {
    padding-top: 24px;
}
.comparison-section.variant-v1 .comp-cell.col-highlight {
    background: color-mix(in srgb, var(--accent) 5%, transparent);
}

/* VARIANTE v2: Glow / Destacado Vertical */
.comparison-section.variant-v2 .comp-grid {
    background: transparent;
    border: none;
    box-shadow: none;
}
.comparison-section.variant-v2 .comp-row {
    border-bottom: 1px solid color-mix(in srgb, var(--text) 8%, transparent);
}
.comparison-section.variant-v2 .comp-row:last-child {
    border-bottom: none;
}
/* La magia de v2: la columna destacada se ve como una tarjeta flotante */
.comparison-section.variant-v2 .comp-cell.col-highlight {
    background: var(--bg);
    box-shadow: 0 10px 40px color-mix(in srgb, var(--accent) 15%, transparent);
    border-left: 1px solid color-mix(in srgb, var(--accent) 20%, transparent);
    border-right: 1px solid color-mix(in srgb, var(--accent) 20%, transparent);
    z-index: 5;
    position: relative;
}
.comparison-section.variant-v2 .comp-row.comp-thead .comp-header.col-highlight {
    border-top: 3px solid var(--accent);
    border-top-left-radius: 16px;
    border-top-right-radius: 16px;
}
.comparison-section.variant-v2 .comp-tbody .comp-row:last-child .comp-cell.col-highlight {
    border-bottom: 1px solid color-mix(in srgb, var(--accent) 20%, transparent);
    border-bottom-left-radius: 16px;
    border-bottom-right-radius: 16px;
}

/* VARIANTE v3: Enterprise/Cards separadas */
.comparison-section.variant-v3 .comp-grid {
    background: transparent;
    border: none;
    box-shadow: none;
    overflow: visible;
}
.comparison-section.variant-v3 .comp-row {
    background: var(--card-bg, color-mix(in srgb, var(--text) 4%, transparent));
    border: var(--card-border, 1px solid color-mix(in srgb, var(--text) 10%, transparent));
    border-radius: var(--card-radius, 12px);
    margin-bottom: 12px;
}
.comparison-section.variant-v3 .comp-row.comp-thead {
    background: transparent;
    border: none;
    margin-bottom: 20px;
}
.comparison-section.variant-v3 .comp-cell {
    padding: 20px 16px;
}
.comparison-section.variant-v3 .comp-cell.col-highlight {
    background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.comparison-section.variant-v3 .comp-row:not(.comp-thead) .comp-cell:first-child {
    border-top-left-radius: var(--card-radius, 12px);
    border-bottom-left-radius: var(--card-radius, 12px);
}
.comparison-section.variant-v3 .comp-row:not(.comp-thead) .comp-cell:last-child {
    border-top-right-radius: var(--card-radius, 12px);
    border-bottom-right-radius: var(--card-radius, 12px);
}

/* ═══════════════════════════════════════════════════════════════
   EXTRAS E1: GHOST ELEMENTS (TEXT & NUMBERS)
   ═══════════════════════════════════════════════════════════════ */

.ghost-text {
    position: absolute;
    top: 50%;
    left: 50%;
    /* El desplazamiento del parallax entra por --ghost-x/--ghost-y y NO por `transform`:
       interactive.js escribía `transform: translate3d(...)` directamente y machacaba este
       translate(-50%,-50%), así que al primer movimiento del ratón el ghost perdía el
       centrado y saltaba media anchura de sí mismo (con nowrap + 8-25rem, cientos de px
       fuera de la sección). Bug latente hasta que el ghost dejó de ser inalcanzable. */
    transform: translate(calc(-50% + var(--ghost-x, 0px)), calc(-50% + var(--ghost-y, 0px)));
    max-width: 100%;
    /* Se dimensiona POR LO QUE MIDE, no a ciegas: con white-space:nowrap un tamaño fijo
       hace que un texto de 8 caracteres ("MALLORCA") desborde la sección. `--ghost-len`
       (nº de caracteres, lo emite el render del bloque) reparte el ancho disponible:
       ~0.62em de avance por letra en mayúsculas bold ⇒ cabe en ~115cqw. El tope de 13cqw
       evita que un texto de 2-3 letras se coma la sección. */
    font-size: clamp(2.5rem, min(13cqw, calc(115cqw / var(--ghost-len, 8))), 16rem);
    font-weight: 900;
    color: var(--text);
    opacity: 0.04;
    pointer-events: none; 
    white-space: nowrap; 
    z-index: 0;
    text-transform: uppercase; 
    letter-spacing: -0.05em;
    font-family: var(--font-display, inherit);
}

.ghost-number {
    position: absolute; 
    font-weight: 200;
    color: var(--text);
    opacity: 0.04;
    pointer-events: none; 
    z-index: 0;
    font-family: var(--font-display, inherit);
    line-height: 1;
}

/* Ocultar decoración enorme en móviles para evitar overflow visual */
@container (max-width: 768px) {
    .ghost-text, .ghost-number { 
        display: none !important; 
    }
}

/* ═══════════════════════════════════════════════════════════════
   EXTRAS E2: SECTION DIVIDERS
   ═══════════════════════════════════════════════════════════════ */

.section-divider {
    position: absolute;
    bottom: -1px; /* Evita línea subpíxel blanca */
    left: 0;
    width: 100%;
    height: 60px;
    z-index: 10;
    pointer-events: none;
    transform: translateZ(0); /* Aceleración GPU */
}

/* Mayor altura antes de bloques de conversión (pricing, cta) para generar expectación */
.section-divider--preconversion {
    height: 100px;
}

/* Forma decorativa SVG (Canvas Shape Designer — Fase 2). `z-index:-1` por el contrato de capas
   de los fondos animados (ver `.bg-layer`): su sección se aísla y la forma pinta encima del
   color de la sección y del fondo animado, y DEBAJO del contenido, sin «lift».
   `!important` porque el render la emite con `z-index:0` EN LÍNEA (`render.js::buildCanvasShapeSVG`,
   también en todo lo ya publicado) y el estilo en línea gana a la hoja: sin él, quitado el lift, la
   forma quedaba ENCIMA de los titulares y párrafos del bloque (revisor del P2). */
.canvas-shape-decor {
    position: absolute;
    pointer-events: none;
    z-index: -1 !important;
    overflow: visible;
}

/* Secciones con forma decorativa deben clipear la forma. `clip` y no solo `hidden`: `hidden`
   convierte la sección en contenedor de scroll y un hijo `position:sticky` deja de pegarse a la
   ventana (journey v2 y la timeline v4 con forma no se fijaban). `clip` recorta igual sin serlo;
   `hidden` queda de respaldo para los navegadores que no lo entienden. */
section:has(.canvas-shape-decor) {
    overflow: hidden;
    overflow: clip;
    position: relative;
}

/* Animación de "respiración" para dividers.
   Usa propiedades individuales (scale, translate) para NO conflictar
   con transform:scaleY(-1) del flip ni transform:translateZ(0) base. */
.divider-animated {
    animation: dividerBreath 5s ease-in-out infinite alternate;
}

@keyframes dividerBreath {
    0%   { translate: 0 0;    scale: 1 1;   }
    50%  { translate: 0 -3px; scale: 1 1.08; }
    100% { translate: 0 0;    scale: 1 1;    }
}

/* ── Track B (Ola 1.1): dividers con movimiento real ──────────────
   El parallax (olas multicapa al scroll) y el morph (ola viva) los
   aplica js/animations/divider-motion.js regenerando el path; ya
   respetan prefers-reduced-motion en JS. Aquí solo añadimos el gate
   de accesibilidad que faltaba para el "breath" CSS de arriba. */
@media (prefers-reduced-motion: reduce) {
    .divider-animated { animation: none !important; }
}

/* ── RECORTE del borde entre dos secciones (el modelo POR DEFECTO del separador) ──
   El `.section-divider` de arriba es un PARCHE: pinta la forma con el color PLANO de la
   sección siguiente, así que la cuña "parece" la de abajo pero sigue siendo la de arriba.
   Con eso, cualquier fondo NO plano de la sección de abajo (forma decorativa, degradado,
   fondo animado, foto) queda cortado por la línea del separador — vive dentro de ella, que
   además clipea (`section:has(.canvas-shape-decor){overflow:hidden}`), y no hay z-index que
   lo saque. El recorte le da la vuelta: no se pinta nada, se RECORTA el borde.
     · la sección de arriba conserva lo que queda POR ENCIMA de la curva en su banda inferior;
     · la de abajo sube H px (margin-top negativo), conserva lo de DEBAJO de la curva y
       compensa con un hueco en flujo (`.n3i-recorte-hueco`) para que su contenido no se mueva
       ni la página cambie de alto.
   El polígono lo calcula `js/core/dividers.js` (una sola var por sección: si la sección tiene
   separador arriba Y abajo, las dos curvas van en el MISMO `polygon()`).
   `clip-path` recorta también el HIT-TESTING: lo que queda fuera no recibe clics, que es lo
   que hace que la cuña sea clicable "de" la sección de abajo. */
[data-recorte] {
    -webkit-clip-path: var(--recorte-clip);
            clip-path: var(--recorte-clip);
}

[data-recorte~="arriba"] {
    margin-top: calc(-1 * var(--recorte-h));
}

/* MANDA EL DE FUERA. Cada bloque puede ir DENTRO de un envoltorio que también pinta su fondo:
   el `.block-editor-wrapper` del editor y de la vista previa (render.js le copia los mismos
   atributos) y el shim que `public/public-init.js` monta en el sitio publicado para las
   animaciones. Ese envoltorio TIENE que recortarse —si no, su color plano taparía la cuña que
   la sección acaba de abrir, y encima gana z-index al pasar el ratón en el editor—, y entonces
   la <section> de dentro debe dejar de hacerlo o la banda se contaría dos veces (el margen
   negativo, además, colapsaría con el del padre). El selector es por ATRIBUTO y no por clase
   para cubrir los dos envoltorios; si no hay ninguno (HTML publicado sin JS), no matchea y
   manda la <section>, que es lo correcto. */
[data-recorte] > [data-recorte] {
    -webkit-clip-path: none;
            clip-path: none;
    margin-top: 0;
}

/* El hueco que devuelve al contenido de la sección de abajo su posición de siempre. Es el
   PRIMER hijo del <section> (flex column), aria-hidden y sin fondo: no se ve ni se lee. */
.n3i-recorte-hueco {
    flex: 0 0 auto;
    width: 100%;
    height: var(--recorte-h);
    pointer-events: none;
}

/* ═══════════════════════════════════════════════════════════════
   CLASES DE LAYOUT RESPONSIVE (MIGRACIÓN INLINE → CSS)
   ═══════════════════════════════════════════════════════════════ */

/* --- Padding Adaptable Global --- */
section.reveal, 
[id^="bloque-"] {
    padding: clamp(60px, 8cqw, 100px) clamp(16px, 6cqw, 8%);
}

/* --- Hero Layout --- */
/* `flex-direction: row` NO es decorativo, es el arreglo de un bug de años: la regla base
   `section { display:flex; flex-direction:column }` (arriba en este mismo fichero) matchea
   TAMBIÉN al `<section class="hero-layout">`, y esta regla nunca declaró la dirección → el
   hero v3 "split" (texto | media) se pintaba APILADO, y en un contenedor `column` de altura
   automática `flex-grow` no reparte nada, así que `--media-flex`/`--text-flex` (que el render
   SÍ emitía) no movían un píxel. Que el autor lo daba por fila se ve en la media query de
   abajo, que lo pasa a `column` en móvil: un no-op si ya lo fuera. */
.hero-layout {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 60px;
    flex-wrap: wrap;
    position: relative;
    overflow: hidden;
}
.hero-layout-centered {
    min-height: 85vh;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    overflow: hidden;
}

/* --- Content Layout --- */
.content-layout {
    display: flex;
    gap: 60px;
    flex-wrap: wrap;
    align-items: center;
}

/* --- Pricing Layout --- */
/* `.card` (compartida con faq/stats/process/features) va overflow:hidden; aquí NO:
   el badge "recomendado" monta sobre el borde superior (top negativo) y el CTA
   .btn-action se sale de la tarjeta con el hover magnético (animations/interactive.js).
   OJO: esta regla ya se PERDIÓ una vez (merge 13e8cd7, resuelto con `checkout --theirs`
   sobre el archivo entero) — si vuelve a desaparecer, el badge sale recortado. */
.pricing-card {
    overflow: visible;
}

.pricing-container {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 40px;
    width: 100%;
    align-items: stretch;
}

/* --- Stats Layout --- */
.stats-flex {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    gap: 60px;
}
.stats-divider {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: stretch;
}
.stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 30px;
}

/* --- Maps Layout --- */
.maps-split {
    display: flex;
    gap: 40px;
    align-items: stretch;
}

/* --- Gallery Layout --- */
.gallery-featured {
    display: flex;
    gap: 16px;
    align-items: stretch;
    flex-wrap: wrap;
}

/* --- Media Queries (Tablet & Mobile) --- */
@container (max-width: 768px) {
    section.reveal, 
    [id^="bloque-"] {
        padding: 60px 5%;
    }

    .hero-layout {
        flex-direction: column;
        gap: 30px;
    }
    .hero-layout-centered {
        min-height: 60vh;
    }
    .hero-text-side,
    .hero-image-side {
        min-width: 100% !important;
        flex: 1 1 100% !important;
    }
    .hero-image-side img {
        max-height: 240px;
        object-fit: cover;
    }

    .content-layout {
        flex-direction: column;
        gap: 30px;
    }
    .content-text-side,
    .content-image-side {
        min-width: 100% !important;
        flex: 1 1 100% !important;
    }
    .content-image-side {
        order: -1; /* Imagen arriba */
    }

    .pricing-container {
        flex-direction: column;
        align-items: center;
        gap: 24px;
    }
    .pricing-card {
        width: 100% !important;
        max-width: 400px !important;
        flex: 1 1 auto !important;
    }

    .stats-flex {
        gap: 30px;
    }
    .stats-flex .stat-item {
        min-width: 100% !important;
        flex: 1 1 100% !important;
    }
    .stats-divider {
        flex-direction: column;
        gap: 20px;
    }
    .stats-divider .stat-item {
        border-left: none !important;
        border-top: 1px solid color-mix(in srgb, var(--text) 15%, transparent);
        padding: 20px 0 !important;
    }

    .maps-split {
        flex-direction: column;
        gap: 20px;
    }

    .gallery-featured > div {
        min-width: 100% !important;
        flex: 1 1 100% !important;
    }

    /* Comparison: reducir min-width, evitar scroll siempre que sea posible */
    .comparison-container {
        overflow-x: auto;
    }

    /* Footer layout overrides */
    .footer-minimal-container {
        display: flex;
        align-items: center;
        justify-content: space-between;
        flex-wrap: wrap;
        gap: 20px;
    }
}

@container (max-width: 768px) {
    .footer-minimal-container {
        flex-direction: column;
        text-align: center;
        gap: 24px;
    }
    .stats-grid {
        grid-template-columns: 1fr !important;
    }
}

@container (max-width: 480px) {
    .comp-grid {
        --comp-sangria: 14px;      /* 6 de .comp-cell + 8 del div */
    }
    .comp-cell {
        padding: 12px 6px;
        font-size: 0.82rem;
    }
    .comp-header {
        font-size: 0.78rem;
    }
    .comp-content svg {
        width: 20px;
        height: 20px;
    }

    /* Before-after: section padding y texto */
    #bloque-before-after {
        padding: 60px 4% !important;
    }
}

/* --- Footer layout --- */
.footer-layout {
    padding: clamp(60px, 8cqw, 80px) clamp(16px, 6cqw, 8%);
    text-align: center;
    border-top: 1px solid color-mix(in srgb, var(--text) 7%, transparent);
}
.footer-minimal {
    padding: 40px clamp(16px, 6cqw, 8%);
    border-top: 1px solid color-mix(in srgb, var(--text) 7%, transparent);
}

/* Enlaces del pie (footer.js): las páginas ocultas de la nav (legales) se enlazan aquí, en
   letra pequeña, no en el menú del header. Se envuelven en varias líneas en móvil.
   El color/hover lo heredan de las reglas .editable-link (compartidas con el header). */
.footer-links {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 8px 20px;
    margin-top: 24px;
}
.footer-links .footer-link {
    color: inherit;
    text-decoration: none;
    font-size: 0.8rem;
    opacity: 0.55;
    transition: opacity 0.3s;
}
.footer-links .footer-link:hover {
    opacity: 1;
}
/* v2 ("fila horizontal"): el pie es UNA fila (marca | copyright | tel | enlaces) →
   los enlaces no necesitan el margen superior de los pies apilados. */
.footer-minimal-container .footer-links {
    margin-top: 0;
}

/* --- CTA Bar Responsive and Editor Styles --- */
.cta-bar-editor {
    border: 2px dashed color-mix(in srgb, var(--accent) 53%, transparent);
    padding: 30px;
    text-align: center;
    border-radius: 16px;
    margin: 40px clamp(16px, 8cqw, 8%);
    position: relative;
}
@container (max-width: 768px) {
    .cta-sticky-bar {
        flex-direction: column !important;
        text-align: center !important;
        gap: 12px !important;
        padding: 16px 20px !important;
    }
    .cta-sticky-bar .cta-bar-text {
        font-size: 0.85rem !important;
    }
    .cta-sticky-bar .cta-bar-btn {
        width: 100% !important;
        text-align: center !important;
    }
    .cta-bar-editor {
        padding: 24px 16px;
        margin: 20px 5%;
    }
}

/* ── MICRO-ANIMACIONES PREMIUM Y MIGRACIÓN A CSS ────────────────── */

/* M-12: Pulso infinito del botón de play en CSS (evita loop JS) */
.preview-mode .video-play-btn {
    animation: playPulse 1.8s ease-in-out 1.4s infinite;
}

@keyframes playPulse {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.1); }
}

/* M-13: Hover Zoom fluido en imágenes de Hero y Content sides */
.preview-mode .content-image-side img,
.preview-mode .hero-image-side img {
    transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
    will-change: transform;
}

.preview-mode .content-image-side:hover img,
.preview-mode .hero-image-side:hover img {
    transform: scale(1.03);
}

/* content v1/v3 (texto + imagen a dos columnas): la foto LLENA la altura de su columna.
   Antes las dos columnas eran flex:1 (50/50) y la <img> llevaba aspect-ratio 16/9 inline, así que
   con ancho de contenido "normal" (--cw 1080px) quedaba en ~510×287px: una miniatura flotando en
   medio de una columna de texto de 400px. Ahora la columna de la imagen se ESTIRA a la altura de
   la fila (align-self: stretch) y la foto se recorta a esa altura (object-fit: cover) con un suelo
   de altura para textos cortos; gana además algo de ancho (1.15 frente a 1). En "narrow" (720px)
   dos columnas de 330px no dan para nada: la foto pasa a ancho completo. El apilado móvil (≤992px)
   lo sigue haciendo el <style> inline del bloque. !important porque pelea con estilos INLINE del
   render. v2 (apilada) y v4 (editorial) no se tocan.

   La <img> va DENTRO de `.n3i-caja` (contrato del motor de dimensión, js/utils/dimension.js): la
   caja es la que hereda lo que antes tenía la foto —crecer con la columna y el suelo de altura—
   porque es la caja lo que el cliente arrastra. Sin ningún prop de tamaño la geometría es la
   MISMA que antes de existir el motor: el rótulo "Cargando activo visual…" sigue fuera de ella y
   la foto sigue llenando la columna con `object-fit: cover`. */
#resultado #bloque-content:is(.variant-v1, .variant-v3) > div > .content-image-side {
    align-self: stretch;
    /* `!important`: `editor/unsplash.js` escribe `container.style.display = "block"` en esta
       columna al resolver la foto (en el editor Y en el publicado), y con `display:block` la
       caja deja de ser flex item (`flex: 1 1 auto` inerte) y el `height:100%` de la foto se
       resuelve a `auto`: la foto ya no llenaba la columna. */
    display: flex !important;
    flex-direction: column !important;
    /* `--media-flex` = el REPARTO que el cliente le ha dado a mano arrastrando el divisor (lo
       emite el propio render del bloque en el <section>, ver js/utils/dimension.js). Se consume
       AQUÍ DENTRO y no en una regla nueva porque este `!important` le gana a cualquier inline: una
       regla aparte no llegaría a aplicarse nunca. Sin el prop, el 1.15 de siempre.
       El apilado de móvil NO depende de este número: lo impone el `min-width:100% !important`
       del `@container (max-width:768px)`, que sigue ganando. */
    flex: var(--media-flex, 1.15) 1 0 !important;
}
#resultado #bloque-content:is(.variant-v1, .variant-v3) > div > .content-image-side > .n3i-caja {
    flex: 1 1 auto;
    /* `width:100%` explícito y no heredado del estiramiento: en móvil el <style> inline del bloque
       pasa la columna a `display:flex; justify-content:center`, y ahí una caja sin ancho propio se
       encogería al contenido. Con ancho de caja a mano manda `--media-w` (`!important` del motor). */
    width: 100%;
    min-height: clamp(300px, 26vw, 480px);
}
#resultado #bloque-content:is(.variant-v1, .variant-v3) > div > .content-image-side img {
    width: 100%;
    height: 100%;
    min-height: 0;
    aspect-ratio: auto !important;
    object-fit: cover;
    display: block;
}
#resultado #bloque-content:is(.variant-v1, .variant-v3)[data-content-width="narrow"] > div > .content-image-side {
    flex-basis: 100% !important;
    min-width: 100% !important;
}
#resultado #bloque-content:is(.variant-v1, .variant-v3)[data-content-width="narrow"] > div > .content-image-side > .n3i-caja {
    min-height: 0;
}
#resultado #bloque-content:is(.variant-v1, .variant-v3)[data-content-width="narrow"] > div > .content-image-side img {
    height: auto;
    aspect-ratio: 16 / 9 !important;
}
@media (max-width: 992px) {
    #resultado #bloque-content:is(.variant-v1, .variant-v3) > div > .content-image-side > .n3i-caja {
        min-height: 0;
    }
    #resultado #bloque-content:is(.variant-v1, .variant-v3) > div > .content-image-side img {
        height: auto;
        aspect-ratio: 16 / 9 !important;
    }
}

/* M-20: Animación de atención infinita para el badge destacado de planes de precios */
.preview-mode .plan-card.featured .plan-badge,
.preview-mode .plan-card[data-featured="true"] .plan-badge {
    animation: badgeAttention 3s ease-in-out 2s infinite;
}

@keyframes badgeAttention {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.06); }
}

/* ── CORRECCIONES DE CABECERA EN MÓVIL ────────────────────────────────── */

/* Cabecera en preview móvil (Simulador de Editor).
   KEY por #hoisted-header: render.js pone ese ID al hoistear el <header> FUERA de #resultado,
   y SOLO en preview (en editor lo quita). Así la regla:
     · matchea exactamente preview+móvil (body.mobile-view + header hoisted), sin depender de
       que `.preview-mode` esté en <body> (que era el bug: no matcheaba → caía a la base
       `.nav-pro { width:100%; left:0 }` → header full-viewport, sobresalía).
     · specificity (1,1,x) por el ID → vence TODAS las bases de .nav-pro (0,3,x).
   Centrado SIN transform (la animación headerReveal pisa cualquier transform → un translateX
   de centrado se descarta). left:calc(50% − media-anchura): el header es position:absolute y su
   bloque contenedor es el VIEWPORT (ningún ancestro posicionado/transformado: body.mobile-view
   solo pinta fondo; el padre es <main container-fluid p-0 m-0>). El 50% resuelve contra el
   viewport, igual que el frame (margin:auto en contenedor full-width) → alineados a toda
   resolución. */
body.mobile-view #hoisted-header {
    position: absolute !important;
    z-index: 10000 !important;
    /* Centrado por margin:auto (NO calc, NO transform): con left+right:0, width fija y
       margin-left/right:auto el header se centra en su bloque contenedor sea cual sea su
       ancho. Inmune a la animación (solo translateY) y a qué ancestro sea el CB. */
    left: 0 !important;
    right: 0 !important;
    margin-left: auto !important;
    margin-right: auto !important;
}

body.mobile-view #hoisted-header:not(.pill) {
    top: 80px !important;
    width: 375px !important;
}

body.mobile-view #hoisted-header.pill {
    top: 100px !important;
    width: 335px !important;
    margin-top: 0 !important;
    margin-bottom: 0 !important;
}

/* Soporte para menú móvil colapsable (Hamburger Menu) en dispositivos reales y simulador */
@media (max-width: 900px) {
    .nav-pro .nav-wrapper { position: static !important; flex: 0 !important; padding: 0 !important; }
    .nav-pro .nav-links-container {
        display: none !important; position: absolute !important; top: 100% !important; left: 0 !important; width: 100% !important; 
        background: var(--bg) !important; flex-direction: column !important; padding: 25px 20px !important; 
        box-shadow: 0 20px 40px rgba(0,0,0,0.15) !important; border-top: 1px solid color-mix(in srgb, var(--text) 13%, transparent) !important;
        mask-image: none !important; -webkit-mask-image: none !important;
    }
    .nav-pro .nav-links-container.open { display: flex !important; }
    .nav-pro .menu-toggle { display: block !important; margin-left: auto !important; }
    .nav-pro .header-cta-wrapper { display: none !important; }
}

body.mobile-view .nav-pro .nav-wrapper { position: static !important; flex: 0 !important; padding: 0 !important; }
body.mobile-view .nav-pro .nav-links-container {
    display: none !important; position: absolute !important; top: 100% !important; left: 0 !important; width: 100% !important; 
    background: var(--bg) !important; flex-direction: column !important; padding: 25px 20px !important; 
    box-shadow: 0 20px 40px rgba(0,0,0,0.15) !important; border-top: 1px solid color-mix(in srgb, var(--text) 13%, transparent) !important;
    mask-image: none !important; -webkit-mask-image: none !important;
}
body.mobile-view .nav-pro .nav-links-container.open { display: flex !important; }
body.mobile-view .nav-pro .menu-toggle { display: block !important; margin-left: auto !important; }
body.mobile-view .nav-pro .header-cta-wrapper { display: none !important; }

/* Drag-to-scroll horizontal (navbar / tiras de cards) — ver js/vendor/hscroll.js.
   Solo activo durante un arrastre real: cursor grabbing, sin selección de texto
   y sin hover/click en los hijos (evita jitter por las animaciones de hover). */
.hscroll-dragging { cursor: grabbing !important; }

/* ═══════════════════════════════════════════════════════════════
   BENTO GRID — grid modular asimétrico (Apple / Linear / Vercel).
   Reusa items[] + toolbar estándar (sortable-item). Los tamaños los
   asigna bento.js por patrón; aquí solo los traducimos a spans.
   El tile va overflow:visible (para no recortar la toolbar del editor,
   que se posiciona en -14/-20px); la decoración se clipa en .bento-skin.
   ═══════════════════════════════════════════════════════════════ */
.bento-section { color: var(--text); font-family: var(--font-body, var(--font, 'Inter')), sans-serif; background: var(--bg); }
.bento-head { text-align: center; max-width: 720px; margin: 0 auto 56px; }
.bento-head :is(h1, h2) { font-size: clamp(2rem, 4cqw, 3rem); font-weight: 900; letter-spacing: -0.02em; margin: 0 0 14px; line-height: 1.1; }
.bento-head p { font-size: 1.1rem; opacity: 0.6; margin: 0; line-height: 1.5; }

.bento-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    grid-auto-rows: minmax(150px, auto);
    grid-auto-flow: dense;
    gap: 18px;
    max-width: 1200px;
    margin: 0 auto;
}
.bento-lg   { grid-column: span 2; grid-row: span 2; }
.bento-wide { grid-column: span 2; }
.bento-tall { grid-row: span 2; }

.bento-tile {
    position: relative;
    overflow: visible;
    /* Grid item: sin min-width:0 el min-content de una escena ancha (rating/gauge/ticker/
       avatars) infla el track (con 1fr = minmax(auto,1fr)) → overflow-x en móvil (medido:
       track de 882px en publicado@375). Va junto a .bento-grid { minmax(0,1fr) } (cinturón
       y tirantes). NO tocar overflow:visible (la toolbar del editor se posiciona fuera). */
    min-width: 0;
    border-radius: var(--card-radius, 20px);
    background: var(--card-bg, color-mix(in srgb, var(--text, #1a1a1a) 4%, transparent));
    border: var(--card-border, 1px solid color-mix(in srgb, var(--text, #1a1a1a) 10%, transparent));
    box-shadow: var(--shadow-card, none);
    padding: 24px;
    display: flex;
    transition: transform var(--transition, 0.4s cubic-bezier(0.16,1,0.3,1)), box-shadow 0.4s, border-color 0.4s;
}
/* Capa decorativa: clipa al radio del tile sin recortar la toolbar exterior. */
.bento-skin { position: absolute; inset: 0; border-radius: inherit; overflow: hidden; z-index: 0; pointer-events: none; }
.bento-corner { position: absolute; right: 0; bottom: 0; width: 130px; height: 130px; color: var(--accent); }

.bento-tile-inner { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 14px; width: 100%; height: 100%; }
.bento-lg .bento-tile-inner { justify-content: space-between; }

.bento-ico {
    display: inline-flex; align-items: center; justify-content: center;
    width: 46px; height: 46px; border-radius: 12px; flex-shrink: 0;
    color: var(--accent);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.bento-ico svg { width: 24px; height: 24px; }
.bento-lg .bento-ico { width: 56px; height: 56px; }
.bento-lg .bento-ico svg { width: 30px; height: 30px; }

.bento-tile-text h3 { font-size: 1.05rem; font-weight: 700; margin: 0 0 6px; letter-spacing: -0.01em; }
.bento-lg .bento-tile-text h3 { font-size: 1.5rem; }
.bento-tile-text p { font-size: 0.9rem; opacity: 0.62; margin: 0; line-height: 1.5; }

.bento-tile:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-hover, 0 18px 40px rgba(0,0,0,0.10));
    border-color: color-mix(in srgb, var(--accent) 35%, transparent);
}

/* ── v2 Editorial: número display, sin caja, hairline inferior ── */
.bento-section.variant-v2 .bento-tile {
    background: transparent; border: none; box-shadow: none; border-radius: 0;
    border-bottom: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
    padding: 22px 18px;
}
.bento-section.variant-v2 .bento-num { font-size: 2.4rem; font-weight: 900; line-height: 1; color: var(--accent); letter-spacing: -0.04em; }
.bento-section.variant-v2 .bento-lg .bento-num { font-size: 3.6rem; }
.bento-section.variant-v2 .bento-lg .bento-tile-text h3 { font-size: 1.7rem; }
.bento-section.variant-v2 .bento-tile:hover { transform: none; background: color-mix(in srgb, var(--text) 3%, transparent); }

/* ── v3 Glow: glass + borde gradiente + glow en hover / tile grande ── */
.bento-section.variant-v3 .bento-tile {
    background: color-mix(in srgb, var(--text) 4%, transparent);
    border: 1px solid color-mix(in srgb, var(--text) 10%, transparent);
    -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
.bento-section.variant-v3 .bento-skin::before {
    content: ''; position: absolute; inset: 0; border-radius: inherit; padding: 1px;
    background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 65%, transparent), transparent 60%);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor; mask-composite: exclude; opacity: 0.8;
}
.bento-section.variant-v3 .bento-lg .bento-skin {
    background: radial-gradient(120% 120% at 0% 0%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 55%);
}
.bento-section.variant-v3 .bento-tile:hover {
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent), 0 20px 50px color-mix(in srgb, var(--accent) 18%, transparent);
}

/* ── Responsive: 4 → 2 → 1 columnas; los spans se neutralizan ── */
@container (max-width: 900px) {
    .bento-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .bento-lg { grid-column: span 2; grid-row: span 1; }
    .bento-wide { grid-column: span 2; }
    .bento-tall { grid-row: span 1; }
}
@container (max-width: 560px) {
    .bento-grid { grid-template-columns: minmax(0, 1fr); gap: 14px; }
    .bento-lg, .bento-wide, .bento-tall { grid-column: span 1; grid-row: span 1; }
    .bento-corner { width: 100px; height: 100px; }
}

/* Respeta reduce-motion: sin desplazamientos en hover. */
@media (prefers-reduced-motion: reduce) {
    .bento-tile { transition: box-shadow 0.3s, border-color 0.3s; }
    .bento-tile:hover { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   RESPONSIVE — preview móvil del editor + móvil real (container query)
   ───────────────────────────────────────────────────────────────────────────
   El preview móvil del editor hace `body.mobile-view #resultado { width:375px }`
   pero el VIEWPORT sigue ancho → los @media (viewport) NO disparan ahí. #resultado
   es el único `container-type: inline-size`, así que estos @container (keyed a
   #resultado) SÍ disparan tanto en el preview de 375px COMO en móvil real.
   Replican el colapso de los bloques que NO se vuelven fluidos solos (los demás ya
   usan auto-fit/flex-wrap/scroll). NOTA: TODOS los @media (max/min-width) de bloque se
   convirtieron a @container (el publicado SÍ envuelve sections en #resultado, verificado)
   → preview == publicado, mismas reglas disparan en ambos. Solo siguen como @media: header
   900 (hoisted FUERA de #resultado en preview) y prefers-*. !important: vence base + inline. */
@container (max-width: 640px) {
    /* bento: grid 4-col fija → 1 col, spans neutralizados */
    .bento-grid { grid-template-columns: minmax(0, 1fr) !important; }
    .bento-lg, .bento-wide, .bento-tall { grid-column: span 1 !important; grid-row: span 1 !important; }
    /* form (split) → columna */
    .form-split { flex-direction: column !important; align-items: stretch !important; }
    .form-split-text { text-align: center !important; }
    .form-split .form-grid { grid-template-columns: 1fr !important; }
    /* maps (split) → columna */
    .maps-split { flex-direction: column !important; }
    /* menu v2 (grid 2-col) → 1 col */
    .menu-section.variant-v2 .menu-categories { grid-template-columns: 1fr !important; }
    /* gallery masonry/grid → 1 col */
    .gallery-masonry { columns: 1 !important; }
    .gallery-grid { grid-template-columns: 1fr !important; }
    /* process v2 (zigzag 3-col) → 1 col, sin conectores */
    .pz-item.left, .pz-item.right { grid-template-columns: 1fr !important; gap: 0 !important; }
    .pz-svg-wrap, .pz-empty { display: none !important; }
    /* journey: pila vertical centrada, pin desactivado. El motor (resize tras el toggle)
       recalcula el SVG path desde los dots reales → al apilarse, el trazo se vuelve
       VERTICAL conservando la animación scroll-draw. */
    .journey-pinned { height: auto !important; }
    .journey-v1 .journey-step,
    .journey-v1 .journey-step.is-left,
    .journey-v1 .journey-step.is-right { width: 100% !important; align-self: stretch !important; }
    /* v2 (pinned horizontal): `flex:1 1 0` hacía las cajas diminutas (1/N del ancho).
       Apilar vertical + cajas FULL-WIDTH, sin la onda (margin-top even) ni el reparto flex. */
    .journey-v2 .journey-steps { flex-direction: column !important; gap: clamp(30px, 7cqw, 56px) !important; align-items: stretch !important; }
    .journey-v2 .journey-step { flex: none !important; width: 100% !important; margin-top: 0 !important; }
    .journey-v2 .journey-card { width: 100% !important; }
    /* content v4 (split con solape negativo) → columna sin solape */
    [id^="bloque-content"].variant-v4 > div { flex-direction: column !important; gap: 32px !important; }
    [id^="bloque-content"].variant-v4 .content-image-side { margin-left: 0 !important; }
    /* footer minimal → columna */
    .footer-minimal-container { flex-direction: column !important; text-align: center !important; }
    /* comparison → scroll horizontal (cabe en el frame estrecho) */
    .comp-grid { overflow-x: auto !important; -webkit-overflow-scrolling: touch; }
    /* títulos de sección (h2): el clamp con vw mira el viewport ANCHO → gigantes en el
       frame de 375px. Reescalar al contenedor con cqw (hero h1 ya lo cubre su mobile-view).
       `:is(h1, h2)` para que también alcance al título de sección PROMOVIDO a <h1> por la
       jerarquía de página; `:not(.n3i-h2-as-h1)` para NO alcanzar al titular de hero/video
       DEGRADADO a <h2> (era <h1> y nunca entró aquí: su tamaño lo fija su style inline, y
       este !important se lo robaría). Ver los marcadores arriba, junto a h1/h2. */
    #resultado section :is(h1, h2):not(.n3i-h2-as-h1) { font-size: calc(clamp(1.5rem, 7cqw, 2.3rem) * var(--type-scale, 1)) !important; }
    /* decoraciones gigantes (ghost, font-size 20cqw, nowrap) → ocultar para no desbordar 375px */
    .ghost-text, .ghost-number { display: none !important; }
}

/* GARANTÍA para el preview móvil del editor (body.mobile-view persiste al pulsar "Ver
   Sitio"). El @container de arriba cubre móvil REAL (y normalmente el preview), pero por
   robustez replicamos el colapso por CLASE — independiente de que #resultado sea el
   container query. Mismos colapsos, scope body.mobile-view. */
body.mobile-view .bento-grid { grid-template-columns: minmax(0, 1fr) !important; }
body.mobile-view .bento-lg, body.mobile-view .bento-wide, body.mobile-view .bento-tall { grid-column: span 1 !important; grid-row: span 1 !important; }
body.mobile-view .form-split { flex-direction: column !important; align-items: stretch !important; }
body.mobile-view .form-split .form-grid { grid-template-columns: 1fr !important; }
body.mobile-view .maps-split { flex-direction: column !important; }
body.mobile-view .menu-section.variant-v2 .menu-categories { grid-template-columns: 1fr !important; }
body.mobile-view .gallery-masonry { columns: 1 !important; }
body.mobile-view .gallery-grid { grid-template-columns: 1fr !important; }
body.mobile-view .pz-item.left, body.mobile-view .pz-item.right { grid-template-columns: 1fr !important; gap: 0 !important; }
body.mobile-view .pz-svg-wrap, body.mobile-view .pz-empty { display: none !important; }
body.mobile-view .journey-v2 .journey-steps { flex-direction: column !important; align-items: stretch !important; }
body.mobile-view .journey-v2 .journey-step { flex: none !important; width: 100% !important; margin-top: 0 !important; }
body.mobile-view .footer-minimal-container { flex-direction: column !important; text-align: center !important; }
body.mobile-view .comp-grid { overflow-x: auto !important; }
body.mobile-view .ghost-text, body.mobile-view .ghost-number { display: none !important; }

/* ═══════════════════════════════════════════════════════════════
   BENTO SCENES — mini-widgets de contenido (counter / ticker / terminal).
   Render en js/blocks/bento-scenes.js. La animación solo corre en
   preview/público (clase .scene-playing la añade el JS); en el editor y en
   reduce-motion/gama baja se ve el contenido completo estático.
   ═══════════════════════════════════════════════════════════════ */
.bento-tile.has-scene .bento-tile-inner { gap: 12px; justify-content: space-between; }
.bento-tile.has-scene .bento-tile-text h3 { font-size: 0.95rem; opacity: 0.85; }
/* La escena CRECE para llenar el tile (evita huecos en blanco). min-width:0: como flex-item
   de column-flex, evita que el min-content del widget (ticker/rating/avatars) empuje el ancho. */
.bento-scene { flex: 1 1 auto; min-width: 0; min-height: 116px; display: flex; width: 100%; align-items: stretch; }
.bento-lg .bento-scene { min-height: 200px; }
.bento-wide .bento-scene, .bento-tall .bento-scene { min-height: 150px; }

/* ── counter ── */
.scene-counter { flex-direction: column; align-items: flex-start; justify-content: center; gap: 4px; }
.scene-num { font-size: clamp(2rem, 5cqw, 3.2rem); font-weight: 900; color: var(--accent); letter-spacing: -0.03em; line-height: 1; }
.bento-lg .scene-num { font-size: clamp(2.6rem, 6cqw, 4rem); }
.scene-label { font-size: 0.82rem; opacity: 0.6; font-weight: 500; }

/* ── ticker (etiquetas en bucle) ── */
.scene-ticker { align-items: center; overflow: hidden;
    -webkit-mask: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
    mask: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent); }
.tk-track { display: flex; gap: 10px; width: max-content; will-change: transform; }
.scene-ticker.scene-playing .tk-track { animation: bento-marquee 16s linear infinite; }
.tk-tag {
    white-space: nowrap; padding: 6px 13px; border-radius: 999px;
    font-size: 0.8rem; font-weight: 600; color: var(--accent);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
}
@keyframes bento-marquee { to { transform: translateX(-50%); } }

/* ── terminal (teclea líneas) ── */
.scene-terminal {
    flex-direction: column; align-self: stretch; border-radius: 12px; overflow: hidden;
    background: #0b1020; border: 1px solid rgba(255, 255, 255, 0.1);
    font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
}
.term-bar { display: flex; gap: 6px; padding: 9px 12px; background: rgba(255, 255, 255, 0.04); flex: 0 0 auto; }
.term-bar i { width: 9px; height: 9px; border-radius: 50%; background: #ff5f56; }
.term-bar i:nth-child(2) { background: #ffbd2e; }
.term-bar i:nth-child(3) { background: #27c93f; }
.term-body { padding: 12px 14px; display: flex; flex-direction: column; gap: 6px; font-size: 0.8rem; line-height: 1.5; color: #cbd5e1; min-height: 64px; }
.term-line { white-space: pre-wrap; word-break: break-word; min-height: 1.2em; }
.term-line.typing::after { content: '▋'; color: var(--accent); animation: term-caret 0.9s steps(1) infinite; }
@keyframes term-caret { 50% { opacity: 0; } }

/* ── menu / lista (etiqueta · valor — cartas, precios, specs) ── */
.scene-menu { flex-direction: column; justify-content: center; gap: 0; }
.mn-row { display: flex; align-items: baseline; gap: 8px; padding: 9px 0; font-size: 0.92rem; border-bottom: 1px solid color-mix(in srgb, var(--text) 9%, transparent); }
.mn-row:last-child { border-bottom: none; }
.mn-label { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mn-dots { flex: 1; border-bottom: 1px dotted color-mix(in srgb, var(--text) 24%, transparent); transform: translateY(-3px); min-width: 12px; }
.mn-val { font-weight: 800; color: var(--accent); white-space: nowrap; }
.scene-playing .mn-row { animation: mn-in 0.5s both; animation-delay: calc(var(--mn-i) * 80ms); }
@keyframes mn-in { from { opacity: 0; transform: translateX(-8px); } to { opacity: 1; transform: none; } }

/* ── rating (estrellas + nota) ── */
.scene-rating { flex-direction: column; align-items: flex-start; justify-content: center; gap: 6px; }
.rt-val { font-size: clamp(1.8rem, 4.5cqw, 2.8rem); font-weight: 900; color: var(--accent); line-height: 1; }
.rt-stars { display: inline-flex; gap: 3px; }
.rt-star { width: 20px; height: 20px; display: block; fill: color-mix(in srgb, var(--text) 18%, transparent); }
.rt-stars .on .rt-star { fill: #f5b301; }
.rt-count { font-size: 0.8rem; opacity: 0.6; }
.scene-playing .rt-stars .on { animation: rt-pop 0.45s both; }
@keyframes rt-pop { from { transform: scale(0) rotate(-30deg); opacity: 0; } to { transform: none; opacity: 1; } }

/* ── gauge (anillo de progreso) ── */
.scene-gauge { position: relative; flex-direction: column; align-items: center; justify-content: center; gap: 4px; }
.gg-svg { width: clamp(92px, 46%, 150px); aspect-ratio: 1; transform: rotate(-90deg); }
.gg-track { fill: none; stroke: color-mix(in srgb, var(--text) 12%, transparent); stroke-width: 9; }
.gg-fill { fill: none; stroke: var(--accent); stroke-width: 9; stroke-linecap: round; filter: drop-shadow(0 0 5px color-mix(in srgb, var(--accent) 45%, transparent)); }
.gg-center { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: clamp(1.1rem, 3cqw, 1.7rem); font-weight: 900; color: var(--text); pointer-events: none; }
.bento-lg .gg-center { margin-top: -6%; }
.gg-label { font-size: 0.8rem; opacity: 0.62; }

/* ── bars (mini gráfico de barras) ── */
.scene-bars { flex-direction: column; justify-content: flex-end; gap: 8px; }
.br-track { display: flex; align-items: flex-end; gap: clamp(5px, 1.6cqw, 11px); flex: 1; min-height: 80px; }
.br { flex: 1; height: var(--h); min-height: 6px; border-radius: 6px 6px 2px 2px; background: linear-gradient(var(--accent), color-mix(in srgb, var(--accent) 38%, transparent)); }
.scene-playing .br { animation: br-grow 0.9s cubic-bezier(0.16, 1, 0.3, 1) both; animation-delay: calc(var(--br-i) * 70ms); }
@keyframes br-grow { from { height: 0; } to { height: var(--h); } }
.br-labels { display: flex; gap: clamp(5px, 1.6cqw, 11px); }
.br-labels span { flex: 1; text-align: center; font-size: 0.68rem; opacity: 0.55; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ── avatars (prueba social) ── */
.scene-avatars { align-items: center; gap: 14px; }
.av-stack { display: flex; }
.av-face { width: 42px; height: 42px; border-radius: 50%; border: 2px solid var(--bg); margin-left: -12px; box-shadow: 0 2px 6px rgba(0,0,0,0.15); background: conic-gradient(from calc(var(--av-i) * 80deg), var(--accent), color-mix(in srgb, var(--accent) 30%, #ffffff)); }
.av-face:first-child { margin-left: 0; }
.scene-playing .av-face { animation: av-in 0.45s both; animation-delay: calc(var(--av-i) * 90ms); }
@keyframes av-in { from { opacity: 0; transform: scale(0.4) translateX(10px); } to { opacity: 1; transform: none; } }
.av-text { display: flex; flex-direction: column; }
.av-count { font-size: clamp(1.3rem, 3.5cqw, 2rem); font-weight: 900; color: var(--accent); line-height: 1; }
.av-label { font-size: 0.82rem; opacity: 0.6; }

/* ── map (ubicación con pin pulsante) ── */
.scene-map { position: relative; align-items: center; justify-content: center; overflow: hidden; border-radius: 14px; min-height: 130px; background: color-mix(in srgb, var(--accent) 7%, transparent); }
.mp-svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.mp-grid { stroke: color-mix(in srgb, var(--text) 10%, transparent); stroke-width: 1; fill: none; }
.mp-road { stroke: color-mix(in srgb, var(--accent) 55%, transparent); stroke-width: 2.5; fill: none; stroke-linecap: round; }
.mp-pin { position: relative; width: 16px; height: 16px; border-radius: 50%; background: var(--accent); z-index: 2; box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 22%, transparent), 0 4px 8px rgba(0,0,0,0.25); }
.mp-pulse { position: absolute; inset: -4px; border-radius: 50%; border: 2px solid var(--accent); }
.scene-playing .mp-pulse { animation: mp-ping 1.8s ease-out infinite; }
@keyframes mp-ping { 0% { transform: scale(0.6); opacity: 0.9; } 100% { transform: scale(2.6); opacity: 0; } }
.mp-label { position: absolute; bottom: 8px; left: 8px; right: 8px; z-index: 2; font-size: 0.8rem; font-weight: 600; padding: 5px 9px; border-radius: 8px; background: color-mix(in srgb, var(--bg) 86%, transparent); -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); }

/* ── hours (estado abierto/cerrado) ── */
.scene-hours { flex-direction: column; align-items: flex-start; justify-content: center; gap: 8px; }
.hr-badge { display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; border-radius: 999px; font-weight: 700; font-size: 0.95rem; background: color-mix(in srgb, var(--text) 8%, transparent); }
.hr-dot { width: 9px; height: 9px; border-radius: 50%; background: #94a3b8; }
.scene-hours.open .hr-badge { background: color-mix(in srgb, #22c55e 16%, transparent); color: #16a34a; }
.scene-hours.open .hr-dot { background: #22c55e; }
.scene-hours.open.scene-playing .hr-dot { animation: hr-blink 1.4s ease-in-out infinite; }
@keyframes hr-blink { 50% { opacity: 0.35; } }
.hr-detail { font-size: 0.86rem; opacity: 0.65; }

/* ── quote (testimonio breve) ── */
.scene-quote { flex-direction: column; justify-content: center; gap: 6px; }
.qt-mark { font-family: Georgia, 'Times New Roman', serif; font-size: 3rem; line-height: 0.6; color: color-mix(in srgb, var(--accent) 45%, transparent); }
.qt-text { margin: 0; font-size: 1rem; font-weight: 600; line-height: 1.45; }
.qt-author { font-size: 0.82rem; opacity: 0.6; }
.scene-playing .qt-text, .scene-playing .qt-author { animation: qt-in 0.6s both; }
@keyframes qt-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* Belt-and-suspenders: en reduce-motion, todo estático (sin bucles ni reveals). */
@media (prefers-reduced-motion: reduce) {
    .scene-ticker .tk-track,
    .scene-playing .mn-row,
    .scene-playing .rt-stars .on,
    .scene-playing .br,
    .scene-playing .av-face,
    .scene-playing .mp-pulse,
    .scene-hours.open.scene-playing .hr-dot,
    .scene-playing .qt-text,
    .scene-playing .qt-author { animation: none !important; }
    .scene-playing .br { height: var(--h); }
}
.hscroll-dragging * { cursor: grabbing !important; user-select: none !important; pointer-events: none !important; }
.hscroll-dragging * { cursor: grabbing !important; user-select: none !important; pointer-events: none !important; }

/* ═══════════════════════════════════════════════════════════════════════
   JOURNEY — Track B · Ola 1.3 (bloque "Recorrido", trazo SVG atado al scroll).
   Colores 100% desde la paleta de la IA: var(--bg/--text/--accent).
   Motor del dibujo: js/animations/journey-scroll.js. 3 variantes:
   v1 serpenteante · v2 pinned horizontal · v3 espina recta central.
   ═══════════════════════════════════════════════════════════════════════ */
.journey-layout {
    background: var(--bg);
    color: var(--text);
    font-family: var(--font), system-ui, sans-serif;
    padding: clamp(72px, 9cqw, 130px) 8%;
    position: relative;
    overflow: hidden;
}
.journey-head { max-width: 720px; margin: 0 auto clamp(40px, 6cqw, 72px); text-align: center; }
.journey-head :is(h1, h2) { font-size: clamp(1.9rem, 4cqw, 3rem); font-weight: 900; letter-spacing: -0.02em; margin: 0 0 14px; }
.journey-head p  { font-size: 1.1rem; opacity: 0.62; margin: 0; }

.journey-track { position: relative; max-width: 1000px; margin: 0 auto; }
.journey-svg { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; overflow: visible; }
.journey-rail  { fill: none; stroke: color-mix(in srgb, var(--text) 12%, transparent); stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.journey-route { fill: none; stroke-width: 3.5; stroke-linecap: round; stroke-linejoin: round; filter: drop-shadow(0 0 6px color-mix(in srgb, var(--accent) 42%, transparent)); }
.journey-tip   { fill: var(--accent); opacity: 0; filter: drop-shadow(0 0 7px color-mix(in srgb, var(--accent) 70%, transparent)); transition: opacity 0.25s ease; }

.journey-steps { position: relative; z-index: 1; display: flex; flex-direction: column; gap: clamp(26px, 5cqw, 60px); }
/* Modelo común a las 3 variantes: cada paso es una COLUMNA con el dot coronando
   la tarjeta. Las variantes solo cambian dirección/anchura/alineación. */
.journey-step  { position: relative; display: flex; flex-direction: column; align-items: center; }

/* Marcador (dot) sobre el trazo — contiene el número de paso */
.journey-dot {
    width: 36px; height: 36px; border-radius: 50%;
    background: var(--bg);
    border: 2px solid color-mix(in srgb, var(--text) 28%, transparent);
    display: grid; place-items: center;
    position: relative; z-index: 2; flex: 0 0 auto; margin-bottom: 16px;
    transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1), background 0.45s ease, border-color 0.45s ease, box-shadow 0.45s ease;
}
.journey-num { font-size: 0.82rem; font-weight: 800; color: color-mix(in srgb, var(--text) 55%, transparent); transition: color 0.4s ease; }
.journey-step.is-active .journey-dot {
    background: var(--accent); border-color: var(--accent); transform: scale(1.12);
    box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent) 15%, transparent), 0 0 20px color-mix(in srgb, var(--accent) 50%, transparent);
}
.journey-step.is-active .journey-num { color: #fff; }

/* Tarjeta de contenido (oculta hasta que el trazo la alcanza) */
.journey-card {
    background: color-mix(in srgb, var(--accent) 6%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent) 15%, transparent);
    border-radius: var(--card-radius, 18px);
    padding: 22px 26px; text-align: center;
    opacity: 0.32; transform: translateY(14px);
    transition: opacity 0.55s ease, transform 0.55s ease, border-color 0.55s ease;
}
.journey-step.is-active .journey-card { opacity: 1; transform: translateY(0); border-color: color-mix(in srgb, var(--accent) 28%, transparent); }
.journey-icon { color: var(--accent); margin-bottom: 10px; display: inline-flex; }
.journey-card h3 { margin: 0 0 6px; font-size: 1.15rem; font-weight: 700; }
.journey-card p  { margin: 0; opacity: 0.7; font-size: 0.93rem; line-height: 1.55; }

/* Toolbar de edición de items */
.journey-step .item-tools { position: absolute; top: 8px; right: 8px; z-index: 5; }

/* ── v1 "Sendero": cada paso se alinea a un lado y el trazo SERPENTEA entre los
   centros de las tarjetas (curva orgánica — distinto de la línea recta de Timeline). ── */
.journey-v1 .journey-steps { gap: clamp(50px, 7cqw, 104px); }
.journey-v1 .journey-step { width: min(46%, 420px); }
.journey-v1 .journey-step.is-left  { align-self: flex-start; }
.journey-v1 .journey-step.is-right { align-self: flex-end; }

/* ── v3 "Ascenso": COLUMNA CENTRAL única. Las tarjetas NO van a los lados (a
   diferencia de Timeline); el hilo desciende coronando cada paso y se ve en los
   huecos. Tarjeta opaca para que el hilo no cruce el texto. ── */
.journey-v3 .journey-track { max-width: 600px; }
.journey-v3 .journey-steps { gap: clamp(42px, 6cqw, 88px); }
.journey-v3 .journey-step { width: 100%; }
.journey-v3 .journey-card { width: 100%; background: color-mix(in srgb, var(--bg) 88%, var(--accent)); }

/* ── v2 "Ruta Fija": pinned + trazo horizontal; columnas que se encienden ── */
.journey-v2 .journey-track { max-width: 1140px; }
.journey-v2 .journey-steps { flex-direction: row; align-items: flex-start; gap: clamp(16px, 2.5cqw, 30px); }
.journey-v2 .journey-step { flex: 1 1 0; }
.journey-v2 .journey-dot { margin-bottom: 22px; }
.journey-v2 .journey-card { width: 100%; }
.journey-v2 .journey-step:nth-child(even) { margin-top: 26px; } /* leve onda orgánica del trazo */
.journey-pinned { overflow: visible; } /* no atrapar position:sticky */
.preview-mode .journey-pinned { height: 300vh; padding-top: 0; padding-bottom: 0; }
.preview-mode .journey-pinned .journey-pin {
    /* Mismo caso que `.tl-ss-sticky` (ver su nota): pegado al viewport, así que el header
       fijo le comía el encabezado y ninguna corrección sobre la sección podía moverlo.
       `--header-h` entra en el `top` y en la altura mínima — NO también en el padding, que
       lo descontaría por tercera vez y dejaría sin sitio al último paso en pantallas bajas. */
    position: sticky; top: var(--header-h, 0px); min-height: calc(100vh - var(--header-h, 0px));
    /* Contenido alineado hacia arriba (no centrado) para no dejar un hueco enorme
       sobre el encabezado en "Ver Sitio". El trazo horizontal se dibuja igual por
       scroll: journey-scroll.js no depende de la alineación vertical. */
    display: flex; flex-direction: column; justify-content: flex-start;
    padding: clamp(44px, 8vh, 100px) 8% 56px;
}

/* Reduced motion: trazo completo y todo visible (respaldo CSS si el JS no corre). */
@media (prefers-reduced-motion: reduce) {
    .journey-card { opacity: 1; transform: none; }
    .journey-tip { display: none; }
}

/* Tablet: estrechar un poco las tarjetas alternas para que el serpenteo respire. */
@container (max-width: 1024px) {
    .journey-v1 .journey-step { width: min(50%, 380px); }
    .journey-v2 .journey-steps { gap: 14px; }
}

/* Móvil: las 3 variantes pasan a una pila vertical centrada; el pin se desactiva.
   El motor recalcula el trazo (puntos centrados → línea vertical) al cambiar el layout. */
@container (max-width: 760px) {
    .journey-pinned { height: auto !important; }
    .preview-mode .journey-pinned .journey-pin { position: static; min-height: 0; padding: 0; }
    .journey-v1 .journey-step,
    .journey-v1 .journey-step.is-left,
    .journey-v1 .journey-step.is-right { width: 100%; align-self: stretch; }
    .journey-v3 .journey-track { max-width: 520px; }
    .journey-v2 .journey-steps { flex-direction: column; gap: clamp(36px, 9cqw, 64px); align-items: stretch; }
    .journey-v2 .journey-step { flex: none !important; width: 100% !important; margin-top: 0 !important; }
    .journey-card { width: 100%; max-width: none; }
}

/* ═══════════════════════════════════════════════════════════════
   FORM — Captación / contacto. v1 centrado · v2 split.
   ═══════════════════════════════════════════════════════════════ */
#resultado .ai-form { width: 100%; }
#resultado .ai-form-center { max-width: 560px; margin: 0 auto; }
.form-head-center { text-align: center; max-width: 720px; margin: 0 auto 50px; }
.form-title { font-size: clamp(2rem, 4cqw, 3rem); font-weight: 900; margin-bottom: 16px; line-height: 1.1; }
.form-subtitle { opacity: 0.65; font-size: 1.1rem; line-height: 1.6; }
.form-grid { display: grid; grid-template-columns: 1fr; gap: 22px; }
.form-field-wide { grid-column: 1 / -1; }
.form-field { position: relative; display: flex; flex-direction: column; gap: 8px; }
.form-label-row { display: flex; align-items: center; gap: 5px; }
.form-label { font-size: 0.9rem; font-weight: 600; color: var(--text); opacity: 0.9; outline: none; min-width: 24px; }
.form-req { color: var(--accent); font-weight: 800; line-height: 1; }
body.editor-active .form-label:empty::before { content: 'Etiqueta del campo'; opacity: 0.4; }
#resultado .form-input {
    width: 100%; font-family: inherit; font-size: 1rem; color: var(--text);
    background: var(--card-bg, color-mix(in srgb, var(--text) 4%, transparent));
    border: var(--card-border, 1px solid color-mix(in srgb, var(--text) 14%, transparent));
    border-radius: var(--card-radius, 16px);
    padding: 15px 18px; transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
    -webkit-appearance: none; appearance: none;
}
#resultado .form-input::placeholder { color: color-mix(in srgb, var(--text) 40%, transparent); }
#resultado .form-input:focus {
    outline: none; border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
    background: color-mix(in srgb, var(--text) 6%, transparent);
}
#resultado .form-textarea { resize: vertical; min-height: 120px; line-height: 1.6; }
.form-actions { margin-top: 28px; display: flex; }
#resultado .ai-form-center .form-actions { justify-content: center; }
#resultado .form-submit { border: none; cursor: pointer; }
.form-success {
    display: none; margin-top: 22px; padding: 16px 20px;
    border-radius: var(--card-radius, 16px);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
    color: var(--text); font-weight: 600; line-height: 1.5;
}
.form-success.is-visible { display: block; animation: form-success-in 0.4s cubic-bezier(0.16,1,0.3,1); }
body.editor-active .form-success { display: block; opacity: 0.7; }
@keyframes form-success-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.form-hp { position: absolute !important; left: -9999px !important; width: 1px; height: 1px; overflow: hidden; }

/* Rótulo del EDITOR del bloque form/reservas (.editor-only → compileSiteHtml lo elimina).
   Dice las dos cosas que el cliente no podía saber: que dentro del canvas el envío es inerte
   y a qué buzón llegan los mensajes (editable ahí mismo). Chrome oscuro, el mismo lenguaje
   que la barra del carrusel. */
.form-editor-note {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 14px;
    margin-top: 18px;
    padding: 10px 14px;
    border-radius: 12px;
    background: #0b1220;
    border: 1px solid rgba(255, 255, 255, 0.10);
    color: rgba(226, 232, 240, 0.82);
    font: 500 0.78rem/1.45 system-ui, -apple-system, sans-serif;
    text-align: left;
}
.form-editor-note__msg { display: inline-flex; align-items: center; gap: 6px; }
.form-editor-note__dest { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.form-dest-value {
    display: inline-block;
    min-width: 120px;
    padding: 2px 8px;
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.07);
    border: 1px dashed rgba(255, 255, 255, 0.22);
    color: #e2e8f0;
    outline: none;
}
.form-dest-value:focus { border-style: solid; border-color: rgba(255, 255, 255, 0.45); }
/* Sin destinatario: se explica qué pasa entonces (el servidor usa el email de la ficha). */
.form-dest-value:empty::before {
    content: attr(data-placeholder);
    color: rgba(226, 232, 240, 0.45);
    font-style: italic;
}
.form-split { display: flex; gap: 60px; align-items: center; max-width: 1100px; margin: 0 auto; }
.form-split-text { flex: 1; min-width: min(100%, 320px); text-align: left; }
.form-split-form { flex: 1.1; min-width: min(100%, 340px); }
.form-split .form-grid { grid-template-columns: 1fr 1fr; }
@container (max-width: 820px) {
    .form-split { flex-direction: column; gap: 40px; align-items: stretch; }
    .form-split-text { text-align: center; }
}
@container (max-width: 640px) { .form-split .form-grid { grid-template-columns: 1fr; } }

/* ═══════════════════════════════════════════════════════════════
   MENU — Carta de restaurante (categorías + platos con precio).
   v1 = una columna elegante · v2 = dos columnas. Líder de puntos.
   ═══════════════════════════════════════════════════════════════ */
.menu-section { color: var(--text); font-family: var(--font-body, var(--font, 'Inter')), sans-serif; background: var(--bg); }
.menu-head { text-align: center; max-width: 720px; margin: 0 auto 64px; }
.menu-head :is(h1, h2) { font-size: clamp(2rem, 4cqw, 3rem); font-weight: 900; letter-spacing: -0.02em; margin: 0 0 14px; line-height: 1.1; }
.menu-head p { font-size: 1.1rem; opacity: 0.6; margin: 0; line-height: 1.5; }
.menu-categories { max-width: 760px; margin: 0 auto; display: flex; flex-direction: column; gap: 56px; }
.menu-category { position: relative; }
.menu-cat-name {
    font-size: 0.95rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.18em;
    color: var(--accent); margin: 0 0 24px; padding-bottom: 12px;
    border-bottom: 1px solid color-mix(in srgb, var(--text) 16%, transparent);
}
.menu-items { display: flex; flex-direction: column; gap: 22px; }
.menu-dish-head { display: flex; align-items: baseline; gap: 10px; width: 100%; }
.dish-name { font-size: 1.08rem; font-weight: 600; letter-spacing: -0.01em; flex-shrink: 0; max-width: 75%; }
.menu-leader {
    flex: 1 1 auto; min-width: 18px; align-self: baseline; transform: translateY(-0.35em);
    border-bottom: 1.5px dotted color-mix(in srgb, var(--text) 35%, transparent);
}
.dish-price { font-size: 1.05rem; font-weight: 700; color: var(--accent); flex-shrink: 0; font-variant-numeric: tabular-nums; white-space: nowrap; }
.dish-desc { font-size: 0.9rem; opacity: 0.6; margin: 6px 0 0; line-height: 1.5; max-width: 90%; }
.dish-desc:empty { display: none; }
.editor-active .dish-desc:empty { display: block; min-height: 1.1em; opacity: 0.4; }
/* Precio vacío (plato recién añadido con price:''): el span es inline, flex-shrink:0 y sin min-width
   → colapsaría a 0px y no se podría clicar para definirlo. Placeholder editable en el editor (mismo
   patrón que .dish-desc:empty / .form-label:empty). En publicado un precio vacío simplemente no se pinta. */
.editor-active .dish-price:empty::before { content: 'Precio'; opacity: 0.4; font-weight: 500; }
.menu-section.variant-v2 .menu-categories {
    max-width: 1100px; display: grid; grid-template-columns: repeat(2, 1fr);
    gap: 52px 72px; align-items: start;
}
@container (max-width: 760px) {
    .menu-section.variant-v2 .menu-categories { grid-template-columns: 1fr; gap: 48px; }
    .menu-categories { gap: 44px; }
}
@container (max-width: 480px) {
    .dish-name { max-width: 60%; }
    .menu-head { margin-bottom: 48px; }
}
/* PODADO menu v2 mobile-view (gap:44px inventado): el @container 760 ya colapsa a 1 col con
   gap:48px (valor del publicado) y la GARANTÍA 3506 asegura 1 col por clase → sobraba. */

/* ═══════════════════════════════════════════════════════════════
   SERVICES — Servicios con precio (peluquerías, talleres, vets, oficios).
   v1 = tarjetas · v2 = lista.
   ═══════════════════════════════════════════════════════════════ */
.service-head { text-align: center; max-width: 720px; margin: 0 auto 56px; }
.service-head :is(h1, h2) { font-size: clamp(2rem, 4cqw, 3rem); font-weight: 900; letter-spacing: -0.02em; margin: 0 0 14px; line-height: 1.1; }
.service-head p { font-size: 1.1rem; opacity: 0.6; margin: 0; line-height: 1.5; }
.service-icon, .service-row-icon {
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
    color: var(--accent, #6366f1);
    background: color-mix(in srgb, var(--accent, #6366f1) 12%, transparent);
}
.service-icon svg, .service-row-icon svg { stroke: currentColor; fill: none; }
.service-price { color: var(--accent, #6366f1); font-weight: 900; letter-spacing: -0.02em; line-height: 1; white-space: nowrap; }
.service-name { font-weight: 700; letter-spacing: -0.01em; margin: 0; }
.service-desc { opacity: 0.62; line-height: 1.55; margin: 0; }
.services-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 24px; max-width: 1200px; margin: 0 auto; }
.variant-v1 .service-card {
    position: relative; display: flex; flex-direction: column; gap: 14px; padding: 34px 30px;
    border-radius: var(--card-radius, 20px);
    background: var(--card-bg, color-mix(in srgb, var(--text, #1a1a1a) 4%, transparent));
    border: var(--card-border, 1px solid color-mix(in srgb, var(--text, #1a1a1a) 10%, transparent));
    box-shadow: var(--shadow-card, none);
    transition: transform var(--transition, 0.4s cubic-bezier(0.16,1,0.3,1)), box-shadow 0.4s, border-color 0.4s;
}
.variant-v1 .service-icon { width: 48px; height: 48px; border-radius: 14px; }
.variant-v1 .service-icon svg { width: 24px; height: 24px; }
.variant-v1 .service-name { font-size: 1.2rem; }
.variant-v1 .service-desc { font-size: 0.92rem; flex-grow: 1; }
.variant-v1 .service-price { font-size: 1.6rem; margin-top: 6px; padding-top: 16px; border-top: 1px solid color-mix(in srgb, var(--text, #1a1a1a) 10%, transparent); }
.variant-v1 .service-card:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-hover, 0 18px 40px rgba(0,0,0,0.10));
    border-color: color-mix(in srgb, var(--accent, #6366f1) 35%, transparent);
}
.services-list { display: flex; flex-direction: column; max-width: 820px; margin: 0 auto; }
.variant-v2 .service-row {
    position: relative; display: flex; align-items: center; gap: 22px; padding: 26px 12px;
    border-bottom: 1px solid color-mix(in srgb, var(--text, #1a1a1a) 12%, transparent);
    transition: background var(--transition, 0.3s ease);
}
.variant-v2 .services-list .service-row:first-child { border-top: 1px solid color-mix(in srgb, var(--text, #1a1a1a) 12%, transparent); }
.variant-v2 .service-row-icon { width: 44px; height: 44px; border-radius: 12px; }
.variant-v2 .service-row-icon svg { width: 22px; height: 22px; }
.variant-v2 .service-row-text { flex: 1; min-width: 0; }
.variant-v2 .service-name { font-size: 1.1rem; margin-bottom: 4px; }
.variant-v2 .service-desc { font-size: 0.9rem; }
.variant-v2 .service-price { font-size: 1.5rem; margin-left: auto; }
.variant-v2 .service-row:hover { background: color-mix(in srgb, var(--text, #1a1a1a) 3%, transparent); }
@container (max-width: 768px) {
    .service-head { margin-bottom: 40px; }
    .services-grid { grid-template-columns: 1fr; gap: 18px; }
    .variant-v2 .service-row { gap: 16px; padding: 20px 8px; }
    .variant-v2 .service-price { font-size: 1.25rem; }
}
@container (max-width: 480px) {
    .variant-v2 .service-row { flex-wrap: wrap; }
    .variant-v2 .service-price { margin-left: 60px; font-size: 1.2rem; }
}


/* ── Menu: edición de platos (solo editor) ── */
.menu-add-dish, .menu-del-dish { display: none; }
.editor-active .menu-add-dish {
    display: inline-flex; align-items: center; gap: 6px; margin-top: 16px;
    padding: 8px 14px; border: 1px dashed color-mix(in srgb, var(--accent) 45%, transparent);
    border-radius: 10px; background: color-mix(in srgb, var(--accent) 8%, transparent);
    color: var(--accent); font-size: 0.85rem; font-weight: 700; cursor: pointer;
    font-family: inherit; transition: background 0.2s, border-color 0.2s;
}
.editor-active .menu-add-dish:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); border-style: solid; }
.menu-dish { position: relative; }
.editor-active .menu-dish .menu-del-dish {
    position: absolute; top: -6px; right: -6px; width: 24px; height: 24px;
    border: none; border-radius: 50%; background: #ef4444; color: #fff; cursor: pointer;
    align-items: center; justify-content: center; z-index: 5; box-shadow: 0 2px 6px rgba(0,0,0,0.25);
    opacity: 0; transition: opacity 0.2s;
}
.editor-active .menu-dish .menu-del-dish svg { width: 13px; height: 13px; }
.editor-active .menu-dish:hover .menu-del-dish { display: inline-flex; opacity: 1; }

/* ── Services v1: scroll horizontal (estilo pricing) cuando hay >2 servicios ── */
.services-scroll {
    display: flex; flex-wrap: nowrap; gap: 24px; overflow-x: auto;
    /* max-width:min(100%,1200px): el 100% impide que los márgenes auto, al desactivar el
       align-items:stretch del column-flex de la <section>, dimensionen el scroller a su
       max-content (suma de cards sin shrink) → desbordaba la página. Capado al 100% del
       contenedor, overflow-x:auto scrollea las cards en lugar de crecer. Conserva tope 1200. */
    max-width: min(100%, 1200px); min-width: 0;
    margin: 0 auto; padding: 6px 4px 38px; scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
}
.services-scroll::-webkit-scrollbar { display: none; }
.services-scroll { -ms-overflow-style: none; scrollbar-width: none; }
/* cap alto (480) para que el 32% escale con el contenedor: con data-content-width="wide"
   el contenedor se libera (max-width:none) → cards crecen (~376px normal vs ~438px wide).
   Con cap 340 ambos modos topaban a 340 → "ancho" no se notaba. */
.variant-v1 .services-scroll .service-card { flex: 0 0 clamp(260px, 32%, 480px); scroll-snap-align: center; }
/* La ÚLTIMA card no se podía leer: scroll-snap-align:center no puede centrar la última
   (no hay espacio de arrastre detrás) → el snap volvía a la penúltima. Anclar las cards
   de los extremos a inicio/fin para que SIEMPRE se vean completas; las del medio centran. */
.variant-v1 .services-scroll .service-card:first-child { scroll-snap-align: start; }
.variant-v1 .services-scroll .service-card:last-child  { scroll-snap-align: end; }
/* Espacio de arrastre para que el snap pueda alcanzar ambos extremos. */
.variant-v1 .services-scroll { scroll-padding-inline: 4px; }
@container (max-width: 768px) {
    .variant-v1 .services-scroll .service-card { flex-basis: 80%; }
}

/* ── Animación de entrada de los platos del menú (stagger por fila) ── */
.menu-dish { transition: transform 0.3s ease; }
.editor-active .menu-dish:hover { transform: translateX(2px); }

/* ===== hero · v4 (Marco editorial) — variant-expansion ===== */
/* ═══════════════════════════════════════════════════════════════
   HERO · VARIANTE v4 "Marco editorial" (CSS PURO — sin tocar render)
   Reestiliza el HTML por defecto (rama FONDO, no-split) como un panel
   glass enmarcado, anclado a la izquierda, con riel de acento, kicker
   y un marco inset que encuadra todo el hero.
   Concepto nuevo, distinto de v1 (texto full-bleed izq), v2 (centrado)
   y v3 (split texto+media). Solo tokens + color-mix → seguro claro/oscuro.
   ═══════════════════════════════════════════════════════════════ */

.hero-section.variant-v4 {
    justify-content: flex-start;
    text-align: left;
    padding-left: clamp(28px, 9%, 150px);
    padding-right: clamp(28px, 9%, 150px);
    isolation: isolate;
}

/* Marco editorial: borde inset que encuadra todo el hero */
.hero-section.variant-v4::before {
    content: "";
    position: absolute;
    inset: clamp(16px, 3cqw, 42px);
    border: 1px solid color-mix(in srgb, var(--text) 18%, transparent);
    border-radius: var(--card-radius, 24px);
    pointer-events: none;
    z-index: 1;
}

/* Foco de acento (spotlight) detrás del panel, hacia la izquierda */
.hero-section.variant-v4::after {
    content: "";
    position: absolute;
    top: 12%;
    left: clamp(16px, 6cqw, 90px);
    width: min(46cqw, 460px);
    height: min(46cqw, 460px);
    background: radial-gradient(circle at 35% 35%, color-mix(in srgb, var(--accent) 55%, transparent), transparent 68%);
    filter: blur(46px);
    opacity: 0.55;
    pointer-events: none;
    z-index: 0;
}

/* Panel de contenido: tarjeta glass con riel de acento a la izquierda */
.hero-section.variant-v4 > div:has(> [data-field="title"]) {
    position: relative;
    z-index: 2;
    width: min(680px, 100%);
    max-width: 680px !important;
    margin-left: clamp(8px, 4cqw, 70px);
    padding: clamp(30px, 4cqw, 58px);
    padding-left: clamp(34px, 4.5cqw, 66px);
    background: color-mix(in srgb, var(--bg) 90%, transparent);
    border: var(--card-border, 1px solid color-mix(in srgb, var(--text) 14%, transparent));
    border-left: 3px solid var(--accent);
    border-radius: var(--card-radius, 24px);
    box-shadow: var(--shadow-card, 0 34px 80px color-mix(in srgb, var(--text) 22%, transparent));
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    transition: transform var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1)), box-shadow var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1));
}

/* Kicker: barra corta de acento sobre el título */
.hero-section.variant-v4 > div:has(> [data-field="title"])::before {
    content: "";
    display: block;
    width: 58px;
    height: 4px;
    margin-bottom: 24px;
    border-radius: 4px;
    background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 8%, transparent));
}

/* Esquina tipo visor (top-right) — detalle editorial, decorativo */
.hero-section.variant-v4 > div:has(> [data-field="title"])::after {
    content: "";
    position: absolute;
    top: 18px;
    right: 18px;
    width: 26px;
    height: 26px;
    border-top: 2px solid color-mix(in srgb, var(--accent) 70%, transparent);
    border-right: 2px solid color-mix(in srgb, var(--accent) 70%, transparent);
    border-top-right-radius: 6px;
    pointer-events: none;
}

/* Título oversize editorial */
.hero-section.variant-v4 [data-field="title"] {
    font-size: clamp(2.6rem, 6cqw, 5rem) !important;
    line-height: 1.04;
    letter-spacing: -0.04em;
    margin-bottom: 20px !important;
}

/* Subtítulo: regla superior + medida cómoda de lectura */
.hero-section.variant-v4 [data-field="subtitle"] {
    max-width: 50ch;
    font-size: 1.2rem !important;
    margin: 24px 0 34px !important;
    padding-top: 24px;
    border-top: 1px solid color-mix(in srgb, var(--text) 14%, transparent);
}

/* CTA: sombra coherente con el acento (color lo hereda de #resultado .btn-action) */
.hero-section.variant-v4 .btn-action {
    box-shadow: 0 16px 34px color-mix(in srgb, var(--accent) 38%, transparent);
}

/* Hover sutil del panel — solo en preview/público, no molesta al editar */
.preview-mode .hero-section.variant-v4 > div:has(> [data-field="title"]):hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-card, 0 44px 96px color-mix(in srgb, var(--text) 26%, transparent));
}

/* ---- Responsive: móvil real ---- */
@container (max-width: 768px) {
    .hero-section.variant-v4 {
        justify-content: center;
        padding-left: 6%;
        padding-right: 6%;
    }
    .hero-section.variant-v4::before {
        inset: 12px;
        border-radius: 18px;
    }
    .hero-section.variant-v4::after {
        left: 50%;
        transform: translateX(-50%);
        opacity: 0.4;
    }
    .hero-section.variant-v4 > div:has(> [data-field="title"]) {
        margin-left: 0;
        width: 100%;
        max-width: 100% !important;
        padding: 28px 22px;
        padding-left: 26px;
    }
    .hero-section.variant-v4 [data-field="subtitle"] {
        max-width: 100%;
    }
}

/* PODADO hero v4 mobile-view: el @container 768 de arriba ya reposiciona el panel en el editor
   (container = #resultado = 375px). El override por clase fijaba padding 26px 20px / 24px
   (inventado) ≠ publicado 28px 22px / 26px → causaba la divergencia. Ahora el editor usa el
   mismo @container que el publicado. */


/* ===== features · v4 (Glass flotante) — variant-expansion ===== */
/* ════════════════════════════════════════════════════════════
   FEATURES · VARIANTE v4 — "Glass flotante"
   CSS puro sobre el HTML por defecto (v1); el render NO cambia.
   Tarjetas glassmorphism con medallón de icono, filo de acento
   y elevación al hover. Solo var(--bg/--text/--accent)+color-mix
   → contraste seguro en claro y oscuro. Conserva toolbars/+ del
   editor (sin overflow:hidden, sin display:none).
   ════════════════════════════════════════════════════════════ */

/* Tarjeta cristal: translúcida, desenfocada, borde suave */
#bloque-features.variant-v4 .feature-card {
    background: color-mix(in srgb, var(--accent) 6%, color-mix(in srgb, var(--bg) 82%, transparent)) !important;
    border: 1px solid color-mix(in srgb, var(--text) 12%, transparent) !important;
    border-radius: var(--card-radius, 24px) !important;
    padding: 34px 30px !important;
    -webkit-backdrop-filter: blur(10px) saturate(1.25);
    backdrop-filter: blur(10px) saturate(1.25);
    /* La capa de caída lee --shadow-card (Estudio de Tema) con su valor actual de
       fallback, igual que --card-radius arriba; el filo interior se mantiene aparte.
       Mismo patrón que .cta-sticky-bar.variant-v2 / la tarjeta flotante del hero. */
    box-shadow:
        inset 0 1px 0 color-mix(in srgb, var(--bg) 55%, transparent),
        var(--shadow-card, 0 14px 34px color-mix(in srgb, var(--text) 11%, transparent)) !important;
    overflow: visible !important;
    transition:
        transform var(--transition, 0.4s cubic-bezier(0.16,1,0.3,1)),
        box-shadow var(--transition, 0.4s cubic-bezier(0.16,1,0.3,1)),
        border-color var(--transition, 0.4s cubic-bezier(0.16,1,0.3,1));
}

/* Filo superior de acento (firma de la variante) */
#bloque-features.variant-v4 .feature-card::before {
    content: "";
    position: absolute;
    top: 0;
    left: 26px;
    right: 26px;
    height: 2px;
    border-radius: 0 0 3px 3px;
    background: linear-gradient(90deg, transparent, var(--accent), transparent);
    opacity: 0.7;
    pointer-events: none;
    z-index: 1;
    transition: opacity 0.4s ease, left 0.4s ease, right 0.4s ease;
}

/* Medallón del icono / número (cuadrado redondeado, tinte de acento) */
#bloque-features.variant-v4 .feature-card .block-icon-svg {
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    flex: 0 0 auto;
    margin-bottom: 22px !important;
    border-radius: 18px;
    background: color-mix(in srgb, var(--accent) 14%, transparent) !important;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 32%, transparent);
    color: var(--accent) !important;
    font-size: 1.15rem !important;
    font-weight: 800 !important;
    line-height: 1;
    transition:
        transform var(--transition, 0.4s cubic-bezier(0.16,1,0.3,1)),
        background var(--transition, 0.4s cubic-bezier(0.16,1,0.3,1));
}

/* Texto dentro del cristal */
#bloque-features.variant-v4 .feature-card h3 {
    font-size: 1.25rem !important;
    letter-spacing: -0.01em;
}
#bloque-features.variant-v4 .feature-card p {
    opacity: 0.72 !important;
    line-height: 1.6;
}

/* Hover (solo en vista previa): elevación + halo de acento */
.preview-mode #bloque-features.variant-v4 .feature-card:hover {
    transform: translateY(-6px);
    border-color: color-mix(in srgb, var(--accent) 42%, transparent) !important;
    box-shadow:
        0 26px 55px color-mix(in srgb, var(--accent) 20%, transparent),
        0 12px 24px color-mix(in srgb, var(--text) 12%, transparent) !important;
}
.preview-mode #bloque-features.variant-v4 .feature-card:hover::before {
    opacity: 1;
    left: 18px;
    right: 18px;
}
.preview-mode #bloque-features.variant-v4 .feature-card:hover .block-icon-svg {
    transform: translateY(-2px) rotate(-5deg) scale(1.06);
    background: color-mix(in srgb, var(--accent) 20%, transparent) !important;
}

/* Responsive móvil (el centrado lo aporta el sistema global @container) */
@container (max-width: 768px) {
    #bloque-features.variant-v4 .feature-card {
        padding: 26px 22px !important;
    }
    #bloque-features.variant-v4 .feature-card .block-icon-svg {
        width: 52px;
        height: 52px;
        margin-bottom: 18px !important;
    }
    #bloque-features.variant-v4 .feature-card::before {
        left: 22px;
        right: 22px;
    }
}

/* Respeto a "reduce motion": sin desplazamientos en hover */
@media (prefers-reduced-motion: reduce) {
    .preview-mode #bloque-features.variant-v4 .feature-card:hover,
    .preview-mode #bloque-features.variant-v4 .feature-card:hover .block-icon-svg {
        transform: none !important;
    }
}


/* ===== pricing · v4 (Glass flotante) — variant-expansion ===== */
/* ═══════════════════════════════════════════════════════════════
   PRICING · VARIANTE v4 — "Glass flotante" (CSS puro)
   Reestiliza el HTML por defecto (v1) bajo #bloque-pricing.variant-v4.
   Tarjetas glass translúcidas (backdrop-blur) con borde en degradado,
   aura de acento detrás de la fila, nombre como kicker, precio en
   degradado y CTA separado por hairline. El énfasis de la tarjeta
   destacada es por glow/borde, NO por transform (lo gestiona el
   sistema stagger/.in-view, no debe pisarse). Sin tocar el render.
   Solo AÑADE reglas; respeta tokens (--bg/--text/--accent/--card-radius).
   ═══════════════════════════════════════════════════════════════ */

/* Aura de acento detrás de las tarjetas (no interactiva, bajo z-index) */
#bloque-pricing.variant-v4::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background:
        radial-gradient(58% 48% at 50% 16%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 72%),
        radial-gradient(46% 42% at 86% 88%, color-mix(in srgb, var(--accent) 10%, transparent), transparent 72%);
}

/* Título por encima de la aura */
#bloque-pricing.variant-v4 > :is(h1, h2) {
    position: relative;
    z-index: 1;
}

/* Tarjeta glass: relleno translúcido (padding-box) + borde en degradado
   (border-box) + desenfoque del fondo. !important para vencer el style inline. */
#bloque-pricing.variant-v4 .pricing-card {
    background:
        linear-gradient(160deg,
            color-mix(in srgb, var(--bg) 72%, transparent),
            color-mix(in srgb, var(--bg) 46%, transparent)) padding-box,
        linear-gradient(135deg,
            color-mix(in srgb, var(--accent) 45%, transparent),
            color-mix(in srgb, var(--text) 16%, transparent)) border-box !important;
    border: 1px solid transparent !important;
    border-radius: var(--card-radius, 28px) !important;
    padding: 46px 32px 40px !important;
    margin-top: 0 !important;
    /* Capa de caída = --shadow-card (Estudio de Tema) con su valor actual de fallback,
       igual que --card-radius arriba; el filo interior se mantiene aparte. */
    box-shadow:
        var(--shadow-card, 0 14px 40px color-mix(in srgb, var(--text) 12%, transparent)),
        inset 0 1px 1px color-mix(in srgb, var(--bg) 55%, transparent) !important;
    -webkit-backdrop-filter: blur(16px) saturate(150%);
    backdrop-filter: blur(16px) saturate(150%);
}

/* Tarjeta destacada: borde de acento pleno, leve tinte y glow mayor.
   Hook = scale(1.05) inline del plan featured (rama por defecto/v4). */
#bloque-pricing.variant-v4 .pricing-card[style*="scale(1.05)"] {
    background:
        linear-gradient(160deg,
            color-mix(in srgb, var(--accent) 16%, color-mix(in srgb, var(--bg) 70%, transparent)),
            color-mix(in srgb, var(--bg) 44%, transparent)) padding-box,
        linear-gradient(135deg,
            var(--accent),
            color-mix(in srgb, var(--accent) 40%, transparent)) border-box !important;
    border: 1.5px solid transparent !important;
    padding-top: 60px !important;
    box-shadow:
        0 22px 55px color-mix(in srgb, var(--accent) 26%, transparent),
        0 4px 16px color-mix(in srgb, var(--text) 10%, transparent),
        inset 0 1px 1px color-mix(in srgb, var(--bg) 55%, transparent) !important;
}

/* Nombre del plan como kicker en mayúsculas */
#bloque-pricing.variant-v4 .pricing-card h3[data-field="name"] {
    font-size: 0.82rem !important;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    opacity: 0.7 !important;
    margin-bottom: 6px;
}

/* Hairline divisor entre la descripción y el CTA */
#bloque-pricing.variant-v4 .pricing-card .desc-scroll {
    border-bottom: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
    padding-bottom: 24px;
}

/* Precio en degradado (solo en preview: en el editor queda sólido y editable) */
.preview-mode #bloque-pricing.variant-v4 .pricing-card [data-field="price"] {
    background: linear-gradient(135deg, var(--text), color-mix(in srgb, var(--accent) 70%, var(--text)));
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}

/* Hover: intensifica el glow de acento SIN transform (compatible con stagger) */
.preview-mode #bloque-pricing.variant-v4 .pricing-card:hover {
    box-shadow:
        0 26px 64px color-mix(in srgb, var(--accent) 24%, transparent),
        0 6px 18px color-mix(in srgb, var(--text) 10%, transparent),
        inset 0 1px 1px color-mix(in srgb, var(--bg) 55%, transparent) !important;
}

/* Responsive móvil real */
@container (max-width: 768px) {
    #bloque-pricing.variant-v4 .pricing-card {
        padding: 36px 24px 32px !important;
        -webkit-backdrop-filter: blur(12px) saturate(140%);
        backdrop-filter: blur(12px) saturate(140%);
    }
    #bloque-pricing.variant-v4 .pricing-card[style*="scale(1.05)"] {
        padding-top: 52px !important;
    }
    #bloque-pricing.variant-v4::before {
        background:
            radial-gradient(72% 36% at 50% 8%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 72%);
    }
}

/* PODADO pricing v4 mobile-view: el @container 768 de arriba ya ajusta el padding de la card
   en el editor (container = #resultado). El override por clase ponía 38px/34px/54px (inventado)
   ≠ publicado 36px/32px/52px → divergía. Ahora editor == publicado vía el mismo @container. */


/* ===== testimonials · v4 (Glass flotante) — variant-expansion ===== */

/* ================================================
   TESTIMONIALS — VARIANTE v4 "Glass flotante" (CSS PURO)
   Reestiliza el HTML por defecto (v1). Concepto nuevo:
   tarjetas glassmorphism flotantes con borde gradiente de
   acento, glow al hover y comilla gigante de marca de agua.
   Distinto de v1 (plano translúcido), v2 (editorial centrado)
   y v3 (burbuja de chat). Theme-safe (claro/oscuro) via tokens.
   Solo AÑADE reglas scopeadas a .variant-v4.
   ================================================ */

/* Glow ambiental sutil para dar profundidad al cristal */
#bloque-testimonials.variant-v4 {
    background:
        radial-gradient(120% 80% at 50% -10%, color-mix(in srgb, var(--accent) 9%, transparent), transparent 60%),
        var(--bg) !important;
}

/* Acento centrado bajo el título */
#bloque-testimonials.variant-v4 :is(h1, h2)[data-field="title"] {
    position: relative;
}
#bloque-testimonials.variant-v4 :is(h1, h2)[data-field="title"]::after {
    content: "";
    position: absolute;
    left: 50%;
    bottom: -22px;
    transform: translateX(-50%);
    width: 64px;
    height: 4px;
    border-radius: 4px;
    background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 20%, transparent));
    pointer-events: none;
}

/* Tarjeta cristal flotante (override de estilos inline → !important) */
#bloque-testimonials.variant-v4 .testimonial-card {
    background: color-mix(in srgb, var(--bg) 68%, transparent) !important;
    -webkit-backdrop-filter: blur(16px) saturate(1.3);
    backdrop-filter: blur(16px) saturate(1.3);
    border: 1px solid color-mix(in srgb, var(--text) 10%, transparent) !important;
    border-radius: var(--card-radius, 22px) !important;
    padding: 40px 34px 34px !important;
    box-shadow: var(--shadow-card, 0 24px 50px -30px color-mix(in srgb, var(--text) 55%, transparent));
    transition:
        transform var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1)),
        box-shadow var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1));
}

/* Borde gradiente de acento (técnica mask: SIN overflow:hidden,
   para no recortar +/tools del editor que viven fuera del borde).
   DUOTONO: el FILO de las variantes premium arranca en el secundario y muere en el acento →
   el borde deja de ser un degradado de un solo color. Sin secundario, ambos extremos son el
   acento y el resultado es el de siempre. */
#bloque-testimonials.variant-v4 .testimonial-card::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1px;
    background: linear-gradient(140deg,
        color-mix(in srgb, var(--secondary, var(--accent)) 60%, transparent),
        color-mix(in srgb, var(--accent) 6%, transparent) 55%,
        transparent);
    -webkit-mask:
        linear-gradient(#fff 0 0) content-box,
        linear-gradient(#fff 0 0);
    -webkit-mask-composite: xor;
            mask-composite: exclude;
    pointer-events: none;
    z-index: 2;
    transition: background var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1));
}

/* Elevación + glow de acento al hover */
#bloque-testimonials.variant-v4 .testimonial-card:hover {
    transform: translateY(-6px);
    box-shadow:
        0 30px 60px -28px color-mix(in srgb, var(--accent) 50%, transparent),
        0 8px 22px -18px color-mix(in srgb, var(--text) 45%, transparent);
}
#bloque-testimonials.variant-v4 .testimonial-card:hover::before {
    background: linear-gradient(140deg,
        color-mix(in srgb, var(--accent) 90%, transparent),
        color-mix(in srgb, var(--accent) 18%, transparent) 60%,
        transparent);
}

/* Comilla gigante como marca de agua (reusa el div serif del HTML v1) */
#bloque-testimonials.variant-v4 .testimonial-card > div[style*="serif"] {
    position: absolute !important;
    top: 6px;
    right: 24px;
    margin: 0 !important;
    font-size: 7rem !important;
    line-height: 1 !important;
    opacity: 0.12 !important;
    color: var(--accent) !important;
    pointer-events: none;
    z-index: 0;
}

/* Texto: vertical (sin cursiva), mayor cuerpo, por encima del watermark */
#bloque-testimonials.variant-v4 .testimonial-card p[data-field="text"] {
    position: relative;
    z-index: 1;
    font-style: normal !important;
    font-size: 1.16rem !important;
    line-height: 1.72 !important;
    font-weight: 400 !important;
    opacity: 0.92 !important;
}

/* Fila de autor con separador superior */
#bloque-testimonials.variant-v4 .testimonial-card > div:has(> [data-field="author"]) {
    position: relative;
    z-index: 1;
    margin-top: 28px !important;
    padding-top: 22px;
    border-top: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
}

/* Avatar: cuadrado redondeado con anillo de acento (era círculo en v1) */
#bloque-testimonials.variant-v4 .testimonial-card > div:has(> [data-field="author"]) > div:first-child {
    width: 50px !important;
    height: 50px !important;
    border-radius: 15px !important;
    opacity: 1 !important;
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent),
        0 10px 20px -10px color-mix(in srgb, var(--accent) 55%, transparent);
}

/* Autor con matiz de acento (mayoría --text → contraste seguro en ambos temas) */
#bloque-testimonials.variant-v4 .testimonial-card [data-field="author"] {
    color: color-mix(in srgb, var(--text) 78%, var(--accent));
    letter-spacing: 0.2px;
}

/* ── Responsive móvil ── */
@container (max-width: 768px) {
    #bloque-testimonials.variant-v4 .testimonial-card {
        padding: 32px 24px 28px !important;
        -webkit-backdrop-filter: blur(10px) saturate(1.2);
        backdrop-filter: blur(10px) saturate(1.2);
    }
    #bloque-testimonials.variant-v4 .testimonial-card > div[style*="serif"] {
        font-size: 5.5rem !important;
        right: 18px;
    }
    #bloque-testimonials.variant-v4 .testimonial-card p[data-field="text"] {
        font-size: 1.06rem !important;
    }
    #bloque-testimonials.variant-v4 :is(h1, h2)[data-field="title"]::after {
        bottom: -18px;
        width: 52px;
    }
}



/* ===== content · v4 (Editorial flotante) — variant-expansion ===== */

/* ============================================================
   Bloque CONTENT · variante v4 — "Editorial flotante"
   Concepto NUEVO (no es v1/v2/v3): imagen protagonista con marco
   de acento desplazado + tarjeta de texto OPACA que flota y solapa
   la imagen sobre el eje Z. CSS puro sobre el HTML por defecto (v1).
   Scope: #bloque-content.variant-v4
   ============================================================ */

#bloque-content.variant-v4 > div {
    position: relative;
    gap: 0 !important;
    align-items: center !important;
}

/* Imagen protagonista, desplazada para que la tarjeta la solape */
#bloque-content.variant-v4 .content-image-side {
    flex: 1.3 1 56% !important;
    margin-left: clamp(-110px, -7cqw, -56px);
    z-index: 1;
}
#bloque-content.variant-v4 .content-image-side img {
    border-radius: var(--card-radius, 24px) !important;
    aspect-ratio: 4 / 3 !important;
    box-shadow: var(--shadow-card, 0 32px 64px -24px color-mix(in srgb, var(--text) 38%, transparent)) !important;
}
/* Marco de acento desplazado tras la esquina superior de la imagen */
#bloque-content.variant-v4 .content-image-side::after {
    content: "";
    position: absolute;
    top: -16px;
    right: -16px;
    width: 46%;
    height: 56%;
    border: 2px solid color-mix(in srgb, var(--accent) 55%, transparent);
    border-radius: var(--card-radius, 24px);
    z-index: -1;
    pointer-events: none;
}

/* Tarjeta de texto flotante (fondo opaco para legibilidad sobre la foto) */
#bloque-content.variant-v4 .content-text-side {
    flex: 0 1 46% !important;
    position: relative;
    z-index: 2;
    padding: clamp(28px, 3.4cqw, 52px);
    background: color-mix(in srgb, var(--bg) 92%, var(--text) 8%);
    border: var(--card-border, 1px solid color-mix(in srgb, var(--text) 12%, transparent));
    border-radius: var(--card-radius, 22px);
    box-shadow: var(--shadow-card, 0 26px 56px -22px color-mix(in srgb, var(--text) 30%, transparent));
    text-align: left !important;
    overflow: visible;
}
/* Barra de acento superior de la tarjeta */
#bloque-content.variant-v4 .content-text-side::before {
    content: "";
    position: absolute;
    top: 0;
    left: clamp(28px, 3.4cqw, 52px);
    width: clamp(40px, 22%, 84px);
    height: 4px;
    border-radius: 0 0 4px 4px;
    background: var(--accent);
}
#bloque-content.variant-v4 .content-text-side :is(h1, h2) {
    font-size: clamp(1.7rem, 2.7cqw, 2.6rem) !important;
    margin-bottom: 16px !important;
}
#bloque-content.variant-v4 .content-text-side p {
    font-size: 1.05rem !important;
    line-height: 1.7 !important;
    opacity: 0.82 !important;
}
#bloque-content.variant-v4 .content-text-side .btn-action {
    margin-top: 24px !important;
    align-self: flex-start !important;
}

/* Responsive: se apila, sin solape ni márgenes negativos */
@container (max-width: 992px) {
    #bloque-content.variant-v4 > div {
        flex-direction: column !important;
        gap: 32px !important;
    }
    #bloque-content.variant-v4 .content-image-side {
        margin-left: 0 !important;
        flex: 1 1 100% !important;
        order: -1;
    }
    #bloque-content.variant-v4 .content-text-side {
        flex: 1 1 100% !important;
        width: 100%;
    }
    #bloque-content.variant-v4 .content-text-side::before {
        left: 50%;
        transform: translateX(-50%);
    }
    #bloque-content.variant-v4 .content-image-side::after {
        top: -10px;
        right: -10px;
    }
}



/* ===== gallery · v4 (Banner editorial) — variant-expansion ===== */
/* ================================================
   GALLERY BLOCK — v4: Editorial cinematografico (CSS puro)
   Reestiliza el HTML "featured" (fallback de variantes
   desconocidas) como banner full-width + tira de miniaturas,
   con marcos y subtitulo de acento siempre visible.
   No toca el render. Scope: #bloque-gallery.variant-v4
   ================================================ */

/* Encabezado: guino de acento bajo el titulo */
#bloque-gallery.variant-v4 :is(h1, h2)[data-field="title"] {
    position: relative;
    display: inline-block;
}
#bloque-gallery.variant-v4 :is(h1, h2)[data-field="title"]::after {
    content: "";
    position: absolute;
    left: 50%;
    bottom: -12px;
    transform: translateX(-50%);
    width: 54px;
    height: 3px;
    border-radius: 3px;
    background: var(--accent, #6366f1);
}

/* Layout vertical: banner arriba, tira de miniaturas debajo */
#bloque-gallery.variant-v4 .gallery-featured {
    flex-direction: column;
    align-items: stretch;
    gap: 18px;
}
#bloque-gallery.variant-v4 .gallery-featured > div {
    flex: 0 0 auto !important;
    width: 100% !important;
    min-width: 0 !important;
}
#bloque-gallery.variant-v4 .gallery-featured > div:first-child .gallery-item {
    min-height: 440px !important;
}
#bloque-gallery.variant-v4 .gallery-featured > div:last-child {
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) !important;
    gap: 18px !important;
}

/* Marco comun de cada pieza (banner, tira y grid extra) */
#bloque-gallery.variant-v4 .gallery-item {
    border: var(--card-border, 1px solid color-mix(in srgb, var(--text) 12%, transparent));
    box-shadow: var(--shadow-card, 0 20px 45px -28px color-mix(in srgb, var(--text) 55%, transparent));
    transition: transform .45s cubic-bezier(.16, 1, .3, 1), box-shadow .45s ease;
}
#bloque-gallery.variant-v4 .gallery-img {
    transition: transform .7s cubic-bezier(.16, 1, .3, 1);
}
#bloque-gallery.variant-v4 .gallery-item:hover {
    transform: translateY(-5px);
    box-shadow: 0 26px 55px -26px color-mix(in srgb, var(--text) 60%, transparent);
}
#bloque-gallery.variant-v4 .gallery-item:hover .gallery-img {
    transform: scale(1.06);
}

/* Subtitulo siempre visible con barra de acento.
   pointer-events:none evita que el overlay tape toolbars/botones +;
   el span editable los re-activa para seguir editando en el editor. */
#bloque-gallery.variant-v4 .caption-overlay {
    opacity: 1;
    pointer-events: none;
    align-items: flex-end;
    padding: 22px 18px 16px 20px;
    background: linear-gradient(to top,
        color-mix(in srgb, #000 80%, transparent) 0%,
        color-mix(in srgb, #000 34%, transparent) 46%,
        transparent 82%);
}
#bloque-gallery.variant-v4 .caption-overlay span[data-field="caption"] {
    pointer-events: auto;
    display: inline-block;
    border-left: 3px solid var(--accent, #6366f1);
    padding-left: 12px;
    letter-spacing: 0.2px;
}

/* Etiqueta tipo glass acorde al concepto editorial */
#bloque-gallery.variant-v4 .gallery-tag {
    background: color-mix(in srgb, var(--accent, #6366f1) 86%, transparent);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    box-shadow: 0 6px 18px -8px color-mix(in srgb, var(--accent, #6366f1) 70%, transparent);
}

/* Responsive */
@container (max-width: 768px) {
    #bloque-gallery.variant-v4 .gallery-featured > div:first-child .gallery-item {
        min-height: 300px !important;
    }
}
@container (max-width: 560px) {
    #bloque-gallery.variant-v4 .gallery-featured > div:last-child {
        grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
        gap: 12px !important;
    }
    #bloque-gallery.variant-v4 .gallery-featured > div:last-child .gallery-item {
        height: 150px !important;
    }
    #bloque-gallery.variant-v4 .gallery-featured > div:first-child .gallery-item {
        min-height: 240px !important;
    }
}


/* ===== faq · v4 (Acordeón glass) — variant-expansion ===== */
/* ════════════════════════════════════════════════════════════
   FAQ · VARIANTE v4 — "Acordeón glass / conversación"
   CSS PURO sobre el markup por defecto (fallback = v3: grid de
   .card.faq-card con caret › + nombre en h3, y desc en p).
   Reestiliza a columna única de tarjetas glass, con el caret
   convertido en chip circular de acento (afford. acordeón) y la
   respuesta indentada como hilo de conversación.
   Solo AÑADE reglas scopeadas a #bloque-faq.variant-v4.
   Seguro en claro/oscuro: --bg/--text/--accent + color-mix().
   ════════════════════════════════════════════════════════════ */

/* Contenedor: de grid 2-col (v3) a columna única centrada y estrecha */
#bloque-faq.variant-v4 > div {
    display: flex !important;
    flex-direction: column;
    gap: 18px !important;
    max-width: 780px !important;
}

/* Tarjeta glass */
#bloque-faq.variant-v4 .faq-card {
    padding: 28px 32px !important;
    background: color-mix(in srgb, var(--text) 4%, var(--bg));
    border: 1px solid color-mix(in srgb, var(--text) 10%, transparent);
    border-radius: var(--card-radius, 20px);
    box-shadow: var(--shadow-card, 0 6px 22px color-mix(in srgb, var(--text) 6%, transparent));
    backdrop-filter: blur(5px);
    -webkit-backdrop-filter: blur(5px);
    overflow: visible;
    transition: transform var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1)),
                box-shadow var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1)),
                border-color var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1));
}

#bloque-faq.variant-v4 .faq-card:hover {
    transform: translateY(-3px);
    border-color: color-mix(in srgb, var(--accent) 45%, transparent);
    box-shadow: 0 16px 38px color-mix(in srgb, var(--accent) 16%, transparent);
}

/* Pregunta (el h3 ya es flex: chip + nombre) */
#bloque-faq.variant-v4 .faq-card h3 {
    align-items: center !important;
    gap: 16px !important;
    font-size: 1.18rem !important;
    line-height: 1.35;
    margin-bottom: 14px !important;
}

/* Caret › -> chip circular de acento, rotado a caret descendente (acordeón) */
#bloque-faq.variant-v4 .faq-card h3 > span:first-child {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.2rem !important;
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    color: var(--accent) !important;
    transform: rotate(90deg);
    transition: transform var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1)),
                background var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1));
}

#bloque-faq.variant-v4 .faq-card:hover h3 > span:first-child {
    background: color-mix(in srgb, var(--accent) 24%, transparent);
    transform: rotate(90deg) scale(1.08);
}

/* Respuesta: indentada bajo la pregunta, como hilo de conversación */
#bloque-faq.variant-v4 .faq-card p {
    margin-left: 50px;
    padding-left: 16px;
    border-left: 2px solid color-mix(in srgb, var(--accent) 28%, transparent);
    opacity: 0.72 !important;
    font-size: 1.02rem !important;
    line-height: 1.75 !important;
}

/* ─── Responsive móvil ─── */
@container (max-width: 768px) {
    #bloque-faq.variant-v4 > div {
        max-width: 100% !important;
        gap: 14px !important;
    }
    #bloque-faq.variant-v4 .faq-card {
        padding: 22px 20px !important;
    }
    #bloque-faq.variant-v4 .faq-card h3 {
        gap: 12px !important;
        font-size: 1.08rem !important;
    }
    #bloque-faq.variant-v4 .faq-card h3 > span:first-child {
        width: 30px;
        height: 30px;
        font-size: 1.1rem !important;
    }
    #bloque-faq.variant-v4 .faq-card p {
        margin-left: 0;
        padding-left: 14px;
    }
}


/* ===== stats · v4 (Tablero KPI) — variant-expansion ===== */

/* ═══════════════════════════════════════════════════════════════
   STATS · VARIANTE v4 "Tablero KPI"  (CSS puro · re-estiliza el HTML
   por defecto del render —contenedor .stats-divider— sin tocar JS).
   Tiles en rejilla con pestaña de acento superior, índice editorial
   y badge de icono. Solo tokens (--bg/--text/--accent) + color-mix();
   sin colores que rompan contraste; conserva data-field /
   contenteditable / toolbars (sin overflow:hidden ni display:none
   sobre ellos). Scope por id para no afectar el variant-v4 de otros
   bloques.
   ═══════════════════════════════════════════════════════════════ */

/* Layout: rejilla de tiles. Cubre el contenedor por defecto
   (.stats-divider) y, por robustez, .stats-flex por si el fallback
   del render cambiara a v1. */
#bloque-stats.variant-v4 .stats-divider,
#bloque-stats.variant-v4 .stats-flex {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 22px;
    align-items: stretch;
    counter-reset: statv4;
}

/* Tile KPI: superficie de tarjeta, sin recortar toolbars */
#bloque-stats.variant-v4 .stat-item {
    counter-increment: statv4;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 44px 28px 38px !important;
    border: var(--card-border, 1px solid color-mix(in srgb, var(--text) 12%, transparent));
    border-radius: var(--card-radius, 20px);
    background:
        radial-gradient(130% 90% at 50% 0%, color-mix(in srgb, var(--accent) 7%, transparent), transparent 62%),
        var(--card-bg, color-mix(in srgb, var(--text) 4%, transparent));
    box-shadow: var(--shadow-card, 0 18px 44px -26px color-mix(in srgb, var(--text) 45%, transparent));
    isolation: isolate;
    transition:
        transform 0.45s cubic-bezier(0.16, 1, 0.3, 1),
        box-shadow 0.45s cubic-bezier(0.16, 1, 0.3, 1),
        border-color 0.4s ease,
        background 0.4s ease;
}

/* Pestaña de acento superior (centrada).
   DUOTONO: la barra de la tarjeta de datos va del acento al secundario → dos colores de marca
   en la pieza más "de dato" del sitio. Sin secundario, el degradado queda como estaba. */
#bloque-stats.variant-v4 .stat-item::before {
    content: "";
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 46px;
    height: 4px;
    border-radius: 0 0 6px 6px;
    background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--secondary, var(--accent)) 35%, transparent));
    z-index: 2;
    pointer-events: none;
    transition: width 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}

/* Índice editorial 01 / 02 / 03 (decorativo, esquina superior izq.) */
#bloque-stats.variant-v4 .stat-item::after {
    content: counter(statv4, decimal-leading-zero);
    position: absolute;
    top: 16px;
    left: 18px;
    font-size: 0.8rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    font-variant-numeric: tabular-nums;
    color: color-mix(in srgb, var(--accent) 65%, var(--text));
    opacity: 0.4;
    z-index: 2;
    pointer-events: none;
}

/* Badge del icono en acento (centrado, no se estira) */
#bloque-stats.variant-v4 .block-icon-svg {
    align-self: center;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: 16px;
    background: color-mix(in srgb, var(--accent) 13%, transparent);
    color: var(--accent);
    opacity: 1 !important;
    margin: 0 0 18px !important;
    transition: background 0.4s ease, transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

/* Número y etiqueta: a salvo de desbordes; texto sobre --text (contraste seguro) */
#bloque-stats.variant-v4 [data-field="name"] {
    max-width: 100%;
    overflow-wrap: break-word;
}
#bloque-stats.variant-v4 [data-field="desc"] {
    max-width: 100%;
    overflow-wrap: break-word;
    opacity: 0.62 !important;
    margin-top: 4px;
}

/* La decoración fantasma no aplica en este layout de tiles */
#bloque-stats.variant-v4 .ghost-number {
    display: none;
}

/* Hover: elevación + glow de acento + pestaña que crece + pop del badge */
#bloque-stats.variant-v4 .stat-item:hover {
    transform: translateY(-7px);
    border-color: color-mix(in srgb, var(--accent) 50%, transparent);
    background:
        radial-gradient(130% 95% at 50% 0%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 62%),
        var(--card-bg, color-mix(in srgb, var(--text) 5%, transparent));
    box-shadow:
        0 26px 50px -24px color-mix(in srgb, var(--accent) 40%, transparent),
        0 10px 24px -18px color-mix(in srgb, var(--text) 40%, transparent);
}
#bloque-stats.variant-v4 .stat-item:hover::before {
    width: 74px;
}
#bloque-stats.variant-v4 .stat-item:hover .block-icon-svg {
    background: color-mix(in srgb, var(--accent) 20%, transparent);
    transform: translateY(-2px);
}

/* Respeto a usuarios con movimiento reducido */
@media (prefers-reduced-motion: reduce) {
    #bloque-stats.variant-v4 .stat-item,
    #bloque-stats.variant-v4 .stat-item::before,
    #bloque-stats.variant-v4 .block-icon-svg {
        transition: none;
    }
    #bloque-stats.variant-v4 .stat-item:hover {
        transform: none;
    }
}

/* Responsive móvil: una columna y borde/padding propios del tile
   (anula el padding:20px 0 y border-left:none del layout divider) */
@container (max-width: 768px) {
    #bloque-stats.variant-v4 .stats-divider,
    #bloque-stats.variant-v4 .stats-flex {
        grid-template-columns: 1fr;
        gap: 16px;
    }
    #bloque-stats.variant-v4 .stat-item {
        padding: 34px 24px 30px !important;
        border: var(--card-border, 1px solid color-mix(in srgb, var(--text) 12%, transparent)) !important;
        min-width: 0 !important;
    }
}



/* ===== cta-bar · v2 (Glass flotante) — variant-expansion ===== */
/* ═══ CTA Bar · v2 «Glass flotante» ═══════════════════════════════════
   Concepto fresco: la barra se desprende del borde inferior y flota como
   una pildora de cristal centrada, con borde gradiente de acento, glow
   suave y un punto de estado pulsante. Distinta del v1 (banner a sangre
   completa, plano, pegado al fondo).
   Solo var(--bg)/--text/--accent + color-mix; reusa --shadow-card,
   --transition y --accent-contrast (definidos por el bloque). Los
   !important solo neutralizan el background/border-top inline del render. */
.cta-sticky-bar.variant-v2 {
    left: 50%;
    transform: translateX(-50%);
    width: auto;
    max-width: min(760px, calc(100% - 32px));
    bottom: clamp(16px, 3vw, 28px);
    gap: 16px;
    padding: 12px 14px 12px 26px;
    border: 1.5px solid transparent !important;
    border-radius: 999px;
    background:
        linear-gradient(color-mix(in srgb, var(--bg) 90%, transparent), color-mix(in srgb, var(--bg) 93%, transparent)) padding-box,
        linear-gradient(125deg, color-mix(in srgb, var(--accent) 72%, transparent), color-mix(in srgb, var(--accent) 14%, transparent) 55%, color-mix(in srgb, var(--text) 16%, transparent)) border-box !important;
    -webkit-backdrop-filter: blur(16px) saturate(135%);
    backdrop-filter: blur(16px) saturate(135%);
    box-shadow:
        var(--shadow-card, 0 10px 30px -8px color-mix(in srgb, var(--text) 20%, transparent)),
        0 20px 50px -18px color-mix(in srgb, var(--accent) 48%, transparent);
    transition: box-shadow var(--transition, 0.3s cubic-bezier(0.16, 1, 0.3, 1));
}

/* Texto con punto de estado vivo */
.cta-sticky-bar.variant-v2 .cta-bar-text {
    display: inline-flex;
    align-items: center;
    gap: 11px;
}
.cta-sticky-bar.variant-v2 .cta-bar-text::before {
    content: "";
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex-shrink: 0;
    background: var(--accent);
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent);
    animation: ctaV2Pulse 2.2s ease-out infinite;
}
@keyframes ctaV2Pulse {
    0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
    70%  { box-shadow: 0 0 0 11px color-mix(in srgb, var(--accent) 0%, transparent); }
    100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent); }
}

/* Boton: pildora de acento solido con glow propio */
.cta-sticky-bar.variant-v2 .cta-bar-btn {
    background: var(--accent);
    color: var(--accent-contrast, var(--bg));
    border-radius: 999px;
    padding: 11px 26px;
    box-shadow: 0 8px 20px -6px color-mix(in srgb, var(--accent) 60%, transparent);
}
.cta-sticky-bar.variant-v2 .cta-bar-btn:hover {
    transform: translateY(-2px) scale(1.03);
    box-shadow: 0 13px 28px -6px color-mix(in srgb, var(--accent) 72%, transparent);
}

/* Cerrar: chip circular discreto dentro de la pildora */
.cta-sticky-bar.variant-v2 .cta-bar-close {
    margin-left: -4px;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.cta-sticky-bar.variant-v2 .cta-bar-close:hover {
    background: color-mix(in srgb, var(--text) 10%, transparent);
}

/* Movil real: tarjeta flotante casi a lo ancho, sin pildora extrema */
@container (max-width: 768px) {
    .cta-sticky-bar.variant-v2 {
        max-width: calc(100% - 24px) !important;
        bottom: 12px !important;
        border-radius: 22px !important;
        padding: 16px 18px !important;
    }
    .cta-sticky-bar.variant-v2 .cta-bar-text {
        justify-content: center;
    }
    .cta-sticky-bar.variant-v2 .cta-bar-btn {
        width: 100% !important;
    }
}

/* Respeta movimiento reducido */
@media (prefers-reduced-motion: reduce) {
    .cta-sticky-bar.variant-v2 .cta-bar-text::before { animation: none; }
}


/* ===== countdown · v2 (Panel flip-clock) — variant-expansion ===== */

/* ===================================================
   COUNTDOWN — Variante v2 «Panel flip-clock»
   Cada unidad pasa de dígito suelto (v1) a tarjeta con
   barra de acento superior y etiqueta a pie con divisor.
   Solo AÑADE reglas nuevas scopeadas a .variant-v2.
   =================================================== */
.countdown-section.variant-v2 .countdown-display {
    gap: 16px;
}

/* Los «:» decorativos sobran cuando hay tarjetas (no son campos editables) */
.countdown-section.variant-v2 .cd-sep {
    display: none;
}

/* Unidad → tarjeta tipo flip-clock (reusa tokens de tarjeta) */
.countdown-section.variant-v2 .cd-unit {
    position: relative;
    gap: 0;
    min-width: 118px;
    padding: 26px 16px 0;
    overflow: hidden;
    border-radius: var(--card-radius, 20px);
    background: var(--card-bg, color-mix(in srgb, var(--text) 5%, transparent));
    border: var(--card-border, 1px solid color-mix(in srgb, var(--text) 12%, transparent));
    box-shadow: var(--shadow-card, 0 18px 44px -22px color-mix(in srgb, var(--text) 45%, transparent));
    transition: transform var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1)),
                box-shadow var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1)),
                border-color var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1));
}

/* Barra de acento superior, clipada por el radio de la tarjeta */
.countdown-section.variant-v2 .cd-unit::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 4px;
    background: var(--accent);
}

.countdown-section.variant-v2 .cd-unit:hover {
    transform: translateY(-6px);
    border-color: color-mix(in srgb, var(--accent) 45%, transparent);
    box-shadow: var(--shadow-card, 0 26px 60px -24px color-mix(in srgb, var(--accent) 38%, transparent));
}

/* Número dentro del panel (más contenido que el dígito gigante del v1) */
.countdown-section.variant-v2 .cd-num {
    font-size: clamp(2.4rem, 6cqi, 4.2rem);
    letter-spacing: -0.04em;
    padding-bottom: 18px;
}

/* Etiqueta como pie a sangre, con divisor y tinte de acento */
.countdown-section.variant-v2 .cd-label {
    align-self: stretch;
    margin: 0 -16px;
    padding: 12px 8px 14px;
    text-align: center;
    opacity: 1;
    color: var(--accent);
    border-top: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
    background: color-mix(in srgb, var(--accent) 7%, transparent);
}

@container (max-width: 768px) {
    .countdown-section.variant-v2 .countdown-display { gap: 12px; }
    .countdown-section.variant-v2 .cd-unit { min-width: 96px; padding: 20px 12px 0; }
    .countdown-section.variant-v2 .cd-num { padding-bottom: 14px; }
    .countdown-section.variant-v2 .cd-label {
        margin: 0 -12px;
        padding: 10px 6px 12px;
        font-size: 0.7rem;
        letter-spacing: 1.5px;
    }
}

@container (max-width: 420px) {
    .countdown-section.variant-v2 .countdown-display { gap: 10px; }
    .countdown-section.variant-v2 .cd-unit { min-width: calc(50% - 5px); }
}

/* PODADO countdown v2 mobile-view (rejilla 2x2): los @container 768 + 420 de arriba ya la
   producen en el editor (container = #resultado = 375 < 420) con calc(50% - 5px). El override
   por clase repetía lo mismo con calc(50% - 6px) (1px inventado) → sobraba. */



/* ===== video · v2 (Cine glass) — variant-expansion ===== */

/* ── VIDEO HERO · v2 "Cine glass" ──────────────────────────────────
   Concepto fresco vs v1 (texto flotante a sangre completa): pantalla de
   cine ENMARCADA + caption agrupado en una TARJETA DE VIDRIO. Solo añade
   reglas nuevas scopeadas a .variant-v2; no toca reglas existentes.
   El bloque siempre pinta texto blanco sobre overlay oscuro, así que la
   tarjeta se oscurece con backdrop brightness y se tiñe con var(--accent)
   (no depende de --bg/--text): contraste seguro en claro Y oscuro.
   Pseudo-elementos con pointer-events:none y sin overflow:hidden en la
   tarjeta → no rompe data-field/contenteditable/toolbars del editor. */

/* Marco fino tipo "pantalla de cine" — hairline de acento, decorativo */
.video-hero-section.variant-v2::before {
    content: "";
    position: absolute;
    inset: 18px;
    z-index: 3;
    pointer-events: none;
    border: 1px solid color-mix(in srgb, var(--accent) 38%, transparent);
    border-radius: calc(var(--card-radius, 24px) + 8px);
}

/* Wash de acento (luz) mezclado sobre el video oscuro: marca + profundidad */
.video-hero-section.variant-v2::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    mix-blend-mode: screen;
    background:
        radial-gradient(120% 78% at 50% 0%, color-mix(in srgb, var(--accent) 28%, transparent), transparent 58%),
        radial-gradient(110% 70% at 50% 100%, color-mix(in srgb, var(--accent) 18%, transparent), transparent 55%);
}

/* Caption: tarjeta de vidrio que reagrupa título / subtítulo / CTA */
.video-hero-section.variant-v2 > div:has(> [data-field="title"]) {
    padding: clamp(36px, 5cqw, 64px) clamp(28px, 5cqw, 72px);
    border-radius: var(--card-radius, 24px);
    border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
    background: linear-gradient(155deg,
        color-mix(in srgb, var(--accent) 20%, transparent),
        color-mix(in srgb, var(--accent) 6%, transparent));
    -webkit-backdrop-filter: blur(16px) saturate(140%) brightness(0.7);
    backdrop-filter: blur(16px) saturate(140%) brightness(0.7);
    box-shadow:
        var(--shadow-card, 0 30px 80px rgba(0, 0, 0, 0.45)),
        inset 0 1px 0 color-mix(in srgb, var(--accent-contrast, #fff) 14%, transparent);
    transition: transform var(--transition, 0.6s cubic-bezier(0.16, 1, 0.3, 1)),
                box-shadow var(--transition, 0.6s cubic-bezier(0.16, 1, 0.3, 1));
}

/* Fallback sin backdrop-filter: scrim oscuro garantizado bajo el texto blanco */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .video-hero-section.variant-v2 > div:has(> [data-field="title"]) {
        background: linear-gradient(155deg,
            color-mix(in srgb, var(--accent) 30%, rgba(6, 8, 16, 0.66)),
            color-mix(in srgb, var(--accent) 12%, rgba(6, 8, 16, 0.6)));
    }
}

/* Kicker de acento sobre el título (no necesita overflow) */
.video-hero-section.variant-v2 > div:has(> [data-field="title"])::before {
    content: "";
    display: block;
    width: 56px;
    height: 4px;
    margin: 0 auto clamp(20px, 2.5cqw, 30px);
    border-radius: 999px;
    background: var(--accent);
    box-shadow: 0 0 22px color-mix(in srgb, var(--accent) 70%, transparent);
}

/* Botón play (solo MP4 nativo): de glass blanco a pastilla de acento */
.video-hero-section.variant-v2 .video-play-btn {
    width: 64px;
    height: 64px;
    background: color-mix(in srgb, var(--accent) 88%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent-contrast, #fff) 30%, transparent);
    color: var(--accent-contrast, #fff);
    box-shadow: 0 12px 32px color-mix(in srgb, var(--accent) 45%, transparent);
}
.video-hero-section.variant-v2 .video-play-btn:hover {
    background: var(--accent);
    transform: scale(1.08);
}

/* ── Responsive: móvil real + preview del editor (body.mobile-view) ── */
@container (max-width: 768px) {
    .video-hero-section.variant-v2::before { inset: 10px; border-radius: var(--card-radius, 24px); }
    .video-hero-section.variant-v2 > div:has(> [data-field="title"]) {
        padding: clamp(26px, 6cqw, 40px) clamp(20px, 6cqw, 30px);
        border-radius: var(--card-radius, 20px);
    }
    .video-hero-section.variant-v2 .video-play-btn { width: 56px; height: 56px; }
}
/* PODADO video v2 mobile-view: era un duplicado EXACTO del @container 768 de arriba (que ahora
   usa 6cqw). El @container ya dispara en el editor (container = #resultado) → mismo resultado en
   ambas superficies sin el override por clase (que además llevaba el 6vw viewport-relative). */



/* ===== before-after · v2 (Visor HUD) — variant-expansion ===== */

/* ════════════════════════════════════════════════════════════════
   BEFORE / AFTER — v2 "Visor HUD" (marco técnico + divisor luminoso)
   Concepto distinto del v1 (tarjeta limpia redondeada): viewfinder
   interior sobre las imágenes, línea divisoria con glow de acento,
   manija tipo reticle con halo y etiquetas glass cuadradas. Solo
   AÑADE reglas scopeadas a .variant-v2; los fondos de la manija y de
   la etiqueta derecha quedan en var(--accent) por estilo inline (no
   se tocan) y las capas de imagen conservan su background inline. */

/* Marco exterior: redondeo de tarjeta, sombra tokenizada + aro de acento */
#bloque-before-after.variant-v2 .ba-container {
    border-radius: var(--card-radius, 18px);
    box-shadow:
        0 24px 60px color-mix(in srgb, var(--text) 16%, transparent),
        0 0 0 1px color-mix(in srgb, var(--accent) 38%, transparent);
}

/* Viewfinder: marco interior fino de acento por encima de las imágenes
   (z 4: sobre .ba-after/.ba-before, bajo divisor z5, manija z10 y chips
   del editor z15). pointer-events:none para no estorbar el arrastre. */
#bloque-before-after.variant-v2 .ba-container::after {
    content: '';
    position: absolute;
    inset: 12px;
    z-index: 4;
    border-radius: calc(var(--card-radius, 18px) - 8px);
    border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, #fff 10%, transparent);
    pointer-events: none;
}

/* Divisor luminoso: acento con glow (la base era una línea blanca 2px).
   El background es overridable porque el inline solo fija --ba-accent. */
#bloque-before-after.variant-v2 .ba-slider-line {
    width: 3px;
    background: linear-gradient(
        to bottom,
        transparent,
        color-mix(in srgb, var(--accent) 80%, #fff) 14%,
        color-mix(in srgb, var(--accent) 80%, #fff) 86%,
        transparent);
    box-shadow: 0 0 14px color-mix(in srgb, var(--accent) 70%, transparent);
}

/* Manija reticle: halo de acento + aro blanco (fondo accent queda inline) */
#bloque-before-after.variant-v2 .ba-handle {
    width: 48px;
    height: 48px;
    border: 2px solid color-mix(in srgb, #fff 88%, transparent);
    box-shadow:
        0 0 0 6px color-mix(in srgb, var(--accent) 16%, transparent),
        0 0 22px color-mix(in srgb, var(--accent) 55%, transparent),
        0 6px 18px color-mix(in srgb, var(--text) 30%, transparent);
    transition: box-shadow var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1)), transform 0.25s ease;
}
#bloque-before-after.variant-v2 .ba-handle:hover {
    transform: translate(-50%, -50%) scale(1.08);
    box-shadow:
        0 0 0 8px color-mix(in srgb, var(--accent) 22%, transparent),
        0 0 30px color-mix(in srgb, var(--accent) 70%, transparent),
        0 8px 22px color-mix(in srgb, var(--text) 36%, transparent);
}

/* Etiquetas glass cuadradas con borde de acento (HUD).
   Mantienen su background base (izq: scrim oscuro; der: accent inline)
   y el texto blanco; el text-shadow garantiza legibilidad sobre
   cualquier imagen en claro y oscuro. */
#bloque-before-after.variant-v2 .ba-label {
    border-radius: 7px;
    padding: 7px 13px;
    letter-spacing: 2.5px;
    border: 1px solid color-mix(in srgb, var(--accent) 50%, transparent);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    text-shadow: 0 1px 6px color-mix(in srgb, #000 70%, transparent);
}

/* Móvil: marco interior, halo de manija y etiquetas más compactos */
@container (max-width: 768px) {
    #bloque-before-after.variant-v2 .ba-container::after {
        inset: 8px;
    }
    #bloque-before-after.variant-v2 .ba-handle {
        width: 40px;
        height: 40px;
        box-shadow:
            0 0 0 5px color-mix(in srgb, var(--accent) 16%, transparent),
            0 0 16px color-mix(in srgb, var(--accent) 55%, transparent),
            0 5px 14px color-mix(in srgb, var(--text) 30%, transparent);
    }
    #bloque-before-after.variant-v2 .ba-label {
        letter-spacing: 1.5px;
        padding: 5px 10px;
    }
}

/* ── (Aquí vivían las dos reglas de «lift» del fondo animado y la forma decorativa) ──
   `position: relative; z-index: 1` a todo hijo directo de la sección —una lista fija para
   form/menu/services/reservas/trust/reviews y otra genérica con `:has(> .bg-layer)`— para
   subir el contenido por encima de una capa que iba a `z-index:0`. Las sustituye el contrato
   de capas de `.bg-layer` (la sección se aísla y la capa baja a `z-index:-1`): el contenido
   queda encima sin tocarlo, y ningún hijo absoluto o sticky cambia de posición. No las
   vuelvas a poner: eran la causa del journey v2 sin fijar, el `.banner-overlay` a 0 px y el
   glow apagado (plan del motor de fondos, §1.2). */

/* ── Ajustes de sección (C3): espaciado vertical + ancho del contenido ───────
   render.js / viewer.js graban data-spacing / data-content-width en el <section>. */

/* Espaciado vertical (independiente del ancho). */
#resultado section[data-spacing="compact"]  { padding-top: 56px  !important; padding-bottom: 56px  !important; }
#resultado section[data-spacing="spacious"] { padding-top: 184px !important; padding-bottom: 184px !important; }

/* ── Ancho del contenido (rediseño): UN eje (max-width), UNA variable heredada.
   La <section> publica --cw por nivel; un ÚNICO aplicador la baja a los hijos EN FLUJO.
   Sin lista hardcoded de bloques, sin override de padding lateral (eso peleaba con el
   max-width y dependía del ancho de #resultado). Sin atributo → el selector NO matchea →
   cada bloque conserva su max-width natural (no se rompe nada). Valores ABSOLUTOS →
   consistentes en desktop y en el frame móvil. Escalable: cualquier bloque nuevo hereda el
   control gratis (la convención del repo es: contenedor primario = hijo directo del section). */
#resultado section[data-content-width="narrow"] { --cw: 720px;  }
#resultado section[data-content-width="normal"] { --cw: 1080px; }
#resultado section[data-content-width="wide"]   { --cw: 100%;   }

/* 'wide' a sangre con gutter FIJA (no %, para no reabrir el segundo eje).
   `.carrusel-section` fuera por lo mismo que `.hero-section` en el aplicador de abajo:
   es full-bleed por diseño y una gutter lateral le abriría bandas de color a los lados. Y el
   bloque «Generar con IA» (.ai-custom-section) por lo mismo: la densidad `compact` le horneaba
   `wide` en lo ya publicado y esta gutter le abría bandas a los lados del iframe (hoy render.js ya
   no le pone ancho: TIPOS_SIN_ANCHO). */
#resultado section[data-content-width="wide"]:not(.carrusel-section):not(.ai-custom-section) {
    padding-left:  clamp(16px, 4cqw, 44px) !important;
    padding-right: clamp(16px, 4cqw, 44px) !important;
}

/* Aplicador único: hijos EN FLUJO del <section>. El HERO entero queda fuera del ajuste de
   ancho (section:not(.hero-section)) — decisión de producto: el hero usa siempre su diseño
   propio (sin panel de ajustes) — y lo mismo `.carrusel-section`, que es full-bleed y cuyo
   hijo directo es el contenedor de Splide. Excluye además capas decorativas absolutas (bg/glow/overlay/
   shape/divider/ghost/unsplash + marquees a sangre), el contenedor pinned de journey v2
   (.journey-pin, cuyo trazo SVG mide el track), el overlay full-bleed de banner v2
   (.banner-overlay, absolute por CSS sin style inline → el [style*=] no lo caza), los CTA
   hijo-directo (.btn-action, inline-block que se estiraría con width:100%) y los heads que deben
   conservar su medida de lectura (.bento-head), y el marco del bloque `imagen` (.imagen-marco):
   esa sección no tiene ancho de contenido (TIPOS_SIN_ANCHO / _LAYOUT_EXCLUDE) y su anchura la
   pone el cliente a mano (`--media-w`, motor de dimensión); el `width:100% !important` de aquí
   se la pisaría si el atributo llegara por cualquier vía. Y el iframe del bloque «Generar con IA»
   (.ai-custom-frame): con la densidad «editorial» el render le ponía `narrow` y quedaba en 720 px
   sin control para cambiarlo; render.js ya no se lo pone (TIPOS_SIN_ANCHO), y esta exclusión
   arregla lo YA publicado, que lleva el atributo horneado. */
#resultado section[data-content-width]:not(.hero-section):not(.carrusel-section) > :not(.bg-layer):not(.bg-overlay):not(.bg-gradient-layer):not(.ambient-glow-layer):not(.canvas-shape-decor):not(.section-divider):not(.unsplash-bg):not(.ghost-text):not(.logos-track):not(.gg-track):not(.br-track):not(.tk-track):not(.journey-pin):not(.banner-overlay):not(.btn-action):not(.bento-head):not(.imagen-marco):not(.ai-custom-frame):not([style*="position:absolute"]):not([style*="position: absolute"]):not([style*="position:fixed"]):not([style*="position: fixed"]):not(style):not(script) {
    /* width:100% (NO auto): en el column-flex del <section> (align-items:stretch), el
       margin-inline:auto desactiva el stretch → con width:auto el hijo colapsaba a fit-content
       (form/texto/contenido centrado salían estrechos, ignorando --cw). width:100% da un ancho
       base que --cw capa y margin:auto centra. border-box → no desborda con el padding. */
    max-width: var(--cw) !important;
    width: 100% !important;
    margin-inline: auto !important;
}

/* ── Fase 2: ajustes por-bloque sobre el aplicador (todos CSS-only → se propagan a los
   publicados sin rebuild). Los que pelean con el aplicador (hijo directo del section)
   suben especificidad con :not(#_) —id inexistente que siempre matchea, 2 ids > el 1 id
   del aplicador— para ganar sin depender de una clase que el elemento no tiene. ── */

/* maps: la ficha de contacto (.maps-card) es NIETO → el aplicador no la toca → quedaba
   inmutable en 440px pese al ancho elegido. Que herede --cw para responder como el resto
   del bloque. Fallback 440 si la sección no lleva ancho. En v2 la columna flex la limita. */
#resultado section[data-content-width] .maps-card {
    max-width: var(--cw, 440px) !important;
}

/* timeline v1 (zigzag): las .tl-card se anclan al eje central; en 'wide' a sangre se
   separan demasiado del eje. Tope 1100 SOLO en wide (narrow/normal siguen al aplicador →
   responden). :not(#_) para ganar al aplicador que capa el container a var(--cw)=100%. */
#resultado section.timeline-layout[data-content-width="wide"]:not(#_) .timeline-container {
    max-width: 1100px !important;
}

/* timeline v2 (lineal): el wrapper de texto corrido (padding-left:50px para la línea, sin
   clase) se estiraba a --cw → líneas larguísimas. Responde ESCALONADO con topes de lectura
   (620/760/900) para que el ancho se note sin volver el texto ilegible. El header (otro div
   hijo) sigue respondiendo al ancho vía el aplicador. */
#resultado section.timeline-layout.variant-v2[data-content-width="narrow"]:not(#_) > div[style*="padding-left: 50px"] { max-width: 620px !important; }
#resultado section.timeline-layout.variant-v2[data-content-width="normal"]:not(#_) > div[style*="padding-left: 50px"] { max-width: 760px !important; }
#resultado section.timeline-layout.variant-v2[data-content-width="wide"]:not(#_)   > div[style*="padding-left: 50px"] { max-width: 900px !important; }

/* ═══════════════════════════════════════════════════════════════
   HOVERS COSMÉTICOS POR CLASE (Fase 2b)
   Antes vivían como handlers on*mouseover/out inline en los renderers
   (maps.js, faq.js, footer.js). Se movieron aquí para que el saneo XSS
   server-side (html_sanitizer.py) pueda borrar TODOS los on* del HTML
   publicado sin perder el efecto: :hover engancha por CLASE, que sobrevive
   al saneo. Se replican EXACTO los valores que ponían los handlers borrados.
   !important donde el estado base va inline (opacidad/fondo) y la regla
   externa no ganaría a la especificidad inline.
   ═══════════════════════════════════════════════════════════════ */

/* maps: enlace "Abrir en Google Maps" (era maps.js onmouseover/out).
   opacity/transform no están en el style inline base → sin !important. */
.maps-open-link:hover {
    opacity: 0.88;
    transform: translateY(-2px);
}

/* faq v1: tarjeta con border-left accent (era faq.js onmouseover/out).
   El background base va inline (var --text 3%) → !important para ganarle;
   transform no está inline → sin !important. */
#bloque-faq.variant-v1 .faq-card:hover {
    background: color-mix(in srgb, var(--text, #1a1a1a) 6%, transparent) !important;
    transform: translateX(10px);
}

/* footer: enlace de teléfono click-to-call (era footer.js onmouseover/out).
   opacity base va inline (0.7) → !important para ganarle.

   `color: inherit` NO es cosmética: el estilo inline del ancla trae `text-decoration:none`
   pero NINGÚN color, así que el `<a href="tel:…">` se quedaba con el AZUL por defecto del
   navegador (y morado al visitarlo) en mitad de un pie que usa `var(--text)` — parecía HTML
   sin estilar. Era el ÚNICO ancla de todos los bloques sin color declarado: sus tres
   hermanas (`.footer-link`, `.logo-link`, `.rv-author-link`) ya lo traían, dos de ellas con
   este mismo `inherit`. Va en el CSS y no en el inline a propósito: así lo heredan también
   los sitios YA PUBLICADOS al recargar, sin tener que volver a publicarlos. */
.footer-phone-link {
    color: inherit;
}
.footer-phone-link:hover {
    opacity: 1 !important;
}

/* Línea-guía de inserción al reordenar bloques/tarjetas (dragdrop.js).
   Elemento único con posición fija; sus coordenadas (top/left/width/height) las
   fija el JS en el borde exacto donde caerá el elemento (punto medio del objetivo).
   Solo existe en el editor; inerte a eventos. */
.n3i-drop-guide {
    position: fixed;
    z-index: 2147483000;
    background: linear-gradient(90deg, #6366f1, #a855f7);
    border-radius: 999px;
    box-shadow: 0 0 0 1px rgba(99, 102, 241, 0.35), 0 0 14px 2px rgba(99, 102, 241, 0.6);
    pointer-events: none;
}

/* ═══════════════════════════════════════════════════════════════════════════
   ESTUDIO DE TEMA — style_preset (glass | elegant | bold | minimal | soft)
   Activado por [data-preset], que emiten render.js y compileSiteHtml en CADA
   <section> (paridad gotcha #12; el atributo va por sección porque los valores
   derivan de var(--bg)/var(--text)/var(--accent), que solo existen INLINE ahí).

   ── PRESETS CON ADN: contrato de 8 propiedades ──────────────────────────────
   Antes cada preset redefinía DOS variables (--card-bg, --card-border): por eso
   todas las webs se parecían aunque el cliente cambiase de estilo. Este es el
   contrato completo — leerlo de arriba abajo por columna describe el preset:

   | #  | propiedad          | token             | glass          | elegant        | bold                   | minimal        | soft           |
   |----|--------------------|-------------------|----------------|----------------|------------------------|----------------|----------------|
   | 1  | radio de tarjeta   | --card-radius     | 22px           | 4px            | 2px                    | 0              | 28px           |
   | 2  | relleno + borde    | --card-bg/-border | translúcido    | tinte 3% +     | tinte 8% + borde       | transparente   | tinte de acento|
   |    | (grosor incluido)  |                   | + pelo 12%     | pelo 9%        | GRUESO 3px al 82%      | sin borde      | + pelo acento  |
   | 3  | sombra             | --card-shadow     | difusa suave   | ninguna        | OFFSET DURA 6px sin    | ninguna        | difusa amplia  |
   |    |                    | (identidad)       |                | (plano)        | blur (identidad)       |                |                |
   | 4  | trat. de imagen    | --img-filter      | saturada       | desaturada     | contraste alto         | casi gris      | cálida         |
   | 5  | divider preferido  | --preset-divider  | wave-smooth    | tilt-soft      | zigzag                 | none           | scallop        |
   | 6  | estilo de botón    | --btn-*           | fantasma       | subrayado      | offset duro            | contorno       | píldora        |
   | 7  | tracking display   | --display-tracking| -0.02em        | -0.008em       | -0.045em               | -0.02em        | -0.012em       |
   | 8  | caja de kickers    | --kicker-case     | uppercase      | none           | uppercase              | none           | uppercase      |

   `--preset-divider` (5) NO tiene consumidor CSS a propósito: es metadato legible
   con getComputedStyle para que el catálogo de dividers pueda proponer la familia
   coherente con el preset sin duplicar la tabla en JS.

   `--kicker-case` (8) gobierna la caja de TODO lo que es etiqueta, no solo el kicker
   suelto: `.sys-label`, `.btn-action` y los kickers inline de logos/stats/timeline/
   cta-bar/testimonials (migrados a `var(--kicker-case, uppercase)` con su valor de
   hoy como fallback). Su compañero `--kicker-tracking` hace lo propio con el
   letter-spacing, que en `elegant`/`minimal` aprieta al bajar de versalitas.

   ── CADENA DE DOS NIVELES: el preset PROPONE, el cliente DISPONE ─────────────
   Antes estos tokens iban con `!important` A PROPÓSITO (para ganarle al inline de
   site_tokens en #resultado), lo que convertía preset y slider en RIVALES: el que
   moviese el slider veía cómo el preset se lo comía o al revés. Ahora:

       --card-radius: var(--u-card-radius, <valor del preset>);
              ↑ consumido        ↑ OVERRIDE DEL CLIENTE   ↑ BASE DEL PRESET

   · `--u-<algo>` = **override del cliente** (lo escribe el Estudio de Tema en
     site_tokens). Se declara en #resultado / :root y HEREDA hasta la sección.
   · `--<algo>`   = **valor resuelto** que consumen las reglas de bloque. Lo declara
     el preset EN LA SECCIÓN, así que gana al heredado — de ahí que el override del
     cliente tenga que viajar por el canal `--u-*`, no por el mismo nombre.
   · RETROCOMPAT (invariante "byte-idéntico"): render.js/compileSiteHtml emiten un
     ALIAS `--u-*` por cada token de site_tokens que YA controla el cliente con un
     slider — hoy exactamente `--card-radius` y `--btn-radius` (ver `U_TOKEN_ALIAS`
     en render.js). Un sitio ya generado con `--card-radius: 16px` se sigue viendo a
     16px aunque su preset proponga otro radio.
   · Lo que NO se aliasa es tan importante como lo que sí: `--card-bg`/`--card-border`
     los escribe la IA en site_tokens, no el cliente. El `!important` de antes existía
     justo para pisarlos; sin alias, el preset los sigue ganando (mismo resultado que
     hoy) y `--u-card-bg`/`--u-card-border` quedan libres como canal de override REAL
     del cliente el día que haya un control para ellos.
   · `--card-shadow` (identidad) va POR DELANTE de `--shadow-card` (elevación, la del
     slider) en `.card` → apagar el slider de Sombras ya NO borra el offset duro de
     `bold`. Ver el comentario de `#resultado .card`.
   ═══════════════════════════════════════════════════════════════════════════ */

/* GLASS — vidrio: tarjetas translúcidas, radios generosos, botón fantasma */
[data-preset="glass"] {
    --card-bg: var(--u-card-bg, color-mix(in srgb, var(--bg, #ffffff) 42%, transparent));
    --card-border: var(--u-card-border, 1px solid color-mix(in srgb, var(--text, #1a1a1a) 12%, transparent));
    --card-radius: var(--u-card-radius, 22px);
    --card-shadow: var(--u-card-shadow, var(--shadow-card, 0 18px 40px -26px color-mix(in srgb, var(--text, #1a1a1a) 34%, transparent)));
    --img-filter: saturate(1.12);
    --preset-divider: wave-smooth;
    --display-tracking: -0.02em;
    --kicker-case: uppercase;
    --btn-bg: transparent;
    --btn-fg: color-mix(in srgb, var(--accent, #6366f1) 62%, var(--text, #1a1a1a));
    --btn-border: 1px solid color-mix(in srgb, var(--text, #1a1a1a) 18%, transparent);
    --btn-shadow: none;
}

/* ELEGANT — refinado: borde de pelo, cero sombra, botón subrayado, kickers en caja baja */
[data-preset="elegant"] {
    --card-bg: var(--u-card-bg, color-mix(in srgb, var(--text, #1a1a1a) 3%, transparent));
    --card-border: var(--u-card-border, 1px solid color-mix(in srgb, var(--text, #1a1a1a) 9%, transparent));
    --card-radius: var(--u-card-radius, 4px);
    --card-shadow: var(--u-card-shadow, none);
    --img-filter: saturate(0.9);
    --preset-divider: tilt-soft;
    --display-tracking: -0.008em;
    --kicker-case: none;
    --kicker-tracking: 0.08em;
    --btn-bg: transparent;
    --btn-fg: color-mix(in srgb, var(--accent, #6366f1) 62%, var(--text, #1a1a1a));
    --btn-border: none;
    --btn-shadow: inset 0 -2px 0 0 var(--accent, #6366f1);
    --btn-pad: 14px 6px;
    --btn-radius: var(--u-btn-radius, 0px);
}

/* BOLD — audaz: borde grueso, sombra OFFSET DURA (sin blur), tracking apretadísimo */
[data-preset="bold"] {
    --card-bg: var(--u-card-bg, color-mix(in srgb, var(--text, #1a1a1a) 8%, transparent));
    --card-border: var(--u-card-border, 3px solid color-mix(in srgb, var(--text, #1a1a1a) 82%, transparent));
    --card-radius: var(--u-card-radius, 2px);
    --card-shadow: var(--u-card-shadow, 6px 6px 0 0 color-mix(in srgb, var(--text, #1a1a1a) 86%, transparent));
    --img-filter: contrast(1.07) saturate(1.06);
    --preset-divider: zigzag;
    --display-tracking: -0.045em;
    --kicker-case: uppercase;
    --btn-bg: var(--accent, #6366f1);
    --btn-fg: var(--accent-contrast, #ffffff);
    --btn-border: 2px solid color-mix(in srgb, var(--text, #1a1a1a) 86%, transparent);
    --btn-shadow: 5px 5px 0 0 color-mix(in srgb, var(--text, #1a1a1a) 86%, transparent);
    --btn-radius: var(--u-btn-radius, 2px);
}

/* MINIMAL — plano: sin relleno, sin borde, sin sombra, botón de contorno */
[data-preset="minimal"] {
    --card-bg: var(--u-card-bg, transparent);
    --card-border: var(--u-card-border, none);
    --card-radius: var(--u-card-radius, 0px);
    --card-shadow: var(--u-card-shadow, none);
    --img-filter: grayscale(0.08);
    --preset-divider: none;
    --display-tracking: -0.02em;
    --kicker-case: none;
    --btn-bg: transparent;
    --btn-fg: color-mix(in srgb, var(--accent, #6366f1) 62%, var(--text, #1a1a1a));
    --btn-border: 1px solid currentColor;
    --btn-shadow: none;
    --btn-radius: var(--u-btn-radius, 0px);
}

/* SOFT — suave: tinte de acento, radios amplios, botón píldora */
[data-preset="soft"] {
    --card-bg: var(--u-card-bg, color-mix(in srgb, var(--accent, #6366f1) 7%, transparent));
    --card-border: var(--u-card-border, 1px solid color-mix(in srgb, var(--accent, #6366f1) 16%, transparent));
    --card-radius: var(--u-card-radius, 28px);
    --card-shadow: var(--u-card-shadow, var(--shadow-card, 0 26px 56px -34px color-mix(in srgb, var(--text, #1a1a1a) 40%, transparent)));
    --img-filter: saturate(1.04) brightness(1.02);
    --preset-divider: scallop;
    --display-tracking: -0.012em;
    --kicker-case: uppercase;
    --btn-bg: var(--accent, #6366f1);
    --btn-fg: var(--accent-contrast, #ffffff);
    --btn-border: none;
    --btn-shadow: 0 14px 30px -12px color-mix(in srgb, var(--accent, #6366f1) 55%, transparent);
    --btn-radius: var(--u-btn-radius, 999px);
}

/* ═══════════════════════════════════════════════════════════════════════════
   ESTILO DE BOTÓN COMO TOKEN GLOBAL — [data-btn-style]
   El botón es lo que más mira el visitante y hasta ahora su único grado de
   libertad era --btn-radius (una sola aparición en TODO el CSS). Estas reglas
   redefinen SOLO custom properties heredables (`--btn-*`), que consume el
   selector tokenizado `#resultado .btn-action, header.nav-pro .btn-action` →
   **cero cambios en los renderers de bloque** y sin peleas de especificidad.

   Van DESPUÉS de los presets a propósito: misma especificidad (0,1,0) sobre la
   <section> → gana el orden de aparición, es decir la elección EXPLÍCITA del
   cliente pisa la propuesta del preset. Sin `data-btn-style` no matchea nada.

   INVARIANTE nº19 (accent = FILL, no texto): los estilos SÓLIDOS etiquetan con
   `--accent-contrast` (p.accentContrast). Los que ponen el acento COMO TEXTO
   (contorno/fantasma/subrayado) NO usan el accent puro: usan la mezcla
   `color-mix(accent 62%, var(--text))` — el mismo recurso que `.title-gradient`
   para conservar el matiz de marca sin perder legibilidad sobre el fondo.
   ═══════════════════════════════════════════════════════════════════════════ */
[data-btn-style="solido"] {
    --btn-bg: var(--accent, #6366f1);
    --btn-fg: var(--accent-contrast, #ffffff);
    --btn-border: 1px solid color-mix(in srgb, var(--accent, #6366f1) 20%, transparent);
    --btn-shadow: 0 10px 25px color-mix(in srgb, var(--accent, #6366f1) 30%, transparent);
}
[data-btn-style="contorno"] {
    --btn-bg: transparent;
    --btn-fg: color-mix(in srgb, var(--accent, #6366f1) 62%, var(--text, #1a1a1a));
    --btn-border: 2px solid var(--accent, #6366f1);
    --btn-shadow: none;
}
[data-btn-style="fantasma"] {
    --btn-bg: color-mix(in srgb, var(--accent, #6366f1) 10%, transparent);
    --btn-fg: color-mix(in srgb, var(--accent, #6366f1) 62%, var(--text, #1a1a1a));
    --btn-border: 1px solid transparent;
    --btn-shadow: none;
}
[data-btn-style="pildora"] {
    --btn-bg: var(--accent, #6366f1);
    --btn-fg: var(--accent-contrast, #ffffff);
    --btn-border: none;
    --btn-shadow: 0 14px 30px -12px color-mix(in srgb, var(--accent, #6366f1) 55%, transparent);
    --btn-radius: 999px;
}
[data-btn-style="subrayado"] {
    --btn-bg: transparent;
    --btn-fg: color-mix(in srgb, var(--accent, #6366f1) 62%, var(--text, #1a1a1a));
    --btn-border: none;
    --btn-shadow: inset 0 -2px 0 0 var(--accent, #6366f1);
    --btn-pad: 14px 6px;
    --btn-radius: 0px;
}
[data-btn-style="offset"] {
    --btn-bg: var(--accent, #6366f1);
    --btn-fg: var(--accent-contrast, #ffffff);
    --btn-border: 2px solid color-mix(in srgb, var(--text, #1a1a1a) 86%, transparent);
    --btn-shadow: 5px 5px 0 0 color-mix(in srgb, var(--text, #1a1a1a) 86%, transparent);
    --btn-radius: 2px;
}
/* El hover base sube el botón 3px; con el offset duro eso "despega" la sombra
   sólida. Aquí el hover ACERCA el botón a su sombra (patrón neo-brutalista). */
[data-btn-style="offset"] .btn-action:hover,
[data-preset="bold"] .btn-action:hover {
    transform: translate(2px, 2px);
    --btn-shadow: 3px 3px 0 0 color-mix(in srgb, var(--text, #1a1a1a) 86%, transparent);
}

/* ═══════════════════════════════════════════════════════════════════════════
   DENSIDAD COMO UN SOLO CONTROL — [data-density]
   Compacto / Cómodo / Editorial mueven A LA VEZ el relleno de tarjeta, el hueco
   del grid y la escala tipográfica (el ancho por defecto lo resuelve el render,
   que rellena `data-content-width` cuando el bloque no trae uno propio).

   Molde YA PROBADO de `--section-pad-y`: TOKENS NUEVOS con fallback al valor
   actual, nunca reutilizar uno que la IA ya escribe en site_tokens — si no, se
   re-tramarían de golpe todos los sitios ya generados. Sin `data-density` el
   selector no matchea y no cambia absolutamente nada.
   `cozy` es la línea base explícita (idéntica a los fallbacks) para que elegirlo
   sea una forma de VOLVER al estado de fábrica, no un no-op silencioso.
   `--type-scale` viaja por la CADENA DE DOS NIVELES (igual que --card-radius): la
   densidad lo PROPONE y el slider "Escala tipográfica" del Estudio de Tema lo
   DISPONE por el canal `--u-type-scale` (U_TOKEN_ALIAS en render.js). Sin ese
   canal, elegir una densidad dejaba el slider mudo: `[data-density]` declara el
   token EN LA SECCIÓN y una declaración local gana a la heredada de #resultado.
   ═══════════════════════════════════════════════════════════════════════════ */
[data-density="compact"] {
    --card-pad: 34px 26px;
    --grid-gap: 24px;
    --type-scale: var(--u-type-scale, 0.92);
}
[data-density="cozy"] {
    --card-pad: 50px 40px;
    --grid-gap: 40px;
    --type-scale: var(--u-type-scale, 1);
}
[data-density="editorial"] {
    --card-pad: 64px 56px;
    --grid-gap: 56px;
    --type-scale: var(--u-type-scale, 1.08);
}

/* ── SKIP-LINK ("saltar al contenido") ──────────────────────────────────────
   Solo existe en el HTML PUBLICADO (lo antepone compileSiteHtml) y apunta al
   landmark <main id="contenido">. Fuera de pantalla hasta que recibe el foco:
   quien navega con teclado se salta el menú entero sin que nadie más lo vea.
   Se usa `clip-path` + tamaño 1px (patrón visually-hidden moderno) en vez de
   display:none, que lo haría INALCANZABLE para el teclado y el lector. */
.n3i-skip-link {
    /* `fixed` y no `absolute`: al enfocarlo tiene que aparecer SIEMPRE arriba del
       viewport, por delante del header .nav-pro (que también es fixed) y sin depender
       de qué ancestro esté posicionado. */
    position: fixed;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    z-index: 2147483001;   /* por encima del switcher de idioma del publicado */
}
.n3i-skip-link:focus {
    width: auto;
    height: auto;
    clip-path: none;
    top: 12px;
    left: 12px;
    padding: 12px 20px;
    border-radius: 999px;
    background: #111;
    color: #fff;
    text-decoration: none;
    font: 700 0.9rem/1 system-ui, -apple-system, sans-serif;
}

/* ═══════════════════════════════════════════════════════════════
   QUICK-WINS (g-blocks): announcement bar (cta v3) · newsletter (form v3)
   · campos date/select/number · trust badges · reseñas + microdata
   ═══════════════════════════════════════════════════════════════ */

/* ---------------------------------------------------------------
   ANNOUNCEMENT BAR — cta-bar v3 (barra de anuncio SUPERIOR).
   core/block-actions.js iza esta barra a <body> (fija de verdad) y añade
   body.n3i-has-announce + --n3i-announce-h para bajar el header hoisteado y el
   contenido, evitando el solape con el header fixed (applyFirstSectionHeaderOffset).
   --------------------------------------------------------------- */
.cta-sticky-bar.variant-v3 {
    top: 0;
    bottom: auto;
    border-top: none;
    z-index: 1200;                 /* sobre el header (1000); bajo los modales del editor */
    padding: 11px 30px;
    animation: ctaSlideDown 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: 0.2s;
}
@keyframes ctaSlideDown {
    from { transform: translateY(-100%); opacity: 0; }
    to   { transform: translateY(0); opacity: 1; }
}
/* Empuje del header y del contenido cuando hay barra de anuncio activa.
   margin-top (additivo, gana con !important a top:0 de las bases .nav-pro y
   .preview-mode .nav-pro) sirve para las 3 variantes de header (incl. píldora). */
body.n3i-has-announce .nav-pro { margin-top: var(--n3i-announce-h, 46px) !important; }
body.n3i-has-announce #resultado { padding-top: var(--n3i-announce-h, 46px); }
/* Compactado de la barra. Aquí @media es CORRECTO y NO debe migrarse a @container
   (excepción documentada, como el header): block-actions.js::syncAnnounceBars clona
   la barra a <body>, fuera de #resultado, así que NINGÚN @container la alcanza. */
@media (max-width: 560px) {
    .cta-sticky-bar.variant-v3 { padding: 9px 14px; gap: 12px; }
    .cta-sticky-bar.variant-v3 .cta-bar-text { font-size: 0.82rem; }
}
/* Preview móvil del editor: la barra superior redondea abajo (la base la redondea
   arriba, pensada para la barra inferior).
   Y ESPEJA el compactado del @media 560: body.mobile-view estrecha la barra a 375px
   (regla base .cta-sticky-bar) pero el VIEWPORT sigue siendo ancho → el @media no
   dispara → sin esto la barra se vería con padding/tipografía de escritorio dentro
   del teléfono simulado. Mantener estos valores en sinc con el bloque @media. */
body.mobile-view .cta-sticky-bar.variant-v3 {
    border-radius: 0 0 18px 18px !important;
    padding: 9px 14px;
    gap: 12px;
}
body.mobile-view .cta-sticky-bar.variant-v3 .cta-bar-text { font-size: 0.82rem; }

/* ---------------------------------------------------------------
   NEWSLETTER — form v3 (banda): título/bajada a un lado, input+botón en línea.
   --------------------------------------------------------------- */
.newsletter-band .nl-band {
    display: flex; align-items: center; justify-content: space-between;
    gap: 40px; max-width: 1100px; margin: 0 auto; flex-wrap: wrap;
}
.newsletter-band .nl-band-text { flex: 1 1 320px; min-width: min(100%, 300px); }
.newsletter-band .nl-band-form { flex: 1 1 400px; min-width: min(100%, 300px); }
.newsletter-band .form-title { font-size: clamp(1.4rem, 2.6vw, 2rem); margin-bottom: 8px; }
.newsletter-band .form-subtitle { font-size: 1rem; }
#resultado .ai-form-band { display: flex; align-items: flex-end; gap: 12px; }
#resultado .ai-form-band .form-grid { flex: 1; }
.ai-form-band .form-field { gap: 6px; }
.ai-form-band .form-actions { margin-top: 0; }
#resultado .ai-form-band .form-submit { white-space: nowrap; }
/* 768 = breakpoint de colapso canónico del proyecto (no 720): así la banda colapsa
   a la vez que el resto de bloques y no hay un escalón intermedio incoherente. */
@container (max-width: 768px) {
    .newsletter-band .nl-band { gap: 22px; }
    #resultado .ai-form-band { flex-direction: column; align-items: stretch; }
    #resultado .ai-form-band .form-submit { width: 100%; }
}

/* ---------------------------------------------------------------
   CAMPOS date / number / select (form + reservas) — heredan .form-input.
   --------------------------------------------------------------- */
#resultado .form-select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23888888' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 16px center;
    padding-right: 44px;
    cursor: pointer;
}
#resultado .form-input[type="date"]::-webkit-calendar-picker-indicator,
#resultado .form-input[type="number"]::-webkit-inner-spin-button { opacity: 0.55; cursor: pointer; }

/* ---------------------------------------------------------------
   TRUST — sellos de confianza / garantías.
   --------------------------------------------------------------- */
.trust-section .trust-head { text-align: center; max-width: 720px; margin: 0 auto 40px; }
.trust-section .trust-head :is(h1, h2) { font-size: clamp(1.6rem, 3vw, 2.4rem); font-weight: 900; margin-bottom: 10px; line-height: 1.15; }
.trust-section .trust-head p { opacity: 0.65; font-size: 1.05rem; }
/* overflow:hidden recorta el hairline "huérfano" de cada fila ENVUELTA: :not(:last-child)
   cuenta por índice, no por posición, así que el último sello de una fila que no sea la
   última SÍ dibuja separador… sin nada a su derecha (con 6 sellos ya pasa a 1120px).
   Como los .trust-row crecen (flex-grow) hasta llenar SU fila, el sobrante cae siempre
   justo en el borde del strip → sacando el hairline 1px afuera (right:-1px) el recorte
   se lleva SOLO los huérfanos y respeta los de entre sellos. Sin JS ni contar filas. */
.trust-strip { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px 0; max-width: 1120px; margin: 0 auto; overflow: hidden; }
.trust-row { display: flex; align-items: center; gap: 14px; flex: 1 1 240px; min-width: 220px; padding: 8px 26px; position: relative; }
.trust-strip .trust-row:not(:last-child)::after {
    content: ""; position: absolute; right: -1px; top: 50%; transform: translateY(-50%);
    width: 1px; height: 42px; background: color-mix(in srgb, var(--text) 14%, transparent);
}
/* Contrapartida del overflow:hidden de arriba: los +/- de cada sello cuelgan -14px
   fuera del item (editor.css) y el recorte se los comería en los extremos de la fila.
   Se meten dentro del padding lateral del propio sello (26px) → siguen enteros y
   clicables, y el strip puede recortar. Solo en el editor: en .preview-mode están
   display:none. */
.editor-active .trust-strip .card-add-left  { left: 2px; }
.editor-active .trust-strip .card-add-right { right: 2px; }
.trust-icon {
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
    color: var(--accent); width: 46px; height: 46px; border-radius: 12px;
    background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.trust-text { min-width: 0; }
.trust-title { font-size: 1rem; font-weight: 800; margin: 0 0 2px; line-height: 1.2; }
.trust-desc { font-size: 0.85rem; opacity: 0.6; margin: 0; line-height: 1.35; }
.trust-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 18px; max-width: 1120px; margin: 0 auto; }
.trust-card {
    display: flex; flex-direction: column; align-items: center; text-align: center; gap: 12px;
    padding: 30px 22px; position: relative;
    background: var(--card-bg, color-mix(in srgb, var(--text) 4%, transparent));
    border: var(--card-border, 1px solid color-mix(in srgb, var(--text) 10%, transparent));
    border-radius: var(--card-radius, 18px);
}
.trust-card .trust-icon { width: 56px; height: 56px; border-radius: 16px; }
/* 768 = escalón de colapso canónico: aligera sello e iconos ANTES de apilar, para no
   saltar de medidas de escritorio directo a la columna de 640. */
@container (max-width: 768px) {
    .trust-section .trust-head { margin-bottom: 28px; }
    .trust-row { padding: 8px 18px; gap: 12px; }
    .trust-icon { width: 40px; height: 40px; border-radius: 10px; }
    .trust-card { padding: 24px 18px; }
    .trust-card .trust-icon { width: 48px; height: 48px; border-radius: 14px; }
}
@container (max-width: 640px) {
    .trust-strip { flex-direction: column; }
    .trust-strip .trust-row:not(:last-child)::after { display: none; }
    .trust-row { justify-content: flex-start; }
}

/* ---------------------------------------------------------------
   REVIEWS — reseñas con estrellas + microdata schema.org.
   --------------------------------------------------------------- */
.rv-section .rv-head { text-align: center; max-width: 760px; margin: 0 auto 48px; }
.rv-subject { display: inline-block; font-size: 0.85rem; font-weight: 800; letter-spacing: 1.5px; text-transform: uppercase; color: var(--accent); margin-bottom: 10px; }
body.editor-active .rv-subject-empty:empty::before { content: 'Nombre del negocio/producto (opcional)'; opacity: 0.4; text-transform: none; letter-spacing: 0; font-weight: 500; }
.rv-title { font-size: clamp(1.8rem, 3.4vw, 2.8rem); font-weight: 900; line-height: 1.1; margin-bottom: 14px; }
.rv-subtitle { opacity: 0.65; font-size: 1.1rem; }
.rv-aggregate { display: inline-flex; align-items: center; gap: 10px; margin-top: 18px; flex-wrap: wrap; justify-content: center; }
.rv-avg { font-size: 1.6rem; font-weight: 900; color: var(--accent); }
.rv-outof { opacity: 0.5; font-weight: 700; }
.rv-count { opacity: 0.6; font-size: 0.95rem; }
.rv-stars { display: inline-flex; align-items: center; gap: 2px; color: var(--accent); }
.rv-star { display: inline-block; }
.rv-star-empty { color: color-mix(in srgb, var(--text) 28%, transparent); }
.rv-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 22px; max-width: 1150px; margin: 0 auto; }
.rv-card {
    display: flex; flex-direction: column; gap: 14px; padding: 28px 26px; position: relative;
    background: var(--card-bg, color-mix(in srgb, var(--text) 4%, transparent));
    border: var(--card-border, 1px solid color-mix(in srgb, var(--text) 10%, transparent));
    border-radius: var(--card-radius, 18px);
}
.rv-card-top { display: flex; align-items: center; gap: 8px; }
.rv-text { font-size: 1rem; line-height: 1.6; margin: 0; flex: 1; }
.rv-author { display: flex; flex-direction: column; gap: 2px; }
.rv-name { font-weight: 800; font-size: 0.95rem; }
.rv-role { font-size: 0.82rem; opacity: 0.55; }
body.editor-active .rv-role:empty::before { content: 'Cargo / etiqueta (opcional)'; opacity: 0.4; }
/* Enlace de ATRIBUCIÓN al perfil del autor. Lo exige el ToS de Google para mostrar sus
   reseñas: solo aparece si la reseña trae author_url (las escritas a mano no lo tienen). */
.rv-author-link {
    align-self: flex-start; margin-top: 4px; font-size: 0.75rem; font-weight: 600;
    color: var(--accent); opacity: 0.75; text-decoration: none;
}
.rv-author-link:hover { opacity: 1; text-decoration: underline; }
.rv-list { max-width: 820px; margin: 0 auto; display: flex; flex-direction: column; }
.rv-card.rv-row { border: none; border-bottom: 1px solid color-mix(in srgb, var(--text) 12%, transparent); border-radius: 0; background: transparent; padding: 26px 4px; }
.rv-card.rv-row:last-child { border-bottom: none; }
/* Escalones responsive (@container, NO @media: el preview móvil estrecha #resultado a
   375px pero el viewport sigue ancho → un @media no dispararía). .rv-title ya es fluido
   (clamp) y .rv-grid colapsa solo por auto-fit; lo que quedaba FIJO en móvil era el
   padding de las tarjetas, la nota media y las estrellas. */
@container (max-width: 768px) {
    .rv-section .rv-head { margin-bottom: 32px; }
    .rv-subtitle { font-size: 1rem; }
    .rv-grid { gap: 16px; }
    .rv-card { padding: 24px 22px; gap: 12px; }
    .rv-card.rv-row { padding: 22px 4px; }
}
@container (max-width: 480px) {
    .rv-section .rv-head { margin-bottom: 24px; }
    .rv-aggregate { gap: 7px; margin-top: 14px; }
    .rv-avg { font-size: 1.35rem; }
    /* Las estrellas llevan width/height como ATRIBUTOS del SVG (reviews.js las pinta a
       20px en la cabecera); un atributo de presentación pierde ante cualquier regla CSS,
       así que basta con esto para que no desborden la fila en móvil. */
    .rv-aggregate .rv-star { width: 16px; height: 16px; }
    .rv-card { padding: 20px 18px; }
    .rv-text { font-size: 0.95rem; }
}

/* ════════════════════════════════════════════════════════════
   FAQ · v4 "Acordeón glass" — EL PLEGADO REAL
   block-meta llama a esta variante "Acordeón" y hasta ahora no
   colapsaba nada: se veía como el resto, solo que en columna.
   El render (blocks/faq.js) marca la pregunta con .faq-q y mete
   la respuesta en .faq-a; el comportamiento (aria-expanded +
   .is-open) lo pone core/block-actions.js por delegación de
   clase — nunca un on* inline, que el saneo XSS del publicado
   borra (gotcha nº13). Aquí va SOLO la parte visual.
   Estas reglas van al final del fichero a propósito: amplían el
   bloque "faq · v4 (variant-expansion)" sin tocarlo.
   ════════════════════════════════════════════════════════════ */

/* Panel plegable. El truco de grid 0fr→1fr anima la altura REAL
   sin conocerla (nada de max-height "a ojo" que recorte respuestas
   largas). Donde el navegador no sepa animarlo, el valor 0fr sigue
   colapsando: se pierde la transición, no la función. */
#bloque-faq.variant-v4 .faq-a {
    display: grid;
    grid-template-rows: 0fr;
    opacity: 0;
    transition: grid-template-rows var(--transition, 0.4s cubic-bezier(0.16, 1, 0.3, 1)),
                opacity 0.28s ease;
}
#bloque-faq.variant-v4 .faq-a > * {
    overflow: hidden;
    min-height: 0;      /* sin esto, el contenido impone su alto mínimo y no colapsa */
}
#bloque-faq.variant-v4 .faq-card.is-open .faq-a {
    grid-template-rows: 1fr;
    opacity: 1;
}

/* La pregunta es el disparador: se comporta como botón.
   El selector lleva `.faq-card h3.faq-q` para GANAR en especificidad a la regla
   `#bloque-faq.variant-v4 .faq-card h3 { margin-bottom: 14px !important }` de más arriba
   (cerrada, la pregunta no debe dejar hueco bajo ella). */
#bloque-faq.variant-v4 .faq-card h3.faq-q {
    cursor: pointer;
    margin-bottom: 0 !important;
    user-select: none;
    -webkit-user-select: none;
}
#bloque-faq.variant-v4 .faq-card.is-open h3.faq-q {
    margin-bottom: 14px !important;
}
/* El span editable SÍ debe poder seleccionarse para editarlo en el canvas. */
.editor-active #bloque-faq.variant-v4 h3.faq-q,
#bloque-faq.variant-v4 h3.faq-q [contenteditable="true"] {
    user-select: text;
    -webkit-user-select: text;
}
/* Foco visible: la pregunta se puede recorrer con el tabulador (tabindex=0). */
#bloque-faq.variant-v4 .faq-q:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 4px;
    border-radius: 6px;
}

/* El caret ya es un chip circular rotado 90° (cerrado); al abrir mira hacia arriba. */
#bloque-faq.variant-v4 .faq-card.is-open h3 > span:first-child {
    transform: rotate(270deg);
    background: color-mix(in srgb, var(--accent) 26%, transparent);
}

/* EDITOR: todo abierto y sin cursor de botón. Plegar mientras el cliente escribe en el
   contenteditable de la pregunta haría la edición inline imposible (block-actions.js
   ignora el clic con la misma condición). */
.editor-active #bloque-faq.variant-v4 .faq-a {
    grid-template-rows: 1fr;
    opacity: 1;
}
.editor-active #bloque-faq.variant-v4 .faq-card h3.faq-q {
    cursor: text;
    margin-bottom: 14px !important;
}

@media (prefers-reduced-motion: reduce) {
    #bloque-faq.variant-v4 .faq-a { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ACABADO DE ESTUDIO — capa de remate (APPEND-ONLY)
   ---------------------------------------------------------------------------
   Todo lo de aquí abajo es ADITIVO: sube el suelo estético de los 30 bloques a
   la vez sin reescribir ni una regla anterior (así el merge con el resto de
   lotes visuales es trivial). Va al FINAL a propósito: en igualdad de
   especificidad gana la última declaración, que es justo lo que buscamos para
   rematar lo que los bloques ya pintan.
   Nada de esto hardcodea color ni tipografía: todo deriva de las variables que
   cada <section> declara INLINE (--accent/--text/--bg) o de los tokens de
   tipografía (--font-label/--font-body) → el cliente y la IA siguen mandando.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1. MICROTIPOGRAFÍA ──────────────────────────────────────────────────
   Los titulares ya usaban `text-wrap: balance`; el texto corrido no tenía nada
   (huérfanos de una palabra al final del párrafo = el tell tipográfico nº1 de
   una web hecha deprisa). `pretty` es justo lo contrario de `balance`: optimiza
   SOLO las últimas líneas, sin coste de layout en párrafos largos. */
#resultado p,
#resultado li,
#resultado dd,
#resultado blockquote,
#resultado figcaption,
#resultado .rv-text,
#resultado .service-desc,
#resultado .trust-desc {
    text-wrap: pretty;
}

/* Comillas colgantes en citas: la comilla de apertura sale al margen y el
   bloque de texto queda ópticamente alineado (Safari; el resto lo ignora). */
#resultado blockquote,
#resultado .testimonial-card,
#resultado .rv-card,
#resultado .rv-text {
    hanging-punctuation: first last;
}

/* Cifras tabulares: precios, KPIs, años y el contador NO deben bailar de ancho
   al cambiar de dígito (el countdown, que repinta cada segundo, era el caso
   flagrante: `11` ocupa menos que `00` en cifras proporcionales y la fila
   entera se movía). tnum además alinea las columnas de precios entre tarjetas. */
#resultado .cd-num,
#resultado .stat-item [data-field="name"],
#resultado [data-field="price"],
#resultado .service-price,
#resultado .rv-avg,
#resultado .tl-year,
#resultado .ghost-number {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

/* Capa de RÓTULO: los micro-textos de interfaz de los bloques (unidades del
   countdown, etiquetas de formulario, tags de galería, badges de proceso…)
   comparten ahora la tercera fuente del preset (--font-label, que hasta hoy
   casi no se usaba), con tracking amplio y versalitas. Es el detalle que
   separa "plantilla" de "estudio de diseño". */
#resultado .cd-label,
#resultado .form-label,
#resultado .ba-label,
#resultado .gallery-tag,
#resultado .pz-badge,
#resultado .scene-label,
#resultado .tk-tag,
#resultado #bloque-logos > p[data-field="title"],
#resultado #bloque-catalogo > p[data-field="title"] {
    font-family: var(--font-label, var(--font-body, inherit));
    letter-spacing: 0.14em;
    font-variant-caps: all-small-caps;
    font-weight: 700;
}

/* Excepción de la capa de rótulo: las etiquetas de formulario se LEEN mientras se rellena, no son
   un badge. Mayúsculas normales a 1rem, sin versalitas. Con Bebas Neue (preset industrial: solo
   mayúsculas, condensada) `all-small-caps` a 0.9rem las dejaba minúsculas e ilegibles (producción,
   18-09-2026). Misma especificidad que la regla de arriba ⇒ tiene que ir DESPUÉS. La fuente de
   rótulo del preset se conserva. */
#resultado .form-label { font-size: 1rem; text-transform: uppercase; font-variant-caps: normal; letter-spacing: 0.08em; }

/* ── 2. HUÉRFANOS DE REJILLA ─────────────────────────────────────────────
   `repeat(auto-fit, minmax(X, 1fr))` deja la última tarjeta suelta y pegada a
   la izquierda con 4, 5 o 7 items (el defecto de composición más visible de una
   web generada). Aquí la rejilla pasa a una BASE DE 6 PISTAS con items de
   `span 2` (= 3 por fila EXACTAS, sin depender del ancho) → el nth-child deja
   de ser una suposición y es aritmética cerrada, y la última fila se centra.
   Solo en escritorio: por debajo de 1000px manda el auto-fit fluido de siempre.
   Fuera a propósito: .team-grid (ya centra con justify-content) y .gallery-grid
   (fotos: ahí prima la densidad, no la simetría). */
@container (min-width: 1000px) {

    .services-grid,
    .stats-grid,
    .rv-grid,
    .trust-grid {
        grid-template-columns: repeat(6, minmax(0, 1fr));
    }

    .services-grid > *,
    .stats-grid > *,
    .rv-grid > *,
    .trust-grid > * {
        grid-column: span 2;
    }

    /* Un solo huérfano (4, 7, 10… items) → centrado en las pistas 3-4. */
    .services-grid > *:last-child:nth-child(3n + 1),
    .stats-grid > *:last-child:nth-child(3n + 1),
    .rv-grid > *:last-child:nth-child(3n + 1),
    .trust-grid > *:last-child:nth-child(3n + 1) {
        grid-column: 3 / span 2;
    }

    /* Pareja huérfana (5, 8, 11… items) → la primera arranca en la pista 2 y la
       segunda cae detrás: la fila queda centrada sin tocar el resto. */
    .services-grid > *:nth-child(3n + 1):nth-last-child(2),
    .stats-grid > *:nth-child(3n + 1):nth-last-child(2),
    .rv-grid > *:nth-child(3n + 1):nth-last-child(2),
    .trust-grid > *:nth-child(3n + 1):nth-last-child(2) {
        grid-column: 2 / span 2;
    }

    /* Recuentos que quedan mejor en 2 columnas que en 3 (2 items = mitades;
       4 items = 2x2, en vez de 3 + huérfano). Van DESPUÉS de las reglas de cola
       a propósito: misma especificidad, gana la última → estas mandan. */
    .services-grid:has(> :last-child:nth-child(2)) > *,
    .stats-grid:has(> :last-child:nth-child(2)) > *,
    .rv-grid:has(> :last-child:nth-child(2)) > *,
    .trust-grid:has(> :last-child:nth-child(2)) > *,
    .services-grid:has(> :last-child:nth-child(4)) > *,
    .stats-grid:has(> :last-child:nth-child(4)) > *,
    .rv-grid:has(> :last-child:nth-child(4)) > *,
    .trust-grid:has(> :last-child:nth-child(4)) > * {
        grid-column: span 3;
    }

    /* Item único → ancho completo (con span 2 se quedaría a un tercio). */
    .services-grid > :only-child,
    .stats-grid > :only-child,
    .rv-grid > :only-child,
    .trust-grid > :only-child {
        grid-column: 1 / -1;
    }
}

/* ── 3. MICRO-MARCA DEL NAVEGADOR ────────────────────────────────────────
   Los controles nativos (checkbox/radio/range/progress) se pintaban con el azul
   de Chrome y la selección de texto con el azul del sistema: dos colores AJENOS
   a la marca en cada web generada. `--accent` existe inline en cada <section> y
   lo heredan sus descendientes → cada sección tiñe lo suyo, sin hardcodear. */
#resultado input,
#resultado select,
#resultado textarea,
#resultado progress {
    accent-color: var(--accent, #6366f1);
}

#resultado input,
#resultado textarea,
#resultado [contenteditable="true"] {
    caret-color: var(--accent, #6366f1);
}

/* Selección de texto: tinte del acento (28% sobre el fondo real → funciona
   igual en secciones claras y oscuras) conservando el color de texto. El
   header viaja hoisteado FUERA de #resultado, por eso su selector aparte. */
#resultado ::selection,
header.nav-pro ::selection {
    background: color-mix(in srgb, var(--accent, #6366f1) 28%, transparent);
    color: var(--text, inherit);
}

/* ── 4. TRANSICIÓN DE PÁGINA + PINTADO PEREZOSO ──────────────────────────
   El sitio publicado navega entre páginas con carga completa; con esto el
   navegador hace un fundido cruzado nativo (Chrome; el resto lo ignora sin
   coste). Las dos páginas deben declararlo, y ambas cargan esta hoja. */
@view-transition {
    navigation: auto;
}

/* content-visibility en lo que está MUY por debajo del pliegue (7º bloque en
   adelante): el navegador se salta su render hasta que se acerca. Cautelas:
     · solo en la superficie preview/publicada (.preview-mode), nunca editando;
     · `contain-intrinsic-size: auto 700px` reserva altura y luego RECUERDA la
       real → sin saltos de scroll ni barra que baila;
     · quedan FUERA los bloques que miden el viewport o pintan fuera de su caja
       (journey pinneado, bento, before-after, hero): ahí la contención de
       pintado sí molestaría.
   El IntersectionObserver del orquestador observa ESTE MISMO wrapper (no un
   descendiente), que conserva su caja → la animación de entrada dispara igual. */
.preview-mode > [data-anim]:nth-child(n + 7):not([data-type="journey"]):not([data-type="bento"]):not([data-type="before-after"]):not([data-type="hero"]) {
    content-visibility: auto;
    contain-intrinsic-size: auto 700px;
}

/* ── 5. EL CTA NUNCA SE PIERDE EN MÓVIL ──────────────────────────────────
   Por debajo de 900px se oculta .header-cta-wrapper (único contenedor del
   botón) y el CTA desaparecía: el menú desplegable no lo contenía. header.js
   pinta ahora un espejo del botón DENTRO del nav (misma prop `cta`, mismo
   embudo ctaAnchorAttrs de utils/links.js), oculto por defecto y visible solo
   cuando el nav pasa a desplegable. Se replica en las tres superficies que ya
   duplican las reglas del nav móvil: el @container del propio header.js, el
   @media real (publicado) y el simulador móvil del editor. */
.header-cta-mobile {
    display: none;
}

@media (max-width: 900px) {
    .nav-pro .nav-links-container .header-cta-mobile {
        display: inline-flex !important;
    }
}

body.mobile-view .nav-pro .nav-links-container .header-cta-mobile {
    display: inline-flex !important;
}

/* ---------------------------------------------------------------
   ACCESIBILIDAD — "Saltar al contenido"
   Lo inyecta en runtime core/render.js::applyPublicLandmarks (preview y sitio
   publicado) junto al <main>. Invisible hasta que recibe el foco: quien navega
   con teclado no tiene que tabular el menú entero en cada página.
   Colores propios (chrome de navegador, no de la marca): vive FUERA de toda
   <section>, así que aquí no hay --bg/--text/--accent que heredar.
   --------------------------------------------------------------- */
.n3i-skip-link {
    position: absolute; left: -9999px; top: 0; z-index: 2147483001;
    padding: 12px 20px; border-radius: 0 0 10px 0;
    background: #111827; color: #ffffff; text-decoration: none;
    font: 600 0.9rem/1.2 system-ui, -apple-system, sans-serif;
}
.n3i-skip-link:focus { left: 0; outline: 3px solid #ffffff; outline-offset: -6px; }
/* El <main> es un contenedor puramente semántico: no debe introducir caja ni
   margen que rompa el ritmo vertical de las secciones. */
#n3i-main { display: block; }
#n3i-main:focus { outline: none; }

/* ---------------------------------------------------------------
   BLOQUE reserva-live — asistente de reserva (personas → día → hora)
   Ver js/blocks/reserva-live.js (render) y js/core/reserva-live-runtime.js
   (interacción por delegación de clase).

   TOKENS PROPIOS derivados con color-mix de --bg / --text / --accent, que cada
   <section> declara INLINE. Se declaran DENTRO de .rl-section (no en :root): ahí
   var(--bg)/var(--text) sí resuelven con los colores de ESA sección — mismo motivo
   por el que render.js re-declara --shadow-card por sección. Así una taberna
   terracota tiene un widget terracota y no un semáforo verde fosforito.
   El radio se engancha a los tokens del Estudio de Tema (--btn-radius/--card-radius)
   → el slider "Redondez" redondea también los chips y el calendario.
   --------------------------------------------------------------- */
.rl-section {
    --slot-radius: var(--btn-radius, 12px);
    --field-radius: var(--card-radius, 14px);
    --slot-bg: color-mix(in srgb, var(--text) 6%, var(--bg));
    --slot-border: color-mix(in srgb, var(--text) 18%, transparent);
    --cal-head-bg: color-mix(in srgb, var(--accent) 12%, var(--bg));
    --rl-muted: color-mix(in srgb, var(--text) 55%, transparent);
    background: var(--bg); color: var(--text); font-family: var(--font-body, var(--font));
    position: relative;
}
.rl-wrap { max-width: 900px; margin: 0 auto; }
.rl-wrap.rl-split {
    max-width: 1180px; display: grid; grid-template-columns: 1fr 1fr;
    gap: 56px; align-items: start;
}
.rl-head { margin-bottom: 34px; }
.rl-head-center { text-align: center; }
.rl-title {
    font-family: var(--font-display, var(--font)); font-weight: 800; line-height: 1.1;
    font-size: clamp(1.9rem, 3.4vw, 2.9rem); margin: 0 0 12px;
}
.rl-subtitle { font-size: 1.06rem; line-height: 1.6; color: var(--rl-muted); margin: 0; }

.rl-widget {
    background: color-mix(in srgb, var(--text) 3%, var(--bg));
    border: 1px solid var(--slot-border);
    border-radius: var(--field-radius);
    padding: 26px 26px 22px;
    box-shadow: var(--shadow-card, none);
}
.rl-panel[hidden], .rl-alts[hidden], .rl-state-copy[hidden], .rl-back[hidden], .rl-next[hidden] { display: none !important; }
/* EN EL CANVAS DEL EDITOR se despliegan TODOS los pasos, atenuados. Mismo precedente que
   `body.editor-active .form-success`: el copy tiene que ser alcanzable para editarlo, y con el
   envío inerte la pantalla de confirmación (success_msg) no se alcanzaría NUNCA recorriendo el
   flujo. En preview y en el sitio publicado manda el paso a paso de siempre. */
body.editor-active .rl-panel[hidden] { display: block !important; opacity: .72; }
body.editor-active .rl-panel[data-step] {
    border-top: 1px dashed var(--slot-border); padding-top: 14px; margin-top: 14px;
}
body.editor-active .rl-panel[data-step="people"] { border-top: none; padding-top: 0; margin-top: 0; }

/* Sello del editor: NUNCA viaja al publicado (compileSiteHtml borra .editor-only). */
.rl-preview-badge {
    display: inline-flex; align-items: center; gap: 6px; margin-bottom: 14px;
    padding: 5px 11px; border-radius: 999px;
    background: color-mix(in srgb, var(--accent) 16%, transparent);
    color: var(--text); font-size: 0.74rem; font-weight: 700; letter-spacing: .02em;
}

/* Indicador de pasos */
.rl-steps {
    display: flex; gap: 10px; list-style: none; margin: 0 0 22px; padding: 0;
    flex-wrap: wrap;
}
.rl-step {
    display: inline-flex; align-items: center; gap: 8px;
    font-size: 0.82rem; font-weight: 600; color: var(--rl-muted);
}
.rl-step + .rl-step::before {
    content: ''; width: 18px; height: 1px; background: var(--slot-border); margin-right: 2px;
}
.rl-step-n {
    width: 22px; height: 22px; border-radius: 50%; display: inline-flex;
    align-items: center; justify-content: center; font-size: 0.72rem;
    border: 1px solid var(--slot-border);
}
.rl-step.is-active { color: var(--text); }
.rl-step.is-active .rl-step-n { background: var(--accent); color: var(--accent-contrast); border-color: transparent; }
.rl-step.is-done .rl-step-n { border-color: var(--accent); color: var(--accent); }

.rl-row { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-bottom: 16px; }
.rl-row-label {
    display: inline-flex; align-items: center; gap: 7px;
    font-size: 0.86rem; font-weight: 700; color: var(--rl-muted);
    min-width: 110px;
}
.rl-picked-day { font-size: 0.9rem; font-weight: 600; text-transform: capitalize; }

/* Chips (personas / zonas) */
.rl-people, .rl-zones { display: flex; flex-wrap: wrap; gap: 8px; }
.rl-chip {
    min-width: 44px; padding: 9px 14px; cursor: pointer;
    border: 1px solid var(--slot-border); border-radius: var(--slot-radius);
    background: var(--slot-bg); color: var(--text);
    font: inherit; font-size: 0.92rem; font-weight: 600;
    transition: transform .12s ease, background .15s ease, border-color .15s ease;
}
.rl-chip:hover:not(:disabled) { transform: translateY(-1px); border-color: var(--accent); }
.rl-chip.is-active { background: var(--accent); color: var(--accent-contrast); border-color: transparent; }
.rl-chip:disabled { opacity: .4; cursor: not-allowed; }

/* Calendario */
.rl-cal-head {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    background: var(--cal-head-bg); border-radius: var(--slot-radius);
    padding: 8px 10px; margin-bottom: 12px;
}
.rl-cal-month { font-weight: 700; text-transform: capitalize; font-size: 0.95rem; }
.rl-cal-nav {
    width: 32px; height: 32px; border-radius: 50%; cursor: pointer;
    border: 1px solid var(--slot-border); background: var(--bg); color: var(--text);
    font-size: 1.1rem; line-height: 1;
}
.rl-cal-nav:hover { border-color: var(--accent); }
.rl-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; }
.rl-cal-dow {
    text-align: center; font-size: 0.7rem; font-weight: 700; text-transform: uppercase;
    color: var(--rl-muted); padding-bottom: 4px;
}
.rl-day {
    aspect-ratio: 1 / 1; min-height: 38px; cursor: pointer;
    border: 1px solid transparent; border-radius: var(--slot-radius);
    background: var(--slot-bg); color: var(--text);
    font: inherit; font-size: 0.88rem; font-weight: 600;
}
.rl-day:hover:not(:disabled) { border-color: var(--accent); }
.rl-day.is-active { background: var(--accent); color: var(--accent-contrast); }
.rl-day-void { background: transparent; border: none; }
/* Día imposible: además del color, un RAYADO diagonal. Distinguir estados solo por
   color deja fuera a quien no distingue esos colores — la forma lo cubre. */
.rl-day.is-off {
    cursor: not-allowed; color: var(--rl-muted); opacity: .65;
    background-image: repeating-linear-gradient(45deg,
        transparent 0 4px, color-mix(in srgb, var(--text) 22%, transparent) 4px 5px);
}
.rl-date-native-wrap {
    display: flex; align-items: center; gap: 10px; margin-top: 14px;
    font-size: 0.82rem; color: var(--rl-muted);
}
.rl-date-native {
    flex: 1; padding: 9px 12px; font: inherit; color: var(--text);
    background: var(--slot-bg); border: 1px solid var(--slot-border);
    border-radius: var(--slot-radius);
}

/* Horas */
.rl-slots { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.rl-slot {
    display: inline-flex; flex-direction: column; align-items: center; gap: 2px;
    padding: 9px 15px; cursor: pointer; font: inherit; font-weight: 700; font-size: 0.92rem;
    border: 1px solid var(--slot-border); border-radius: var(--slot-radius);
    background: var(--slot-bg); color: var(--text);
    transition: transform .12s ease, border-color .15s ease;
}
.rl-slot:hover:not(:disabled) { transform: translateY(-1px); border-color: var(--accent); }
.rl-slot.is-active { background: var(--accent); color: var(--accent-contrast); border-color: transparent; }
.rl-slot-left { font-size: 0.68rem; font-weight: 700; opacity: .85; letter-spacing: .01em; }
/* "Juntamos N mesas": no es escasez, es CÓMO te van a sentar. Se pinta más apagado que el "quedan"
   para que se lea como el detalle que es, y en su propia línea porque cambia la decisión de quien
   reserva para un grupo — enterarse al llegar es la peor forma de saberlo. */
.rl-slot-join { display: block; font-size: 0.62rem; font-weight: 600; opacity: .7;
    letter-spacing: .01em; }
/* Lista de espera: borde DISCONTINUO (forma) + acento suave (color). */
.rl-slot-waitlist { border-style: dashed; border-color: color-mix(in srgb, var(--accent) 60%, transparent); }
/* Sin hueco: tachado + rayado. Nunca un rojo hardcodeado. */
.rl-slot-full {
    cursor: not-allowed; opacity: .55; text-decoration: line-through;
    background-image: repeating-linear-gradient(45deg,
        transparent 0 4px, color-mix(in srgb, var(--text) 20%, transparent) 4px 5px);
}
.rl-loading { font-size: 0.9rem; color: var(--rl-muted); margin: 4px 0; }

/* Alternativas: "no hay mesa" nunca es un callejón sin salida */
.rl-alts { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; }
.rl-alt {
    display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
    padding: 10px 14px; cursor: pointer; text-align: left; font: inherit;
    border: 1px dashed color-mix(in srgb, var(--accent) 55%, transparent);
    border-radius: var(--field-radius);
    background: color-mix(in srgb, var(--accent) 7%, transparent); color: var(--text);
}
.rl-alt:hover { border-style: solid; }
.rl-alt-kind { font-size: 0.68rem; font-weight: 700; text-transform: uppercase; color: var(--rl-muted); }
.rl-alt-label { font-size: 0.94rem; font-weight: 700; }

/* Copy de estado (editable por el cliente).
   En el canvas del editor se ven LOS CINCO con su etiqueta (.rl-state-tag, editor-only) para
   poder reescribirlos sin provocar cada situación; en el sitio servido el runtime deja visible
   solo el que decide el endpoint y la fila vacía se colapsa. */
.rl-status { margin-top: 16px; display: flex; flex-direction: column; gap: 6px; }
.rl-state-row { display: flex; gap: 8px; align-items: baseline; margin: 0; }
.rl-state-row:has(> .rl-state-copy[hidden]) { display: none; }
.rl-state-tag {
    flex-shrink: 0; font-size: 0.62rem; font-weight: 800; text-transform: uppercase;
    letter-spacing: .04em; padding: 3px 8px; border-radius: 999px;
    background: color-mix(in srgb, var(--text) 10%, transparent); color: var(--rl-muted);
}
.rl-state-copy { font-size: 0.94rem; line-height: 1.55; margin: 0; color: var(--rl-muted); }
.rl-live { margin: 0; font-size: 0.8rem; color: var(--rl-muted); min-height: 1em; }
/* Aviso que FRENA al visitante (hoy, el tope por dispositivo). El resto de mensajes de esta misma
   región siguen en gris discreto: si todo grita, no grita nada.
   El color se MEZCLA con el `--text` de la sección en vez de ser un rojo fijo, porque el sitio
   publicado puede tener cualquier fondo: sobre uno oscuro un #d92d20 plano queda ilegible, y
   mezclado con el texto de la propia sección aclara o oscurece solo. Mismo recurso que ya usa
   `--rl-muted` unas líneas más arriba. */
.rl-live.rl-live-alerta {
    color: color-mix(in srgb, #d92d20 62%, var(--text));
    font-size: 0.86rem; font-weight: 650; line-height: 1.45;
    padding: 8px 12px; border-radius: 8px;
    border-left: 3px solid color-mix(in srgb, #d92d20 62%, var(--text));
    background: color-mix(in srgb, #d92d20 8%, transparent);
}
/* Sacudida corta al INTENTAR reservar de nuevo: el aviso sale debajo del botón, y quien acaba de
   pulsar está mirando el botón. Sin movimiento, el texto aparece fuera de donde está la vista y
   parece que no ha pasado nada. `translate3d` para que el navegador la componga en GPU y no
   reflote la fila entera en cada fotograma. */
@keyframes rl-sacudir {
    0%, 100% { transform: translate3d(0, 0, 0); }
    15%, 45%, 75% { transform: translate3d(-6px, 0, 0); }
    30%, 60%, 90% { transform: translate3d(6px, 0, 0); }
}
.rl-sacude { animation: rl-sacudir .42s cubic-bezier(.36, .07, .19, .97) both; }
/* Quien pide menos movimiento no lo recibe: se queda el resalte, que es lo que comunica. El aviso
   no depende de la animación para entenderse. */
@media (prefers-reduced-motion: reduce) {
    .rl-sacude { animation: none !important; }
}

/* Datos de contacto */
.rl-summary { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.rl-sum-item {
    display: inline-flex; align-items: baseline; gap: 6px; padding: 6px 12px;
    border-radius: 999px; background: var(--slot-bg); border: 1px solid var(--slot-border);
    font-size: 0.84rem;
}
.rl-sum-k { color: var(--rl-muted); font-weight: 600; }
.rl-sum-v { font-weight: 700; text-transform: capitalize; }
.rl-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.rl-field { display: flex; flex-direction: column; gap: 6px; position: relative; }
.rl-field:has(.rl-textarea) { grid-column: 1 / -1; }
.rl-label { font-size: 0.8rem; font-weight: 700; color: var(--rl-muted); }
.rl-input {
    width: 100%; padding: 11px 13px; font: inherit; font-size: 0.94rem;
    color: var(--text); background: var(--bg);
    border: 1px solid var(--slot-border); border-radius: var(--slot-radius);
}
.rl-input:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: transparent; }
.rl-textarea { resize: vertical; min-height: 84px; }
/* Casillas extra (trona / acceso sin escaleras). Las pinta el runtime SOLO si la política del
   restaurante las pide: sin ellas el contenedor va `hidden` y el formulario se ve como siempre. */
.rl-extras { display: flex; flex-wrap: wrap; gap: 10px 18px; margin-top: 14px; }
/* `display` de clase GANA al `[hidden]` de la hoja del navegador, así que el contenedor vacío
   seguiría ocupando su margen en TODA web con los interruptores apagados (o sea, hoy en todas).
   Mismo remedio y misma razón que la línea de `.rl-panel[hidden]` de más arriba. */
.rl-extras[hidden] { display: none !important; }
.rl-extra {
    display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
    font-size: 0.88rem; font-weight: 600; color: var(--text);
}
.rl-extra input { width: 17px; height: 17px; accent-color: var(--accent); cursor: pointer; }
/* CONSENTIMIENTO de alergias (dato de salud). Solo aparece si el comensal escribe algo, así que
   quien no declara alergias ve el formulario EXACTAMENTE igual que antes. Mismo remedio que
   `.rl-extras[hidden]`: sin el `!important`, el `display` de clase ganaría al `[hidden]` del
   navegador y la caja vacía dejaría su margen en todas las webs. */
.rl-consent { display: block; margin-top: 14px; }
.rl-consent[hidden] { display: none !important; }
/* El texto es una FRASE (dice para qué se usa y remite a la privacidad), no una etiqueta corta:
   arriba la casilla y el texto alineado a su lado, sin centrarlo verticalmente — con dos o tres
   líneas, un checkbox centrado queda a media frase. */
.rl-consent-label { align-items: flex-start; font-weight: 500; font-size: 0.8rem; line-height: 1.5; }
.rl-consent-label input { margin-top: 2px; flex: 0 0 auto; }
.rl-policy { grid-column: 1 / -1; font-size: 0.78rem; line-height: 1.5; color: var(--rl-muted); margin: 14px 0 0; }
/* Honeypot: fuera de la vista y del foco, pero presente para los bots. */
.rl-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.rl-submit {
    margin-top: 16px; width: 100%; padding: 14px 22px; cursor: pointer;
    border: none; border-radius: var(--slot-radius);
    background: var(--accent); color: var(--accent-contrast);
    font: inherit; font-size: 1rem; font-weight: 800;
}
.rl-submit:disabled { opacity: .6; cursor: progress; }

/* Confirmación */
.rl-done { text-align: center; padding: 10px 0 4px; }
.rl-done-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 46px; height: 46px; border-radius: 50%; margin-bottom: 12px;
    background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent);
}
.rl-done-title { font-family: var(--font-display, var(--font)); font-size: 1.4rem; margin: 0 0 14px; }
.rl-done-data { display: grid; gap: 6px; margin: 0 auto 14px; text-align: left; max-width: 340px; }
.rl-done-row { display: flex; justify-content: space-between; gap: 12px; border-bottom: 1px dashed var(--slot-border); padding-bottom: 5px; }
.rl-done-row dt { font-size: 0.82rem; color: var(--rl-muted); font-weight: 600; margin: 0; }
.rl-done-row dd { font-size: 0.9rem; font-weight: 700; margin: 0; text-transform: capitalize; }
.rl-done-msg { font-size: 0.92rem; color: var(--rl-muted); margin: 0; }
/* Acciones sobre la reserva ya hecha (anular / reservar otra vez). Discretas a propósito: el
   protagonista del panel es la confirmación, no los botones. */
.rl-done-actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-top: 18px; }
.rl-done-actions button {
    padding: 10px 18px; cursor: pointer; font: inherit; font-size: 0.88rem; font-weight: 700;
    background: transparent; color: var(--text);
    border: 1px solid var(--slot-border); border-radius: var(--slot-radius);
}
.rl-done-actions button:hover { border-color: var(--accent); color: var(--accent); }
.rl-done-actions button:disabled { opacity: .6; cursor: progress; }

/* Navegación del asistente */
.rl-nav { display: flex; gap: 10px; justify-content: flex-end; margin-top: 18px; }
.rl-back {
    padding: 11px 20px; cursor: pointer; font: inherit; font-weight: 700;
    background: transparent; color: var(--text);
    border: 1px solid var(--slot-border); border-radius: var(--slot-radius);
}
.rl-next {
    padding: 11px 22px; cursor: pointer; font: inherit; font-weight: 800;
    background: var(--accent); color: var(--accent-contrast);
    border: none; border-radius: var(--slot-radius);
}

/* Escalones responsive por @container (NO @media): el preview móvil estrecha
   #resultado a 375px pero el viewport sigue ancho — un @media no dispararía. */
@container (max-width: 860px) {
    .rl-wrap.rl-split { grid-template-columns: 1fr; gap: 30px; }
}
@container (max-width: 560px) {
    .rl-widget { padding: 20px 16px 18px; }
    .rl-fields { grid-template-columns: 1fr; }
    .rl-row-label { min-width: 100%; }
    .rl-nav { justify-content: stretch; }
    .rl-back, .rl-next { flex: 1; }
    .rl-day { min-height: 34px; font-size: 0.82rem; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   CARRUSEL — banda rotativa a anchura completa (blocks/carrusel.js)
   ---------------------------------------------------------------------------
   Puede ocupar el puesto del hero (TIPOS_HERO en js/core/render.js), así que es
   FULL-BLEED: sin padding de sección y con la imagen a sangre. Tres alturas
   (data-altura) y cuatro variantes. La rotación la lleva Splide leyendo los
   data-splide-* del render (animations/splide-init.js); aquí solo va el aspecto.

   OJO A LA CASCADA: la altura del padding de una <section> la fija
   `#resultado section { padding-block: var(--section-pad-y, …) }` (1 id) en el
   EDITOR, y `section { padding: 140px 8% }` (1 tipo) en el PUBLICADO —dos reglas
   distintas en dos superficies distintas—. Por eso el reset de padding se declara
   DOS veces, con la especificidad justa para ganar en cada una, en vez de con un
   !important que luego nadie pueda pisar.
   ═══════════════════════════════════════════════════════════════════════════ */
section.carrusel-section,
#resultado section.carrusel-section {
    padding: 0;
    overflow: hidden;
    position: relative;
    display: block;
}

/* Alturas. `--carrusel-top` es la RESERVA del hueco del header fijo: el equivalente
   al `padding-top:180px` de .hero-section, proporcional a cada altura. Solo se aplica
   cuando el carrusel ABRE la página (ver más abajo), no cuando va a media página. */
.carrusel-section[data-altura="alto"]     { --carrusel-h: 100vh; --carrusel-top: 180px; }
.carrusel-section[data-altura="medio"]    { --carrusel-h: 60vh;  --carrusel-top: 140px; }
.carrusel-section[data-altura="compacto"] { --carrusel-h: 40vh;  --carrusel-top: 110px; }

.carrusel-splide { width: 100%; }
.carrusel-splide .splide__track { overflow: hidden; }

.carrusel-slide {
    position: relative;
    overflow: hidden;
    width: 100%;
    height: var(--carrusel-h, 100vh);
    min-height: 300px;
}

.carrusel-media { position: absolute; inset: 0; overflow: hidden; }
.carrusel-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: opacity 0.4s;
}

/* Velo de legibilidad: el titular va sobre una foto que el cliente elige, y no hay
   forma de garantizar contraste contra una imagen cualquiera. Mismo criterio que el
   .banner-overlay del banner v2. La v3 (split) NO lo lleva: ahí el texto va fuera. */
.carrusel-velo {
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(0, 0, 0, 0.28) 0%, rgba(0, 0, 0, 0.15) 40%, rgba(0, 0, 0, 0.62) 100%);
    pointer-events: none;
    z-index: 1;
}

.carrusel-caption {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 26px;
    text-align: center;
    padding: 8% 8%;
    color: #ffffff;
}

.carrusel-title {
    margin: 0;
    color: #ffffff;
    font-weight: var(--display-weight, 900);
    line-height: 1.08;
    max-width: 18ch;
    font-size: calc(clamp(2rem, 6cqw, 4.5rem) * var(--type-scale, 1));
    text-shadow: 0 2px 24px rgba(0, 0, 0, 0.35);
}
.carrusel-section[data-altura="compacto"] .carrusel-title {
    font-size: calc(clamp(1.6rem, 4cqw, 2.8rem) * var(--type-scale, 1));
}

.carrusel-cta { align-self: center; }

/* Hueco del header FIJO: solo cuando el carrusel es la primera sección de la página.
   Dos selectores porque el árbol difiere por superficie y por momento:
     · publicado ANTES de hidratar → main > section
     · preview y publicado YA hidratado → main > <envoltorio> > section
   El segundo NO nombra `.block-editor-wrapper` A PROPÓSITO: el shim que crea
   `public/public-init.js::shimBlockWrappers` es un `createElement('div')` al que solo se
   le ponen `data-anim`/`data-type`/`data-index` — **nunca esa clase** (el ejemplo de su
   comentario de cabecera sí la muestra, y ahí está la trampa). Con la clase en el selector,
   la reserva se aplicaba en el editor y se PERDÍA en el publicado en cuanto hidrataba, y
   `applyFirstSectionHeaderOffset` tampoco la compensa porque `.carrusel-section` está en
   CLASES_RESERVAN_HEADER → el header fijo tapaba el titular de la 1ª diapositiva.
   `main > :first-child > .carrusel-section` cubre las DOS superficies sin depender de él.
   En el EDITOR no hay <main> (no se aplica) y el header va en flujo: no tapa nada. */
main > .carrusel-section:first-child .carrusel-caption,
main > :first-child > .carrusel-section .carrusel-caption {
    padding-top: calc(var(--carrusel-top, 180px) + 24px);
    padding-bottom: 6%;
}

/* ── v1 · Fundido + zoom lento (Ken Burns). El zoom vive SOLO en CSS y sobre el
   `transform` de la <img>; la coreografía de entrada (variants/misc.js::carruselAnim)
   no toca esa propiedad a propósito: dos escritores sobre el mismo transform es
   exactamente cómo se congela un tween (gotcha nº18). ── */
.carrusel-section.variant-v1 .carrusel-img {
    animation: carruselKenBurns 16s ease-in-out infinite alternate;
    transform-origin: center center;
    /* Promoción explícita a su propia capa. Es una foto a pantalla completa escalando durante
       16s: sin esto el navegador puede re-rasterizarla en el hilo principal mientras crece, y
       eso cae justo encima del momento en que se están decodificando las demás diapositivas.
       La animación es infinita, así que la capa existiría igualmente; declararlo solo evita
       que la decisión dependa de la heurística del navegador. */
    will-change: transform;
}
@keyframes carruselKenBurns {
    from { transform: scale(1); }
    to   { transform: scale(1.12); }
}

/* ── v2 · Deslizamiento: composición editorial, titular anclado abajo a la izquierda
   (la v1 lo centra) y velo lateral en vez de vertical. ── */
.carrusel-section.variant-v2 .carrusel-caption {
    align-items: flex-start;
    justify-content: flex-end;
    text-align: left;
    padding-bottom: 11%;
}
.carrusel-section.variant-v2 .carrusel-cta { align-self: flex-start; }
.carrusel-section.variant-v2 .carrusel-velo {
    background: linear-gradient(90deg, rgba(0, 0, 0, 0.66) 0%, rgba(0, 0, 0, 0.22) 55%, rgba(0, 0, 0, 0) 100%);
}

/* ── v3 · Split: foto a un lado, titular y botón al otro. Sin velo (el texto NO va
   sobre la imagen) → los colores son los de la sección, no blanco forzado. ── */
.carrusel-section.variant-v3 .carrusel-slide {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: stretch;
}
.carrusel-section.variant-v3 .carrusel-media { position: relative; inset: auto; height: 100%; }
.carrusel-section.variant-v3 .carrusel-velo { display: none; }
.carrusel-section.variant-v3 .carrusel-caption {
    position: static;
    inset: auto;
    align-items: flex-start;
    text-align: left;
    padding: 7% 6%;
    color: var(--text);
    background: var(--bg);
}
.carrusel-section.variant-v3 .carrusel-title {
    color: var(--text);
    text-shadow: none;
    font-size: calc(clamp(1.8rem, 4cqw, 3.4rem) * var(--type-scale, 1));
}
.carrusel-section.variant-v3 .carrusel-cta { align-self: flex-start; }
@container (max-width: 860px) {
    .carrusel-section.variant-v3 .carrusel-slide { grid-template-columns: 1fr; grid-template-rows: 1fr auto; }
}

/* ── v4 · Miniaturas debajo. La banda cede altura a la tira sincronizada. ── */
.carrusel-section.variant-v4 .carrusel-slide {
    height: max(240px, calc(var(--carrusel-h, 100vh) - 118px));
}
.carrusel-thumbs { padding: 12px 4% 18px; background: var(--bg); }
.carrusel-thumbs .splide__track { overflow: hidden; }
/* El atenuado de las miniaturas NO activas va por `filter: brightness()` y NO por
   `opacity`, y eso es funcional, no estético: la miniatura lleva ya los atributos de
   hidratación de Unsplash (`unsplash-target` + `data-unsplash`) y `editor/unsplash.js`
   escribe `img.style.opacity = '1'` INLINE al resolver la foto — con el atenuado en
   `opacity` habría dos escritores sobre la misma propiedad y el inline (que gana) dejaría
   todas las miniaturas al 100 %, sin poder distinguir la activa. `opacity` queda entonces
   como canal EXCLUSIVO del fundido de carga (nace en 0 si la imagen es una keyword). */
.carrusel-thumb img {
    width: 100%;
    height: 74px;
    object-fit: cover;
    display: block;
    border-radius: var(--card-radius, 10px);
    filter: brightness(0.55);
    transition: filter 0.25s, transform 0.25s, opacity 0.4s;
}
/* OJO: `.carrusel-thumb` ES el propio `.splide__slide` (el render emite
   `<div class="splide__slide carrusel-thumb">`), no un hijo suyo. Con el compuesto
   `.splide__slide.is-active .carrusel-thumb img` la regla no matchea NADA y todas las
   miniaturas se quedaban atenuadas: el visitante no podía ver cuál está activa,
   que es justo para lo que existe la variante de miniaturas. */
.carrusel-thumbs .splide__slide.is-active img,
.carrusel-thumbs .splide__slide:hover img { filter: none; }
.carrusel-thumbs .splide__slide { border-color: transparent !important; }

/* ── Flechas y puntos de Splide dentro del carrusel (por defecto son grises con la
   flecha en negro: ilegibles sobre una foto). ── */
.carrusel-splide .splide__arrow {
    background: rgba(255, 255, 255, 0.16);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.28);
    width: 46px;
    height: 46px;
    opacity: 0.85;
    z-index: 3;
}
.carrusel-splide .splide__arrow svg { fill: #ffffff; }
.carrusel-splide .splide__arrow:hover:not(:disabled) { background: rgba(255, 255, 255, 0.32); opacity: 1; }
.carrusel-splide .splide__arrow--prev { left: 18px; }
.carrusel-splide .splide__arrow--next { right: 18px; }
.carrusel-splide .splide__pagination { bottom: 18px; z-index: 3; }
/* Los puntos cambian EN EL MISMO TIEMPO que cruza la diapositiva. Splide marca el punto
   activo al EMPEZAR el movimiento, así que sin transición saltaba al instante mientras la
   imagen tardaba los 800ms del fundido: durante esa ventana los puntos decían "1" y en
   pantalla seguía la 3ª. Se notaba sobre todo al dar la vuelta (3ª→1ª), porque ahí el salto
   del punto es hacia atrás y contradice lo que se ve.
   La duración y la curva las publica `splide-init.js` desde las MISMAS constantes que le pasa
   a Splide (--n3i-splide-speed/-easing), así que no pueden desincronizarse al retocar una.
   Los fallbacks cubren el caso de que Splide no llegue a montar. */
.carrusel-splide .splide__pagination__page {
    background: rgba(255, 255, 255, 0.55);
    width: 9px;
    height: 9px;
    margin: 4px;
    opacity: 1;
    transition: background var(--n3i-splide-speed, 800ms) var(--n3i-splide-easing, ease),
                transform  var(--n3i-splide-speed, 800ms) var(--n3i-splide-easing, ease);
}
.carrusel-splide .splide__pagination__page.is-active {
    background: #ffffff;
    transform: scale(1.35);
}

/* Movimiento reducido: sin transición que sincronizar, el punto cambia y punto. */
@media (prefers-reduced-motion: reduce) {
    .carrusel-splide .splide__pagination__page { transition: none; }
}

/* ── MODO EDITOR ─────────────────────────────────────────────────────────────
   El editor tiene que PARECERSE al sitio: UNA diapositiva, a su altura real y con el
   mismo layout. Antes esto era una tira horizontal de diapositivas encogidas (copiada
   de gallery-v2) que no se parecía a nada de lo que ve el visitante — y el cliente da
   por hecho que lo que edita es lo que publica.

   Splide NO se monta en el editor (splide-init.js solo monta fuera de editorMode y
   `toggleSplide` lo destruye al volver), así que la lista es un flex normal: se OCULTAN
   las diapositivas que no se están editando y la que queda ocupa el 100 % del ancho,
   igual que en el sitio. Cuál se edita lo decide la fila de pestañas de la barra del
   bloque (blocks/carrusel.js). Autoplay, flechas y puntos solo existen en "Ver Sitio"
   y en el publicado: es el comportamiento correcto, no un fallo del editor.

   `.is-editing` la pinta el render SOLO en modo editor; en el HTML publicado la clase
   viaja (compileSiteHtml re-renderiza con editorMode=true) pero es INERTE: sin
   `.editor-active` ninguna de estas reglas matchea y se ven todas las diapositivas.
   OJO: estas reglas son del CARRUSEL. Las de gallery-v2 son otras y no se tocan. ── */
.editor-active .carrusel-slide:not(.is-editing) { display: none; }
.editor-active .carrusel-splide .splide__slide { width: 100%; }
/* Sin animación en el editor: el Ken Burns de la v1 mueve la foto mientras el cliente
   escribe encima. La coreografía de entrada ya está dormida en edición. */
.editor-active .carrusel-section.variant-v1 .carrusel-img { animation: none; }
/* La tira de miniaturas de la v4 sí sigue siendo una tira (Splide tampoco la monta aquí):
   informativa, con la diapositiva en edición resaltada igual que lo estaría la activa. */
.editor-active .carrusel-thumbs .splide__list { display: flex; gap: 10px; overflow-x: auto; }
.editor-active .carrusel-thumbs .splide__slide { flex: 0 0 18%; }
.editor-active .carrusel-thumbs .splide__slide.is-editing img { filter: none; }
/* Los clones de bucle solo sirven al posicionamiento interno de Splide: nunca visibles
   (y el extractor los filtra aparte, o cada guardado multiplicaría las diapositivas). */
/* Los clones SOLO se ocultan en el editor. En el sitio publicado NO: con `type:'loop'` la
   diapositiva que Splide trae a la vista al dar la vuelta (última → primera) ES un clon, y
   ocultarlo dejaba un hueco vacío durante los 800ms de la transición. La regla venía copiada de
   gallery-v2, donde a 3 por página apenas se nota; aquí es una sola a pantalla completa. */
.editor-active .carrusel-splide .splide__slide--clone { opacity: 0 !important; pointer-events: none !important; }

/* Titular y botón VACÍOS: sin caja no habría dónde pinchar para escribirlos, y el bloque puede
   nacer vacío (props sin `slides`). Solo en el editor; el botón sin texto ni siquiera viaja al
   publicado (el render lo marca .editor-only y compileSiteHtml lo elimina). */
.editor-active .carrusel-title:empty {
    min-height: 1.1em;
    min-width: min(280px, 60%);
    border-bottom: 2px dashed rgba(255, 255, 255, 0.45);
}
.editor-active .carrusel-section.variant-v3 .carrusel-title:empty {
    border-bottom-color: color-mix(in srgb, var(--text) 40%, transparent);
}
.editor-active .carrusel-cta.is-empty {
    min-width: 130px;
    min-height: 34px;
    opacity: 0.55;
    border: 2px dashed currentColor;
}

/* Barra del EDITOR (.editor-only → compileSiteHtml la elimina). Chrome oscuro, el mismo
   lenguaje que la cabecera de tamaño de Marcas/Catálogo. Va en FLUJO (antes flotaba
   absoluta sobre la banda, justo encima del titular que el cliente está escribiendo) y
   reúne en la misma zona superior las PESTAÑAS de diapositiva y los ajustes del bloque. */
.carrusel-editor-bar {
    position: relative;
    z-index: 20;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 12px 16px;
    background: #0b1220;
    border-bottom: 1px solid rgba(255, 255, 255, 0.10);
}
/* Sobre la banda oscura, la píldora necesita destacar por CLARIDAD (su fondo propio es del
   mismo azul casi negro y desaparecería). Fuera de la barra, .carrusel-seg conserva su
   aspecto original para quien la reutilice. */
.carrusel-editor-bar .carrusel-seg {
    background: rgba(255, 255, 255, 0.07);
    border-color: rgba(255, 255, 255, 0.13);
    box-shadow: none;
}
.carrusel-seg {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    background: rgba(15, 23, 42, 0.92);
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: 24px;
    padding: 3px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.28);
}
.carrusel-seg button {
    appearance: none;
    border: none;
    background: transparent;
    color: rgba(226, 232, 240, 0.75);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.3px;
    padding: 6px 14px;
    border-radius: 20px;
    cursor: pointer;
    transition: background 0.2s, color 0.2s;
}
.carrusel-seg button:hover { color: #ffffff; background: rgba(255, 255, 255, 0.08); }
.carrusel-seg button.is-active {
    color: #ffffff;
    background: #6366f1;
    box-shadow: 0 2px 8px rgba(99, 102, 241, 0.45);
}
.carrusel-ritmo .carrusel-int-val {
    font-size: 0.68rem;
    font-weight: 800;
    color: rgba(226, 232, 240, 0.85);
    min-width: 56px;
    text-align: center;
    user-select: none;
}
.carrusel-ritmo button { padding: 6px 12px; font-size: 1rem; line-height: 1; }

/* Pestañas: una por diapositiva + el botón de añadir. La activa reusa `.is-active` (misma
   píldora índigo que el resto de segmentos: un solo lenguaje visual). Van DESPUÉS de
   `.carrusel-seg button` a propósito: misma especificidad, así que aquí manda el ORDEN
   —igual que `.carrusel-ritmo button` justo arriba—. */
.carrusel-tabs .carrusel-tab {
    min-width: 32px;
    padding: 6px 10px;
    font-variant-numeric: tabular-nums;
}
.carrusel-tabs .carrusel-tab-add {
    padding: 6px 12px;
    font-size: 1rem;
    line-height: 1;
    color: rgba(226, 232, 240, 0.9);
}
/* Borrar la diapositiva EN EDICIÓN, dentro del propio grupo de pestañas. Los iconos no
   traen width/height propios (utils/icons.js): sin esta regla el <svg> se estiraría al
   100% del botón. */
.carrusel-tabs .carrusel-slide-del,
.carrusel-tabs .carrusel-slide-img { padding: 6px 10px; }
.carrusel-tabs .carrusel-slide-del svg,
.carrusel-tabs .carrusel-slide-img svg {
    width: 14px;
    height: 14px;
    display: block;
}
.carrusel-tabs .carrusel-slide-del:hover { color: #fda4af; background: rgba(244, 63, 94, 0.18); }
.carrusel-tabs .image-remove-btn.carrusel-slide-img { color: #fb7185; }

/* Movimiento reducido: el autoplay ya se apaga en JS (splide-init.js); aquí muere el zoom. */
@media (prefers-reduced-motion: reduce) {
    .carrusel-section.variant-v1 .carrusel-img { animation: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════════════════
   BLOQUE IMAGEN — una foto suelta como sección.
   A diferencia de `banner` v2 (fondo CSS) la foto es un <img> real, así que aquí el layout
   lo gobierna la propia imagen: `altura:auto` respeta su proporción y los tres tamaños
   fijos la recortan con object-fit. El <section> no trae `padding` inline (ver la nota en
   blocks/imagen.js), por eso el aire de la v2 se pone aquí y la v1 se queda a cero.
   ═══════════════════════════════════════════════════════════════════════════════════════ */
section.imagen-section,
#resultado section.imagen-section {
    padding: 0;
    position: relative;
    display: block;
    background: var(--bg);
}
.imagen-marco { position: relative; line-height: 0; }
.imagen-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: opacity 0.4s;
}
/* ── ALTURA: DOS MODELOS, UNO POR VARIANTE ──
   Porque las dos variantes son dos cosas distintas, y meterlas en el mismo molde era el error:

     · v1 (a sangre) → es una BANDA. Elegir "compacto" es elegir una franja, así que la foto se
       RECORTA (`object-fit:cover`), igual que en el carrusel y por el mismo motivo. Los valores
       van ahora alineados con los suyos —y con su mismo suelo de 300px— porque antes eran MÁS
       agresivos sin ninguna razón (32/55/82vh contra 40/60/100vh): a igual ancho, menos alto =
       caja más apaisada = más recorte. Medido a 1920×1080 con una foto 3:2, "compacto" enseñaba
       un 27% de la foto contra el 34% del carrusel, y "alto" un 69% contra un 84%.

     · v2 (contenida) → la foto ES el objeto, no el decorado. Aquí la altura NO recorta: pone un
       TECHO y la foto ENCOGE entera hasta caber, centrada, con el fondo de la sección a los
       lados. `auto` sigue siendo "sin techo". De paso desaparece una rareza: con alto fijo, la
       caja de la v2 llegaba a ser MÁS ALTA QUE ANCHA en "alto" (1,14:1) y empezaba a recortar
       por los LADOS, así que subir la altura te quitaba ancho de foto. */
/* `auto` = manda la proporción real de la foto (sin alto fijo, sin recorte). */
.imagen-section[data-altura="auto"] .imagen-marco { height: auto; }
.imagen-section[data-altura="auto"] .imagen-img { height: auto; }
/* v1 · el alto es la VENTANA (recorta). El `:not(.variant-v2)` es lo que separa los dos
   modelos: sin él, la contenida heredaría también el `min-height` y le quedaría hueco muerto
   bajo una foto que ya no lo llena. */
.imagen-section:not(.variant-v2)[data-altura="compacto"] .imagen-marco { height: 40vh; min-height: 300px; }
.imagen-section:not(.variant-v2)[data-altura="medio"]    .imagen-marco { height: 60vh; min-height: 300px; }
.imagen-section:not(.variant-v2)[data-altura="alto"]     .imagen-marco { height: 85vh; min-height: 300px; }

/* v2 · Contenida: márgenes y esquinas redondeadas, dentro del ritmo de la página en vez de
   cortarlo. El aire vertical usa el MISMO token que el resto de secciones para no romper el
   ritmo que gradúa el slider de Espaciado. */
/* Mismo PAR de selectores que la regla base de arriba, y no `.imagen-section.variant-v2` a
   secas: `#resultado section.imagen-section{padding:0}` (1-1-1) le ganaría a una clase doble
   (0-2-0) y la v2 se quedaría pegada a las secciones vecinas — en el editor Y en el publicado,
   que también vive dentro de #resultado. */
section.imagen-section.variant-v2,
#resultado section.imagen-section.variant-v2 {
    padding-block: var(--section-pad-y, var(--section-gap, 100px));
    /* El aire LATERAL lo pone ahora la sección, no el marco. Se mudó aquí porque el marco pasa
       a ajustarse a la foto (`width:fit-content`) y un padding suyo contaría como parte de su
       caja: el botón, que se posiciona en % del MARCO, se podría soltar fuera de la imagen. */
    padding-inline: 8%;
}
.imagen-section.variant-v2 .imagen-marco {
    /* Se ajusta a la foto, para que el % del botón y los límites de `arrastre-libre.js` (que
       acota contra ESTA caja) sean los de la imagen y no los de una columna más ancha. */
    width: fit-content;
    /* `--media-w` = el ancho que el cliente le ha dado A MANO (% de la sección; lo emite el
       render, ver js/utils/dimension.js). Aquí el número NO puede ir en el eje flex —no hay
       fila que repartir, es un marco centrado— así que es un TECHO de anchura, y al encoger
       arrastra consigo al botón que va sobre la foto (se posiciona en % de ESTE marco).
       Sin el prop, el tope histórico de 1200px. */
    max-width: min(var(--media-w, 1200px), 100%);
    margin-inline: auto;
}
.imagen-section.variant-v2 .imagen-img {
    /* La foto manda: conserva su proporción y la altura solo le pone un techo (abajo). */
    width: auto;
    height: auto;
    max-width: 100%;
    border-radius: var(--card-radius, 16px);
    box-shadow: var(--shadow-card, 0 18px 50px rgba(0, 0, 0, 0.18));
}
/* El ancho puesto A MANO ya NO tiene regla propia aquí: `.imagen-marco` lleva las clases del
   contrato (`n3i-media n3i-caja`) y lo resuelve el CSS genérico del MOTOR DE DIMENSIÓN (al final
   de este fichero), que le da `width: var(--media-w) !important` y hace que la foto llene la
   caja sin techo de altura. Antes eran dos reglas propias de este bloque, con su clase
   `imagen-w-manual` en el <section>. */
.imagen-section.variant-v2[data-altura="compacto"] .imagen-img { max-height: 40vh; }
.imagen-section.variant-v2[data-altura="medio"]    .imagen-img { max-height: 60vh; }
.imagen-section.variant-v2[data-altura="alto"]     .imagen-img { max-height: 85vh; }

/* ── HUECO DEL HEADER FIJO cuando `imagen` ES LA PRIMERA SECCIÓN de la página ──
   La otra mitad de esta reserva vive en `core/render.js::CLASES_RESERVAN_HEADER`, que ahora
   incluye `.imagen-section`: sin ella, `applyFirstSectionHeaderOffset` le escribía
   `padding-top: headerH+24px` INLINE !important contra el `padding:0` de aquí, y eso en un
   bloque a sangre no es un hueco, es una BANDA de `--bg` sobre la foto (en preview y en el
   publicado). Al quitar ese offset hay que reservar el hueco donde corresponde, y eso NO es
   lo mismo en las dos variantes:

     · v1 (a sangre) → la FOTO pasa POR DEBAJO del header, igual que la del hero y la del
       carrusel: es lo natural en un full-bleed. Lo único que no puede quedar tapado es el
       BOTÓN que va sobre ella, así que se le pone un SUELO a su posición vertical.
     · v2 (contenida) → la foto va enmarcada y con márgenes; ahí sí se reserva con padding,
       porque una foto con marco medio tapada por el header no es un efecto, es un recorte.

   DOS PARES DE SELECTORES por el mismo motivo que en `.carrusel-section` (ver su bloque):
   el árbol difiere por superficie (publicado sin hidratar `main > section`; preview y
   publicado ya hidratado `main > <envoltorio> > section`, donde el envoltorio del shim de
   public-init.js NO lleva `.block-editor-wrapper`), y la variante `#resultado …` existe para
   ganarle en especificidad a `#resultado section.imagen-section.variant-v2` (1-2-1). En el
   EDITOR no hay <main> → no se aplica, y ahí el header va en flujo y no tapa nada. */
.imagen-section { --imagen-top: 140px; }

/* SOLO v1: suelo de la posición del botón. `min(--imagen-top, 88%)` acota el suelo AL PROPIO
   MARCO: en una foto baja (altura "compacto" en una pantalla corta) 140px caerían FUERA de
   la imagen y el botón se saldría. `--pos-y` sigue mandando en cuanto es mayor (el defecto
   es 88% = abajo), así que esto solo actúa cuando el cliente sube el botón a la zona que el
   header fijo tapa.
   `:not(.variant-v2)` NO es cosmético: en la v2 el `padding-top` de la regla de abajo YA baja
   el `.imagen-marco` entero por debajo del header, y `.imagen-cta-wrap` es `position:absolute`
   DENTRO de ese marco → aplicarle además el suelo cuenta el hueco DOS VECES y hunde ~100px un
   botón que el cliente colocó arriba (en un marco "compacto" lo deja pasada la mitad). Además,
   en el EDITOR no hay <main> → allí se ve donde el cliente lo dejó y en el publicado no: la
   clase exacta de desajuste que este bloque de CSS existe para cerrar. Las dos mitades de la
   decisión están arriba: v1 = suelo al botón · v2 = reserva con padding. */
main > .imagen-section:not(.variant-v2):first-child .imagen-cta-wrap,
main > :first-child > .imagen-section:not(.variant-v2) .imagen-cta-wrap {
    top: max(var(--pos-y, 88%), min(var(--imagen-top), 88%));
}

main > .imagen-section.variant-v2:first-child,
main > :first-child > .imagen-section.variant-v2,
#resultado main > .imagen-section.variant-v2:first-child,
#resultado main > :first-child > .imagen-section.variant-v2 {
    padding-top: calc(var(--imagen-top) + 24px);
}

/* Botón sobre la foto. Su POSICIÓN llega en --pos-x/--pos-y (% del marco) desde props, y se
   consume aquí y no como `left/top` inline a propósito: un inline le ganaría a la media query
   de móvil de más abajo, que es la que evita que un botón lateral se salga en pantalla
   estrecha. Los valores los escribe tanto el arrastre libre como el imán de la rejilla. */
.imagen-cta-wrap {
    position: absolute;
    left: var(--pos-x, 50%);
    top: var(--pos-y, 88%);
    transform: translate(-50%, -50%);
    z-index: 2;
    line-height: normal;
    /* `width: max-content` NO es cosmético: sin él, un absolute con solo `left` puesto tiene
       como ancho disponible LO QUE QUEDA desde `left` hasta el borde derecho del contenedor.
       Con el botón al 92% le quedaba un 8% de la foto, y el texto se partía hasta quedar en
       vertical, una letra por línea. El `translateX(-50%)` no devuelve ese espacio: se aplica
       DESPUÉS de calcular el layout. Fijando el ancho por contenido, la posición deja de
       influir en él y solo manda `max-width`. */
    width: max-content;
    max-width: min(78%, 46ch);
}
/* El botón NO puede crecer más que la foto. `.btn-action` no trae ni `max-width` ni control de
   salto de línea, así que un texto largo —lo alarga la IA, o el cliente, o una traducción al
   alemán— lo estiraba hasta desbordar la imagen por los dos lados. Se acota y se le deja
   partir en varias líneas, que es preferible a salirse. El acotado de POSICIÓN es aparte y vive
   en animations/interactive.js: se aplica tras cada render, no solo al arrastrar. */
.imagen-cta {
    max-width: 100%;
    white-space: normal;
    text-align: center;
    overflow-wrap: break-word;   /* parte una palabra kilométrica, NO letra a letra */
    /* Y TAMPOCO crece sin tope a lo ALTO. El ancho ya estaba acotado, el alto no: una ristra de
       `<br>` dentro del contenteditable estiraba el botón y, como `.imagen-cta-wrap` es
       `position:absolute` centrado con `translate(-50%,-50%)`, se salía del marco por arriba y
       por abajo (reportado: 70 `<br>` → 1298 px de botón sobre una foto de 542).
       El tope va en EM y no en %: el `position:absolute` es el WRAP, no este `<a>`, así que un
       `max-height:60%` aquí se resolvería contra un padre de altura `auto` y el navegador lo
       ignora (medido). 9em ≈ 6 líneas de botón: nunca corta un CTA legítimo —ni el alemán más
       largo— y convierte el desbordamiento en un botón grande dentro de su foto.
       Es el CINTURÓN: los saltos ya no se pueden meter (Enter bloqueado en `tools.js`) ni
       sobreviven al guardado (`extractor.js::_unaLinea`); esto cubre lo guardado de antes. */
    max-height: 9em;
    overflow: hidden;
}

/* Asa de arrastre y × de quitar: chrome de edición, aparece al acercarse al botón. */
.imagen-cta-asa,
.imagen-cta-quitar {
    position: absolute;
    top: -11px;
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, 0.22);
    background: rgba(15, 23, 42, 0.92);
    color: #e2e8f0;
    padding: 0;
    opacity: 0;
    transition: opacity 0.18s, background 0.18s;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
}
.imagen-cta-asa { left: -11px; cursor: grab; touch-action: none; }
.imagen-cta-quitar { right: -11px; cursor: pointer; }
.editor-active .imagen-cta-wrap:hover .imagen-cta-asa,
.editor-active .imagen-cta-wrap:hover .imagen-cta-quitar,
.editor-active .imagen-cta-wrap:focus-within .imagen-cta-asa,
.editor-active .imagen-cta-wrap:focus-within .imagen-cta-quitar { opacity: 1; }
.imagen-cta-quitar:hover { background: #9f1239; color: #fff; }
.imagen-cta-asa:hover { background: #4f46e5; color: #fff; }

/* Durante el gesto: sin transición (perseguiría al puntero con retardo) y el cursor de agarre
   en TODO el documento, para que no parpadee al salirse del asa mientras se arrastra. */
.imagen-cta-wrap.n3i-arrastrando { transition: none; }
.imagen-cta-wrap.n3i-arrastrando .imagen-cta-asa { cursor: grabbing; }
body.n3i-arrastrando-activo,
body.n3i-arrastrando-activo * { cursor: grabbing !important; user-select: none !important; }

/* MÓVIL: se conserva la ALTURA elegida pero el botón se recentra en horizontal. Una posición
   lateral que en escritorio queda bien puede dejar un texto largo pegado al borde en 380px, y
   eso el cliente no lo ve al colocarlo. Funciona porque arriba solo se declara la variable, no
   `left` inline: aquí la regla sí puede ganar. */
@media (max-width: 640px) {
    .imagen-cta-wrap { left: 50%; }
}

/* Píldora "Añadir botón" (editor-only): el bloque no emite ancla cuando `cta` está vacío,
   así que esta es la única puerta para crearlo. Mismo lenguaje que los "Añadir imagen" de
   before-after. */
.imagen-add-cta {
    position: absolute;
    left: 50%;
    bottom: 6%;
    transform: translateX(-50%);
    z-index: 15;
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    background: rgba(15, 23, 42, 0.85);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 30px;
    padding: 9px 18px;
    color: #f1f5f9;
    font-size: 0.8rem;
    font-weight: 700;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
    transition: background 0.2s, border-color 0.2s;
}
.imagen-add-cta:hover { background: rgba(30, 41, 59, 0.95); border-color: rgba(255, 255, 255, 0.32); }
.imagen-add-cta svg { display: block; }

/* El CSS del PANEL de altura (`.imagen-alturas` / `.imagen-altura-btn`) NO está aquí: es
   chrome del EDITOR y vive en css/editor.css. site.css se hornea entera en cada web
   publicada, y ese panel cuelga de <body> (fuera de #resultado), así que sus reglas viajaban
   al sitio del cliente para no aplicar a nada. */
/* ═══════════════════════════════════════════════════════════════════════════════════════
   MOTOR DE DIMENSIÓN — consumo de las variables + asas. POR CAPACIDAD, NO POR BLOQUE.
   ═══════════════════════════════════════════════════════════════════════════════════════
   Ésta es la otra mitad de `js/utils/dimension.js`: el número que el cliente elige no hace nada
   si nadie lo lee. Antes había TRES juegos de reglas (uno para content, otro para hero y otro
   para `imagen`) más una excepción para las asas del hero: adoptar el redimensionado en un
   bloque nuevo costaba ~40 líneas de CSS. Ahora hay UNO, y lo que decide si aplica no es qué
   bloque es sino qué CLASES emite:

     .n3i-dim    → hay REPARTO de columnas   (variables --media-flex / --text-flex)
     .n3i-dim-w  → hay ANCHO de la caja de la IMAGEN  (variable --media-w)
     .n3i-dim-h  → hay ALTO de la caja de la IMAGEN   (variable --media-h)
     .n3i-dim-t  → hay ANCHO de la caja del TEXTO     (variable --text-w)
     .n3i-fila / .n3i-texto / .n3i-media / .n3i-caja / .n3i-caja-texto → las piezas del contrato
     [data-interior="e|w"] en las dos columnas → el borde que da a la vecina: de ahí sale el
       ANCLAJE de cada caja (se pega al borde CONTRARIO, el exterior) y el signo del arrastre.

   ADOPTARLO EN OTRO BLOQUE = declararlo en `BLOQUES_REDIMENSIONABLES` + emitir esas clases +
   llamar a `asasCaja(ficha, interior)` / `asaTexto(interior)` en su render. Ni CSS ni JS nuevos.

   Las clases `n3i-dim*` SOLO se emiten cuando hay prop (`clasesDimension`), y por eso estas
   reglas pueden consumir las variables sin miedo: sin prop no se aplican y el bloque se pinta
   con su layout de siempre. Los `!important` no son pereza — pelean contra estilos INLINE del
   propio render. Y el apilado de móvil NO depende de ellos: lo impone el
   `min-width: 100% !important` de las media/container queries, que es OTRA propiedad.

   El DOBLE selector (`.n3i-dim .n3i-fila` y `.n3i-dim.n3i-fila`) es porque en `hero` la fila ES
   el `<section>` (sus dos columnas son hijas directas), y un combinador descendente no matchea
   un elemento consigo mismo. */

/* ── EL REPARTO: un solo número mueve la frontera ──
   `--media-flex: s` y `--text-flex: 100-s` son COMPLEMENTARIOS y salen del MISMO prop; con
   `flex-basis: 0` el reparto es exactamente s : (100−s). Hasta la v2 eran dos props
   independientes calculados "suponiendo el otro lado en 1": con los dos puestos a 70 el reparto
   real era 50/50 y los sliders mentían. */
.n3i-dim .n3i-fila > .n3i-media,
.n3i-dim.n3i-fila > .n3i-media { flex: var(--media-flex, 1) 1 0 !important; }
.n3i-dim .n3i-fila > .n3i-texto,
.n3i-dim.n3i-fila > .n3i-texto { flex: var(--text-flex, 1) 1 0 !important; }

/* ── LA CAJA DE LA IMAGEN: lo que se mide, se arrastra y se recorta ──
   Es la caja y no la columna: la columna puede llevar además rótulos, el `<svg>` del clip-path o
   el botón de vídeo, y medirla arrancaba el gesto con píxeles de más. `position: relative` para
   que sus asas se posicionen contra ella (todas van POR DENTRO, ver más abajo). */
.n3i-caja {
    position: relative;
    /* Cuánto se meten las asas de ESQUINA hacia dentro: la esquina redondeada muerde
       `r·(1−1/√2) ≈ 0,3·r`, así que `0,3·r + 4px` las deja visibles con cualquier radio (el
       Estudio de Tema lo sube), también con radio 0. */
    /* El fallback del radio es el MISMO que el de `border-radius: var(--card-radius, 30px)` de
       la caja del hero: sin token de tema, 24 dejaba las esquinas mordidas por el redondeo. */
    --n3i-asa-dentro: calc(var(--card-radius, 30px) * 0.3 + 4px);
}
.n3i-media { position: relative; }

/* ── LA CAJA DEL TEXTO: el otro grado de libertad, y el que el cliente pidió ──
   Envuelve titular + párrafo + CTA. Sin `--text-w` es TRANSPARENTE (ni ancho ni anclaje): la
   geometría del bloque es exactamente la de antes de existir. `position: relative` para que su
   asa se posicione contra ella y no contra la columna (que es más ancha en cuanto hay prop). */
.n3i-caja-texto { position: relative; }
.n3i-dim-t .n3i-caja-texto {
    width: var(--text-w) !important;
    max-width: 100%;
}

/* ANCHO de la caja de la imagen dentro de su hueco (su columna, o la sección entera en modo
   'marco'). Por defecto CENTRADA — que es lo que hace falta en 'marco', donde la caja flota en
   medio de la sección; en una FILA la desplaza el anclaje de abajo. */
.n3i-dim-w .n3i-caja {
    width: var(--media-w) !important;
    margin-inline: auto;
}

/* ── ANCLAJE HACIA FUERA: cada caja pegada al borde EXTERIOR de su columna ──
   Es LA regla que hace que "cada caja se mueva sola" (PDR-0k). Con la caja centrada, encogerla
   movía sus DOS bordes y el hueco aparecía por los dos lados; anclada al borde exterior —el que
   está clavado al padding de la sección— el único borde que se mueve es el que el cliente
   arrastra, y el espacio que suelta queda VACÍO junto a la vecina, que no se entera.
   `data-interior` es el borde que da a la vecina, así que el anclaje es el contrario: interior
   'e' ⇒ pegada al oeste (`margin-inline: 0 auto`) e interior 'w' ⇒ pegada al este.
   Una pareja de reglas para las dos cajas: son el mismo problema. La especificidad (0,4,0) le gana
   al `margin-inline: auto` de arriba (0,2,0) sin depender del orden del fichero.

   CADA CAJA VA GATEADA POR SU CLASE (`.n3i-dim-t` la del texto, `.n3i-dim-w` la de la imagen), y NO
   por `data-interior` a secas, que se emite siempre que el bloque sea redimensionable: un margen
   `auto` en el eje CRUZADO de un contenedor `flex-direction: column` CANCELA el `align-items:
   stretch` (es el truco de toda la vida para centrar un hijo en una columna flex). La columna de
   `content` es exactamente eso (`display:flex; flex-direction:column` inline), así que sin la
   compuerta la caja del texto de un bloque SIN NINGÚN PROP dejaba de estirarse y pasaba a
   `fit-content`: el CTA de la v3 (`align-self: flex-start`) se despegaba del borde y el panel abría
   marcando menos del 100 %. Con prop no hay diferencia: ahí la caja tiene `width` explícito y el
   `stretch` no pinta nada. Sin prop, ni una regla del motor aplica — que es la promesa de arriba. */
.n3i-dim-t .n3i-texto[data-interior="e"] > .n3i-caja-texto,
.n3i-dim-w .n3i-media[data-interior="e"] > .n3i-caja { margin-inline: 0 auto; }
.n3i-dim-t .n3i-texto[data-interior="w"] > .n3i-caja-texto,
.n3i-dim-w .n3i-media[data-interior="w"] > .n3i-caja { margin-inline: auto 0; }
/* La foto llena la caja. El TECHO de altura del bloque (las reglas `[data-altura]` de `imagen`
   v2) se RESPETA: es un mando que el cliente eligió y la ficha declara que gobierna el eje Y.
   Con el ancho fijado y el alto capado la foto se RECORTA (`object-fit: cover`), no se
   deforma; antes un `max-height: none !important` anulaba el selector de altura en silencio en
   cuanto se tocaba el ancho. */
.n3i-dim-w .n3i-caja > :is(img, video, iframe) {
    width: 100% !important;
    max-width: 100% !important;
    object-fit: cover;
}

/* ALTO propio de la caja. Sin esto, una foto con `height:auto` DERIVA su alto del ancho y
   arrastrar un lateral movería los dos ejes a la vez (que es justo lo que un editor visual no
   debe hacer). Con la caja de alto fijo + `object-fit: cover` cada eje se mueve solo — es el
   mismo molde que usan `imagen` v1 y el carrusel.
   El `> div` del selector es el envoltorio intermedio que `renderVideoCard` pone alrededor del
   <iframe> con `aspect-ratio:16/9` INLINE: sin él, el alto llenaba la caja y el vídeo se quedaba
   a 16/9 dentro (banda vacía o recorte por abajo). */
/* `min-height: 0`: el layout base de un bloque puede darle a la caja un SUELO de altura (content:
   `min-height: clamp(300px, 26vw, 480px)` para textos cortos) y `min-height` gana a `height`
   aunque éste lleve `!important`: la mitad inferior del rango del slider (120-480 px) no movía
   un píxel mientras la etiqueta viva decía lo contrario. Con alto A MANO manda el cliente. */
.n3i-dim-h .n3i-caja { height: var(--media-h) !important; min-height: 0 !important; }
.n3i-dim-h .n3i-caja > :is(img, video, iframe, div) {
    height: 100% !important;
    aspect-ratio: auto !important;
    object-fit: cover;
}
/* Con alto propio la columna deja de estirarse a la altura de la fila: la altura la manda el
   cliente y la caja se centra en su columna. */
.n3i-dim-h .n3i-fila > .n3i-media,
.n3i-dim-h.n3i-fila > .n3i-media { align-self: center !important; }

/* NARROW ("Ancho de contenido: una sola columna", knob de Ajustes de sección): ahí no hay fila
   que repartir ni hueco donde encoger la caja, así que el tamaño a mano se ignora en vez de
   pelear contra reglas `!important` que van a ganar igual. Por CAPACIDAD + knob: sirve para
   cualquier bloque que tenga los dos. */
.n3i-dim-h[data-content-width="narrow"] .n3i-caja { height: auto !important; }
/* Doble forma (descendente y `.n3i-fila` en el propio <section>): en `hero` la fila ES la
   sección y un combinador descendente no casa un elemento consigo mismo (ver arriba). */
.n3i-dim-w[data-content-width="narrow"] .n3i-fila .n3i-caja,
.n3i-dim-w[data-content-width="narrow"].n3i-fila .n3i-caja { width: 100% !important; }
.n3i-dim-t[data-content-width="narrow"] .n3i-fila .n3i-caja-texto,
.n3i-dim-t[data-content-width="narrow"].n3i-fila .n3i-caja-texto { width: 100% !important; }

/* ═══ ASAS (chrome de EDICIÓN) ═══
   Son `.editor-only`: `compileSiteHtml` las borra al publicar y
   `body:not(.editor-active) .editor-only{display:none}` las esconde en el borrador servido en
   vivo. Viven en site.css —y no en editor.css— porque se emiten DENTRO del bloque y su posición
   depende de su caja. Ninguna lleva `on*` (invariante nº13): el gesto va por delegación. */
.n3i-asa {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    opacity: 0;
    /* Invisible = INERTE. Un asa a `opacity:0` con `z-index:14` seguía recibiendo el
       `mousedown`: en `imagen` v2 la lateral se comía el arrastre del botón (`.imagen-cta-wrap`,
       z-index 2) cuando el cliente lo dejaba cerca de un borde. Las reglas de visibilidad de
       abajo vuelven a encender `pointer-events` junto con la opacidad. */
    pointer-events: none;
    z-index: 14;
    touch-action: none;   /* sin esto el navegador se queda el gesto en táctil (scroll) */
    transition: opacity 0.18s, background 0.18s, border-color 0.18s;
}
.n3i-asa svg { display: block; pointer-events: none; }

/* ── ASAS DE LA CAJA: los cuadraditos de Canva, SIEMPRE POR DENTRO del borde ──
   Blancos con borde del ACENTO DE LA SECCIÓN (`--accent`, que el render declara inline en cada
   <section> ⇒ se tiñen con la marca del cliente, no con un color fijo). Los laterales van
   ALARGADOS en su eje: la forma ya dice qué mueve el arrastre antes de tocarla.
   POR DENTRO y no a caballo del borde porque hay cajas RECORTADAS (`.hero-media-wrapper` lleva
   `overflow:hidden` + `border-radius` inline): con desplazamientos negativos, medido en Chrome,
   el centro de 5 de las 8 asas del hero no recibía el clic y las CUATRO de esquina no se veían
   en absoluto. Metiéndolas dentro vale para las dos clases de caja y desaparece la excepción. */
.n3i-asa-caja {
    width: 14px;
    height: 14px;
    border-radius: 4px;
    background: #fff;
    border: 2px solid var(--accent, #4f46e5);
    box-shadow: 0 1px 6px rgba(0, 0, 0, 0.35);
}
.n3i-asa-caja:hover { background: var(--accent, #4f46e5); }
.n3i-asa-caja[data-lado="e"],
.n3i-asa-caja[data-lado="w"] { width: 10px; height: 30px; }
.n3i-asa-caja[data-lado="n"],
.n3i-asa-caja[data-lado="s"] { width: 30px; height: 10px; }
.n3i-asa-caja[data-lado="e"] { right: 3px; }
.n3i-asa-caja[data-lado="w"] { left: 3px; }
.n3i-asa-caja[data-lado="n"] { top: 3px; }
.n3i-asa-caja[data-lado="s"] { bottom: 3px; }
.n3i-asa-caja[data-lado="nw"] { top: var(--n3i-asa-dentro); left: var(--n3i-asa-dentro); }
.n3i-asa-caja[data-lado="ne"] { top: var(--n3i-asa-dentro); right: var(--n3i-asa-dentro); }
.n3i-asa-caja[data-lado="sw"] { bottom: var(--n3i-asa-dentro); left: var(--n3i-asa-dentro); }
.n3i-asa-caja[data-lado="se"] { bottom: var(--n3i-asa-dentro); right: var(--n3i-asa-dentro); }

/* ── EL ASA DE LA CAJA DEL TEXTO: una píldora en SU borde interior ──
   Sustituye al divisor de la v3, que vivía en el hueco y movía la FRONTERA: al arrastrarlo cambiaba
   el ancho de la foto a la vez que el del texto. El cliente quería ese agarre para achicar el
   texto y nada más, así que ahora la píldora vive DENTRO de la caja del texto y solo la mueve a
   ella. Por dentro del borde (`3px`) y no a caballo, por el mismo motivo que las de la caja de la
   imagen: hay cajas recortadas y a caballo del borde el clic se pierde. */
.n3i-asa-texto {
    width: 18px;
    height: 64px;
    top: 50%;
    border-radius: 9px;
    border: 1px solid rgba(255, 255, 255, 0.22);
    background: rgba(15, 23, 42, 0.92);
    color: #e2e8f0;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
    cursor: ew-resize;
    transform: translateY(-50%);
}
.n3i-asa-texto:hover { background: #4f46e5; color: #fff; }
/* POR FUERA del borde interior de la caja del texto (no por dentro como las de la imagen): por
   dentro caía sobre las primeras/últimas letras del párrafo y el clic para poner el cursor
   arrancaba un redimensionado. La caja del texto no se recorta, y hacia ese lado está el hueco
   entre columnas: hay sitio. */
.n3i-asa-texto[data-lado="e"] { right: -22px; }
.n3i-asa-texto[data-lado="w"] { left: -22px; }
/* PUENTE invisible entre la caja y la píldora. La píldora vive 4 px POR FUERA de la caja y se
   revela con el hover de la caja: al cruzar ese hueco el puntero salía de la caja, la píldora
   se apagaba (`pointer-events:none`) y desaparecía justo antes de poder agarrarla. El `::before`
   prolonga la zona de hover de la propia píldora hasta el borde de la caja (y algo hacia dentro),
   y como el asa es hija de la caja, el hover sobre ella mantiene a la caja en hover. */
.n3i-asa-texto::before {
    content: '';
    position: absolute;
    top: -10px;
    bottom: -10px;
    /* 18 px de píldora + 4 px de hueco + 6 px que entran en la caja (sobre el margen del párrafo,
       nunca sobre las letras: la caja del texto no lleva padding y el hueco es hacia fuera). */
    width: 28px;
}
.n3i-asa-texto[data-lado="e"]::before { right: 0; }
.n3i-asa-texto[data-lado="w"]::before { left: 0; }

/* CURSORES por lado. Ya NO dependen de la variante del bloque: el LADO viaja en el `data-lado`
   que emite el render, que es el mismo del que el gesto saca los signos — una sola verdad para
   posición y matemática. */
.n3i-asa-caja[data-lado="e"],
.n3i-asa-caja[data-lado="w"] { top: 50%; transform: translateY(-50%); cursor: ew-resize; }
.n3i-asa-caja[data-lado="n"],
.n3i-asa-caja[data-lado="s"] { left: 50%; transform: translateX(-50%); cursor: ns-resize; }
.n3i-asa-caja[data-lado="nw"],
.n3i-asa-caja[data-lado="se"] { cursor: nwse-resize; }
.n3i-asa-caja[data-lado="ne"],
.n3i-asa-caja[data-lado="sw"] { cursor: nesw-resize; }

/* ── VISIBILIDAD: CADA CAJA ENSEÑA LAS SUYAS ──
   Las de la imagen salen con el hover de SU caja; la del texto, con el de la suya. Ya no hay nada
   en el hueco, así que tampoco hay parpadeo al cruzarlo (era el motivo de que el divisor colgara
   del hover de la fila entera). Todas las asas van POR DENTRO de su caja, así que agarrar una
   nunca saca el puntero del elemento que las revela. SIN `:focus-within`: las asas son puntero
   puro (`tabindex="-1"`), así que el foco solo podía venir de un `contenteditable` — y entonces el
   asa se plantaba en medio del titular mientras el cliente lo escribía. */
.editor-active .n3i-caja:hover .n3i-asa-caja,
.editor-active .n3i-caja-texto:hover .n3i-asa-texto { opacity: 1; pointer-events: auto; }
/* La píldora del texto queda AGARRABLE aunque el puntero salga de la caja: sin esto, en cuanto
   el hover de la caja se pierde, `pointer-events:none` vuelve y el asa se esfuma bajo el cursor.
   La zona sensible es la del puente de arriba; sigue invisible fuera del hover de la caja. */
.editor-active .n3i-caja-texto .n3i-asa-texto { pointer-events: auto; }

/* Y mientras dura el gesto, SOLO las del elemento activo: los listeners van en `window`, así que
   el puntero puede salir del bloque sin soltar; sin esto el asa que el cliente sujeta
   desaparecía a mitad de arrastre. `> .n3i-asa` (hijo directo) para que activar la columna por
   el divisor no encienda de paso las de la caja. La clase la pone editor/redimensionar.js. */
body.n3i-redimensionando-activo .n3i-asa-activa > .n3i-asa,
body.n3i-redimensionando-activo .n3i-asa.n3i-asa-activa { opacity: 1; pointer-events: auto; }

/* Durante el gesto: sin transición (perseguiría al puntero con retardo) y el cursor de
   redimensionar en TODO el documento, para que no parpadee al salirse del asa. El cursor depende
   del EJE que se está moviendo — con `ew-resize` fijo, arrastrar el borde inferior decía una cosa
   y hacía otra. Son cuatro familias (no ocho lados) porque cuatro son los cursores que existen;
   el mapa lado→familia vive en editor/dimension-editor.js. */
body.n3i-redimensionando-activo .n3i-asa-activa,
body.n3i-redimensionando-activo .n3i-asa-activa * { transition: none !important; }

/* ── SE VE LA CAJA QUE SE ESTÁ MIDIENDO ──
   Arrastrando el asa del TEXTO no se veía lo que pasaba: la caja no tiene fondo, y si el texto
   es corto no llena su ancho, así que el párrafo no se mueve y solo se veía viajar el asa. Con
   el contorno del contenedor el gesto se explica solo — que es justo lo que un editor visual
   tiene que hacer. Se pinta sobre la caja ACTIVA (la que `redimensionar.js` marca durante el
   gesto), así que vale igual para la del texto y la de la imagen.
   `outline` y no `border`: no ocupa espacio, así que no altera la medida que se está tomando.
   Va con `!important` porque compite con el `outline` del propio wrapper del editor. */
body.n3i-redimensionando-activo :is(.n3i-caja-texto, .n3i-caja).n3i-asa-activa {
    outline: 2px dashed var(--accent, #4f46e5) !important;
    outline-offset: 2px;
    background-color: color-mix(in srgb, var(--accent, #4f46e5) 6%, transparent);
    border-radius: 4px;
}

/* Y un adelanto tenue al posarse en el asa, ANTES de arrastrar: dice qué caja se va a mover.
   Cuelga del hover del ASA (no del de la caja) a propósito: con el hover de la caja el contorno
   saldría con solo pasar por encima del párrafo, que es lo que el cliente hace para editarlo. */
.editor-active .n3i-caja-texto:has(> .n3i-asa-texto:hover) {
    outline: 1px dashed color-mix(in srgb, var(--accent, #4f46e5) 55%, transparent);
    outline-offset: 2px;
    border-radius: 4px;
}
body.n3i-redimensionando-activo,
body.n3i-redimensionando-activo * { user-select: none !important; }
body.n3i-cursor-ew, body.n3i-cursor-ew * { cursor: ew-resize !important; }
body.n3i-cursor-ns, body.n3i-cursor-ns * { cursor: ns-resize !important; }
body.n3i-cursor-nwse, body.n3i-cursor-nwse * { cursor: nwse-resize !important; }
body.n3i-cursor-nesw, body.n3i-cursor-nesw * { cursor: nesw-resize !important; }

/* NARROW y MÓVIL: aquí no hay dos columnas que repartir (el knob "Ancho de contenido" ya dijo
   'una sola columna', y por debajo de 768px el layout apila), así que las asas se DESHABILITAN.
   Y el tamaño a mano de la caja se ignora: un alto pensado para una fila de dos columnas no
   significa nada cuando el bloque apila, y una caja al 60 % de una columna que ya ocupa toda la
   pantalla es una foto a medias. Hacen falta las DOS consultas: `@media` mira el viewport y
   `@container` el ancho de `#resultado`, que es lo que estrecha el simulador del editor.
   El marco de `imagen` v2 NO entra: su ancho es un % de la SECCIÓN, que sigue existiendo igual
   en un teléfono (por eso el reset del ancho va scopeado a `.n3i-fila`). */
#resultado section[data-content-width="narrow"] .n3i-asa { display: none; }
@media (max-width: 992px) {
    .n3i-asa { display: none; }   /* sobre un layout apilado el gesto guardaría medidas sin sentido */
    .n3i-dim-h .n3i-caja { height: auto !important; }
    .n3i-fila .n3i-caja { width: 100% !important; }
    .n3i-fila .n3i-caja-texto { width: 100% !important; }
}
@container (max-width: 768px) {
    .n3i-asa { display: none; }
    .n3i-dim-h .n3i-caja { height: auto !important; }
    .n3i-fila .n3i-caja { width: 100% !important; }
    .n3i-fila .n3i-caja-texto { width: 100% !important; }
}

/* ── LOGO DEL HEADER A MANO: tope en MÓVIL (utils/logo-header.js::LOGO_ALTO_MOVIL = 56) ──
   Solo para el logo que el cliente redimensionó con el asa (`img.logo-a-mano`); sin prop no se
   toca nada. Tres superficies, como el resto del header: el lienzo del editor (@container: ahí el
   header va en flujo dentro de #resultado), el simulador móvil de «Ver sitio» (body.mobile-view)
   y el publicado (@media). */
@container (max-width: 900px) { header.nav-pro img.logo-a-mano { max-height: 56px !important; } }
body.mobile-view header.nav-pro img.logo-a-mano { max-height: 56px !important; }
@media (max-width: 900px) { header.nav-pro img.logo-a-mano { max-height: 56px !important; } }
