@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 <h1> son 3 líneas con tamaños distintos que rotan por
     slide (serif / display mayúsculas / serif itálica). Una clase por línea. */
  .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;
  }

  .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;
  }
}

/* ============================================================
   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;
}
