@view-transition {
  navigation: auto;
}

/* Opcional: personalizar la transición */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.3s;
}

@import 'tailwindcss';

/* daisyUI (item 06) — SOLO el componente `drawer` para el menú mobile. `include:
   drawer` filtra el plugin a ese único componente: no trae el theme ni el reset de
   daisyUI, así que NO colisiona con la `.btn` propia del repo (que vive en @layer
   components más abajo) ni pisa nuestros tokens. drawer.css es autocontenido (overlay
   oklch, animación translate:-100%→0) y se emite en @layer utilities. Es la única
   librería nueva del sitio: excepción explícita a "no agregar librerías", acotada al
   drawer. Los tabs del panel son Alpine manual; los botones, el `.btn` del repo. */
@plugin 'daisyui' {
  include: drawer;
}

/* Tokens/colores de diseño (única fuente de verdad). Debe ir tras Tailwind
   para que el @theme extienda el theme por defecto. */
@import './global.css';

/* Fuentes self-hosteadas (Fontsource): fuera del critical path de terceros.
   Inter variable (300–700), Anton (display), DM Serif (titulares + italic). */
@import '@fontsource-variable/inter/wght.css';
@import '@fontsource/anton/latin-400.css';
@import '@fontsource/dm-serif-display/latin-400.css';
@import '@fontsource/dm-serif-display/latin-400-italic.css';

/* Fuentes que Tailwind v4 escanea para generar utilities. */
@source '../../storage/framework/views/*.php';
@source '../**/*.blade.php';
@source '../**/*.js';

/* ============================================================
   Navbar (item 11) — `position: fixed` + spacer clon, fuera del árbol del
   drawer (que ahora envuelve SOLO el header, no toda la página — legibilidad
   del DOM). El header flota sobre el hero; un spacer del alto real del navbar
   reserva su espacio en el flujo del body.

   --nav-h es la altura REAL del navbar, publicada por resources/js/nav-sticky.js
   (offsetHeight, actualizada en resize) — NUNCA un valor a ojo. El fallback
   pre-JS (~104px) evita el salto inicial antes de que el JS mida.
   ============================================================ */
:root {
  --nav-h: 130px;

  /* ---- Altura de heroes: fuente ÚNICA (ver .hero-* en @layer components) ----
     El repo no tiene tokens de tamaño custom (el @theme solo define colores), así
     que estos valores son rem (equivalente a la escala Tailwind que reemplazan).
     El markup de los heroes NO lleva utilities de altura: la altura sale de las
     clases .hero-* que tiran de estas variables, y el responsive vive acá vía las
     media queries que reasignan --hero-min-h por breakpoint. */
  --hero-h: 60dvh; /* fluido compartido (era h-[60dvh]) */
  --hero-max-h: 60rem; /* tope default (era max-h-240) */
  --hero-cap: 37.5rem; /* el "150": max-h-150 — tope de service/gallery-v2 */
  --hero-min-h: 20rem; /* min compartido service/gallery-v2 — MOBILE (base) */
  --hero-home-min: 640px; /* min del home (era min-h-175) — su valor propio */
}
/* Responsive del min compartido: crece con el viewport, siempre ≤ --hero-cap. */
@media (min-width: 640px) {
  :root {
    --hero-min-h: 26rem;
  }
}
@media (min-width: 768px) {
  :root {
    --hero-min-h: 30rem;
    --hero-home-min: 30rem;
  }
}
@media (min-width: 996px) {
  :root {
    --hero-home-min: 43.75rem;
  }
}

@media (max-width: 996px) {
  :root {
    --hero-cap: 32rem;
  }
}

/* ============================================================
   Base
   ============================================================ */
html {
  scroll-behavior: smooth;
}

body {
  background-color: var(--color-surface-2);
  color: var(--color-fg);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
}

button:not(:disabled) {
  cursor: pointer;
}

/* AlpineJS: oculta x-cloak hasta hidratar (evita flash del estado de éxito). */
[x-cloak] {
  display: none !important;
}
/* ============================================================
   Base Styles
   ============================================================ */
@layer base {
  .gradient-navbar {
    @apply bg-linear-to-r via-warm-surface via-47% to-warm-surface/70 rounded-[10px] from-warm-surface-2 from-0% to-100% backdrop-blur-sm;
  }
}
/* ============================================================
   Componentes / utilities custom
   ============================================================ */
@layer components {
  .container {
    @apply relative mx-auto max-w-360! px-6 md:px-12 lg:px-20;
  }

  /* Altura de heroes vía clases semánticas (NO utilities de altura en el HTML).
     Propiedades CSS directas que tiran de las variables de :root; el responsive
     lo dan las @media que reasignan --hero-min-h. Se escriben planas (no vía
     @apply) a propósito: igual que .hero-tint/.hero-eyebrow, no dependen del
     tree-shaking del scanner de Tailwind (con @apply, `.hero-service` a veces no
     se emitía pese a estar en el markup). */
  .hero {
    height: var(--hero-h);
    max-height: var(--hero-max-h);
  }
  .hero-home {
    min-height: var(--hero-home-min);
  }
  .hero-service {
    min-height: var(--hero-min-h);
    max-height: var(--hero-cap);
  }
  .hero-gallery-v2 {
    min-height: var(--hero-min-h);
    max-height: var(--hero-cap);
  }

  /* Eyebrow de sección (mayúsculas + tracking). */
  .eyebrow {
    @apply text-xs font-semibold tracking-[0.18em] text-brand-light uppercase;
  }

  /* ---- Escala tipográfica de títulos: fuente ÚNICA del TAMAÑO ----
     Estas clases controlan SOLO font-size (+ line-height donde ya venía).
     Color, familia, peso, itálica y resaltes inline siguen en el markup de
     cada título: dos títulos pueden compartir tamaño y verse distintos.
     Para reajustar el responsive de todos los títulos, se edita ACÁ y en
     ningún otro lado — el markup no lleva utilities de tamaño. */

  /* Título de sección. Lo usa el componente home/section-header (que cubre la
     mayoría del sitio, incl. las páginas de servicio×localidad) y los títulos
     de sección con markup propio. */
  .section-title {
    @apply text-[1.8rem] sm:text-[2rem] leading-tight md:text-[2.6rem];
  }

  /* Subtítulo de sección: por debajo del título, por encima del body. */
  .section-subtitle {
    @apply text-2xl;
  }

  /* Título de card / ítem dentro de un grid (no es título de sección). */
  .card-title {
    @apply text-xl;
  }

  /* Título de la sección de FAQs: escala propia, a propósito más chica que
     .section-title. (La sección visible aún no está montada en ninguna vista;
     el dato y el JSON-LD sí existen — ver PageFaqMap/PageSeoService.) */
  .title-faqs {
    @apply text-3xl md:text-4xl;
  }

  /* Títulos de hero (<h1>): escala propia, mayor que la de sección. */
  .hero-title {
    @apply text-4xl md:text-5xl;
  }

  /* Hero del home: el titular que rota por slide son 3 líneas con tamaños
     distintos (serif / display mayúsculas / serif itálica). Una clase por línea.
     Ese titular es un <h2> —hay uno por slide—; el <h1> de la página es el
     encabezado sr-only del hero (ver heroes/home.blade.php). */
  .hero-title-lead {
    @apply md:text-5xl text-4xl;
  }
  .hero-title-emph {
    @apply md:text-6xl text-5xl;
  }
  .hero-title-tail {
    @apply md:text-5xl text-4xl;
  }

  /* Título de bloque de una página LEGAL (privacy policy, terms…). Escala propia,
     a propósito más chica que .section-title: un documento legal tiene muchos
     encabezados seguidos y la escala de sección los vuelve un muro. Igual que el
     resto de la capa, controla SOLO el tamaño. */
  .title-legal {
    @apply text-2xl md:text-3xl;
  }

  /* ---- Cuerpo de texto largo de las páginas legales ----
     Reemplaza al `prose` del plugin @tailwindcss/typography, que este proyecto NO
     tiene instalado (sí lo usa el privacy de Apex, de donde viene el contenido).
     Estiliza por DESCENDIENTE para que el markup quede limpio: el blade solo
     escribe <p>/<ul>/<ol>/<a> semánticos. Los <h2> llevan además .title-legal.
     Ritmo vertical y colores salen de los tokens del tema. El ANCHO no: lo pone
     el `.container` de la página, igual que el resto de las secciones. */
  .legal-prose {
    @apply text-base leading-relaxed text-fg/85;
  }
  .legal-prose h2 {
    @apply mt-12 mb-4 font-serif text-fg first:mt-0;
  }
  .legal-prose p {
    @apply mt-4;
  }
  .legal-prose ul,
  .legal-prose ol {
    @apply mt-4 space-y-2 ps-6;
  }
  .legal-prose ul {
    @apply list-disc;
  }
  .legal-prose ol {
    @apply list-decimal;
  }
  .legal-prose li::marker {
    @apply text-muted;
  }
  .legal-prose a {
    @apply text-brand-light underline underline-offset-2 transition-colors hover:text-brand-bright;
  }
  .legal-prose strong {
    @apply font-semibold text-fg;
  }

  /* Botón base + variantes. */
  .btn {
    @apply relative flex cursor-pointer items-center justify-center gap-2 rounded-[3px] px-6 py-3.5 text-base font-semibold tracking-wide uppercase transition-colors;
  }

  /* Deshabilitado (mientras el form envía): atenuado y sin reaccionar al mouse.
     `pointer-events-none` es lo que mata el hover de TODAS las variantes de color
     de una — si no, habría que anular el hover de cada `.btn-*` por separado. */
  .btn:disabled {
    @apply pointer-events-none opacity-50;
  }

  .btn-primary {
    @apply bg-brand text-cream hover:bg-brand-light;
  }

  /* CTA primario CÁLIDO de la Home (Hero, Process, Reviews, Lead Capture).
     En el Figma los CTAs principales son marrones (warm-surface), NO cyan;
     el cyan queda para tags/acentos. Texto cream. */
  .btn-warm {
    @apply bg-warm-surface text-cream hover:bg-warm-surface-2;
  }

  .btn-muted {
    @apply bg-muted/45 text-fg hover:bg-muted/60;
  }

  /* Variante outline (item 06) — usada por el botón de teléfono del drawer mobile.
     El sistema `.btn` del repo no traía outline; sigue el patrón btn + btn-<x>. */
  .btn-outline {
    @apply border border-cream/70 text-fg hover:border-cream hover:bg-cream/10;
  }

  /* CTA de teléfono gateado por CallRail: atenuado y no clickeable hasta que
     swap.js reemplaza el número. */
  .callrail-pending {
    @apply pointer-events-none opacity-50;
  }

  /* Campos de formulario reutilizables (form /estimate). */
  .field-label {
    @apply text-sm font-medium text-muted;
  }

  .field-input {
    @apply w-full rounded-md border border-line bg-surface px-4 py-3 text-[14px] text-fg transition outline-none placeholder:text-muted-2/50 focus:border-brand-light;
  }

  /* Caret ▾ para los pickers del form /estimate (Datepicker y Timepicker Preact),
     portado del .survey-caret del funnel. El <select> del Timepicker pierde su
     flecha nativa con appearance-none, y el Datepicker es un <input> de texto (nunca
     tuvo). La dibujamos como background SVG blanco a la derecha, con padding-right
     para que el texto no la pise. */
  .survey-caret {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 1rem center;
    background-size: 0.85rem;
    padding-right: 2.5rem;
  }

  /* Botón-elección del form /estimate (project type / scope): radio sr-only +
     label estilizado como botón, con estado activo por has-[:checked] (mismo
     efecto que el peer-checked de Apex, pero funciona dentro del x-for de Alpine). */
  .btn-choice {
    @apply flex cursor-pointer items-center justify-center gap-2 rounded-lg border border-line bg-surface px-4 py-2.5 text-center text-sm text-fg transition hover:bg-surface-3 has-[:checked]:border-brand-light has-[:checked]:bg-brand/15 has-[:checked]:text-brand-light;
  }

  /* Modificador de .btn-choice para opciones con descripción (tamaño del
     proyecto): apila título y descripción, alineados a la izquierda. Solo
     cambia el layout — bordes, estados y colores siguen en .btn-choice. */
  .btn-choice-stacked {
    @apply flex-col items-start justify-center gap-0.5 text-left;
  }
}

/* ============================================================
   Toasts de los forms (contact + estimate) — resources/js/toast.js.

   La librería (toastify-js) anima ENTRANDO DESDE ABAJO: su CSS deja el toast en
   `bottom: -150px` y al mostrarlo le escribe `style.bottom = "15px"` INLINE, así
   que su transición `all` termina animando esa propiedad hacia arriba.

   Acá el toast tiene que parecer que sale por el COSTADO derecho, y eso pide dos
   cosas distintas para entrar y para salir:

   • ENTRADA por @keyframes, no por transición. El elemento nace ya con la clase
     `.on` (toastify.js:106 lo construye como "toastify on …"), así que nunca
     existe un frame en el estado inicial y una transición basada en esa clase
     jamás correría. Una animación sí arranca sola al insertarse el nodo.
   • SALIDA por `:not(.on)`: al cerrarse, la lib SÍ quita la clase y espera 400ms
     antes de remover el nodo (toastify.js:319), así que ahí una transición corre.

   El `bottom` inline no se toca: es lo que apila varios toasts, y queda fuera de
   la transición a propósito — el salto vertical ocurre mientras el toast está
   fuera de la pantalla, así que no se ve.
   ============================================================ */
.toastify.toast-site {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  max-width: min(24rem, calc(100vw - 2rem));
  padding: 0.875rem 1rem;
  border: 1px solid rgb(0 0 0 / 0.08);
  /* Franja ancha: a 3px el color del estado casi no se leía. */
  border-left-width: 6px;
  border-radius: 0.5rem;
  /* Fondo CLARO a propósito, contra el tema oscuro del sitio: el toast tiene que
     leerse como una notificación que se despega de la página, no fundirse con
     ella. El texto va con el azul profundo de la marca en vez de negro puro.
     SHORTHAND obligatorio: la lib pinta un `background: linear-gradient(...)` y
     un `background-color` solo se pintaría DEBAJO de ese gradiente; el shorthand
     resetea también la imagen. */
  background: #f5f6f8;
  box-shadow: 0 12px 32px -12px rgb(0 0 0 / 0.55);
  color: var(--color-deep-1);
  font-size: 0.875rem;
  line-height: 1.4;

  /* Estado en reposo = visible: es donde queda al terminar la animación. */
  transform: translateX(0);
  opacity: 1;
  animation: toast-slide-in 0.38s cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes toast-slide-in {
  from {
    /* Fuera de pantalla: el ancho propio + los 15px del `right` de la lib. */
    transform: translateX(calc(100% + 15px));
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* Cerrado: se va por donde vino. `animation: none` cubre el caso de cerrarlo
   antes de que la entrada termine (una animación activa le ganaría a esto). */
.toastify.toast-site:not(.on) {
  animation: none;
  transform: translateX(calc(100% + 15px));
  opacity: 0;
  transition:
    transform 0.3s ease-in,
    opacity 0.25s ease-in;
}

/* Ícono + mensaje. El ícono NO se encoge aunque el texto ocupe dos líneas, y
   queda alineado con la primera (no centrado contra el bloque entero). */
.toast-site__body {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
}

/* El TAMAÑO no se define acá: lo pone el atributo width/height del SVG desde
   ICON_SIZE en resources/js/toast.js (una sola fuente). Acá solo se evita que el
   flex lo encoja y se alinea con la primera línea del texto. */
.toast-site__icon {
  flex: none;
  margin-top: 0.05rem;
}

/* Franja y ícono comparten color: son la misma señal, leída de dos formas
   (la franja se ve de reojo, el ícono dice QUÉ pasó). Tonos medios, no claros:
   sobre el fondo casi blanco del toast un verde pastel se pierde. */
.toastify.toast-site--success {
  border-left-color: #16a34a;
}

.toastify.toast-site--success .toast-site__icon {
  color: #16a34a;
}

.toastify.toast-site--error {
  border-left-color: #dc2626;
}

.toastify.toast-site--error .toast-site__icon {
  color: #dc2626;
}

.toastify.toast-site .toast-close {
  align-self: flex-start;
  color: var(--color-deep-1);
  font-size: 1.125rem;
  line-height: 1;
  opacity: 0.45;
}

.toastify.toast-site .toast-close:hover {
  opacity: 1;
}

/* Sin movimiento: entra y sale con fade, en el mismo lugar. */
@media (prefers-reduced-motion: reduce) {
  .toastify.toast-site {
    animation: toast-fade-in 0.2s ease;
  }

  .toastify.toast-site:not(.on) {
    transform: none;
    transition: opacity 0.2s ease;
  }

  @keyframes toast-fade-in {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
}

/* ============================================================
   Video-placeholder (item 4) — /estimate.
   Port del fade-in de Apex (video-display.css) SIN el flujo de IA. El poster
   (<picture>) tapa al <video> hasta que video-manager.js (flag ON) lo carga y
   le agrega .is-loaded; el poster recibe .is-hidden y se desvanece. Con la flag
   apagada estas clases nunca se aplican: se ve solo el poster. GSAP no interviene
   (transición CSS pura).
   ============================================================ */
.video-display .project-video {
  opacity: 0;
  transition: opacity 600ms ease-in-out;
}
.video-display .project-video.is-loaded {
  opacity: 1;
}
.video-display picture {
  transition: opacity 600ms ease-in-out;
}
.video-display picture.is-hidden {
  opacity: 0;
}

/* ============================================================
   Hero slider (item 01) — port del banner de Apex.
   Tinte de fondo que cambia por tema del slide (CSS :has(), sin JS),
   eyebrow con cross-fade, y bullets numerados con botón pausa/play.
   Init: resources/js/hero-slider.js · markup: heroes/home.blade.php.
   ============================================================ */

/* Anclaje del contenedor Swiper a todo el hero. swiper/css define
   `.swiper { position: relative }` con especificidad (0,1,0) que empata con las
   utilities `absolute inset-0` de Tailwind; según el orden del bundle podría
   ganar `relative` y, como los <img> del slide son `absolute` (sin altura
   intrínseca), el .swiper-wrapper colapsa a 0px (solo se vería el .hero-tint).
   Con (0,2,0) `.hero-swiper.swiper` ganamos SIEMPRE, sin depender del orden. */
.hero-swiper.swiper {
  position: absolute;
  inset: 0;
}

/* Color animable del tinte. Registrado (@property) para poder TRANSICIONAR el
   cambio cuando el navegador resuelve el :has() del slide activo. inherits:true
   → lo hereda el hijo .hero-tint que pinta el gradiente. */
@property --hero-tint-color {
  syntax: '<color>';
  inherits: true;
  initial-value: #1e779a;
}

/* El <section data-hero> de la Home lleva .banner-gradient: es ANCESTRO tanto del
   swiper (imágenes con la clase de tema) como del overlay, por eso el :has()
   vive acá. Solo define/anima el color; el gradiente se pinta en .hero-tint. */
.banner-gradient {
  transition: --hero-tint-color 350ms ease-in-out;
}
.banner-gradient:has(.swiper-slide-active img.pool) {
  --hero-tint-color: var(--color-hero-pool);
}
.banner-gradient:has(.swiper-slide-active img.patio) {
  --hero-tint-color: var(--color-hero-patio);
}
.banner-gradient:has(.swiper-slide-active img.driveway) {
  --hero-tint-color: var(--color-hero-driveway);
}
.banner-gradient:has(.swiper-slide-active img.pooldeck) {
  --hero-tint-color: var(--color-hero-pool);
}
.banner-gradient:has(.swiper-slide-active img.overlay) {
  --hero-tint-color: var(--color-hero-overlay);
}

/* Hero de galería v2 (heroes/gallery-v2.blade.php): imagen ESTÁTICA, la
   categoría activa ya la sabe PHP al renderizar (no hay slides que la
   cambien en runtime), así que en vez de :has() el tema se selecciona por
   [data-theme]. ÚNICA fuente de color real: --hero-tint-color (la misma que
   usa home). El nav/select de categoría (reusado del hero viejo, más abajo)
   sigue escrito contra --gallery-accent — en vez de duplicar el valor por
   categoría en las dos variables, --gallery-accent queda como un ALIAS de
   --hero-tint-color SOLO dentro de [data-theme] (no toca al hero viejo, que
   sigue seteando --gallery-accent inline como siempre). */
.banner-gradient[data-theme='pools'] {
  --hero-tint-color: var(--color-gallery-pools);
}
.banner-gradient[data-theme='patios'] {
  --hero-tint-color: var(--color-gallery-patios);
}
.banner-gradient[data-theme='driveways'] {
  --hero-tint-color: var(--color-gallery-driveways);
}
.banner-gradient[data-theme='concrete-overlays'] {
  --hero-tint-color: var(--color-gallery-overlays);
}
.banner-gradient[data-theme] {
  --gallery-accent: var(--hero-tint-color);
}

/* Overlay de lectura. Dos capas apiladas:
   1) wash del color de tema (identidad, morphea con --hero-tint-color);
   2) scrim de lectura NEUTRO usando surface-3 (el deep más oscuro), con alta
      opacidad concentrada en la zona IZQUIERDA del texto y fade a transparent
      hacia la derecha para lucir la foto. Dimensionado para el peor caso
      (slide overlay, piso beige claro): H1/párrafo/eyebrow legibles sin tapar
      la imagen. Toda la opacidad sale de tokens vía color-mix (no hex inline). */
.hero-tint {
  /* swiper/css aplica z-index:1 a .swiper, así que el tint (z-index auto ≈0)
     quedaba TAPADO por la imagen del slide pese a venir después en el DOM. Con
     z-index:2 el scrim pinta sobre la foto; el contenido (z-10) y la paginación
     (z-20) siguen por encima del tint. position/inset explícitos para no
     depender del orden de las utilities de Tailwind frente al CSS de Swiper. */
  position: absolute;
  inset: 0;
  z-index: 2;
  background-image:
    linear-gradient(
      100deg,
      color-mix(in srgb, var(--hero-tint-color) 55%, transparent) 0%,
      color-mix(in srgb, var(--hero-tint-color) 20%, transparent) 40%,
      transparent 68%
    ),
    linear-gradient(
      100deg,
      color-mix(in srgb, var(--color-surface-3) 94%, transparent) 0%,
      color-mix(in srgb, var(--color-surface-3) 80%, transparent) 28%,
      color-mix(in srgb, var(--color-surface-3) 40%, transparent) 52%,
      transparent 74%
    );
}
@media (min-width: 1024px) {
  /* Desktop: mantenemos el scrim fuerte a la izquierda (legibilidad) pero
     corremos el fade un poco a la derecha para mostrar más foto. */
  .hero-tint {
    background-image:
      linear-gradient(
        100deg,
        color-mix(in srgb, var(--hero-tint-color) 50%, transparent) 0%,
        color-mix(in srgb, var(--hero-tint-color) 15%, transparent) 38%,
        transparent 64%
      ),
      linear-gradient(
        100deg,
        color-mix(in srgb, var(--color-surface-3) 92%, transparent) 0%,
        color-mix(in srgb, var(--color-surface-3) 74%, transparent) 26%,
        color-mix(in srgb, var(--color-surface-3) 30%, transparent) 50%,
        transparent 70%
      );
  }
}

/* Bloque de contenido que rota COMPLETO por slide (eyebrow+headline+desc+CTA):
   los bloques se apilan y solo el .is-active es visible (cross-fade por opacidad;
   el toggle por data-index lo hace hero-slider.js). El .is-active queda `relative`
   → OCUPA espacio y le da alto al wrapper `.hero-slides`; los inactivos van
   `absolute` apilados encima sin ocupar (evita que el wrapper colapse y evita
   saltos de alto entre slides de distinta longitud de texto). */
.hero-slides .hero-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity 300ms ease-in-out;
}
.hero-slides .hero-slide.is-active {
  position: relative;
  opacity: 1;
  pointer-events: auto;
}


.hero-pagination.swiper-pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 0.25rem;
  right: 0;
  bottom: 0 !important;
}

@media (min-width: 768px) {
  .hero-pagination.swiper-pagination {
    bottom: 2.5rem;
    justify-content: flex-start;
    padding-inline: 3rem;
  }
}
@media (min-width: 1024px) {
  .hero-pagination.swiper-pagination {
    padding-inline: 5rem;
  }
}
.hero-pagination .swiper-pagination-bullet {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.5rem;
  min-width: 1.5rem;
  margin: 0 !important;
  padding: 0 0.25rem;
  color: color-mix(in srgb, var(--color-fg) 60%, transparent);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 9999px;
  opacity: 1;
  font-weight: 700;
  transition:
    border-color 200ms ease,
    color 200ms ease;
}
.hero-pagination .bullet-number {
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 1;
}
.hero-pagination .bullet-icon {
  display: none;
}
.hero-pagination .swiper-pagination-bullet-active {
  cursor: pointer;
  color: color-mix(in srgb, var(--color-fg) 60%, transparent);
  border-width: 1.5px;
  border-color: color-mix(in srgb, var(--color-fg) 60%, transparent);
}
.hero-pagination .swiper-pagination-bullet-active .bullet-number {
  display: none;
}
.hero-pagination .swiper-pagination-bullet-active .bullet-icon {
  display: inline-flex;
}

/* ============================================================
   Hero de galería (item 07) — variante para /gallery/{category}.
   TODO el color-por-categoría entra como UNA custom property --gallery-accent
   (emitida por heroes/gallery.blade.php desde el token del enum GalleryCategory).
   Las reglas de acá son GENÉRICAS (category-agnostic): consumen var(--gallery-accent).
   No hay reglas por categoría ni hex literales → agregar una categoría no toca
   este archivo. Contraste con Apex, que tenía .fire-accent/.water-accent/… (una
   regla por categoría, repetida en varios bloques).
   ============================================================ */

/* Altura: acotada, deliberadamente MÁS BAJA que el hero de la Home (que ocupa
   ~viewport). El grid de fotos (items 8/9) va debajo, en @section('content'). */
.gallery-hero {
  min-height: 20rem;
}
@media (min-width: 768px) {
  .gallery-hero {
    min-height: 24rem;
  }
}

/* Fondo: gradiente derivado del acento, mezclado con la superficie oscura para
   mantener legible el texto claro. Cambiar de categoría cambia --gallery-accent
   y con él el tinte, sin tocar CSS. */
.gallery-hero-bg {
  background-image: linear-gradient(
    118deg,
    color-mix(in srgb, var(--gallery-accent) 42%, var(--color-surface)) 0%,
    color-mix(in srgb, var(--gallery-accent) 16%, var(--color-surface)) 42%,
    var(--color-surface) 78%
  );
}

/* Nav inferior por categoría (lista plana). El acento marca el activo y el hover. */
.gallery-hero-nav a {
  border-bottom: 3px solid transparent;
  padding-bottom: 0.35rem;
  transition:
    color 200ms ease,
    border-color 200ms ease;
}
.gallery-hero-nav a:hover {
  border-bottom-color: color-mix(in srgb, var(--gallery-accent) 55%, transparent);
}
.gallery-hero-nav a.is-active {
  color: var(--color-fg);
  border-bottom-color: var(--gallery-accent);
}

/* Select mobile: fondo/borde tintados por el acento de la categoría. */
.gallery-hero-select {
  background-color: color-mix(in srgb, var(--gallery-accent) 30%, var(--color-surface));
  border: 1px solid color-mix(in srgb, var(--gallery-accent) 55%, transparent);
}
.gallery-hero-select option {
  background-color: var(--color-surface);
  color: var(--color-fg);
}

/* Card del ÍNDICE de galería (gallery/index): gradiente de lectura tintado con
   el acento de su categoría — el token entra como --menu-accent inline (mismo
   puente enum→color que el mega-menú y el drawer; sin hex por categoría). */
.gallery-index-card-tint {
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--menu-accent) 75%, var(--color-deep-1)) 0%,
    color-mix(in srgb, var(--menu-accent) 30%, transparent) 40%,
    transparent 70%
  );
}

/* ============================================================
   Grid de la galería (item 09) — isla Preact (GalleryGrid.jsx).
   Masonry por CSS `columns` (cero JS de medición): las cards fluyen columna a
   columna con break-inside-avoid. El aspect-ratio de cada imagen lo reserva el
   propio <img width/height> del contrato del 08 → sin layout-shift. Las reglas
   de acá son las que Preact no puede resolver con utilities: el conteo de
   columnas responsive, el stagger de entrada y el fade-in por imagen.
   ============================================================ */

/* Contenedor multi-columna. gap = separación horizontal; el vertical lo da el
   mb-4 de cada card. */
.gallery-columns {
  column-count: 1;
  column-gap: 1rem;
}
@media (min-width: 640px) {
  .gallery-columns {
    column-count: 2;
  }
}
@media (min-width: 1024px) {
  .gallery-columns {
    column-count: 3;
  }
}

/* Entrada escalonada (decisión 4-A, sin GSAP): cada card entra con fade+subida;
   el retardo lo escala el índice (--i) que la isla emite inline. */
.gallery-card {
  animation: gallery-fade-up 500ms ease-out both;
  animation-delay: calc(var(--i, 0) * 60ms);
}

/* Fade-in por imagen al cargar (decisión 4-B) + zoom-on-hover (Tailwind
   group-hover:scale-105 en GalleryGrid.jsx). `transition: all` en vez de listar
   propiedades: cualquier cambio (opacity del fade-in, transform del hover)
   anima con la misma curva, sin que declaraciones distintas se pisen entre sí. */
.gallery-img {
  display: block;
  height: auto;
  opacity: 0;
  transition: all 500ms ease-out;
}
.gallery-img.is-loaded {
  opacity: 1;
}

/* Respeto de prefers-reduced-motion: sin desplazamiento ni retardos; las
   imágenes se muestran directamente. */
@media (prefers-reduced-motion: reduce) {
  .gallery-card {
    animation: none;
  }
  .gallery-img {
    transition: none;
    opacity: 1;
  }
}

@keyframes gallery-fade-up {
  from {
    opacity: 0;
    transform: translateY(1rem);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ============================================================
   Mega-menú de servicios (item 05) — desktop, CSS-only.
   A diferencia del navbar.js de Protexiones (getBoundingClientRect +
   reposicionamiento), acá NO hay JS de toggle ni de posición: el panel nace
   fijo desde el borde izquierdo del pill (markup: left-0 w-full) y sólo cambia
   de visibilidad por :hover / :focus-within del grupo. focus-within lo hace
   operable por teclado (Tab entra al trigger → abre → Tab recorre los links →
   Tab afuera → cierra). Markup: components/home/{nav,services-menu}.blade.php.
   El color por columna entra como UNA custom property (--menu-accent) emitida
   desde el token del enum ServiceVertical; las reglas de acá son genéricas
   (vertical-agnostic), igual que el hero de galería con --gallery-accent.
   ============================================================ */

/* Cerrado por defecto; se revela por hover/focus-within del contenedor .mega-menu.
   Transición sobre opacity/transform/visibility (no `all`) para no animar layout. */
.mega-menu-panel {
  visibility: hidden;
  opacity: 0;
  transform: translateY(0.5rem);
  transition:
    opacity 300ms ease-out,
    transform 300ms ease-out,
    visibility 300ms ease-out;
}
.mega-menu:hover .mega-menu-panel,
.mega-menu:focus-within .mega-menu-panel {
  visibility: visible;
  opacity: 1;
  transform: translateY(0);
}

/* Chevron del trigger: rota al abrir (paridad con Protexiones). */
.mega-menu-chevron {
  transition: transform 300ms ease;
}
.mega-menu:hover .mega-menu-chevron,
.mega-menu:focus-within .mega-menu-chevron {
  transform: rotate(180deg);
}

/* Columna de color: fondo suavemente tintado con el acento de su vertical sobre
   la superficie clara del panel. El acento llega como --menu-accent (inline). */
.mega-menu-col {
  background-color: color-mix(in srgb, var(--menu-accent) 20%, var(--color-cream));
}
.mega-menu-col-title {
  color: var(--menu-accent);
}

/* Ítem de servicio: hover tintado por el mismo acento; foco visible por teclado. */
.mega-menu-item {
  transition: background-color 180ms ease;
}
.mega-menu-item:hover,
.mega-menu-item:focus-visible {
  background-color: color-mix(in srgb, var(--menu-accent) 16%, transparent);
  outline: none;
}

/* ============================================================
   Drawer mobile (item 06) — port del menú mobile de Apex.
   daisyUI (include: drawer) aporta la mecánica: estructura .drawer, el slide
   del .drawer-side (translate -100%→0, .3s ease-out) y el overlay. Acá viven SOLO
   las piezas propias del panel: su fondo (decisión Q2 = tinte sólido de superficie
   + blur sutil, para legibilidad sobre la Home oscura, a diferencia del panel
   semi-transparente de Apex sobre página clara), los tabs (Alpine manual) y la
   utilidad .no-scrollbar. Los ítems de servicio/galería reusan .mega-menu-item /
   --menu-accent del item 05. Markup: components/home/mobile-drawer.blade.php.
   ============================================================ */

/* Bloqueo del scroll de la página con el drawer abierto (CSS-only, vía :has sobre el
   checkbox). Sin esto, la página de fondo conserva su scrollbar y deja ver una franja
   de ~15px a la derecha del panel fijo; al ocultar su overflow, el .drawer-side (fixed,
   width:100%) cubre el ancho completo del viewport. */
html:has(#mobileDrawer:checked) {
  overflow: hidden;
}

/* daisyUI pone el .drawer-side en overflow-y:auto al abrir, lo que agrega una SEGUNDA
   scrollbar (doble scroll) y come ancho del panel. Lo anulamos (misma especificidad
   que la regla de daisyUI, gana por orden) para que scrollee SOLO el contenido interno
   de los tabs (.no-scrollbar). Paridad con el `overflow-y: clip` que hace Apex. */
.drawer-toggle:checked ~ .drawer-side {
  overflow: hidden;
}

/* Fondo del panel: gradiente cálido (eco del pill del navbar) que baja a la
   superficie profunda. Sólido para asegurar contraste del texto claro; el blur es
   sutil (el panel es opaco, pero mantiene la intención del backdrop de Apex). */
.mobile-drawer {
  background-image: linear-gradient(
    160deg,
    color-mix(in srgb, var(--color-warm-surface-2) 55%, var(--color-surface)) 0%,
    var(--color-surface) 45%
  );
  backdrop-filter: blur(4px);
}

/* Tab del drawer: ocupa 1/3, subrayado + color de marca cuando está activo
   (.is-active lo togglea Alpine). Reemplaza el border-secondary amarillo de Apex. */
.mobile-drawer-tab {
  flex: 1 1 0%;
  padding: 0.75rem;
  text-align: center;
  font-size: 0.95rem;
  color: color-mix(in srgb, var(--color-fg) 75%, transparent);
  border-bottom: 2px solid transparent;
  transition:
    color 180ms ease,
    border-color 180ms ease;
}
.mobile-drawer-tab.is-active {
  font-weight: 600;
  color: var(--color-brand-bright);
  border-bottom-color: var(--color-brand);
}

/* Título de sección (Pools / Hardscape) tintado por el acento del vertical. */
.mobile-drawer-section-title {
  color: var(--menu-accent);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* Oculta la scrollbar del contenido de los tabs (paridad con Apex). */
.no-scrollbar {
  -ms-overflow-style: none;
  scrollbar-width: none;
}
.no-scrollbar::-webkit-scrollbar {
  display: none;
}

/* ---- Marcador de imagen PENDIENTE de reemplazo -------------------------------
   Ayuda de REVISIÓN, no contenido: marca las fotos de sección que quedaron sin
   cambiar en la corrida de reemplazo de imágenes, para detectarlas de un vistazo
   al recorrer el sitio. Lo activa `'pending' => true` en el ítem de imagen del
   $serviceData (el flag vive en la DATA, no en el markup: la misma foto se emite
   dos veces —grilla desktop + swiper móvil— y así viaja sola a ambas).

   Para darlo de baja: borrar esa key del data file. Sin tocar CSS ni componentes.

   Franja translúcida al pie (no overlay completo) para no tapar la foto que se
   está evaluando. Propiedades PLANAS a propósito, no @apply: una clase definida
   solo con @apply de utilities puede no emitirse en Tailwind v4 (le pasó a
   .hero-service). */
.is-pending-image {
  position: relative;
}

.is-pending-image::after {
  content: 'IMAGE NOT REPLACED';
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 20;
  padding: 0.35rem 0.75rem;
  background-color: rgb(180 83 9 / 0.72);
  color: #fff;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-align: center;
  text-transform: uppercase;
  pointer-events: none;
}
