/* ============================================================
   FLORENTI V16 «Nueva planta» · motion.css                (A6)
   Sistema de movimiento del storefront.

   1 reveals [data-motion]  2 .is-entering  3 .f-curtain
   4 .f-marquee  5 View Transitions MPA  6 .drift  7 reduced
   8 iconos animados .ih (itshover.com portado a CSS)

   Reglas del sistema (razonamiento en `mem:frontend/motion`):
   - Solo transform / opacity / clip-path / filter.
   - Estados ocultos SOLO bajo `html.js` y `no-preference`.
   - `animation-timeline` ignora `animation-duration`: toda regla
     ligada al scroll necesita @supports + no-preference propios.
   - El atajo `animation` reinicia `animation-timeline`: atajo
     primero y timeline/range después, en la MISMA regla.
   - `.motion-done` devuelve `transition` a components/pages.
   ============================================================ */

/* Navegación entre documentos. El orden importa: gana la última. */
@view-transition {
  navigation: auto;
}

@media (prefers-reduced-motion: reduce) {
  @view-transition {
    navigation: none;
  }
}

@layer motion {
  /* ==========================================================
     1 · REVEALS DECLARATIVOS
     Las páginas escriben data-motion="rise|fade|mask|stagger".
     Ajustables por página: --reveal-shift, --reveal-dur,
     --reveal-delay, --reveal-blur.
     ========================================================== */

  /* Camino de RESERVA (sin view() timelines): estados ocultos +
     transición, gobernados por el observador de motion.js. */
  @supports not (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    html.js [data-motion="rise"]:not(.is-visible) {
      opacity: 0;
      transform: translateY(var(--reveal-shift, 30px));
      filter: blur(var(--reveal-blur, 0px));
    }

    html.js [data-motion="fade"]:not(.is-visible) { opacity: 0; }

    /* mask recorta al MEDIO, no al contenedor: un [data-motion]
       totalmente recortado tiene intersección vacía y nunca se
       revelaría. Y tampoco a los hermanos de texto (figcaption,
       tarjetas sobre la foto): el recorte redondeado les comía las
       primeras letras por las esquinas. */
    html.js [data-motion="mask"]:not(.is-visible) > :is(img, picture, video) {
      clip-path: inset(100% 0 0 0 round var(--radius-media));
      transform: scale(1.045);
    }

    html.js [data-motion="stagger"]:not(.is-visible) > * {
      opacity: 0;
      transform: translateY(var(--reveal-shift, 22px));
    }

    html.js [data-motion="rise"]:not(.motion-done),
    html.js [data-motion="fade"]:not(.motion-done) {
      transition:
        opacity var(--reveal-dur, var(--dur-editorial)) var(--ease-out),
        filter var(--reveal-dur, var(--dur-editorial)) var(--ease-out),
        transform var(--reveal-dur, var(--dur-editorial)) var(--ease-emph);
      transition-delay: var(--reveal-delay, 0ms);
    }

    html.js [data-motion="mask"]:not(.motion-done) > :is(img, picture, video) {
      transition:
        clip-path var(--reveal-dur, var(--dur-editorial)) var(--ease-emph),
        transform calc(var(--reveal-dur, var(--dur-editorial)) * 1.3) var(--ease-out);
      transition-delay: var(--reveal-delay, 0ms);
    }

    /* motion.js pone --i (0, 1, 2…); retardo capado en 480ms. */
    html.js [data-motion="stagger"]:not(.motion-done) > * {
      transition:
        opacity var(--dur-enter) var(--ease-out),
        transform var(--dur-enter) var(--ease-emph);
      transition-delay: min(calc(var(--i, 0) * 60ms), 480ms);
    }
  }
  }

  /* Camino MODERNO: el reveal se liga al progreso de entrada del
     propio elemento, resuelto por el compositor. `view()` mide
     geometría, no intersección, así que nada puede quedarse
     invisible; y en stagger cada hijo lleva su propio timeline.
     La curva no es lineal: --ease-emph reparte el progreso del
     scroll, el elemento llega pronto y se asienta despacio. */
  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      html.js [data-motion="rise"],
      html.js [data-motion="stagger"] > * {
        animation: f-reveal-rise var(--ease-emph) both;
        animation-timeline: view();
        animation-range: entry 3% entry 46%;
      }

      html.js [data-motion="fade"] {
        animation: f-reveal-fade var(--ease-out) both;
        animation-timeline: view();
        animation-range: entry 3% entry 46%;
      }

      html.js [data-motion="mask"] > :is(img, picture, video) {
        animation: f-reveal-mask var(--ease-emph) both;
        animation-timeline: view();
        animation-range: entry 3% entry 52%;
      }

      @media (max-width: 719.98px) {
        html.js [data-motion="rise"],
        html.js [data-motion="stagger"] > *,
        html.js [data-motion="fade"],
        html.js [data-motion="mask"] > :is(img, picture, video) { animation-range: entry 4% entry 28%; }
      }

      @keyframes f-reveal-rise {
        from {
          opacity: 0;
          transform: translateY(var(--reveal-shift, 30px));
          filter: blur(var(--reveal-blur, 0px));
        }
      }

      @keyframes f-reveal-fade {
        from { opacity: 0; }
      }

      @keyframes f-reveal-mask {
        from {
          clip-path: inset(100% 0 0 0 round var(--radius-media));
          transform: scale(1.045);
        }
      }
    }
  }

  /* La máscara conserva su recorte redondeado en reposo. */
  html.js [data-motion="mask"] > :is(img, picture, video) {
    clip-path: inset(0 round var(--radius-media));
  }

  /* ==========================================================
     2 · ENTRADA DE TARJETAS NUEVAS (catálogo filtrado)
     catalog.js pone --card-index y .is-entering; motion.js
     limpia la clase al terminar (autolimpieza).
     ========================================================== */
  @media (prefers-reduced-motion: no-preference) {
    .is-entering {
      animation: f-card-enter var(--dur-enter) var(--ease-spring) backwards;
      animation-delay: min(calc(var(--card-index, 0) * 38ms), 360ms);
    }

    @keyframes f-card-enter {
      from {
        opacity: 0;
        transform: translateY(18px) scale(0.965);
        filter: blur(4px);
      }
    }
  }

  /* ==========================================================
     3 · TELÓN DE INICIO
     Lo monta motion.js solo en data-page="inicio" y nunca bajo
     reduced-motion:  .f-curtain > .f-curtain__inner >
     .f-curtain__logo + .f-curtain__rule.
     Ciclo: montar → .is-in → .is-leaving → fuera del DOM.
     ========================================================== */
  .f-curtain {
    position: fixed;
    inset: 0;
    z-index: var(--z-curtain);
    display: grid;
    place-items: center;
    overflow: hidden;
    background:
      var(--grain) 0 0 / 180px repeat,
      radial-gradient(circle at 50% 44%, rgb(196 118 140 / 26%), transparent 38%),
      radial-gradient(120% 90% at 50% 118%, rgb(164 131 87 / 16%), transparent 62%),
      var(--wine-deep);
    /* Decorativo: nunca bloquea clics ni lectores (aria-hidden
       lo pone JS). Escape lo salta vía JS. */
    pointer-events: none;
    will-change: transform;
  }

  .f-curtain::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: min(78vmin, 760px);
    aspect-ratio: 1;
    border: 1px solid rgb(216 185 156 / 20%);
    border-radius: 47% 53% 52% 48% / 54% 44% 56% 46%;
    opacity: 0;
    transform: translate(-50%, -50%) scale(.72) rotate(-16deg);
    transition: opacity 700ms var(--ease-out), transform 1100ms var(--ease-emph);
  }

  .f-curtain.is-in::before {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1) rotate(8deg);
  }

  .f-curtain.is-leaving {
    transform: translateY(-101%);
    transition: transform 600ms var(--ease-emph);
  }

  .f-curtain__inner {
    position: relative;
    z-index: 1;
    display: grid;
    justify-items: center;
    row-gap: var(--space-4);
    transition: transform 600ms var(--ease-emph);
  }

  .f-curtain__inner::before {
    content: "BOLSOS Y COMPLEMENTOS";
    color: color-mix(in srgb, var(--brass) 76%, var(--porcelain));
    font: 600 11px / 1 var(--font-sans);
    letter-spacing: 0.34em;
    opacity: 0;
    transform: translateY(10px);
    transition: opacity 420ms var(--ease-out) 100ms, transform 520ms var(--ease-emph) 100ms;
  }

  .f-curtain.is-in .f-curtain__inner::before {
    opacity: 1;
    transform: translateY(0);
  }

  /* Micro-parallax del contenido mientras el panel sube. */
  .f-curtain.is-leaving .f-curtain__inner {
    transform: translateY(14vh);
  }

  /* El logo entra desde el centro: recorte que se abre, foco que
     se resuelve. Nunca se anima letter-spacing. */
  .f-curtain__logo {
    display: block;
    width: min(74vw, 720px);
    height: auto;
    opacity: 0;
    filter: brightness(0) invert(95%) sepia(8%) blur(5px);
    clip-path: inset(0 50% 0 50%);
    transform: translateY(18px) scale(.9);
    transition:
      opacity 520ms var(--ease-out) 50ms,
      filter 620ms var(--ease-out) 50ms,
      transform 660ms var(--ease-emph) 50ms,
      clip-path 700ms var(--ease-emph) 40ms;
  }

  .f-curtain.is-in .f-curtain__logo {
    opacity: 1;
    filter: brightness(0) invert(95%) sepia(8%) blur(0);
    clip-path: inset(0 0 0 0);
    transform: translateY(0) scale(1);
  }

  .f-curtain__rule {
    width: min(210px, 34vw);
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--brass), transparent);
    transform: scaleX(0);
    transition: transform 640ms var(--ease-emph) 320ms;
  }

  .f-curtain.is-in .f-curtain__rule {
    transform: scaleX(1);
  }

  /* ==========================================================
     4 · MARQUEE
     .f-marquee > .f-marquee__track > items. motion.js clona los
     items, mide media pista (--f-marquee-shift) y pone .is-ready.
     El espaciado va EN los items, no como `gap`, para que el
     bucle sea continuo. Sin JS o reduced: fila estática.
     ========================================================== */
  .f-marquee {
    overflow: hidden;
    /* Los extremos se desvanecen en el papel en vez de cortarse. */
    mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  }

  .f-marquee__track {
    display: flex;
    align-items: center;
    width: max-content;
  }

  .f-marquee__track > * {
    flex: none;
  }

  @media (prefers-reduced-motion: no-preference) {
    .f-marquee.is-ready .f-marquee__track {
      animation: f-marquee-scroll 30s linear infinite;
    }

    .f-marquee:hover .f-marquee__track,
    .f-marquee:focus-within .f-marquee__track {
      animation-play-state: paused;
    }

    @keyframes f-marquee-scroll {
      to {
        transform: translateX(var(--f-marquee-shift, -50%));
      }
    }
  }

  /* ==========================================================
     5 · VIEW TRANSITIONS MPA
     Documento: salida corta y entrada con revelado desde abajo.
     Grupos compartidos (pdp-{id}: tarjeta → galería de producto)
     con la curva de marca. El kill de tokens.css (capa base,
     !important) gana bajo reduced-motion.
     ========================================================== */
  ::view-transition-old(root) {
    animation: f-vt-out 260ms var(--ease-in-out) both;
  }

  ::view-transition-new(root) {
    animation: f-vt-in 460ms var(--ease-emph) both;
  }

  ::view-transition-image-pair(root) { background: var(--paper); }

  ::view-transition-group(*) {
    animation-duration: 520ms;
    animation-timing-function: var(--ease-emph);
  }

  @keyframes f-vt-out {
    to {
      opacity: 0;
      transform: translateY(-14px) scale(0.994);
      filter: blur(2px);
    }
  }

  @keyframes f-vt-in {
    from {
      opacity: 0;
      clip-path: inset(0 0 100% 0);
      transform: translateY(18px) scale(1.004);
    }
    to { clip-path: inset(0 0 0 0); }
  }

  .f-page-progress {
    position: fixed;
    top: 0;
    left: 0;
    z-index: calc(var(--z-header) + 1);
    width: 100%;
    height: 2px;
    background: linear-gradient(90deg, var(--wine-deep), var(--wine) 62%, var(--brass));
    border-radius: 0 999px 999px 0;
    box-shadow: 0 0 12px -2px color-mix(in srgb, var(--wine) 62%, transparent);
    transform: scaleX(0);
    transform-origin: left center;
    pointer-events: none;
    will-change: transform;
  }

  /* Progreso resuelto por el compositor. motion.js comprueba el
     MISMO par de condiciones y sólo entonces omite su listener. */
  @supports (animation-timeline: scroll()) {
    @media (prefers-reduced-motion: no-preference) {
      .f-page-progress {
        animation: f-page-progress-grow linear both;
        animation-timeline: scroll(root block);
      }

      @keyframes f-page-progress-grow {
        from { transform: scaleX(0); }
        to { transform: scaleX(1); }
      }
    }
  }

  /* ==========================================================
     6 · PARALLAX NATIVO (scroll-driven, sin JS)
     Las páginas añaden .drift (±4%, escala 1.09) o .drift-soft
     (±2.5%, escala 1.06) a la imagen dentro de un marco que
     recorte. El media query es aquí la única protección válida.
     ========================================================== */
  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      .drift,
      .drift-soft {
        animation-timing-function: linear;
        animation-fill-mode: both;
        animation-timeline: view();
        animation-range: cover 0% cover 100%;
      }

      .drift { animation-name: f-drift; }

      .drift-soft { animation-name: f-drift-soft; }

      @keyframes f-drift {
        from { transform: scale(1.09) translateY(4%); }
        to { transform: scale(1.09) translateY(-4%); }
      }

      @keyframes f-drift-soft {
        from { transform: scale(1.06) translateY(2.5%); }
        to { transform: scale(1.06) translateY(-2.5%); }
      }
    }
  }

  /* ==========================================================
     7 · REDUCED MOTION — cierre de huecos
     tokens.css ya mata duraciones/iteraciones y los pseudos de
     View Transitions. Aquí, lo que solo existe para moverse.
     ========================================================== */
  @media (prefers-reduced-motion: reduce) {
    .f-curtain { display: none; }

    .f-marquee__track { animation: none; }

    .is-entering { animation: none; }

    /* Los iconos animados se quedan en su pose de reposo: sin la
       duración a 0.01ms el hover dejaría la forma deformada. Incluye
       el propio <svg>, porque los avisos y los errores de campo se
       animan sobre la raíz y no sobre un trazo. */
    .ih,
    .ih :is(g, path, circle) {
      animation: none !important;
      transform: none !important;
      translate: none !important;
      scale: none !important;
      rotate: none !important;
    }
  }

  /* ==========================================================
     8 · ICONOS ANIMADOS (itshover.com)
     Los originales son componentes React sobre `motion`; aquí se
     portan a CSS puro para no sumar dependencias ni JS al shell.
     Dispara el control que aloja el icono (botón o formulario),
     nunca el <svg>: el área sensible es la del control.
     Solo transform/translate/scale/rotate salvo el trazado del
     check del escudo, que necesita stroke-dashoffset sobre un
     camino de 6 unidades — es la firma del icono original.
     ========================================================== */

  .ih { overflow: visible; }

  /* `motion` transforma cada trazo en su propia caja; se replica
     para que giros y escalas salgan centrados en la forma. */
  .ih :is(g, path, circle) {
    transform-box: fill-box;
    transform-origin: 50% 50%;
    transition: transform var(--dur-ui) var(--ease-out),
                translate var(--dur-ui) var(--ease-out),
                scale var(--dur-ui) var(--ease-out),
                rotate var(--dur-ui) var(--ease-out);
  }

  /* La lupa llega con viewBox de 32: el trazo se reescala. */
  .ih-magnifier { stroke-width: 2.4; }

  /* Los iconos que construye el JS no llevan presentación en línea:
     su trazo y su tamaño de reposo se definen aquí, una sola vez. */
  .ih-lock,
  .ih-trash,
  .ih-truck-svg,
  .ih-check,
  .ih-alert,
  .ih-bag-btn,
  .ih-cart {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
  }

  .ih-trash { width: 14px; height: 14px; }
  .ih-bag-btn { width: 16px; height: 16px; }
  .ih-cart { width: 20px; height: 20px; }
  .f-card__add .ih-cart { width: 16px; height: 16px; }
  .p-producto__buybar-add .ih-cart { width: 17px; height: 17px; }
  .ih-truck-svg { width: 18px; height: 18px; }
  .ih-lock { width: 19px; height: 19px; }

  /* Los dos trazos que se dibujan van normalizados con pathLength="1"
     en el marcado: así un único @keyframes sirve a ambos largos. */
  .ih-shield__check,
  .ih-check__path {
    stroke-dasharray: 1;
    stroke-dashoffset: 0;
  }

  /* Ni el aviso ni el error de campo admiten hover: se animan al
     aparecer. El error sale de :empty (display:none), y salir de
     display:none ya reinicia la animación por sí solo. */
  .f-toast.is-in .ih-check__path { animation: ih-draw 420ms var(--ease-out); }

  .p-contacto__error svg,
  .f-toast--error svg { animation: ih-nudge 320ms var(--ease-out); }

  .f-toast svg {
    flex: 0 0 auto;
    width: 17px;
    height: 17px;
  }

  /* Piezas con bisagra: giran por su base, no por su centro. */
  .ih-lock__arc,
  .ih-trash__lid,
  .ih-trash__grip { transform-origin: 50% 100%; }

  /* El galón apunta arriba cuando la ficha está desplegada. */
  [open] > summary .ih-chev { rotate: 180deg; }

  /* El camión entra solo: aparece al conseguirse el envío gratis. */
  .ih-truck { animation: ih-drive 900ms var(--ease-out); }

  /* :active entra en el disparo y el bloque NO va dentro de
     (hover: hover): en móvil el dedo es el único puntero y sin él los
     catorce iconos quedarían congelados justo donde más se vende. */
  :is(a, button, summary, .f-header__search, .p-contacto__captcha):is(:hover, :active, :focus-visible, :focus-within) {
    /* «magnifier»: la lente rebusca. */
    .ih-magnifier__g { animation: ih-wiggle 1s var(--ease-in-out); }

    /* «heart»: doble latido. */
    .ih-heart__shape { animation: ih-beat 600ms var(--ease-out); }

    /* «travel-bag»: la tapa cede y el asa se adelanta. */
    .ih-bag__front { transform: skewX(20deg); }
    .ih-bag__el { translate: -1px 0; scale: 0.9; transform: skewX(20deg); }
    .ih-bag__back { translate: 4px 0; }

    /* «cart»: el carrito avanza, la cesta se inclina y las ruedas ruedan. */
    .ih-cart__frame { translate: 1px -1px; rotate: -3deg; }
    .ih-cart__basket { translate: 1px 1px; rotate: -2deg; }
    .ih-cart__wheel { animation: ih-cart-roll 480ms var(--ease-out); }

    /* «arrow-narrow»: la flecha asoma y vuelve. */
    .ih-arrow__l { animation: ih-slide-l 500ms var(--ease-in-out); }
    .ih-arrow__r { animation: ih-slide-r 500ms var(--ease-in-out); }

    /* «x»: las dos varas giran en sentidos opuestos. */
    .ih-x__a { rotate: 15deg; scale: 1.1; }
    .ih-x__b { rotate: -15deg; scale: 1.1; }

    /* «expand»: las esquinas huyen del centro. */
    .ih-exp__tl { translate: -2px -2px; }
    .ih-exp__tr { translate: 2px -2px; }
    .ih-exp__bl { translate: -2px 2px; }
    .ih-exp__br { translate: 2px 2px; }
    .ih-exp__c { scale: 1.08; }

    /* «shield-check»: el cuerpo respira y el check se traza. */
    .ih-shield__body { animation: ih-pop 350ms var(--ease-out); }
    .ih-shield__check { animation: ih-draw 300ms var(--ease-in-out); }

    /* «send-horizontal»: el avión sale y vuelve. */
    .ih-plane { animation: ih-fly 700ms var(--ease-out); }

    /* «down-chevron»: el galón cabecea para invitar a abrir. */
    .ih-chev { animation: ih-bob 800ms var(--ease-in-out); }

    /* «lock»: el arco se descuelga. */
    .ih-lock__arc { rotate: 40deg; translate: 3px -1.7px; }

    /* «trash»: la tapa se levanta antes de tirar. */
    .ih-trash__lid { rotate: -25deg; translate: 0 -4px; }
    .ih-trash__grip { rotate: -35deg; translate: -2px -6px; }
  }

  /* «sliders-horizontal» es el único bucle infinito: en táctil el
     hover pegajoso lo dejaría corriendo para siempre. */
  @media (hover: hover) and (pointer: fine) {
    button:hover {
      .ih-sl__a { animation: ih-slide-l 2s var(--ease-in-out) infinite; }
      .ih-sl__b { animation: ih-slide-r 2s var(--ease-in-out) 200ms infinite; }
      .ih-sl__c { animation: ih-slide-l 2s var(--ease-in-out) 400ms infinite; }
    }
  }

  @keyframes ih-wiggle {
    25% { translate: 1px -1px; rotate: -5deg; }
    50% { translate: 0 -2px; rotate: 5deg; }
    75% { translate: -1px -1px; rotate: -5deg; }
  }

  @keyframes ih-beat {
    25% { scale: 1.15; }
    50% { scale: 1; }
    75% { scale: 1.25; }
  }

  @keyframes ih-slide-l { 50% { translate: -4px 0; } }

  @keyframes ih-slide-r { 50% { translate: 4px 0; } }

  @keyframes ih-pop { 50% { scale: 1.05; } }

  @keyframes ih-draw { from { stroke-dashoffset: 1; opacity: 0; } }

  @keyframes ih-fly { 57% { translate: 2px 0; opacity: 0.7; } }

  @keyframes ih-bob { 50% { translate: 0 6px; } }

  @keyframes ih-drive { from { translate: -14px 0; opacity: 0; } }

  @keyframes ih-cart-roll {
    50% { translate: 1.5px 0; scale: 1.14; }
  }

  @keyframes ih-cart-drive {
    0% { translate: -7px 1px; rotate: -4deg; opacity: .35; }
    52% { translate: 2px -1px; rotate: 2deg; opacity: 1; }
    100% { translate: 0; rotate: 0deg; opacity: 1; }
  }

  .f-header__action.is-cart-bump .ih-cart {
    animation: ih-cart-drive 660ms var(--ease-spring);
  }

  .f-header__action.is-cart-bump .ih-cart__wheel {
    animation: ih-cart-roll 660ms var(--ease-out);
  }

  @keyframes ih-nudge { 40% { translate: 0 -2px; } }
}
