/* =====================================================================
   GREEN LUX — Estilos principales de la tienda
   Paleta y tipografías según el brief de marca.
   ===================================================================== */

/* ── Tipografía de títulos ───────────────────────────────────────────
   Se usa Clash Display, que es la que está en assets/fonts/.

   Antes aquí se declaraba "Quinn Bold" apuntando a cuatro archivos que no
   existían (.woff2, .woff, .otf y .ttf). El navegador los pedía los cuatro
   en CADA visita, se llevaba cuatro errores 404 y acababa pintando el
   sustituto de Google Fonts: el sitio nunca se vio con su propia letra.

   Si algún día se consigue Quinn Bold: deja el .woff2 en assets/fonts/ y
   cambia las dos rutas de abajo. No hace falta tocar nada más.          */
@font-face{
  font-family:"Clash Display";
  src:url("../fonts/ClashDisplay-Semibold.woff2") format("woff2");
  font-weight:600; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"Clash Display";
  src:url("../fonts/ClashDisplay-Bold.woff2") format("woff2");
  font-weight:700 900; font-style:normal; font-display:swap;
}

:root{
  --accent:       #DBFE2D;   /* Verde Green Lux (principal) */
  --accent-2:     #A725FC;   /* Púrpura Green Lux (secundario) */
  --accent-light: #E8FF6B;   /* Verde claro para hovers */
  --accent-deep:  #B4D400;
  --accent-glow:  rgba(219,254,45,.40);
  --purple-glow:  rgba(167,37,252,.40);

  --bg:        #0a0a0f;
  --bg-2:      #101017;
  --surface:   #16161f;
  --surface-2: #1e1e2a;
  --surface-3: #262636;
  --border:    rgba(255,255,255,.08);
  --border-2:  rgba(255,255,255,.16);

  --text:  #f0f0f5;
  --text2: #9090a8;
  --text3: #5a5a72;

  --font-head: "Clash Display","Arial Narrow",system-ui,sans-serif;
  --font-body: "Space Grotesk",system-ui,-apple-system,sans-serif;

  --radius: 14px;
  --radius-lg: 20px;
  --radius-sm: 9px;
  --maxw: 1240px;
  --header-h: 70px;
  --shadow: 0 24px 60px -24px rgba(0,0,0,.8);
  --shadow-glow: 0 18px 50px -18px var(--accent-glow);
  --ease: cubic-bezier(.22,.61,.36,1);
}

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

/* El atributo hidden tiene que ganar SIEMPRE.
   El navegador lo aplica con  [hidden]{display:none}  en su hoja por
   defecto, que es la de menor prioridad: en cuanto una regla nuestra le
   pone display:flex o display:block al mismo elemento, el hidden deja de
   servir y el elemento se ve igualmente. Pasó de verdad: la caja del cupón
   aplicado y la línea "Descuento −$0.00" se mostraban vacías porque ambas
   llevan display:flex. Con esta regla, poner o quitar hidden desde el
   JavaScript funciona en todo el sitio sin acordarse de nada más. */
[hidden]{ display:none !important; }
html{ scroll-behavior:smooth; -webkit-text-size-adjust:100%; }
body{
  font-family:var(--font-body); background:var(--bg); color:var(--text);
  line-height:1.6; min-height:100vh; overflow-x:hidden;
  background-image:
    radial-gradient(1200px 600px at 82% -10%, rgba(167,37,252,.10), transparent 60%),
    radial-gradient(900px 500px at 0% 8%, rgba(219,254,45,.06), transparent 55%);
  background-attachment:fixed;
}
img{ max-width:100%; display:block; }
a{ color:inherit; text-decoration:none; }
button{ font-family:inherit; cursor:pointer; }
ul{ list-style:none; }
/* El word-spacing no es un capricho: el espacio de Clash Display avanza 0.14em
   cuando el de la fuente anterior avanzaba 0.23em. En mayúsculas y con el
   letter-spacing en negativo, "DATOS DE CONTACTO" se leía "DATOSDECONTACTO".
   Con este empujón las palabras vuelven a separarse como antes. */
h1,h2,h3,h4{ font-family:var(--font-head); font-weight:800; line-height:1.03;
  letter-spacing:-.01em; word-spacing:.08em; text-transform:uppercase; }
h1{ font-size:clamp(2.3rem,5.5vw,4.2rem); }
h2{ font-size:clamp(1.7rem,3.6vw,2.8rem); }
h3{ font-size:clamp(1.1rem,2.2vw,1.5rem); }

.container{ width:100%; max-width:var(--maxw); margin-inline:auto; padding-inline:22px; }
.section{ padding-block:clamp(48px,8vw,96px); }
.eyebrow{ font-family:var(--font-body); text-transform:uppercase; letter-spacing:.28em; font-size:.72rem; font-weight:600; color:var(--accent); }
.text-muted{ color:var(--text2); }
.center{ text-align:center; }
.mono{ font-variant-numeric:tabular-nums; }
.site-main{ display:block; min-height:40vh; }

/* ===== Botones ===== */
.btn{ --py:14px; --px:26px; display:inline-flex; align-items:center; justify-content:center; gap:.5em;
  padding:var(--py) var(--px); border:1px solid transparent; border-radius:100px;
  font-weight:700; font-size:.94rem; text-transform:uppercase; letter-spacing:.01em;
  transition:transform .18s var(--ease), box-shadow .25s var(--ease), background .2s, color .2s; white-space:nowrap; }
.btn:active{ transform:translateY(1px) scale(.99); }
.btn--primary{ background:linear-gradient(180deg,var(--accent-light),var(--accent)); color:#0a0a0f; box-shadow:var(--shadow-glow); }
.btn--primary:hover{ transform:translateY(-2px); box-shadow:0 22px 55px -14px var(--accent-glow); }
.btn--ghost{ background:transparent; border-color:var(--border-2); color:var(--text); }
.btn--ghost:hover{ border-color:var(--accent); color:var(--accent); }
.btn--purple{ background:transparent; border-color:var(--accent-2); color:#d9b6ff; }
.btn--purple:hover{ background:var(--accent-2); color:#fff; transform:translateY(-2px); box-shadow:0 18px 45px -16px var(--purple-glow); }
.btn--block{ width:100%; }
.btn--sm{ --py:9px; --px:16px; font-size:.82rem; }
.btn--lg{ --py:17px; --px:34px; font-size:1rem; }
.btn[disabled]{ opacity:.45; cursor:not-allowed; transform:none; box-shadow:none; }

.pill{ display:inline-flex; align-items:center; gap:.4em; padding:6px 13px; border-radius:100px;
  border:1px solid var(--border); background:rgba(255,255,255,.03); font-size:.74rem;
  letter-spacing:.08em; text-transform:uppercase; color:var(--text2); }

/* ===== Zona del pulgar (solo móvil) =====
   En computadora nada de esto existe: la barra inferior y la barra de
   compra están ocultas y el carrusel de beneficios se mantiene igual. */
.tabbar{ display:none; }
.buybar{ display:none; }

@media (max-width:720px){
  /* El carrusel cede su lugar: en el celular esta franja es el espacio
     más valioso de la pantalla y debe llevar a comprar, no informar. */
  .benefit-bar{ display:none; }
  /* WhatsApp pasa a la barra inferior; Telegram queda en el pie. */
  .floating-contact{ display:none; }

  /* ── La zona baja, medida una sola vez ────────────────────────────────
     Aquí había cinco números sueltos —56, 58, 70, 122, 134— repartidos por
     el archivo, cada uno adivinando la altura de la barra de al lado. Ni
     uno acertaba: la barra de navegación mide 60px de alto, así que la de
     compra, colocada a 56px del suelo, se metía 4px POR DEBAJO de ella y
     perdía su borde superior; y el hueco reservado al final de la página
     se quedaba 6px corto, comiéndose la última línea de contenido.

     Ahora la altura se declara una vez y todo lo demás se calcula a partir
     de ella. Cambiar el alto de la barra ya no obliga a acordarse de otros
     cuatro sitios — que es exactamente como se descuadraron. */
  :root{
    --gl-tabbar: 60px;   /* alto real de la barra de navegación */
    --gl-buybar: 68px;   /* alto real de la barra de compra */
    --gl-suelo: env(safe-area-inset-bottom, 0px);
  }

  .tabbar{
    display:grid; grid-template-columns:repeat(4,1fr);
    position:fixed; left:0; right:0; bottom:0; z-index:120;
    background:rgba(12,12,18,.94); backdrop-filter:blur(16px);
    border-top:1px solid var(--border-2);
    /* Alto fijo para que la variable diga la verdad. Antes lo decidía el
       contenido y salían 59.89px, que es de donde venía el descuadre. */
    height:calc(var(--gl-tabbar) + var(--gl-suelo));
    padding-bottom:var(--gl-suelo);   /* iPhone con indicador de inicio */
  }

  /* ── El hueco de debajo de la barra ─────────────────────────────────────
     En un celular con la barra de direcciones ABAJO, entre nuestra barra y
     la del navegador aparecía una franja transparente por la que se veía
     pasar el contenido de la página. La barra parecía flotar a media altura
     con producto colándose por debajo.

     El motivo es que `bottom:0` ancla al borde inferior del viewport de
     maquetación, y ese borde no coincide con lo que se ve cuando el
     navegador mete su propia barra abajo: la altura visible cambia al
     desplazarse, la nuestra no, y en la diferencia queda un hueco. No hay
     una medida en CSS que dé ese hueco de forma fiable en todos los
     navegadores, así que no se intenta calcularlo.

     Se resuelve al revés: la barra prolonga su fondo hacia abajo mucho más
     de lo que ningún hueco puede medir. Lo que sobra cae fuera de la
     pantalla y no se ve; lo que haga falta, queda tapado. Es una sola regla
     y no depende de adivinar la altura de la barra de ningún navegador.

     El color va OPACO y no con la transparencia de la barra: esta franja
     existe justo para que no se vea nada a través, y `backdrop-filter` no
     alcanza a un pseudo-elemento fuera del área desenfocada. Es el mismo
     rgb(12,12,18) de arriba, sin el .94 de alfa — a simple vista, el mismo
     negro. */
  .tabbar::after{
    /* Arranca un pixel ANTES de que acabe la barra. Con `top:100%` exacto, la
       altura resuelta puede caer en 59.9 y dejar una rendija de medio pixel
       por la que se cuela una línea de contenido — que en una pantalla densa
       se ve como un hilo de color. Solapar no cuesta nada; no solapar sí. */
    content:''; position:absolute; left:0; right:0; top:calc(100% - 1px);
    height:160px; background:rgb(12,12,18);
    pointer-events:none;   /* no puede robarle el toque a nada */
  }
  .tabbar a{
    display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px;
    padding:9px 4px 8px; color:var(--text2); font-size:.66rem; font-weight:600;
    letter-spacing:.02em; transition:color .18s; min-height:var(--gl-tabbar);
  }
  .tabbar a.on{ color:var(--accent); }
  .tabbar a:active{ background:rgba(255,255,255,.05); }
  .tabbar svg{ width:22px; height:22px; fill:none; stroke:currentColor;
    stroke-width:1.9; stroke-linecap:round; stroke-linejoin:round; }
  .tabbar a.on svg{ stroke-width:2.3; }
  /* El icono del carrito necesita anclaje propio para su contador */
  .tab-ico{ position:relative; display:block; line-height:0; }
  .tab-ico .cart-badge{ position:absolute; top:-6px; right:-9px; }

  /* Barra de compra: se apila JUSTO encima de la navegación */
  .buybar{
    display:flex; align-items:center; gap:12px;
    position:fixed; left:0; right:0;
    bottom:calc(var(--gl-tabbar) + var(--gl-suelo));
    min-height:var(--gl-buybar);
    z-index:119; padding:10px 14px;
    background:rgba(12,12,18,.96); backdrop-filter:blur(16px);
    border-top:1px solid var(--border-2);
    transform:translateY(115%); transition:transform .28s var(--ease);
  }
  .buybar.visible{ transform:translateY(0); }
  .buybar-info{ flex:1; min-width:0; display:flex; flex-direction:column; }
  .buybar-precio{ font-family:var(--font-head); font-size:1.3rem; color:var(--accent); line-height:1.05; }
  /* Cantidad elegida, junto al precio: este botón la usa, así que tiene que
     decirla. Más chica que el precio para no disputarle la jerarquía. */
  .buybar-cant{ margin-left:7px; font-size:.86rem; color:var(--text2); }
  .buybar-var{ font-size:.72rem; color:var(--text2); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .buybar .btn{ flex-shrink:0; padding:13px 24px; }

}

/* ===== Barra de anuncios ===== */
.announce{ background:linear-gradient(90deg, var(--accent-2), #7d1fd1 55%, var(--accent-2));
  color:#fff; font-size:.82rem; font-weight:600; letter-spacing:.01em; }
.announce-inner{ display:flex; align-items:center; justify-content:center; gap:8px;
  min-height:36px; padding-block:8px; text-align:center; }
@media (max-width:600px){ .announce{ font-size:.76rem; } }

/* ===== Header ===== */
.site-header{ position:sticky; top:0; z-index:60; height:var(--header-h); backdrop-filter:blur(14px);
  background:linear-gradient(180deg, rgba(10,10,15,.94), rgba(10,10,15,.72)); border-bottom:1px solid var(--border); }
.header-inner{ height:100%; display:flex; align-items:center; gap:20px; }
.brand{ display:flex; align-items:center; gap:11px; flex-shrink:0; }
.brand-logo{ height:40px; width:auto; max-width:230px; object-fit:contain; }
.brand-mark{ width:38px; height:38px; object-fit:contain; }
.brand-word{ font-family:var(--font-head); font-weight:800; text-transform:uppercase; line-height:.9; font-size:1.05rem; letter-spacing:.02em; }
.brand-word span{ display:block; font-size:.56rem; letter-spacing:.32em; color:var(--text2); font-weight:600; }
/* El padding derecho (1px + 1px de borde = 2px) iguala el margen vertical del
   botón, para que el círculo quede perfectamente encajado y no "sobre" espacio. */
.header-search{ flex:1; max-width:420px; display:flex; align-items:center; background:rgba(255,255,255,.04);
  border:1px solid var(--border-2); border-radius:100px; padding:0 1px 0 16px; margin-inline:auto; }
.header-search input{ flex:1; background:transparent; border:0; color:var(--text); font-family:inherit; font-size:.9rem; padding:10px 6px; }
.header-search input:focus{ outline:none; }
.header-search button{ width:36px; height:36px; border-radius:50%; border:0; background:var(--accent); color:#0a0a0f; display:grid; place-items:center; }
.nav{ display:flex; align-items:center; gap:4px; flex-shrink:0; }
.nav a{ padding:9px 13px; border-radius:100px; font-size:.9rem; font-weight:500; color:var(--text2); transition:background .18s, color .18s; }
.nav a:hover,.nav a.active{ color:var(--text); background:rgba(255,255,255,.05); }
.header-actions{ display:flex; align-items:center; gap:10px; flex-shrink:0; }
.cart-btn{ position:relative; width:44px; height:44px; border-radius:50%; border:1px solid var(--border-2);
  background:rgba(255,255,255,.03); color:var(--text); display:inline-flex; align-items:center; justify-content:center; transition:border-color .18s, color .18s, transform .18s var(--ease); }
.cart-btn:hover{ border-color:var(--accent); color:var(--accent); }
.cart-badge{ position:absolute; top:-4px; right:-4px; min-width:20px; height:20px; padding:0 5px; background:var(--accent);
  color:#0a0a0f; border-radius:100px; font-size:.7rem; font-weight:800; display:grid; place-items:center; border:2px solid var(--bg); }
.menu-toggle{ display:none; width:44px; height:44px; border-radius:12px; border:1px solid var(--border-2); background:transparent; color:var(--text); }

/* ===== Puerta de edad ===== */
.door{ min-height:100vh; min-height:100dvh; display:grid; place-items:center; padding:28px; position:relative; overflow:hidden;
  background:radial-gradient(1100px 700px at 50% -8%, rgba(167,37,252,.20), transparent 60%),
             radial-gradient(900px 600px at 80% 110%, rgba(219,254,45,.10), transparent 55%), var(--bg); }
.door::before,.door::after{ content:""; position:absolute; border-radius:50%; filter:blur(8px); opacity:.5; z-index:0; }
.door::before{ width:220px; height:220px; right:-40px; top:12%; background:radial-gradient(circle at 35% 30%, #c98bff, var(--accent-2)); animation:floatD 8s var(--ease) infinite; }
.door::after{ width:150px; height:150px; left:-30px; bottom:14%; background:radial-gradient(circle at 35% 30%, var(--accent-light), var(--accent)); animation:floatD 7s var(--ease) 1s infinite; }
@keyframes floatD{ 0%,100%{ transform:translateY(0) } 50%{ transform:translateY(-20px) } }
.door-card{ position:relative; z-index:2; width:100%; max-width:540px; text-align:center;
  background:linear-gradient(180deg, rgba(22,22,31,.94), rgba(10,10,15,.97)); border:1px solid var(--border-2);
  border-radius:26px; padding:46px 38px 34px; box-shadow:var(--shadow); backdrop-filter:blur(10px); animation:cardIn .5s var(--ease); }
@keyframes cardIn{ from{ opacity:0; transform:translateY(16px) } }
.door-card img.iso{ width:92px; height:92px; object-fit:contain; margin:0 auto 20px; }
.door-brand{ word-spacing:.08em; font-family:var(--font-head); text-transform:uppercase; letter-spacing:.04em; line-height:.9; font-size:2rem; }
.door-brand small{ display:block; font-family:var(--font-body); font-size:.62rem; letter-spacing:.5em; color:var(--text2); margin-top:8px; font-weight:600; }
.door-badge{ display:inline-block; margin:22px 0 4px; font-family:var(--font-head); font-size:3.2rem; color:var(--accent); text-shadow:0 0 40px var(--accent-glow); }
.door-card h1{ font-size:1.5rem; }
.door-card p{ color:var(--text2); font-size:.95rem; margin:14px auto 26px; max-width:42ch; }
.door-actions{ display:grid; gap:12px; }
/* Los términos que el visitante acepta al entrar: es lo que da validez a la
   puerta 18+, así que tiene que leerse (--text3 daba 2.9:1). */
.door-legal{ font-size:.74rem; color:var(--text2); margin-top:22px; line-height:1.7; }
.door-legal a{ color:var(--accent); }
.door-foot{ position:relative; z-index:2; margin-top:22px; font-size:.76rem; color:var(--text3); text-align:center; }

/* ── El mismo aviso, pero como capa sobre la tienda ────────────────────
   index.php sigue siendo una página entera —la entrada directa al sitio—,
   y esto es el mismo aviso pintado ENCIMA de la página que el visitante
   pidió. Existe por los buscadores: antes, quien no confirmara la edad
   recibía una redirección, y un buscador nunca confirma nada, así que la
   portada, el catálogo y todas las fichas eran invisibles para Google.
   Con la capa, la página se genera completa y el aviso la tapa.

   Reutiliza .door-card entero: el aviso se ve idéntico en los dos sitios
   sin duplicar una sola regla, y el día que se retoque el diseño cambian
   los dos a la vez.

   El z-index de 200 lo pone por encima de todo lo que flota en la tienda:
   la cabecera pegajosa (60), la barra de compra del celular (119), la
   navegación inferior (120) y los avisos emergentes (200). Va empatado con
   los avisos y gana por ir después en el marcado — que no aparecen con el
   velo puesto, porque no se puede tocar nada debajo.                     */
.edad-velo{
  position:fixed; inset:0; z-index:200;
  display:grid; place-items:center;
  padding:24px; overflow-y:auto;
  background:radial-gradient(1100px 700px at 50% -8%, rgba(167,37,252,.22), transparent 60%),
             radial-gradient(900px 600px at 80% 110%, rgba(219,254,45,.10), transparent 55%),
             rgba(10,10,15,.97);
  backdrop-filter:blur(14px);
}
/* En una pantalla baja el aviso mide más que la ventana: sin esto, el
   principio de la tarjeta queda fuera y no se puede subir a leerlo. */
.edad-velo .door-card{ margin-block:auto; }

/* El bloqueo del desplazamiento lo pone el SERVIDOR en el <body>, no el
   guion: aplicado desde JavaScript, la página alcanza a moverse detrás del
   aviso antes de que la regla llegue. */
body.edad-abierta{ overflow:hidden; }

/* ===== Hero ===== */
.hero{ position:relative; padding-top:clamp(36px,6vw,72px); padding-bottom:clamp(36px,6vw,80px); overflow:hidden; }
.hero-grid{ display:grid; grid-template-columns:1.05fr .95fr; gap:48px; align-items:center; }
.hero h1 span{ color:var(--accent); text-shadow:0 0 40px var(--accent-glow); }
.hero p.lead{ font-size:1.1rem; color:var(--text2); max-width:46ch; margin-top:22px; }
.hero-cta{ display:flex; gap:14px; margin-top:32px; flex-wrap:wrap; }
.hero-highlights{ display:flex; gap:26px; margin-top:40px; flex-wrap:wrap; }
.hero-highlights div{ font-size:.86rem; color:var(--text2); display:flex; align-items:center; gap:8px; }
.hero-highlights b{ color:var(--accent); }
.hero-visual{ position:relative; aspect-ratio:1/1; display:grid; place-items:center; }
/* La imagen y el visor 3D comparten la misma celda: al aparecer el 3D no hay salto */
.hero-visual > *{ grid-area:1/1; }
.hero-orb{ position:absolute; inset:10%; border-radius:50%;
  background:radial-gradient(circle at 35% 30%, var(--accent-light), var(--accent) 45%, var(--accent-deep) 82%);
  box-shadow:0 40px 120px -20px var(--accent-glow), inset -30px -40px 80px rgba(0,0,0,.35); animation:float 7s var(--ease) infinite; }
.hero-orb::before{ content:""; position:absolute; width:15%; height:21%; top:33%; left:33%; background:#0a0a0f; border-radius:50%; box-shadow:24% 0 0 0 #0a0a0f, 48% 0 0 0 #0a0a0f; }
.hero-smile{ position:absolute; width:44%; height:22%; left:28%; top:55%; border-bottom:14px solid #0a0a0f; border-radius:0 0 100px 100px; }
.orb-purple{ position:absolute; width:32%; height:32%; right:-3%; top:-2%; border-radius:50%;
  background:radial-gradient(circle at 35% 30%, #c98bff, var(--accent-2) 55%); box-shadow:0 30px 80px -20px var(--purple-glow); animation:float 6s var(--ease) 1s infinite; }
@keyframes float{ 0%,100%{ transform:translateY(0) rotate(-1deg) } 50%{ transform:translateY(-16px) rotate(1deg) } }
.hero-badge-float{ position:absolute; bottom:8%; left:0; background:var(--surface); border:1px solid var(--border-2);
  padding:12px 18px; border-radius:100px; font-size:.86rem; font-weight:600; box-shadow:var(--shadow); }

/* ===== Carrusel fijo de beneficios (siempre en pantalla) ===== */
.benefit-bar{
  position:fixed; bottom:0; left:0; right:0; z-index:70; overflow:hidden;
  padding-block:11px;
  background:linear-gradient(90deg, rgba(10,10,15,.97), rgba(22,22,31,.97));
  border-top:1px solid rgba(219,254,45,.28);
  backdrop-filter:blur(12px);
  box-shadow:0 -12px 40px rgba(0,0,0,.55), 0 -1px 24px -6px var(--accent-glow);
}
.benefit-track{ display:flex; width:max-content; animation:benefitScroll 42s linear infinite; }
.benefit-bar:hover .benefit-track{ animation-play-state:paused; }
.benefit-item{
  display:inline-flex; align-items:center; gap:9px; padding:0 28px; white-space:nowrap;
  font-size:.85rem; color:var(--text2); border-right:1px solid var(--border);
}
.benefit-item b{ color:var(--text); font-weight:700; }
.benefit-item i{ font-style:normal; font-size:1.05rem; display:inline-flex; align-items:center; }
.benefit-icon{ width:22px; height:22px; object-fit:contain;
  filter:drop-shadow(0 2px 6px rgba(219,254,45,.35)); }
@keyframes benefitScroll{ to{ transform:translateX(-50%); } }

/* El carrusel no debe tapar el contenido */
body{ padding-bottom:46px; }

/* ===== Secciones / encabezados ===== */
.section-head{ display:flex; align-items:flex-end; justify-content:space-between; gap:20px; margin-bottom:30px; flex-wrap:wrap; }

/* ===== Grid categorías ===== */
.cat-grid{ display:grid; grid-template-columns:repeat(4,1fr); gap:18px; }
.cat-card{ padding:28px 22px; border:1px solid var(--border); border-radius:var(--radius); background:var(--surface); transition:.22s var(--ease); text-align:center; }
.cat-card:hover{ transform:translateY(-5px); border-color:var(--accent); box-shadow:var(--shadow); }
.cat-card .ci{ font-size:2.2rem; margin-bottom:12px; min-height:76px; display:grid; place-items:center; }
/* Icono 3D de categoría (PNG con fondo transparente) */
.cat-icon{ width:72px; height:72px; object-fit:contain;
  filter:drop-shadow(0 8px 18px rgba(167,37,252,.35)); transition:transform .3s var(--ease), filter .3s; }
.cat-card:hover .cat-icon{ transform:translateY(-4px) scale(1.06); filter:drop-shadow(0 12px 26px var(--accent-glow)); }
.cat-emoji{ display:inline-block; line-height:1; }
.cat-card h3{ font-size:1.05rem; }
.cat-card span{ font-size:.82rem; color:var(--text2); }

/* ===== Tarjetas de producto ===== */
.product-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(240px,1fr)); gap:22px; }
.product-grid.cols-3{ grid-template-columns:repeat(3,1fr); }
.card{ position:relative; background:linear-gradient(180deg,var(--surface),var(--bg-2)); border:1px solid var(--border);
  border-radius:var(--radius-lg); overflow:hidden; display:flex; flex-direction:column; transition:transform .25s var(--ease), border-color .25s, box-shadow .25s; }
.card:hover{ transform:translateY(-6px); border-color:var(--border-2); box-shadow:var(--shadow); }
/* Halo detrás de la foto. Las fotos de producto vienen recortadas con
   fondo transparente y varios empaques son casi negros: sobre el fondo
   oscuro del sitio se perdían. Este resplandor morado los despega sin
   meterles un recuadro, en la misma línea del resto de la marca. */
.card-media{ position:relative; aspect-ratio:1/1; overflow:hidden; display:block;
  background:radial-gradient(circle at 50% 46%, rgba(167,37,252,.20), transparent 64%), var(--bg-2); }
.card-media img{ width:100%; height:100%; object-fit:cover; transition:transform .5s var(--ease); }
.card:hover .card-media img{ transform:scale(1.05); }
/* ===== Etiquetas de producto (prismáticas con destello) =====
   Cada etiqueta se arma en tres capas dentro de la píldora:
     ::before  gradiente prismático ancho que se desplaza en bucle
     ::after   destello blanco que barre la etiqueta (igual en las tres)
     texto     encima de todo
   Ambas animaciones usan transform, así que las compone la GPU y no
   provocan repintado aunque haya varias etiquetas en pantalla.          */
.badge{
  position:relative; isolation:isolate; overflow:hidden;
  display:inline-block; font-size:.66rem; font-weight:800; letter-spacing:.08em;
  padding:5px 11px; border-radius:100px; text-transform:uppercase;
  color:#141006;                                  /* el prisma es claro: texto oscuro */
  text-shadow:0 1px 0 rgba(255,255,255,.35);
  border:1px solid rgba(255,255,255,.42);
  box-shadow:0 3px 12px -3px var(--glow, rgba(0,0,0,.5)), inset 0 1px 0 rgba(255,255,255,.55);
}
/* Capa prismática — bucle SIN costura.
   Tres condiciones tienen que cumplirse a la vez:
     1. El degradado empieza y termina en el MISMO color.
     2. Se repite en mosaico (background-repeat) en vez de estirarse una
        sola vez a lo ancho: así los bordes de cada baldosa encajan.
     3. La animación avanza EXACTAMENTE una baldosa (50% de un elemento
        del 200% = un ancho de etiqueta), de modo que el fotograma final
        es idéntico al inicial y no se percibe dónde reinicia.
   El degradado va a 90deg (horizontal puro): con inclinación, las
   baldosas quedarían desfasadas en vertical y se vería la unión. */
.badge::before{
  content:""; position:absolute; z-index:-2; top:0; bottom:0; left:0;
  /* La BALDOSA mide 3 anchos de etiqueta (66.6% de una capa del 450%).
     Cuanto más ancha, más despacio cambian los colores dentro del área
     visible: con baldosas de un solo ancho los saltos se veían duros. */
  width:450%;
  background-image:var(--prism);
  background-size:66.6667% 100%;
  background-repeat:repeat;
  animation:prismShift 13s linear infinite;
}
/* Destello — el salto de vuelta ocurre fuera de la etiqueta.
   Entre el 48% y el 100% la franja espera quieta a la derecha, ya
   invisible; cuando regresa a la izquierda tampoco se ve. Así el reinicio
   nunca cae dentro del área visible. */
.badge::after{
  content:""; position:absolute; z-index:-1; top:0; bottom:0; left:0; width:40%;
  background:linear-gradient(100deg, transparent, rgba(255,255,255,.9), transparent);
  transform:translateX(-180%);
  animation:badgeShine 4.4s cubic-bezier(.45,0,.25,1) infinite;
}
/* -66.6667% de la capa = exactamente una baldosa: el fotograma final
   coincide con el inicial y el reinicio sigue siendo imperceptible. */
@keyframes prismShift{ from{ transform:translateX(0) } to{ transform:translateX(-66.6667%) } }
@keyframes badgeShine{ 0%{ transform:translateX(-180%) } 48%,100%{ transform:translateX(340%) } }

/* Pila de etiquetas: un producto puede tener varias (NUEVO + OFERTA, etc.) */
.badge-stack{ position:absolute; top:12px; left:12px; z-index:2; display:flex; flex-direction:column; align-items:flex-start; gap:5px; }
.badge-tag{ position:absolute; top:12px; left:12px; z-index:2; }

/* El primer y el último color de cada degradado son IGUALES: es lo que
   permite que las baldosas se unan sin costura visible.
   Los tonos son vecinos entre sí (sin saltar de un claro fuerte a un
   oscuro fuerte) para que la mezcla se lea difuminada y no a bandas. */
/* TOP — oro y ámbar: "lo mejor" */
.badge-top{
  --prism:linear-gradient(90deg,#ffa62e,#ffc44a,#ffdc82,#ffe8a8,#ffcf5c,#ffb437,#ffc85a,#ffdc8e,#ffbe45,#ffa62e);
  --glow:rgba(255,168,20,.7);
}
/* NUEVO — holográfico pastel: novedad */
.badge-nuevo{
  --prism:linear-gradient(90deg,#dbfe2d,#c2ffa0,#b4ffd4,#bdf3ff,#cfdcff,#dcc9ff,#f0c6f5,#ffd2e4,#f2eab4,#dbfe2d);
  --glow:rgba(165,233,255,.6);
}
/* OFERTA — rojos y coral: rebaja */
.badge-oferta{
  --prism:linear-gradient(90deg,#ff2f52,#ff5c78,#ff7a8f,#ff5f96,#f53d76,#ff5566,#ff7d63,#ff9584,#ff5f6e,#ff2f52);
  --glow:rgba(255,31,75,.7);
  color:#fff; text-shadow:0 1px 2px rgba(90,0,16,.8);
}
/* Accesibilidad: sin movimiento si el sistema lo pide (el color se conserva) */
@media (prefers-reduced-motion: reduce){
  .badge::before{ animation:none; }
  .badge::after{ animation:none; opacity:0; }
}
.stock-tag{ position:absolute; top:12px; right:12px; z-index:2; }
.stock-tag.in{ color:var(--accent); border-color:rgba(219,254,45,.4); background:rgba(219,254,45,.08); }
.stock-tag.out{ color:#ff7a7a; border-color:rgba(255,122,122,.4); background:rgba(255,122,122,.08); }
.card-body{ padding:16px 16px 18px; display:flex; flex-direction:column; gap:7px; flex:1; }
.card-cat{ font-size:.68rem; letter-spacing:.14em; text-transform:uppercase; color:#c084fc; font-weight:700; }
.card-title{ word-spacing:.08em; font-family:var(--font-head); font-size:1.1rem; text-transform:uppercase; }
/* La descripción se recorta a 3 líneas: con el catálogo real unas fichas
   son más largas que otras y cada tarjeta crecía distinto, así que las
   filas quedaban desparejas. El texto completo está en la ficha. */
.card-desc{
  font-size:.84rem; color:var(--text2); flex:1;
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:3;
  line-clamp:3; overflow:hidden;
}
.card-foot{ display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:6px; }
.price{ font-family:var(--font-head); font-size:1.3rem; color:var(--accent); }
.price .old{ font-family:var(--font-body); font-size:.82rem; color:var(--text3); text-decoration:line-through; margin-left:6px; }
/* Cuánto se ahorra, en coral: es el único elemento cálido de una rejilla
   entera en lima y morado, así que la oferta salta sin que nadie la busque.
   Solo aparece cuando hay rebaja real, para que siga siendo la excepción —
   si lo llevaran todas las tarjetas dejaría de destacar ninguna. */
.price .ahorro{
  display:inline-block; margin-left:7px; padding:2px 8px; border-radius:100px;
  background:rgba(255,47,82,.14); border:1px solid rgba(255,47,82,.42);
  color:#ff8b9c; font-family:var(--font-body); font-size:.72rem; font-weight:800;
  letter-spacing:.02em; vertical-align:.14em; white-space:nowrap;
}
.price-por-definir{ font-size:.95rem; color:var(--text2); }

/* ===== Protección de imágenes ===== */
.protected{ position:relative; -webkit-user-select:none; user-select:none; }
.protected img{ -webkit-user-drag:none; user-select:none; pointer-events:none; }
.protected::after{ content:""; position:absolute; inset:0; z-index:3; }

/* ===== Hero con video de fondo ===== */
.hero--video{ min-height:520px; display:flex; align-items:center; }
.hero--video .hero-grid{ position:relative; z-index:2; width:100%; }
.hero-bg{ position:absolute; inset:0; z-index:0; overflow:hidden; }
/* Póster: primer cuadro del video como fondo INSTANTÁNEO (40 KB).
   El hero se ve completo desde el primer pintado — en móvil es el fondo
   definitivo, y en escritorio el video "cobra vida" encima sin salto. */
.hero-bg::before{
  content:""; position:absolute; inset:0; z-index:0;
  background-image:var(--poster, none);
  background-size:cover; background-position:center;
  opacity:.5;                       /* misma atenuación que el video */
  transition:opacity 1.2s var(--ease);
}
/* Cuando el video arranca, el póster se retira en fundido cruzado:
   si se quedara visible, se vería "doble" a través del video atenuado. */
.hero-bg.video-on::before{ opacity:0; }
.hero-bg video{ position:relative; }
.hero-bg video{
  width:100%; height:100%; object-fit:cover; object-position:center;
  opacity:0; transition:opacity 1.2s var(--ease);
}
.hero-bg video.on{ opacity:.5; }   /* atenuado: es ambiente, no protagonista */
/* Velo de legibilidad: más oscuro donde está el texto (izquierda) y en los bordes */
.hero-bg::after{
  content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:
    linear-gradient(90deg, rgba(10,10,15,.92) 0%, rgba(10,10,15,.72) 38%, rgba(10,10,15,.28) 70%, rgba(10,10,15,.45) 100%),
    linear-gradient(180deg, rgba(10,10,15,.55) 0%, transparent 25%, transparent 70%, rgba(10,10,15,.85) 100%);
}
/* Texto del hero con sombra sutil extra sobre video */
.hero--video h1, .hero--video .lead{ text-shadow:0 2px 18px rgba(0,0,0,.55); }
@media (max-width:960px){ .hero--video{ min-height:440px; } }

/* ===== Franja / marquee ===== */
.marquee{ border-block:1px solid var(--border); background:rgba(0,0,0,.25); overflow:hidden; padding-block:13px; }
.marquee-track{ display:flex; gap:40px; width:max-content; animation:scroll 26s linear infinite; }
.marquee-track span{ font-family:var(--font-head); text-transform:uppercase; letter-spacing:.06em; color:var(--text2); display:flex; align-items:center; gap:40px; }
.marquee-track span::after{ content:"✦"; color:var(--accent); }
@keyframes scroll{ to{ transform:translateX(-50%) } }

/* ===== Features / bloques ===== */
.features{ display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:18px; }
.feature{ padding:26px; border:1px solid var(--border); border-radius:var(--radius); background:rgba(255,255,255,.015); }
/* El recuadro tintado solo se pinta cuando el icono es un emoji de respaldo;
   los iconos 3D traen su propio glow y lucen mejor sin caja. */
.feature .ico{ width:46px; height:46px; border-radius:12px; display:grid; place-items:center;
  background:rgba(219,254,45,.12); color:var(--accent); margin-bottom:16px; font-size:1.3rem; }
.feature .ico:has(.feature-icon){ background:transparent; width:auto; height:44px; justify-items:start; }
.feature-icon{ width:40px; height:40px; object-fit:contain;
  filter:drop-shadow(0 4px 12px rgba(167,37,252,.40)); transition:transform .3s var(--ease); }
.feature:hover .feature-icon{ transform:translateY(-3px) scale(1.08); }
.feature h3{ font-size:1.05rem; margin-bottom:8px; }
.feature p{ font-size:.9rem; color:var(--text2); }

/* ===== Newsletter ===== */
.newsletter{ background:linear-gradient(120deg, rgba(219,254,45,.14), rgba(167,37,252,.18)); border:1px solid var(--border-2); border-radius:var(--radius-lg); padding:52px 30px; text-align:center; }
.newsletter-title{ display:flex; align-items:center; justify-content:center; gap:16px; flex-wrap:wrap; }
.newsletter-icon{ width:56px; height:56px; object-fit:contain; flex-shrink:0;
  filter:drop-shadow(0 6px 16px rgba(219,254,45,.40)); animation:float 6s var(--ease) infinite; }
@media (max-width:560px){ .newsletter-icon{ width:44px; height:44px; } }
.newsletter form{ display:flex; gap:10px; max-width:460px; margin:22px auto 0; flex-wrap:wrap; }
.newsletter input{ flex:1; min-width:200px; padding:14px 18px; border-radius:100px; border:1px solid var(--border-2); background:rgba(10,10,15,.5); color:var(--text); font-family:inherit; }
.newsletter input:focus{ outline:none; border-color:var(--accent); }

/* ===== Breadcrumb ===== */
.breadcrumb{ font-size:.84rem; color:var(--text2); margin-bottom:18px; }
.breadcrumb a{ color:var(--text2); }
.breadcrumb a:hover{ color:var(--accent); }
.breadcrumb span{ color:var(--text3); }

/* ===== Shop (catálogo con filtros) ===== */
/* Título de la página: mas contenido que el hero, no debe competir con el */
.shop-title{ margin:6px 0 28px; }
.shop-title h1{ font-size:clamp(1.9rem,4vw,3rem); }
.shop-layout{ display:grid; grid-template-columns:260px 1fr; gap:30px; align-items:start; }
/* Botón de filtros: invisible en escritorio, donde la barra lateral ya se ve */
.filters-toggle{ display:none; }
.shop-sidebar{ position:sticky; top:90px; background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:22px; }
.shop-sidebar h4{ font-size:.78rem; letter-spacing:.14em; color:var(--text2); margin:18px 0 12px; text-transform:uppercase; }
.shop-sidebar h4:first-child{ margin-top:0; }

/* ── Grupos de filtro plegables ──────────────────────────────────────────
   Eran tres <h4> sueltos con sus controles debajo. Ahora cada grupo es un
   <details>: se pliega, y plegado sigue diciendo qué hay elegido, que es lo
   que le falta a un filtro escondido para poder esconderse sin engañar. */
.filtro-grupo{ border-top:1px solid var(--border); }
.filtro-grupo:first-of-type{ border-top:0; }
.filtro-grupo > summary{
  display:flex; align-items:center; gap:10px; cursor:pointer;
  padding:15px 0; list-style:none;
  font-size:.78rem; letter-spacing:.14em; text-transform:uppercase;
  color:var(--text2); transition:color .18s;
}
/* El triángulo de fábrica sobra: hay una flecha propia que sí gira. */
.filtro-grupo > summary::-webkit-details-marker{ display:none; }
.filtro-grupo > summary::marker{ content:''; }
.filtro-grupo > summary:hover{ color:var(--text); }
/* El foco se pinta en el summary entero, no en un contorno pegado al texto:
   con el teclado hay que ver QUÉ grupo se va a abrir. */
.filtro-grupo > summary:focus-visible{
  outline:2px solid var(--accent); outline-offset:3px; border-radius:8px; color:var(--text);
}
.fg-titulo{ font-weight:600; }
.fg-elegido{
  margin-left:auto; font-size:.76rem; letter-spacing:.02em; text-transform:none;
  color:var(--accent); max-width:52%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* Abierto, el valor elegido ya se ve marcado en la lista: repetirlo arriba
   es ruido. Se reserva para cuando el grupo está plegado. */
.filtro-grupo[open] > summary .fg-elegido{ display:none; }
.fg-flecha{
  width:16px; height:16px; flex:none; margin-left:auto; fill:none; stroke:currentColor;
  stroke-width:2.2; stroke-linecap:round; stroke-linejoin:round;
  transition:transform .2s var(--ease);
}
.fg-elegido + .fg-flecha{ margin-left:0; }
.filtro-grupo[open] > summary .fg-flecha{ transform:rotate(180deg); }
.fg-cuerpo{ padding-bottom:14px; }

.filter-opt{
  display:flex; align-items:center; gap:10px; padding:8px 10px; margin:0 -10px;
  border-radius:10px; font-size:.92rem; color:var(--text2); cursor:pointer;
  transition:background-color .16s, color .16s;
}
.filter-opt input{ accent-color:var(--accent); flex:none; }
.filter-opt:hover{ color:var(--text); background:rgba(255,255,255,.04); }
.fo-txt{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.filter-opt .count{ margin-left:auto; font-size:.78rem; color:var(--text3); }
/* La fila entera se enciende con la opción elegida. Va con :has() y no con
   una clase escrita desde PHP porque el estado tiene que seguir al radio
   también cuando el cliente lo cambia sin recargar (el catálogo filtra sin
   recarga, ver catalogo.js). Donde :has() no exista, el radio sigue
   marcándose con accent-color: se pierde el realce, no la información. */
.filter-opt:has(input:checked){ background:rgba(219,254,45,.09); color:var(--text); }
.filter-opt:has(input:checked) .count{ color:var(--accent); }

/* ── La etiqueta de verdad dentro del filtro ─────────────────────────────
   Aquí había un punto de color, y obligaba a traducir: «el rojo era oferta,
   ¿no?». Ahora es la MISMA etiqueta que lleva la tarjeta del producto —las
   clases .badge y .badge-* sin tocar—, así que no hay nada que traducir: se
   reconoce lo que se va a encontrar.

   Lo único que se quita es el destello que la barre cada 4 segundos. En una
   tarjeta ese brillo es lo que hace girar la cabeza hacia la mercancía; en
   un panel de filtros serían tres luces parpadeando en el borde de la
   pantalla, compitiendo por la atención con lo que se ha venido a mirar. El
   degradado se queda, que es lo que la hace reconocible. */
.fo-badge{ flex:none; }
.fo-badge::after{ display:none; }
/* Un pelo más pequeña que en la tarjeta: aquí es una opción de una lista, no
   el sello de un producto, y a igual tamaño le disputaba el peso al nombre
   de la categoría de la lista de arriba. */
.fo-badge{ font-size:.62rem; padding:4px 10px; }

/* ── Tiras de atajos (solo en el celular) ────────────────────────────────
   En escritorio no existen: allí el panel lateral ya está abierto y enseña lo
   mismo con más detalle. Se declaran ocultas aquí y se encienden en el bloque
   de móvil, para que no haya forma de que se cuelen en pantalla grande. */
.chips-movil{ display:none; }

.price-range{ margin-top:2px; }
.pr-topes{ display:flex; justify-content:space-between; font-size:.82rem; color:var(--text2); margin-bottom:6px; }
.pr-topes #maxLbl{ color:var(--accent); font-weight:600; }
.price-range input[type=range]{ width:100%; accent-color:var(--accent); }
.shop-head{ display:flex; align-items:center; justify-content:space-between; gap:16px; margin-bottom:22px; flex-wrap:wrap; }
.shop-head .count{ font-size:.9rem; color:var(--text2); }
.sort-select,.input{ padding:11px 14px; border-radius:10px; border:1px solid var(--border-2); background:rgba(255,255,255,.03); color:var(--text); font-family:inherit; font-size:.9rem; }
.sort-select:focus,.input:focus{ outline:none; border-color:var(--accent); box-shadow:0 0 0 3px rgba(219,254,45,.14); }

/* ── Lista desplegada de un <select> ──
   La lista que abre el sistema operativo NO hereda el fondo del campo: en
   Windows tomaba el  rgba(255,255,255,.03)  del control —practicamente
   transparente— y pintaba texto gris sobre el fondo oscuro de la pagina.
   Resultado: los 32 estados de Mexico se leian a duras penas y el elegido
   quedaba sobre un azul de sistema que no pinta nada aqui.
   Las <option> necesitan color SOLIDO y propio. Se usan valores literales
   a proposito: varios navegadores no resuelven var() al dibujar la lista
   nativa, asi que una variable aqui volveria al mismo problema. */
select.input option,
.sort-select option{
  background-color:#16161f;      /* --surface */
  color:#f0f0f5;                 /* --text */
}
select.input option:checked,
.sort-select option:checked{
  background-color:#262636;      /* --surface-3 */
  color:#DBFE2D;                 /* --accent: el elegido en verde de marca */
  font-weight:700;
}
/* La primera opcion ("Selecciona…") es una instruccion, no un valor. */
select.input option[value=""]{ color:#9090a8; }
/* El campo cerrado tambien lleva fondo solido: con el traslucido, sobre el
   panel claro del checkout el texto perdia contraste. */
select.input,.sort-select{ background-color:var(--surface); }
.pagination{ display:flex; justify-content:center; gap:8px; margin-top:40px; }
/* 44 y no 42: el minimo comodo para un dedo es 44 (Apple) / 48 (Android).
   El propio CSS de este sitio ya lo dice mas abajo para los botones del
   catalogo; la paginacion se habia quedado dos pixeles corta. */
.pagination a,.pagination span{ min-width:44px; height:44px; padding:0 12px; display:inline-flex; align-items:center; justify-content:center; border:1px solid var(--border-2); border-radius:10px; color:var(--text2); font-weight:600; }
/* Transición base: el hover entra suave y el hundido del press REGRESA
   suave (la regla :active de abajo solo acorta la ida a 70ms). */
.pagination a{ transition:border-color .18s, color .18s, transform .16s var(--ease); }
.pagination a:hover{ border-color:var(--accent); color:var(--accent); }
.pagination .current{ background:var(--accent); color:#0a0a0f; border-color:var(--accent); }

/* ===== Producto (detalle) ===== */
.pdp{ display:grid; grid-template-columns:1fr 1fr; gap:44px; align-items:start; }
.pdp-media{ border:1px solid var(--border); border-radius:var(--radius-lg); overflow:hidden; position:sticky; top:90px;
  background:radial-gradient(circle at 50% 46%, rgba(167,37,252,.20), transparent 64%), var(--surface); }
.pdp-info h1{ font-size:clamp(1.9rem,4.5vw,3rem); margin:12px 0; }
.pdp-price{ font-family:var(--font-head); font-size:2.2rem; color:var(--accent); margin:8px 0; }
/* ── Selector de presentación ──
   Tarjetas grandes en lugar de un menú desplegable: en el celular se tocan
   mejor y el cliente compara precios de un vistazo, sin abrir nada. */
.variantes{ border:0; padding:0; margin:0; }
.variantes legend{ font-size:.74rem; letter-spacing:.14em; text-transform:uppercase;
  color:var(--text2); font-weight:700; padding:0; margin-bottom:10px; }
.variantes-lista{ display:grid; gap:9px; }
.variante{ display:flex; align-items:center; gap:11px; cursor:pointer;
  padding:13px 15px; border-radius:14px; border:1px solid var(--border-2);
  background:rgba(255,255,255,.03);
  transition:border-color .18s, background .18s, box-shadow .18s, transform .18s var(--ease); }
.variante:hover{ border-color:var(--border-2); background:rgba(255,255,255,.05); }
.variante input{ accent-color:var(--accent); width:17px; height:17px; flex-shrink:0; }
.variante-nombre{ flex:1; font-weight:600; font-size:.94rem; }
.variante-precio{ font-family:var(--font-head); font-weight:800; color:var(--accent); font-size:1.05rem; }
/* La elegida se resalta con el color de marca */
.variante:has(input:checked){ border-color:var(--accent); background:rgba(219,254,45,.07);
  box-shadow:0 0 0 1px var(--accent) inset; }
.variante.agotada{ opacity:.45; cursor:not-allowed; }
.variante.agotada .variante-precio{ color:var(--text2); }
.variante-agotada{ font-size:.7rem; text-transform:uppercase; letter-spacing:.08em; color:#ff9a9a; }
/* ===== Rastreo de pedido ===== */
.rastreo-form{ background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:22px; }
.rastreo-resultado{ background:var(--surface); border:1px solid var(--border-2); border-radius:var(--radius-lg); padding:26px; }
.rr-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:18px; flex-wrap:wrap; }
.rr-total{ text-align:right; }
.rr-total span{ display:block; font-size:.72rem; letter-spacing:.12em; text-transform:uppercase; color:var(--text2); }
.rr-total b{ font-family:var(--font-head); font-size:1.5rem; color:var(--accent); }
/* Línea de tiempo del envío */
.linea-tiempo{ list-style:none; margin:0; padding:0; position:relative; }
.linea-tiempo::before{ content:""; position:absolute; left:8px; top:10px; bottom:14px; width:2px; background:var(--border-2); }
.linea-tiempo li{ position:relative; padding:0 0 22px 32px; }
.linea-tiempo li:last-child{ padding-bottom:0; }
.lt-punto{ position:absolute; left:0; top:3px; width:18px; height:18px; border-radius:50%;
  background:var(--bg-2); border:2px solid var(--border-2); z-index:1; }
.linea-tiempo li.hecho .lt-punto{ background:var(--accent); border-color:var(--accent); }
.linea-tiempo li.hecho b{ color:var(--text); }
.linea-tiempo li b{ color:var(--text2); font-size:.98rem; }
.linea-tiempo li.actual .lt-punto{ box-shadow:0 0 0 5px rgba(219,254,45,.16); }
.linea-tiempo li.actual b{ color:var(--accent); }
.linea-tiempo li p{ color:var(--text2); font-size:.87rem; margin-top:4px; }
/* Guía de paquetería */
.rr-guia{ display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap;
  padding:16px 18px; border-radius:var(--radius); border:1px solid rgba(167,37,252,.32); background:rgba(167,37,252,.08); }
.rr-guia b{ display:block; margin-top:2px; }
.rr-num{ font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:1.05rem; color:var(--accent); letter-spacing:.04em; margin-top:3px; }
.rr-items .line{ display:flex; justify-content:space-between; gap:14px; padding:9px 0; border-bottom:1px solid var(--border); font-size:.92rem; }
.rr-items .line:last-child{ border-bottom:0; }
.rr-ayuda{ display:flex; align-items:center; justify-content:space-between; gap:14px; flex-wrap:wrap;
  padding-top:18px; border-top:1px solid var(--border); color:var(--text2); font-size:.9rem; }

/* Garantías bajo el botón de compra */
.garantias{ list-style:none; margin:18px 0 0; padding:16px 18px; display:grid; gap:11px;
  border:1px solid var(--border); border-radius:var(--radius); background:rgba(255,255,255,.02); }
.garantias li{ display:flex; align-items:flex-start; gap:10px; font-size:.88rem; color:var(--text2); line-height:1.45; }
.garantias li b{ color:var(--text); font-weight:600; }
.garantias svg{ width:17px; height:17px; flex-shrink:0; margin-top:2px; fill:none;
  stroke:var(--accent); stroke-width:2.6; stroke-linecap:round; stroke-linejoin:round; }

/* "Desde $…" en las tarjetas del catálogo */
/* Presentación elegida, dentro de la línea del carrito */
.ci-variante{ display:inline-block; margin-top:3px; padding:2px 9px; border-radius:100px;
  background:rgba(219,254,45,.1); border:1px solid rgba(219,254,45,.28);
  color:var(--accent); font-size:.74rem; font-weight:600; }
.price-desde{ font-family:var(--font-body); font-size:.62rem; font-weight:600;
  text-transform:uppercase; letter-spacing:.1em; color:var(--text2); display:block; line-height:1; margin-bottom:2px; }
#pdpDisponibilidad.ok{ color:var(--accent); }
#pdpDisponibilidad.out{ color:#ff9a9a; }
.pdp-price .old{ font-family:var(--font-body); font-size:1.1rem; color:var(--text3); text-decoration:line-through; margin-left:10px; }
/* ── Selector de cantidad ──
   Antes era un óvalo con dos signos de texto (− y +) que solo cambiaban de
   color al pasar el ratón. Los signos tipográficos no casan de tamaño entre
   sí y no se podían apagar de forma creíble. Ahora son vectores del mismo
   grosor que el resto de iconos del sitio, el número vive en su propio
   hueco hundido —se lee como un valor, no como una etiqueta— y los botones
   tienen estados de verdad: reposo, encima, pulsado y apagado. */
/* El riel: hundido, no plano. La luz cae desde arriba, así que el degradado
   va de oscuro arriba a menos oscuro abajo y una línea clarísima marca el
   labio superior. Ese par de sombras interiores es lo que hace que la pieza
   parezca excavada en la tarjeta en vez de pegada encima. */
.qty{
  display:inline-flex; align-items:center; gap:3px; padding:4px;
  border:1px solid rgba(255,255,255,.09); border-radius:100px;
  background:linear-gradient(180deg, rgba(0,0,0,.38), rgba(0,0,0,.16));
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05),
             inset 0 3px 9px rgba(0,0,0,.5);
  transition:border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.qty:focus-within{
  border-color:rgba(219,254,45,.55);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05), inset 0 3px 9px rgba(0,0,0,.5),
             0 0 0 3px rgba(219,254,45,.13);
}
/* Los botones sí sobresalen: sombra bajo el disco y brillo en el borde
   superior. Es la contraparte del riel hundido y lo que los hace pedir
   que los pulses. */
.qty button{
  width:34px; height:34px; flex:none; border-radius:50%;
  display:grid; place-items:center; color:var(--text2); cursor:pointer;
  background:linear-gradient(180deg, var(--surface-3), var(--surface-2));
  border:1px solid rgba(255,255,255,.08);
  box-shadow:0 1px 3px rgba(0,0,0,.5), inset 0 1px 0 rgba(255,255,255,.07);
  transition:background .16s var(--ease), color .16s var(--ease),
             box-shadow .16s var(--ease), transform .1s var(--ease);
}
.qty button svg{ width:15px; height:15px; fill:none; stroke:currentColor;
  stroke-width:2.4; stroke-linecap:round; }
/* Al pasar el ratón se enciende en lima con su resplandor. */
.qty button:hover:not(:disabled){
  color:#0a0a0f; border-color:transparent;
  background:linear-gradient(180deg, var(--accent-light), var(--accent));
  box-shadow:0 3px 12px -3px var(--accent-glow), inset 0 1px 0 rgba(255,255,255,.3);
}
/* Al pulsarlo se hunde y pierde su sombra: se mete en el riel. */
.qty button:active:not(:disabled){
  transform:scale(.92);
  box-shadow:inset 0 2px 5px rgba(0,0,0,.45);
}
/* Apagado: pierde el relieve por completo y se funde con el riel. Sin
   cursor de mano, que es lo que hace que la gente insista pulsando algo
   muerto. */
.qty button:disabled{
  color:var(--text3); opacity:.4; cursor:not-allowed;
  background:none; border-color:transparent; box-shadow:none;
}
/* El número flota sobre el riel, sin caja propia: la caja ya es el riel. */
.qty input{
  width:38px; height:34px; text-align:center; border:0; background:transparent;
  color:var(--text); font-family:var(--font-head); font-size:1.05rem;
  line-height:34px; letter-spacing:.02em;
}
.qty input:focus{ outline:none; }
/* Se llegó al tope de existencias: el riel se tiñe de ámbar y el número
   también. Se entiende sin leer nada. */
.qty--tope{ border-color:rgba(255,180,55,.45); }
.qty--tope input{ color:#ffb437; }
/* Quita las flechitas del campo numérico, que aquí sobran y descuadran. */
.qty input::-webkit-outer-spin-button,
.qty input::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }
/* Cambio de foto al elegir presentación: un parpadeo corto, no un corte seco.
   El desenfoque leve funde las dos fotos durante el cruce: sin él se
   alcanzan a ver los dos empaques superpuestos un instante, que se lee
   como un fallo. Con blur, el ojo percibe UNA transformación.
   Se respeta a quien pidió menos movimiento en su sistema. */
#pdpFoto{ transition:opacity .18s var(--ease), filter .18s var(--ease); }
#pdpFoto.cambiando{ opacity:.35; filter:blur(5px); }
@media (prefers-reduced-motion: reduce){
  #pdpFoto{ transition:none; }
  #pdpFoto.cambiando{ opacity:1; filter:none; }
}

/* Leyenda obligatoria: discreta pero legible. No es letra chica de adorno. */
/* La leyenda que exige COFEPRIS. Iba en --text3 (#5a5a72), que sobre el fondo
   negro da 2.9:1 de contraste cuando el mínimo legible es 4.5:1: estaba puesta
   para cumplir y no se podía leer. En --text2 sube a 6.3:1 sin cambiar de
   tamaño ni pesar más que el resto de la ficha. */
.aviso-legal{ margin-top:12px; font-size:.78rem; line-height:1.5; color:var(--text2); }
.pdp-meta{ margin-top:24px; border-top:1px solid var(--border); padding-top:20px; display:grid; gap:12px; }
.pdp-meta .row{ display:flex; justify-content:space-between; font-size:.9rem; }
.pdp-meta .row span:first-child{ color:var(--text2); }

/* ===== Carrito ===== */
.cart-layout{ display:grid; grid-template-columns:1fr 360px; gap:30px; align-items:start; }
.cart-item{ display:grid; grid-template-columns:80px 1fr auto; gap:16px; align-items:center; padding:16px 0; border-bottom:1px solid var(--border); }
.cart-item img{ width:80px; height:80px; border-radius:12px; object-fit:cover; }
.cart-item .ci-title{ word-spacing:.08em; font-family:var(--font-head); text-transform:uppercase; font-size:1rem; }
.cart-item .ci-cat{ font-size:.72rem; color:#c084fc; text-transform:uppercase; letter-spacing:.1em; }
/* ── Quitar del carrito (ley de Fitts) ──
   Medido antes de este cambio: 12x19 px, y a 47 px del boton "+", que es el
   que mas se usa de toda la linea. Un objetivo diminuto, IRREVERSIBLE y
   pegado a uno frecuente: el error no era probable, era cuestion de tiempo.
   Fitts dice que el tiempo para acertar depende del tamano y la distancia,
   asi que aqui se corrigen las dos cosas a la vez:
     - 44x44 reales, el minimo comodo para un dedo.
     - separado del selector de cantidad con su propio margen, para que un
       toque impreciso en "+" no caiga nunca sobre "eliminar".
   Y al reves que en los demas botones: este NO se agranda al pasar el raton
   ni pide que lo pulses. Un destructivo no se promociona. */
.remove{
  width:44px; height:44px; margin-left:6px; flex:none;
  display:grid; place-items:center;
  background:transparent; border:1px solid transparent; border-radius:12px;
  color:var(--text3); font-size:1.25rem; line-height:1;
  transition:color .18s, background .18s, border-color .18s;
}
.remove:hover{ color:#ff7a7a; background:rgba(255,122,122,.09); border-color:rgba(255,122,122,.3); }
.remove:active{ transform:scale(.94); transition-duration:.07s; }
.remove-form{ display:inline-flex; }
.summary{ position:sticky; top:90px; background:linear-gradient(180deg,var(--surface),var(--bg-2)); border:1px solid var(--border); border-radius:var(--radius-lg); padding:26px; }
.summary h3{ margin-bottom:18px; }
.summary .line{ display:flex; justify-content:space-between; margin-bottom:12px; font-size:.95rem; }
.summary .line.total{ border-top:1px solid var(--border); padding-top:16px; margin-top:6px; font-family:var(--font-head); font-size:1.35rem; }
.summary .total .price{ font-size:1.6rem; }
.ship-note{ font-size:.82rem; color:var(--text2); background:rgba(219,254,45,.07); border:1px solid rgba(219,254,45,.2); border-radius:12px; padding:12px 14px; margin:14px 0; }
/* Cuando el código deja al cliente por debajo del mínimo, este aviso deja de
   ser una promesa y pasa a explicar un cobro que no esperaba: en verde ácido
   —el color con el que la tienda celebra— se lee como si algo bueno hubiera
   pasado. En ámbar se lee como lo que es, y sigue siendo accionable. */
.ship-note--perdido{ background:rgba(251,191,36,.08); border-color:rgba(251,191,36,.28); color:#fcd77a; }
.ship-note--perdido .progress > i{ background:linear-gradient(90deg,#f59e0b,#fbbf24); }
.progress{ height:8px; border-radius:100px; background:rgba(255,255,255,.08); overflow:hidden; margin-top:8px; }
.progress > i{ display:block; height:100%; background:linear-gradient(90deg,var(--accent-light),var(--accent)); border-radius:100px; transition:width .4s var(--ease); }
.pay-icons{ display:flex; gap:8px; justify-content:center; margin-top:14px; font-size:.72rem; color:var(--text3); flex-wrap:wrap; }

/* ═══════════ Código de descuento ═══════════
   Plegado tras un enlace discreto: la mayoría de la gente no trae código, y
   un campo vacío siempre a la vista le recuerda a todo el mundo que existe
   un precio mejor que el suyo — eso frena más compras de las que ayuda.
   Quien sí lo tiene, lo busca y lo encuentra en un toque. */
.cupon{ margin:2px 0 14px; padding-bottom:14px; border-bottom:1px solid var(--border); }
/* Con un código puesto, el enlace de abrir sobra: su sitio lo ocupa la
   ficha del código aplicado. Así el bloque nunca muestra dos cosas que
   dicen lo mismo, y el resumen no crece. */
.cupon[data-aplicado="1"] .cupon-abrir{ display:none; }
.cupon-abrir{
  display:flex; align-items:center; gap:9px; width:100%;
  padding:9px 12px; border-radius:12px; min-height:44px;   /* Fitts */
  background:transparent; border:1px dashed var(--border-2); color:var(--text2);
  font-family:inherit; font-size:.84rem; font-weight:600; text-align:left; cursor:pointer;
  transition:border-color .18s, color .18s, background .18s;
}
.cupon-abrir:hover{ border-color:var(--accent); color:var(--accent); background:rgba(219,254,45,.04); }
.cupon-abrir:active{ transform:scale(.985); transition-duration:.07s; }
.cupon-abrir svg{ width:18px; height:18px; flex:none; fill:none; stroke:currentColor;
  stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round; }
.cupon-abrir[aria-expanded="true"]{ border-style:solid; border-color:var(--border-2); }

.cupon-caja{ margin-top:10px; }
.cupon-fila{ display:flex; gap:8px; }
/* El código se teclea en mayúsculas: así se lee igual que en el anuncio de
   donde lo copió, y no parece que el sitio se lo "cambió". */
.cupon-fila .input{ flex:1; min-width:0; text-transform:uppercase; letter-spacing:.06em; font-weight:600; }
.cupon-fila .input::placeholder{ text-transform:none; letter-spacing:normal; font-weight:400; }
.cupon-fila .btn{ flex:none; min-height:44px; }
.cupon-aviso{ margin-top:8px; font-size:.82rem; line-height:1.4; font-weight:600; color:var(--accent); }
.cupon-aviso:empty{ display:none; }
.cupon-aviso--mal{ color:#ff8f8f; }
.cupon-aviso--mal::before{ content:"⚠ "; }

/* Aplicado: UNA sola línea. Antes eran dos renglones (código arriba,
   descripción abajo) dentro de una caja alta: 68 px para decir algo que
   cabe en 36. El código y lo que hace van en la misma fila, separados por
   un punto, y el botón de quitar a la derecha. */
.cupon-activo{
  display:flex; align-items:center; gap:10px; margin-top:8px;
  padding:6px 6px 6px 12px; border-radius:100px;
  background:rgba(219,254,45,.08); border:1px solid rgba(219,254,45,.32);
}
.cupon-activo > div{ flex:1; min-width:0; display:flex; align-items:baseline; gap:7px; flex-wrap:wrap; }
.cupon-activo b{ font-family:var(--font-head); font-size:.88rem;
  letter-spacing:.04em; color:var(--accent); text-transform:uppercase; }
.cupon-activo small{ font-size:.76rem; color:var(--text2); }
/* El punto separador solo aparece si hay descripción que separar. */
.cupon-activo small:not(:empty)::before{ content:"· "; color:var(--text3); }
.cupon-quitar{
  position:relative; flex:none; height:34px; padding:0 13px; border-radius:100px;
  background:transparent; border:1px solid var(--border-2); color:var(--text2);
  font-family:inherit; font-size:.78rem; font-weight:600; cursor:pointer;
  transition:color .18s, border-color .18s;
}
/* Se dibuja de 34 px para no engordar la fila, pero se TOCA como uno de 44:
   el área sensible se estira con un pseudoelemento invisible que no ocupa
   sitio en el diseño. El tamaño visual y el objetivo de Fitts no tienen por
   qué ser el mismo rectángulo. */
.cupon-quitar::after{
  content:""; position:absolute; left:0; right:0; top:50%;
  height:44px; transform:translateY(-50%);
}
.cupon-quitar:hover{ color:#ff9a9a; border-color:rgba(255,122,122,.45); }
.cupon-quitar:active{ transform:scale(.96); transition-duration:.07s; }

/* La línea del descuento en el resumen: en verde y con el signo delante,
   para que se lea como lo que es — dinero que NO se paga. */
.line-descuento{ color:var(--accent); font-weight:600; }
.line-descuento small{ font-family:var(--font-body); font-size:.74rem; opacity:.75;
  letter-spacing:.06em; margin-left:5px; }

/* ===== Formularios / checkout ===== */
.field{ margin-bottom:16px; }
.field label{ display:block; font-size:.82rem; color:var(--text2); margin-bottom:7px; }
.field .input,select.input,textarea.input{ width:100%; }
.grid-2{ display:grid; grid-template-columns:1fr 1fr; gap:14px; }
.step{ margin-bottom:30px; }
.step-head{ display:flex; align-items:center; gap:12px; margin-bottom:16px; }
.step-num{ width:32px; height:32px; border-radius:50%; background:var(--accent); color:#0a0a0f; font-family:var(--font-head); font-weight:800; display:grid; place-items:center;
  transition:background .2s var(--ease), color .2s var(--ease); }

/* ── Progreso del pago (efecto Zeigarnik) ──
   El bucle abierto a la vista: cuánto llevas y cuánto falta.

   Va PEGADO bajo el encabezado. En la primera versión era una barra suelta
   encima del formulario y desaparecía al segundo campo — justo cuando saber
   cuánto falta es lo que sostiene las ganas de terminar. El encabezado
   cambia de alto entre móvil y escritorio, así que su altura real la mide
   el JS y la deja en --cabecera-real. */
.progreso-pago{
  position:sticky; top:var(--cabecera-real, 70px); z-index:40;
  margin:22px 0 4px; padding:11px 0 0;
  background:linear-gradient(180deg, var(--bg) 72%, rgba(10,10,15,0));
}
.pp-fila{ display:flex; align-items:center; gap:11px; margin-bottom:8px; flex-wrap:wrap; }
.pp-texto{ display:flex; gap:6px; flex-wrap:wrap; align-items:baseline; font-size:.86rem; }
.pp-texto b{ color:var(--text); font-weight:700; }
.pp-texto .text-muted{ font-weight:400; }
/* Un punto por paso: el avance se lee sin contar ni leer. */
.pp-pasos{ display:inline-flex; gap:5px; flex:none; }
.pp-punto{ width:8px; height:8px; border-radius:50%; background:rgba(255,255,255,.16);
  transition:background .3s var(--ease), transform .3s var(--ease); }
.pp-punto.on{ background:var(--accent); transform:scale(1.15); }
.pp-barra{ height:5px; border-radius:100px; background:rgba(255,255,255,.08); overflow:hidden; }
.pp-barra > i{ display:block; height:100%; border-radius:100px; width:0;
  background:linear-gradient(90deg, var(--accent-2), var(--accent));
  transition:width .45s var(--ease); }

/* Paso terminado: el número se convierte en paloma y el bloque se atenúa
   un punto, para que la vista caiga sola en el que falta. */
.step-head{ position:relative; }
.step--hecho .step-num{ background:var(--accent-2); color:#fff; font-size:0; }
.step--hecho .step-num::after{ content:"✓"; font-size:1rem; line-height:1; }
.step--hecho .step-head h3{ color:var(--text2); }
/* "Listo" junto al título: el estado también vive donde están los campos,
   no solo en la tira de arriba. */
.step-estado{ margin-left:auto; font-size:.72rem; font-weight:700;
  letter-spacing:.1em; text-transform:uppercase; color:var(--accent-2); }
.step-estado:empty{ display:none; }
.step--hecho .step-estado::before{ content:"✓ "; }

/* ── Respuesta en vivo de un campo (correo y teléfono) ──
   El color no basta: quien no distingue el verde del rojo necesita el
   texto, y quien usa lector de pantalla necesita el role. Por eso el
   mensaje escrito es la señal principal y el color solo acompaña. */
.campo-pista{ display:block; margin-top:6px; font-size:.8rem; line-height:1.4; font-weight:600; }
.campo-pista:empty{ display:none; }
.campo--ok .campo-pista{ color:var(--accent); }
.campo--ok .campo-pista::before{ content:"✓ "; }
.campo--mal .campo-pista{ color:#ff8f8f; }
.campo--mal .campo-pista::before{ content:"⚠ "; }
.campo--ok .input{ border-color:rgba(219,254,45,.55); }
.campo--mal .input{ border-color:#ff7a7a; }
.campo--ok .input:focus{ box-shadow:0 0 0 3px rgba(219,254,45,.16); }
.campo--mal .input:focus{ box-shadow:0 0 0 3px rgba(255,122,122,.2); }
.step-head.check .step-num{ background:var(--accent-2); color:#fff; }
.pay-opt{ display:flex; align-items:center; gap:12px; padding:16px; border:1px solid var(--border-2); border-radius:12px; margin-bottom:10px; cursor:pointer; transition:border-color .18s; }
.pay-opt:hover{ border-color:var(--accent); }
.pay-opt input{ accent-color:var(--accent); }
.pay-opt.disabled{ opacity:.5; }
/* El plazo para pagar, debajo de la opción de Mercado Pago. Va pegado a
   ella —margen negativo contra el margin-bottom de .pay-opt— para que se
   lea como parte de esa opción y no como una nota suelta del paso. */
.pago-plazo{ display:block; margin:-4px 0 12px; padding:0 4px; font-size:.84rem;
             line-height:1.5; color:var(--text2); }
.pago-plazo strong{ color:var(--accent-light); }

/* ── Elegir cómo pagar, dentro del panel del pedido ────────────────────
   Vivía al final de la columna larga del formulario, a una pantalla de
   scroll del único botón que lo usa. Con la elección obligatoria eso
   significaba pulsar, no pasar nada, y tener que buscar por qué. */
.pago-metodos{ margin-top:12px; padding-top:14px; border-top:1px solid var(--border); }
.pago-metodos .pay-opt{ padding:13px 14px; font-size:.92rem; }
.pago-metodos .pago-plazo{ margin-bottom:0; }

/* El aviso de por qué el botón está apagado. Va ENTRE el botón y las
   opciones porque es lo único que une las dos cosas; `hidden` lo retira en
   cuanto hay elección, para no dejar en pantalla una instrucción ya
   cumplida. */
.pago-aviso{ margin:10px 0 0; font-size:.82rem; line-height:1.5;
             text-align:center; color:var(--accent-light); }
.pago-aviso[hidden]{ display:none; }

/* Un botón apagado tiene que PARECER apagado. Sin esto conserva el amarillo
   de marca y el cursor de mano, así que se lee como pulsable: el cliente
   toca tres veces, no pasa nada, y concluye que la tienda está rota. */
#btnPagar:disabled{ opacity:.45; cursor:not-allowed; filter:saturate(.55); }
.notice{ padding:14px 16px; border-radius:12px; font-size:.9rem; margin:14px 0; }
.notice.warn{ background:rgba(255,122,122,.1); border:1px solid rgba(255,122,122,.35); color:#ffb4b4; }
.notice.info{ background:rgba(167,37,252,.12); border:1px solid rgba(167,37,252,.3); color:#e6c6f5; }
.notice.ok{ background:rgba(219,254,45,.1); border:1px solid rgba(219,254,45,.3); color:var(--accent-light); }

/* ===== Prosa legal ===== */
.prose{ max-width:820px; }
.prose h2{ font-size:1.5rem; margin:32px 0 12px; }
.prose h3{ font-size:1.1rem; margin:22px 0 8px; color:var(--accent); }
.prose p,.prose li{ color:#cfc8d6; font-size:.98rem; margin-bottom:12px; }
.prose ul{ list-style:disc; padding-left:22px; }
.prose strong{ color:var(--text); }

/* ===== Toast / estados ===== */
.toast-wrap{ position:fixed; bottom:24px; left:50%; transform:translateX(-50%); z-index:200; display:flex; flex-direction:column; gap:10px; align-items:center; }
.toast{ background:var(--surface-2); border:1px solid var(--border-2); border-left:4px solid var(--accent); padding:13px 18px; border-radius:12px; box-shadow:var(--shadow); font-size:.9rem; min-width:240px; animation:toastIn .34s var(--ease) both; }
.toast--sale{ animation:toastOut .3s var(--ease) both; }

/* ── Aviso corto (inventario, límites, errores) ──
   Antes era una caja gris con una rayita de color al costado: no se
   parecía a nada más del sitio y no decía de qué tipo de aviso se trataba.
   Ahora comparte lenguaje con el aviso de "agregado al carrito" —icono en
   disco, encabezado en versalitas, barra de cuenta atrás— pero con el color
   de su tono. El icono acompaña al color a propósito: quien no distingue
   el rojo necesita otra pista, y en el celular esto se lee de reojo.     */
.toast--corto{
  --tono:var(--accent);
  --tono-suave:rgba(219,254,45,.13);
  display:flex; align-items:center; gap:12px; position:relative; overflow:hidden;
  padding:12px 16px 13px 13px; border-left-width:1px;
  min-width:300px; max-width:min(94vw,400px);
  background:linear-gradient(115deg, var(--tono-suave), transparent 62%), var(--surface-2);
  border-color:color-mix(in srgb, var(--tono) 34%, transparent);
  box-shadow:var(--shadow), 0 10px 38px -14px color-mix(in srgb, var(--tono) 45%, transparent);
}
/* Navegadores sin color-mix (Safari viejo): borde visible de todas formas. */
@supports not (color: color-mix(in srgb, red 50%, blue)){
  .toast--corto{ border-color:var(--border-2); }
}
.tc-ico{
  flex:none; width:34px; height:34px; border-radius:50%;
  display:grid; place-items:center;
  background:var(--tono-suave);
  border:1px solid color-mix(in srgb, var(--tono) 30%, transparent);
}
.tc-ico svg{ width:18px; height:18px; fill:none; stroke:var(--tono);
  stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
.tc-txt{ display:flex; flex-direction:column; gap:1px; min-width:0; }
.tc-titulo{ font-family:var(--font-head); font-size:.68rem; letter-spacing:.11em;
  text-transform:uppercase; color:var(--tono); }
.tc-msg{ font-size:.9rem; color:var(--text); line-height:1.35; }

/* Ámbar para los límites: el sistema funciona, solo hay un tope que
   respetar. Pintarlo de rojo asustaría sin motivo. */
.toast--aviso{ --tono:#ffb437; --tono-suave:rgba(255,180,55,.14); }
/* Rojo solo cuando algo falló de verdad. */
.toast--error{ --tono:#ff5c6e; --tono-suave:rgba(255,92,110,.14); }
.toast--error{ border-left-color:#ff5c6e; }
/* La barra de cuenta atrás toma el color del tono (se reutiliza .tp-barra). */
.toast--corto .tp-barra{ background:var(--tono); }
@keyframes toastIn{ from{ opacity:0; transform:translateY(16px) scale(.96) } }
@keyframes toastOut{ to{ opacity:0; transform:translateY(10px) scale(.97) } }

/* ── Aviso de "agregado al carrito" ──
   Lleva la foto del producto para que el cliente confirme de un vistazo
   QUÉ agregó, y un acceso directo al carrito para no perder el impulso. */
.toast--producto{
  display:flex; flex-direction:column; gap:11px; position:relative; overflow:hidden;
  padding:12px 14px 13px 12px; border-left-width:1px; min-width:340px; max-width:min(94vw,430px);
  background:linear-gradient(120deg, rgba(219,254,45,.10), rgba(167,37,252,.12)), var(--surface-2);
  border-color:rgba(219,254,45,.38);
  box-shadow:var(--shadow), 0 0 0 1px rgba(219,254,45,.10), 0 10px 40px -12px rgba(219,254,45,.35);
}
.tp-fila{ display:flex; align-items:center; gap:13px; }
.tp-img{ width:52px; height:52px; border-radius:11px; object-fit:cover; flex-shrink:0;
  border:1px solid var(--border-2); background:var(--bg-2); }
.tp-txt{ flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.tp-titulo{ display:flex; align-items:center; gap:6px; font-family:var(--font-head);
  font-size:.74rem; letter-spacing:.08em; text-transform:uppercase; color:var(--accent); }
.tp-titulo svg{ width:14px; height:14px; fill:none; stroke:var(--accent);
  stroke-width:3.2; stroke-linecap:round; stroke-linejoin:round;
  /* El palomeo se dibuja solo, como si se firmara la confirmación */
  stroke-dasharray:26; stroke-dashoffset:26; animation:palomeo .45s .12s var(--ease) forwards; }
@keyframes palomeo{ to{ stroke-dashoffset:0 } }
.tp-nombre{ font-weight:600; font-size:.93rem; color:var(--text);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.tp-var{ font-size:.76rem; color:var(--accent); opacity:.9; }
/* Cantidad dentro del aviso: acompaña al nombre sin competir con él. Va como
   línea propia y no dentro de .tp-nombre, que recorta con puntos suspensivos
   y se la comía en cuanto el nombre era largo. */
.tp-cant{ font-weight:700; font-size:.82rem; color:var(--accent); }
.tp-ver{ flex-shrink:0; font-size:.78rem; font-weight:700; letter-spacing:.04em;
  text-transform:uppercase; color:#0a0a0f; background:var(--accent);
  padding:8px 13px; border-radius:100px; transition:transform .18s var(--ease), filter .18s; }
.tp-ver:hover{ filter:brightness(1.08); transform:translateY(-1px); }
/* ── Cuánto falta para el envío gratis ──
   Va en su propia fila, separada del producto, para que se lea como un
   dato aparte y no como parte del nombre. */
.tp-envio{
  padding:9px 11px; border-radius:11px;
  background:rgba(219,254,45,.07); border:1px solid rgba(219,254,45,.20);
}
.tp-envio-txt{ display:block; font-size:.82rem; color:var(--text2); line-height:1.35; }
.tp-envio-txt b{ color:var(--accent); font-weight:700; }
/* Barra de avance hacia el envío gratis */
.tp-prog{ display:block; height:5px; margin-top:7px; border-radius:100px;
  background:rgba(255,255,255,.09); overflow:hidden; }
.tp-prog > i{ display:block; height:100%; border-radius:100px;
  background:linear-gradient(90deg, var(--accent-2), var(--accent));
  animation:tpProg .75s .1s var(--ease) both; transform-origin:left; }
@keyframes tpProg{ from{ transform:scaleX(0) } to{ transform:scaleX(1) } }
/* Ya lo consiguió: se celebra */
.tp-envio--logrado{ background:rgba(219,254,45,.13); border-color:rgba(219,254,45,.42); }
.tp-envio--logrado .tp-envio-txt{ color:var(--text); }

/* Barra que muestra cuánto falta para que se cierre solo */
.tp-barra{ position:absolute; left:0; bottom:0; height:2px; width:100%;
  background:linear-gradient(90deg, var(--accent), var(--accent-2));
  transform-origin:left; animation:tpBarra var(--dur,4200ms) linear forwards; }
@keyframes tpBarra{ from{ transform:scaleX(1) } to{ transform:scaleX(0) } }
/* Al pasar el cursor se congela: da tiempo de leer o de tocar "Ver carrito" */
.toast--fijo .tp-barra{ animation-play-state:paused; }

/* ── Producto que vuela hacia el carrito ── */
.gl-vuela{ position:fixed; z-index:300; pointer-events:none; object-fit:cover;
  border-radius:16px; box-shadow:0 12px 40px -8px rgba(219,254,45,.5); will-change:transform,opacity; }

/* ── Reacción del ícono del carrito ── */
.cart-pop{ animation:cartPop .62s var(--ease); }
@keyframes cartPop{
  0%{ transform:scale(1) }
  28%{ transform:scale(.86) }
  52%{ transform:scale(1.18) }
  74%{ transform:scale(.97) }
  100%{ transform:scale(1) }
}
/* Anillo que se expande al recibir el producto */
.cart-pop::after{
  content:""; position:absolute; inset:-2px; border-radius:50%;
  border:2px solid var(--accent); animation:cartAnillo .62s var(--ease) forwards; pointer-events:none;
}
@keyframes cartAnillo{
  from{ transform:scale(.9); opacity:.85 }
  to{ transform:scale(1.9); opacity:0 }
}
.cart-pop .cart-badge{ animation:badgePop .5s .12s var(--ease); }
@keyframes badgePop{
  0%{ transform:scale(1) } 40%{ transform:scale(1.45) } 100%{ transform:scale(1) }
}

/* ── Botón confirmado ── */
.btn--listo{ background:var(--accent) !important; color:#0a0a0f !important;
  box-shadow:0 0 0 4px rgba(219,254,45,.18) !important; }

/* Mientras el servidor responde. Oculta el texto sin cambiar el tamaño del
   botón (el JS congela el ancho) y pone un aro girando en su lugar. */
.btn--cargando{ pointer-events:none; position:relative; }
/* El texto solo se esconde si de verdad va a haber un aro que lo sustituya.
   Con movimiento reducido no hay aro, así que el texto debe seguir ahí. */
@media not (prefers-reduced-motion: reduce){
  .btn--cargando{ color:transparent !important; }
}
/* Doble clase a propósito: .btn--primary::after ya existe (el destello que
   cruza el botón) y se declara más abajo. Con una sola clase ganaría el
   destello y el aro saldría desplazado y relleno de degradado. */
.btn.btn--cargando::after{
  content:""; position:absolute; inset:0; margin:auto;
  width:17px; height:17px; border-radius:50%;
  border:2px solid currentColor; border-top-color:transparent;
  background:none; transform:none;
  color:#0a0a0f; opacity:.85;
  animation:btnGira .6s linear infinite;
}
/* En los botones de contorno el aro va en lima, no en tinta. */
.btn--outline.btn--cargando::after,
.btn--purple.btn--cargando::after{ color:var(--accent); }
@keyframes btnGira{ to{ transform:rotate(360deg) } }
/* Sin movimiento el aro quedaría congelado a media vuelta, que parece un
   error. Se muestra el texto atenuado, que comunica lo mismo sin girar. */
@media (prefers-reduced-motion: reduce){
  .btn--cargando{ opacity:.55; }
  .btn.btn--cargando::after{ display:none; }
}

.empty-state{ text-align:center; padding:70px 20px; color:var(--text2); }
.empty-state .ei{ font-size:3rem; margin-bottom:14px; }
/* El icono del estado vacío. Va dentro de un disco tenue para que no quede
   flotando solo en medio de tanto espacio en blanco. */
.empty-state .ei-svg{ width:34px; height:34px; fill:none; stroke:var(--accent);
  stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round;
  box-sizing:content-box; padding:19px; border-radius:50%;
  background:rgba(219,254,45,.07); border:1px solid rgba(219,254,45,.16); }

/* El sello de "pedido confirmado". Antes era una palomita tecleada como
   texto, que cambiaba de forma según el sistema operativo; ahora es el mismo
   trazo dibujado que usa el resto del sitio. Se dibuja sola al aparecer:
   es el único momento del viaje que merece que algo se celebre.

   Ojo con el stroke-dashoffset de aquí abajo: vale 26, que es la palomita SIN
   dibujar, y solo la animación lo lleva a 0. Para quien pide menos movimiento
   la animación no corre (la apaga el bloque global del final del archivo, con
   !important), así que si nadie devuelve el dashoffset a 0 el sello se queda
   en blanco. De eso se encarga la regla marcada más abajo. */
.sello-listo{ width:76px; height:76px; margin:0 auto 18px; border-radius:50%;
  display:grid; place-items:center; background:rgba(219,254,45,.13);
  border:1px solid rgba(219,254,45,.28); }
.sello-listo .ei-svg{ width:34px; height:34px; padding:0; border:0; background:none;
  stroke-width:2.2; stroke-dasharray:26; stroke-dashoffset:26;
  animation:palomeo .5s .15s var(--ease) forwards; }
.atencion-abierto strong{ color:var(--accent); }

@media (prefers-reduced-motion: reduce){
  .toast,.toast--sale,.tp-titulo svg,.tp-barra,.tp-prog > i,
  .cart-pop,.cart-pop::after,.cart-pop .cart-badge,
  .sello-listo .ei-svg{ animation:none; }
  /* ESTA es la regla que hay que respetar: sin ella, quien pidió menos
     movimiento no ve una palomita a medias, ve un sello VACÍO, porque la
     animación que iba a dibujarla está apagada. Lleva !important para que
     siga funcionando aunque alguien mueva de sitio el bloque de arriba. */
  .tp-titulo svg,.sello-listo .ei-svg{ stroke-dashoffset:0 !important; }
  .tp-prog > i{ transform:scaleX(1); }
}

/* ===== Footer ===== */
.site-footer{ border-top:1px solid var(--border); background:var(--bg-2); padding-block:50px 28px; margin-top:40px; }
.footer-grid{ display:grid; grid-template-columns:1.5fr 1fr 1fr 1fr; gap:32px; }
.footer-brand img{ max-width:240px; margin-bottom:14px; }
.site-footer h4{ font-size:.78rem; letter-spacing:.16em; color:var(--text2); margin-bottom:14px; text-transform:uppercase; }
.site-footer a{ color:var(--text2); font-size:.9rem; display:block; padding:5px 0; transition:color .15s; }
.site-footer a:hover{ color:var(--accent); }
/* Mismo caso que .aviso-legal: es texto legal, tiene que poder leerse. */
.footer-disclaimer{ font-size:.8rem; color:var(--text2); max-width:52ch; margin-top:12px; }
/* Aquí va la nota del asterisco: los estados a los que NO se envía. Es la
   aclaración de "Envío discreto en todo México*", así que tiene que leerse
   igual que la frase que la llama (iba en --text3, 2.8:1). */
.footer-bottom{ display:flex; justify-content:space-between; gap:16px; flex-wrap:wrap; border-top:1px solid var(--border); margin-top:36px; padding-top:20px; font-size:.8rem; color:var(--text2); }

/* ===== Utilidades ===== */
.mt-1{margin-top:8px}.mt-2{margin-top:16px}.mt-3{margin-top:24px}.mt-4{margin-top:36px}
.hidden{ display:none !important; }
/* Aparición al hacer scroll. El retraso lo pone main.js en --i según la
   posición del elemento dentro de su fila/rejilla, así una cuadrícula de
   productos entra en cascada en vez de aparecer toda de golpe. */
.fade-up{ opacity:0; transform:translateY(20px);
  transition:opacity .55s var(--ease), transform .55s var(--ease), filter .55s var(--ease);
  transition-delay:calc(var(--i, 0) * 55ms); }
.fade-up.in{ opacity:1; transform:none; }

/* ===== Responsive ===== */
@media (max-width:1024px){
  .benefits-grid{ grid-template-columns:repeat(2,1fr); }
  .cat-grid{ grid-template-columns:repeat(2,1fr); }
  .product-grid.cols-3{ grid-template-columns:repeat(2,1fr); }
}
@media (max-width:960px){
  /* minmax(0,1fr) y no 1fr a secas. `1fr` significa en realidad
     `minmax(auto,1fr)`, y ese `auto` impide que la columna baje del ancho
     mínimo de su contenido: no encoge, empuja. En una pantalla de 320px
     —iPhone SE y buena parte de la gama baja de Android— el checkout se
     salía 42px por la derecha y había que arrastrar la página de lado
     para leer los campos mientras se escribe la dirección.
     Con el 0 como mínimo, la columna sí puede encoger. Medido: de 42px
     de desborde a cero, y sin que nada más se salga. */
  .hero-grid,.pdp,.cart-layout,.shop-layout{ grid-template-columns:minmax(0,1fr); }
  .hero-visual{ max-width:400px; margin-inline:auto; order:-1; }
  /* La foto de la ficha deja de ir pegada al hacer scroll, pero se queda en
     'relative' y NO en 'static': lleva encima un escudo transparente contra
     el clic derecho (.protected::after, arriba) que se coloca respecto al
     ancestro posicionado más cercano. Puesta en 'static', ese escudo pasaba
     a medirse contra .site-main y tapaba la página entera con z-index 3: en
     móvil y tablet no se podía tocar el selector de cantidad, ni elegir
     presentación, ni pulsar "Agregar al carrito" — solo respondía la barra
     fija de abajo, que va por encima. */
  /* `top:auto` y no solo `position:relative`. Al quitar el `sticky` quedaba
     el `top:90px` que lo acompañaba arriba —el hueco para que la foto se
     pegue bajo el encabezado en escritorio—, y contra `relative` ese valor
     ya no ancla nada: empuja. Eran 90px de vacío entre las migas y la foto,
     el 11% de una pantalla de celular, gastados en nada y empujando el
     nombre del producto por debajo de la barra de compra.

     Se conserva `relative` porque la capa que protege la imagen se
     posiciona contra ella; `static` la soltaría a la página. */
  .pdp-media{ position:relative; top:auto; }
  .summary,.shop-sidebar{ position:static; }
  .footer-grid{ grid-template-columns:1fr 1fr; }
  /* Filtros plegados: el cliente ve productos sin scrollear una pantalla entera */
  /* ── Las dos tiras de atajos ──────────────────────────────────────────
     Sustituyen al párrafo de presentación, que en el celular ocupaba cuatro
     líneas antes del primer producto para decir algo que no ayuda a comprar. */
  .shop-intro{ display:none; }
  .chips-movil{ display:block; margin:14px 0 4px; }

  .chips-fila{
    display:flex; gap:8px; overflow-x:auto; overscroll-behavior-x:contain;
    /* position:relative es lo que hace que offsetLeft de cada chip se mida
       contra ESTA tira: catalogo.js lo usa para volver a centrar el chip
       activo después de filtrar (ver centrarChips). */
    position:relative;
    /* Sangrado hasta los bordes de la pantalla. No es estética: si la tira
       terminara con el margen del contenedor, el último chip cabría entero y
       nada diría que hay más a la derecha. Cortado por el borde, se ve que
       sigue. */
    margin-inline:-22px; padding:2px 22px 12px;
    scroll-snap-type:x proximity;
    scrollbar-width:none; -ms-overflow-style:none;
  }
  .chips-fila::-webkit-scrollbar{ display:none; }
  .chips-fila + .chips-fila{ padding-top:0; margin-top:-6px; }

  .chip{
    display:inline-flex; align-items:center; gap:7px; flex:none;
    scroll-snap-align:start;
    padding:9px 15px; min-height:40px; border-radius:100px;
    border:1px solid var(--border-2); background:var(--surface);
    color:var(--text2); font-size:.85rem; font-weight:600; white-space:nowrap;
    transition:border-color .18s, color .18s, background-color .18s;
  }
  .chip i{ font-style:normal; font-size:.74rem; color:var(--text3); }
  .chip:active{ transform:scale(.97); }
  .chip.is-on{
    border-color:var(--accent); background:rgba(219,254,45,.12); color:var(--accent);
  }
  .chip.is-on i{ color:var(--accent); }

  /* Los de tipo llevan la etiqueta dentro, así que el chip aporta solo el
     marco: menos relleno para que no parezca una cápsula dentro de otra. */
  .chip--tipo{ padding:6px 12px 6px 7px; gap:8px; }
  .chip--tipo .badge{ font-size:.62rem; padding:4px 10px; }

  /* El botón de filtros encoge y deja de ocupar el ancho entero: con la
     categoría y el tipo ya delante, lo que queda detrás es el precio y el
     orden —lo que casi nadie toca—, así que ya no es la puerta principal y no
     tiene por qué parecerlo. Sigue midiendo 40px de alto, que es lo que se
     acierta con el pulgar. */
  .filters-toggle{
    display:inline-flex; align-items:center; justify-content:center; gap:8px; width:auto;
    padding:10px 16px; min-height:40px; border-radius:100px; cursor:pointer;
    border:1px solid var(--border-2); background:var(--surface); color:var(--text2);
    font-family:var(--font-head); font-size:.72rem; font-weight:800;
    letter-spacing:.06em; text-transform:uppercase;
    transition:border-color .18s, color .18s, transform .18s var(--ease);
  }
  .filters-toggle:hover,.filters-toggle[aria-expanded="true"]{ border-color:var(--accent); color:var(--accent); }
  .filters-count{ display:grid; place-items:center; min-width:20px; height:20px; padding:0 6px;
    border-radius:100px; background:var(--accent); color:#0a0a0f; font-size:.7rem; }
  .shop-sidebar{ display:none; }
  .shop-sidebar.is-open{ display:block; animation:filtersIn .25s var(--ease); }
  /* Cada opción de filtro pasa de 40 a 44px de alto. Es el mínimo que se
     acierta con el pulgar sin apuntar, y estas filas se tocan una detrás de
     otra: fallar y darle a la de al lado cambia el catálogo entero. */
  .filter-opt{ padding-top:10px; padding-bottom:10px; }
  @keyframes filtersIn{ from{ opacity:0; transform:translateY(-8px) } }
  /* Entrada y salida SIMÉTRICAS donde el navegador lo permite. Antes el
     panel entraba animado pero al cerrarse desaparecía de golpe: una
     superficie debe salir por donde entró. La salida es más corta que la
     entrada (.18s vs .25s) a propósito: al cerrar, el usuario ya decidió
     y el sistema debe responder rápido. En navegadores viejos esta regla
     completa se ignora y queda el comportamiento anterior. */
  @supports (transition-behavior: allow-discrete){
    .shop-sidebar{
      opacity:0; transform:translateY(-8px);
      transition:opacity .18s var(--ease), transform .18s var(--ease),
                 display .18s allow-discrete;
    }
    .shop-sidebar.is-open{
      animation:none; opacity:1; transform:none;
      transition-duration:.25s;
    }
    @starting-style{
      .shop-sidebar.is-open{ opacity:0; transform:translateY(-8px); }
    }
  }
  /* El buscador NO se oculta: pasa a su propia fila bajo el logo.
     Una tienda sin buscador en el celular pierde ventas. */
  .site-header{ height:auto; }
  .header-inner{ flex-wrap:wrap; padding-block:12px; gap:12px; }
  .header-search{ order:3; flex:1 0 100%; max-width:none; margin-inline:0; }
  .brand{ margin-right:auto; }
}
@media (max-width:720px){
  /* Se ancla al borde inferior del header (no a una altura fija), porque
     ahora el header crece para alojar el buscador en su propia fila. */
  .nav{ position:absolute; inset:100% 0 auto 0; flex-direction:column; align-items:stretch; background:var(--bg-2);
    border-bottom:1px solid var(--border); padding:14px 22px 22px; gap:2px; transform:translateY(-120%); z-index:55;
    /* visibility, no solo transform: el fondo del header es semitransparente
       y el menú replegado se alcanzaba a ver por detrás. */
    visibility:hidden; transition:transform .3s var(--ease), visibility .3s; }
  .nav.open{ transform:translateY(0); visibility:visible; }
  .nav a{ padding:14px; border-radius:12px; }
  .menu-toggle{ display:grid; place-items:center; }
  .grid-2{ grid-template-columns:1fr; }
  .benefits-grid{ grid-template-columns:1fr; }
  .cat-grid,.product-grid.cols-3{ grid-template-columns:1fr 1fr; }
  .footer-grid{ grid-template-columns:1fr; }
  .cart-item{ grid-template-columns:64px 1fr; }
  .cart-item img{ width:64px; height:64px; }
  /* Tarjetas a 2 columnas ≈163px: con el tamaño de escritorio, la etiqueta
     de la izquierda (NUEVO/TOP/OFERTA) chocaba con la de stock a la derecha. */
  .badge{ font-size:.58rem; padding:4px 8px; letter-spacing:.05em; }
  .stock-tag{ font-size:.58rem; padding:4px 8px; }
  .badge-stack{ top:9px; left:9px; gap:4px; }
  .stock-tag{ top:9px; right:9px; }
}
@media (max-width:400px){
  /* Pantallas muy angostas: una sola columna para que el producto respire */
  .cat-grid,.product-grid.cols-3{ grid-template-columns:1fr; }
}

/* =====================================================================
   POLISH PREMIUM — texturas, glows, micro-interacciones (Fase 1)
   ===================================================================== */

/* Grano de película + viñeta global (sutil, sobre todo el sitio) */
body::after{
  content:""; position:fixed; inset:0; z-index:1000; pointer-events:none; opacity:.05;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  mix-blend-mode:overlay;
}
.site-main{ position:relative; }
.site-main::before{
  content:""; position:fixed; inset:0; z-index:0; pointer-events:none;
  box-shadow:inset 0 0 240px 40px rgba(0,0,0,.55);   /* viñeta */
}

/* Selección y scrollbar de marca */
::selection{ background:var(--accent); color:#0a0a0f; }
html{ scrollbar-color:var(--accent-2) var(--bg); }
::-webkit-scrollbar{ width:11px; height:11px; }
::-webkit-scrollbar-track{ background:var(--bg-2); }
::-webkit-scrollbar-thumb{ background:linear-gradient(var(--accent-2),#6a1a99); border-radius:100px; border:2px solid var(--bg-2); }
::-webkit-scrollbar-thumb:hover{ background:var(--accent); }

/* Aura viva detrás del hero */
.hero::before{
  content:""; position:absolute; top:-20%; right:-10%; width:70%; height:120%; z-index:-1; pointer-events:none;
  background:radial-gradient(closest-side, rgba(219,254,45,.16), transparent 70%);
  filter:blur(20px); animation:auraPulse 9s var(--ease) infinite;
}
@keyframes auraPulse{ 0%,100%{ opacity:.7; transform:scale(1) } 50%{ opacity:1; transform:scale(1.08) } }

/* Título del hero con degradado neón + glow */
.hero h1 span{
  background:linear-gradient(100deg, var(--accent-light), var(--accent) 40%, #a7e000 70%, var(--accent-light));
  -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent;
  filter:drop-shadow(0 0 26px var(--accent-glow));
}

/* Botón primario: barrido de brillo al hover */
.btn--primary{ position:relative; overflow:hidden; }
.btn--primary::after{
  content:""; position:absolute; inset:0; transform:translateX(-120%);
  background:linear-gradient(100deg, transparent 30%, rgba(255,255,255,.55) 50%, transparent 70%);
}
.btn--primary:hover::after{ transform:translateX(120%); transition:transform .7s var(--ease); }

/* Tarjetas: brillo que sigue el borde + gloss al hover */
.card{ transform-style:preserve-3d; }
.card::before{
  content:""; position:absolute; inset:0; z-index:4; pointer-events:none; opacity:0; border-radius:inherit;
  background:radial-gradient(400px circle at var(--mx,50%) var(--my,0%), rgba(219,254,45,.12), transparent 45%);
  transition:opacity .3s;
}
.card:hover::before{ opacity:1; }
.card:hover{ box-shadow:0 30px 70px -28px rgba(0,0,0,.85), 0 0 0 1px rgba(219,254,45,.25); }

/* Chips de categoría / feature: elevación con glow */
.cat-card:hover{ box-shadow:0 24px 50px -24px var(--accent-glow), 0 0 0 1px rgba(219,254,45,.3); }
.feature{ transition:transform .25s var(--ease), border-color .25s, background .25s; }
.feature:hover{ transform:translateY(-4px); border-color:rgba(219,254,45,.3); background:rgba(219,254,45,.03); }

/* Marca del header con leve glow al hover */
.brand-logo,.brand-mark{ transition:filter .3s; }
.brand:hover .brand-logo,
.brand:hover .brand-mark{ filter:drop-shadow(0 0 12px var(--accent-glow)); }

/* El logo horizontal se achica en pantallas chicas */
@media (max-width:900px){ .brand-logo{ height:34px; max-width:180px; } }
@media (max-width:480px){ .brand-logo{ height:29px; max-width:150px; } }

/* El desenfoque de entrada. La transición vive junto al resto de .fade-up
   (en Utilidades); antes se declaraba aquí sin transición y el blur
   desaparecía de golpe en vez de resolverse. */
.fade-up{ filter:blur(5px); }
.fade-up.in{ filter:blur(0); }

/* Imagen hero (isotipo 3D, PNG con fondo transparente).
   Giro suave sobre su propio eje Y con perspectiva: la sombra ya viene
   pintada en el render, así que la oscilación se lee como volumen real. */
.hero-visual{ perspective:1400px; perspective-origin:50% 45%; }
.hero-img{ width:100%; transform-style:preserve-3d; will-change:transform;
  filter:drop-shadow(0 30px 70px rgba(167,37,252,.40));
  animation:heroSpin3d 11s ease-in-out infinite; }
/* Visor del modelo 3D real (GLB) — gira 360° en loop y se puede arrastrar */
.hero-3d{
  /* height:auto es imprescindible: model-viewer trae height:150px por defecto
     y sin esto el visor sale aplastado en vez de cuadrado. */
  width:100%; height:auto; aspect-ratio:1/1; display:block; background:transparent;
  --poster-color:transparent;
  filter:drop-shadow(0 30px 70px rgba(167,37,252,.40));
  cursor:grab;
}
.hero-3d:active{ cursor:grabbing; }
.hero-3d::part(default-progress-bar){ background:var(--accent); }
/* Si el navegador no soporta el visor, la imagen de respaldo se ve igual de bien */
.hero-3d[data-fallback] + .hero-img{ display:block; }

@keyframes heroSpin3d{
  0%   { transform:translateY(0)     rotateY(-24deg) rotateX(3deg); }
  25%  { transform:translateY(-10px) rotateY(0deg)   rotateX(0deg); }
  50%  { transform:translateY(-16px) rotateY(24deg)  rotateX(-3deg); }
  75%  { transform:translateY(-10px) rotateY(0deg)   rotateX(0deg); }
  100% { transform:translateY(0)     rotateY(-24deg) rotateX(3deg); }
}
.hero-visual .hero-badge-float{ z-index:3; }

/* ===== Blog ===== */
.post-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:24px; }
.post-card{ background:linear-gradient(180deg,var(--surface),var(--bg-2)); border:1px solid var(--border);
  border-radius:var(--radius-lg); overflow:hidden; display:flex; flex-direction:column;
  transition:transform .25s var(--ease), border-color .25s, box-shadow .25s; }
.post-card:hover{ transform:translateY(-5px); border-color:var(--border-2); box-shadow:var(--shadow); }
.post-card-media{ display:block; aspect-ratio:16/9; overflow:hidden; background:var(--bg-2); }
.post-card-media img{ width:100%; height:100%; object-fit:cover; transition:transform .5s var(--ease); }
.post-card:hover .post-card-media img{ transform:scale(1.05); }
.post-card-body{ padding:20px; display:flex; flex-direction:column; gap:10px; flex:1; }
.post-meta{ font-size:.78rem; color:var(--text3); letter-spacing:.02em; }
.post-card-title{ word-spacing:.08em; font-family:var(--font-head); font-size:1.2rem; text-transform:uppercase; line-height:1.15; }
.post-card:hover .post-card-title{ color:var(--accent); }
.post-card-excerpt{ font-size:.9rem; color:var(--text2); flex:1; }
.post-link{ color:var(--accent); font-weight:600; font-size:.88rem; }

/* Artículo */
.post-single{ max-width:780px; }
.post-single h1{ font-size:clamp(1.9rem,4.5vw,3rem); margin-top:10px; }
.post-cover{ border-radius:var(--radius-lg); overflow:hidden; border:1px solid var(--border); }
.post-cover img{ width:100%; height:auto; }
.post-body{ max-width:none; }
.post-body p,.post-body li{ font-size:1.02rem; line-height:1.8; }
.post-body h2{ margin-top:36px; }
.post-body a{ color:var(--accent); text-decoration:underline; text-underline-offset:3px; }
.post-share{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-top:36px;
  padding-top:22px; border-top:1px solid var(--border); font-size:.88rem; color:var(--text2); }
.post-cta{ margin-top:40px; padding:32px; border-radius:var(--radius-lg); text-align:center;
  background:linear-gradient(120deg, rgba(219,254,45,.10), rgba(167,37,252,.14)); border:1px solid var(--border-2); }

/* ===== Preguntas frecuentes ===== */
.faq-list{ display:grid; gap:12px; }
.faq-item{ background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden; }
.faq-item summary{ cursor:pointer; list-style:none; padding:18px 20px; font-family:var(--font-head);
  text-transform:uppercase; font-size:1rem; display:flex; justify-content:space-between; align-items:center; gap:14px; }
.faq-item summary::-webkit-details-marker{ display:none; }
.faq-item summary::after{ content:"+"; color:var(--accent); font-size:1.4rem; font-family:var(--font-body); transition:transform .25s var(--ease); }
.faq-item[open] summary::after{ transform:rotate(45deg); }
.faq-item[open]{ border-color:rgba(219,254,45,.35); }
.faq-item p{ padding:0 20px 18px; color:var(--text2); margin:0; }

/* ===== Reseñas ===== */
.review-grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:20px; }
.review-card{ background:linear-gradient(180deg,var(--surface),var(--bg-2)); border:1px solid var(--border);
  border-radius:var(--radius-lg); padding:24px; display:flex; flex-direction:column; gap:14px; margin:0; }
.review-card .stars{ color:var(--accent); letter-spacing:3px; font-size:1.05rem; }
.review-card blockquote{ margin:0; font-size:.95rem; color:#d8d2e0; line-height:1.7; flex:1; }
.review-card figcaption{ display:flex; align-items:center; gap:12px; }
.review-avatar{ width:40px; height:40px; border-radius:50%; flex-shrink:0; display:grid; place-items:center;
  background:linear-gradient(135deg,var(--accent),var(--accent-2)); color:#0a0a0f; font-weight:800; font-family:var(--font-head); }
.review-card figcaption b{ display:block; font-size:.92rem; }
.review-card figcaption small{ display:block; color:var(--text3); font-size:.78rem; }

/* ===== Contacto flotante (WhatsApp / Telegram) ===== */
.floating-contact{
  position:fixed; right:20px; bottom:62px; z-index:80;   /* 62px = encima del carrusel */
  display:flex; flex-direction:column; gap:12px;
}
.fab{
  width:54px; height:54px; border-radius:50%; display:grid; place-items:center; color:#fff;
  box-shadow:0 10px 26px -8px rgba(0,0,0,.7); position:relative;
  transition:transform .22s var(--ease), box-shadow .22s var(--ease);
}
.fab svg{ width:28px; height:28px; }
.fab--wa{ background:#25D366; }
.fab--tg{ background:#229ED9; }
.fab:hover{ transform:translateY(-3px) scale(1.06); }
.fab--wa:hover{ box-shadow:0 14px 30px -8px rgba(37,211,102,.65); }
.fab--tg:hover{ box-shadow:0 14px 30px -8px rgba(34,158,217,.65); }
/* Halo que late, para invitar al clic */
.fab::before{
  content:""; position:absolute; inset:0; border-radius:50%; background:inherit;
  animation:fabPulse 2.6s ease-out infinite; z-index:-1;
}
@keyframes fabPulse{ 0%{ transform:scale(1); opacity:.55 } 70%,100%{ transform:scale(1.5); opacity:0 } }
/* Etiqueta al pasar el mouse */
.fab::after{
  content:attr(data-tip); position:absolute; right:calc(100% + 12px); top:50%; transform:translateY(-50%) translateX(6px);
  background:var(--surface-2); color:var(--text); border:1px solid var(--border-2);
  padding:7px 13px; border-radius:100px; font-size:.8rem; white-space:nowrap;
  opacity:0; pointer-events:none; transition:opacity .2s, transform .2s;
}
.fab:hover::after{ opacity:1; transform:translateY(-50%) translateX(0); }

@media (max-width:720px){
  .fab{ width:48px; height:48px; }
  .fab svg{ width:25px; height:25px; }
  .fab::after{ display:none; }   /* en móvil no hay hover */
}

/* Los avisos flotantes suben por encima del carrusel fijo */
.toast-wrap{ bottom:66px; }

/* ── Ajustes de móvil (van AL FINAL a propósito) ──
   Estas reglas tienen que ganarle a las de arriba, y como los @media no
   añaden especificidad, lo que decide es el ORDEN en el archivo. Aquí se
   define el reparto definitivo de la zona baja en el celular. */
@media (max-width:720px){
  .benefit-bar{ display:none; }        /* el carrusel cede su lugar a la navegación */
  .floating-contact{ display:none; }   /* WhatsApp vive ahora en la barra inferior */
  /* El hueco al final de la página es EXACTAMENTE lo que tapan las barras,
     más un respiro de 8px para que la última línea no quede pegada al filo.
     Calculado, no adivinado: los valores fijos que había aquí se quedaban
     cortos y recortaban contenido. */
  body{ padding-bottom:calc(var(--gl-tabbar) + var(--gl-suelo) + 8px); }
  body.tiene-buybar{ padding-bottom:calc(var(--gl-tabbar) + var(--gl-buybar) + var(--gl-suelo) + 8px); }
  .toast-wrap{ bottom:calc(var(--gl-tabbar) + var(--gl-suelo) + 12px); }
  body.tiene-buybar .toast-wrap{ bottom:calc(var(--gl-tabbar) + var(--gl-buybar) + var(--gl-suelo) + 12px); }

  /* ── Tamaño mínimo para el dedo ──
     Medido en 375x812: el buscador daba 38px de alto, su lupa 36 y los
     botones "Agregar" 37. El mínimo cómodo es 44 (Apple) / 48 (Android);
     por debajo de eso la gente falla el toque y le pega al de al lado.  */
  .header-search{ padding-left:14px; }
  .header-search input{ min-height:44px; padding-block:11px;
    /* 16px no es capricho estético: por debajo de eso Safari en iPhone
       hace zoom a TODA la página al tocar el campo, y el usuario se queda
       con la vista descuadrada. Estaba en 14.4px. */
    font-size:16px; }
  .header-search button,
  .header-search .search-btn{ min-width:44px; min-height:44px; }
  .gl-add,
  .card .btn--sm{ min-height:44px; padding-inline:18px; }
  .qty button{ min-width:44px; min-height:44px; }

  /* ── Los 16px, ahora en TODOS los campos ──────────────────────────────
     El buscador ya los tenía y el resto del sitio no: los once campos del
     checkout —nombre, correo, teléfono, calle, colonia, código postal,
     ciudad, estado, notas— seguían a 14.4px. Safari en iPhone hace zoom a
     la página entera al enfocar un campo por debajo de 16px, y no lo
     deshace al salir: quien escribe su dirección se queda con la vista
     ampliada y corrida, campo tras campo, en la única pantalla del sitio
     donde un abandono cuesta una venta hecha.

     Va en el media query y no en la regla base para no engordar los
     formularios del panel en escritorio, donde el zoom no existe. */
  .input, .sort-select, select.input, textarea.input,
  input[type=text], input[type=email], input[type=tel],
  input[type=search], input[type=number], input[type=password],
  select, textarea{ font-size:16px; }

  /* Y el alto mínimo cómodo, que estos campos tampoco alcanzaban: medían
     42px contra los 44 de Apple y los 48 de Android. */
  .input, .sort-select, select.input{ min-height:44px; }

  /* Todo botón llega a 44px. Los `.btn` de la portada y del catálogo se
     quedaban en 41: no es un fallo visible, es el que hace que un pulgar
     falle el toque y le pegue al de al lado. */
  .btn{ min-height:44px; }

  /* ── Zona de toque sin mover nada de sitio ───────────────────────────
     Once enlaces del pie y las migas de pan daban 33px y 17px de alto.
     La solución no es agrandar el texto —eso rehace el pie entero— sino
     el área que responde al dedo: con `padding-block` el texto se queda
     donde está y lo que crece es el blanco que ya había alrededor.

     El pie es donde vive el enlace de rastrear un pedido, que es lo que
     busca quien ya compró y viene con prisa. */
  .site-footer .footer-grid a{
    padding-block:11px;                /* 22 + 23 de línea = 45px */
    /* Sin esto, dos enlaces contiguos comparten frontera y un toque en el
       borde activa el de arriba. */
    margin-block:-4px;
  }
  .breadcrumb a{ display:inline-block; padding-block:13px; margin-block:-13px; }

  /* El título de una tarjeta de producto es lo que la gente toca —antes que
     la imagen y antes que el botón—, y medía 18px de alto. */
  .card-title, .post-card-title{ display:block; padding-block:6px; }
  /* Separación entre blancos contiguos: sin esto, dos botones pegados
     hacen que un toque impreciso active el equivocado. */
  .card-body .gl-add{ margin-top:4px; }
}

/* ===== Cifras de ancho fijo =====
   En Space Grotesk el "1" es 3.5px más angosto que el "8" (medido sobre seis
   dígitos). Con cifras normales los precios de una rejilla no alinean entre
   sí, y al cambiar de presentación el precio "brinca" de ancho aunque tenga
   los mismos dígitos. tabular-nums le da a cada cifra el mismo ancho: es lo
   que hacen los bancos y las tiendas serias, y se nota aunque no se vea. */
.price, .pdp-price, .price-desde, .variante-precio, .price-por-definir,
.summary .line, .summary .total, .qty input, .card-price,
.cart-count, .cart-badge, .tp-precio, .badge, .stock-tag,
.price-range, .rr-guia, .oferta-precio{
  font-variant-numeric:tabular-nums;
  font-feature-settings:"tnum" 1;
}

/* ===== Respuesta al toque =====
   En el celular no hay :hover, así que todo lo que hoy solo reacciona al
   pasar el ratón no da NINGUNA señal al tocarse. El dedo además tapa lo que
   toca, y sin respuesta inmediata la gente toca dos veces. Un hundido de
   ~2% durante el toque confirma que el golpe se registró.
   Solo dos reglas :active existían en todo el sitio antes de esto. */
.card:active,
.cat-card:active,
.variante:active,
.filters-toggle:active,
.sort-select:active,
.tabbar a:active,
.fab:active,
.cart-btn:active,
/* El :not(:disabled) importa: un botón apagado que se hunde al tocarlo
   comunica lo contrario de lo que queremos. Su hundido propio, algo más
   marcado, vive junto al resto del selector de cantidad. */
.qty button:active:not(:disabled),
/* Antes decía ".pager a", una clase que no existe en el sitio: la
   paginación era el único control muerto al tacto de toda la tienda. */
.pagination a:active{
  transform:scale(.975);
  transition-duration:.07s;
}
/* La tarjeta ya sube 6px al pasar el ratón: al tocarla vuelve a su sitio
   en vez de encogerse sobre el levantamiento, que se veía tembloroso. */
.card:active{ transform:translateY(0) scale(.985); }
.tabbar a:active{ background:rgba(255,255,255,.06); }
/* Solo donde el dedo es el que manda: en escritorio el ratón ya tiene hover. */
@media (hover:hover){
  .card:active{ transform:translateY(-6px) scale(.99); }
}

/* ===== Iconos de línea =====
   Sustituyen a los emojis que había suelto en el pie, la ficha de producto y
   la confirmación de pedido. Un emoji lo dibuja el sistema operativo: cambia
   de forma y de color entre Windows, iPhone y Android, y sus colores planos
   chocan con un diseño oscuro. Estos son vectores que heredan el color. */
.fi{ width:1em; height:1em; flex:none; fill:none; stroke:currentColor;
  stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round;
  vertical-align:-.13em; margin-right:.45em; opacity:.75; }
.footer-datos{ display:flex; flex-direction:column; gap:6px; }
.footer-datos span{ display:flex; align-items:center; }
.footer-horario{ display:flex; align-items:center; padding:5px 0;
  font-size:.85rem; color:var(--text3); }

/* Sello de edad: reemplaza al emoji 🔞, que se veía infantil junto al aviso. */
.edad-sello{ display:inline-block; margin-right:8px; padding:1px 7px;
  border:1.5px solid currentColor; border-radius:6px;
  font-size:.78em; font-weight:800; letter-spacing:.02em; vertical-align:.05em; }

/* Pasos tras la compra: antes eran tres líneas con emoji separadas por <br>. */
.pasos-pedido{ list-style:none; max-width:560px; margin-inline:auto; padding:20px 22px;
  text-align:left; background:rgba(219,254,45,.045);
  border:1px solid rgba(219,254,45,.16); border-radius:var(--radius);
  display:flex; flex-direction:column; gap:14px; }
.pasos-pedido li{ display:flex; gap:12px; align-items:flex-start;
  font-size:.94rem; line-height:1.55; color:var(--text2); }
.pasos-pedido svg{ width:20px; height:20px; flex:none; margin-top:2px;
  fill:none; stroke:var(--accent); stroke-width:1.7;
  stroke-linecap:round; stroke-linejoin:round; }
.pasos-pedido strong{ color:var(--text); }
.pasos-pedido a{ color:var(--accent); text-decoration:underline;
  text-underline-offset:3px; }

/* ===== Foco de teclado =====
   Quien navega con Tab (por discapacidad motriz, por lector de pantalla o
   simplemente por costumbre) no tenía NINGUNA pista de dónde estaba parado:
   varias reglas hacían outline:none sin poner nada en su lugar.
   :focus-visible solo se activa con teclado, así que el clic con ratón sigue
   viéndose igual de limpio que antes. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible{
  outline:2px solid var(--accent);
  outline-offset:3px;
  border-radius:6px;
}
/* Elementos que ya tienen su propia forma redondeada: el anillo la sigue. */
.btn:focus-visible,
.cart-btn:focus-visible,
.fab:focus-visible,
.newsletter input:focus-visible{ border-radius:100px; }
.card a:focus-visible,
.cat-card:focus-visible{ border-radius:20px; }
/* Sobre fondo lima el anillo lima sería invisible: se invierte a tinta. */
.btn--primary:focus-visible{ outline-color:var(--bg); box-shadow:0 0 0 5px rgba(219,254,45,.45); }

/* El buscador quitaba el outline sin reemplazo. Ahora marca todo el campo,
   que es lo que el usuario percibe como "aquí estoy escribiendo". */
.header-search:focus-within{
  border-color:var(--accent);
  box-shadow:0 0 0 3px rgba(219,254,45,.16);
}
.header-search input:focus-visible{ outline:none; }

/* Saltar al contenido: primer tabulador de la página, invisible hasta que
   se enfoca. Ahorra pasar por todo el menú en cada carga. */
.skip-link{
  position:absolute; left:16px; top:-100px; z-index:200;
  background:var(--accent); color:#0a0a0f; font-weight:700;
  padding:12px 20px; border-radius:0 0 12px 12px; text-decoration:none;
  transition:top .18s var(--ease);
}
.skip-link:focus{ top:0; }

/* Respeta reduce-motion (el carrusel se detiene, sigue legible) */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{ animation:none !important; transition:none !important; }
  .benefit-track{ animation:none !important; }
}


/* ===== Lista de correos (newsletter) =====
   El formulario ahora guarda de verdad, así que necesita lo que antes no
   tenía: un sitio donde decir cómo salió, y un aviso de qué se hace con el
   correo. Sin la segunda parte estaríamos pidiendo un dato personal sin
   explicar para qué, que en México no es solo mala educación. */

/* Texto solo para lectores de pantalla: invisible pero presente. No se usa
   display:none porque eso también lo esconde del lector. */
.sr-only{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0;
}

/* Trampa para robots. Fuera de la pantalla en vez de display:none porque
   algunos programas ignoran a propósito los campos ocultos con display. */
.hp-campo{
  position:absolute !important; left:-9999px !important;
  width:1px !important; height:1px !important; opacity:0 !important;
  pointer-events:none;
}

/* Resultado del envío. Reserva su altura desde el principio: si apareciera
   de la nada, empujaría hacia abajo el botón justo después de pulsarlo. */
.news-estado{
  min-height:1.4em; margin:12px auto 0; font-size:.9rem; font-weight:600;
  color:var(--accent); max-width:52ch;
}
.news-estado.news-error{ color:#ff8f8f; }

.news-legal{
  margin:8px auto 0; max-width:52ch;
  font-size:.76rem; line-height:1.5; color:var(--text2); opacity:.85;
}
.news-legal a{ color:var(--accent); }

/* Mientras el servidor responde, el campo y el botón se bloquean: evita
   que un segundo clic mande el mismo correo dos veces. */
.newsletter form.enviando input,
.newsletter form.enviando button{ opacity:.6; pointer-events:none; }

/* ===== Campo con error en el checkout =====
   El rojo por sí solo no basta: hay gente que no lo distingue, y quien usa
   lector de pantalla no ve ningún color. Por eso el campo lleva además
   aria-invalid y un mensaje escrito debajo diciendo qué pasa.
   El borde es grueso (2px) para que se note también en blanco y negro. */
.input--error{
  border-color:#ff7a7a !important;
  box-shadow:0 0 0 3px rgba(255,122,122,.16) !important;
}
.input--error:focus{
  border-color:#ff7a7a !important;
  box-shadow:0 0 0 4px rgba(255,122,122,.28) !important;
}
.campo-error{
  display:block; margin-top:6px;
  font-size:.8rem; line-height:1.4; font-weight:600; color:#ff8f8f;
}
/* El triángulo hace visible el error aunque el color no se perciba. */
.campo-error::before{ content:"⚠ "; }

/* =====================================================================
   EL CATÁLOGO QUE NO RECARGA
   ---------------------------------------------------------------------
   Filtrar, ordenar y pasar de página ya no recargan la ventana: se pide
   la misma dirección por detrás y se sustituye solo el bloque de
   resultados (assets/js/catalogo.js). Aquí vive lo que se VE de eso.

   La idea del movimiento es una sola y se puede decir en una frase: una
   tarjeta que sigue estando después de filtrar no debe desaparecer y
   volver a nacer, debe MOVERSE. Por eso cada tarjeta lleva un nombre de
   transición con el id de su producto: el navegador reconoce que la de
   antes y la de después son la misma y la lleva de una posición a la
   otra. Todo lo demás son detalles al servicio de eso.
   ===================================================================== */

/* Curva propia: sale rápido y llega frenando mucho, que es como se mueven
   las cosas con peso. La de la casa (--ease) es para gestos cortos; un
   recorrido largo por la rejilla pide una caída más marcada. */
:root{ --ease-mov: cubic-bezier(.16,.84,.30,1); }

/* Las tarjetas que se mueven de sitio. */
::view-transition-group(*){
  animation-duration:.46s;
  animation-timing-function:var(--ease-mov);
}

/* Las que se van y las que llegan.
   Se desvanecen con un desplazamiento corto y un desenfoque leve: sin el
   desenfoque, doce tarjetas cruzándose a la vez se leen como parpadeo. Con
   él, lo que entra y lo que sale se separa del plano de lo que permanece,
   y el ojo sabe a qué agarrarse. */
::view-transition-old(*){
  animation:glSale .26s var(--ease) both;
}
::view-transition-new(*){
  animation:glEntra .38s var(--ease-mov) .06s both;
}
@keyframes glSale{
  to{ opacity:0; transform:scale(.965); filter:blur(3px); }
}
@keyframes glEntra{
  from{ opacity:0; transform:scale(.965); filter:blur(3px); }
}

/* El título y el contador cambian de texto, no de sitio: un fundido corto
   basta y cualquier desplazamiento ahí sería ruido. */
::view-transition-old(gl-titulo), ::view-transition-old(gl-cuenta){ animation:glSale .18s var(--ease) both; }
::view-transition-new(gl-titulo), ::view-transition-new(gl-cuenta){ animation:glEntra .24s var(--ease-mov) both; }

/* La foto que viaja de la tarjeta a la ficha. Es el recorrido más largo de
   los dos —cruza toda la pantalla y cambia de proporción—, así que se toma
   su tiempo y no se le añade ni desenfoque ni escala: la foto tiene que
   seguir siendo legible durante todo el trayecto, porque es lo único que
   el cliente está mirando. */
::view-transition-group(gl-foto-producto){
  animation-duration:.52s;
  animation-timing-function:var(--ease-mov);
}
::view-transition-old(gl-foto-producto),
::view-transition-new(gl-foto-producto){
  animation:none; mix-blend-mode:normal; height:100%; object-fit:cover;
}
#pdpFoto{ view-transition-name:gl-foto-producto; }

/* Mientras se piden los resultados nuevos.
   El aviso solo aparece si la respuesta tarda más de 180 ms (lo decide el
   JavaScript): un estado de carga que aparece y se va en 60 ms se lee como
   un fallo, no como una respuesta. Y no se toca la OPACIDAD del bloque
   entero, que apagaría la tienda: se le quita nitidez al contenido y se
   deja una barra fina de color avanzando arriba, que es donde el ojo ya
   está esperando el cambio. */
#shopVista{ position:relative; }
#shopVista .product-grid, #shopVista .empty-state{
  transition:filter .2s var(--ease), opacity .2s var(--ease);
}
#shopVista.cargando .product-grid, #shopVista.cargando .empty-state{
  filter:saturate(.5); opacity:.55; pointer-events:none;
}
#shopVista.cargando .shop-head::after{
  content:""; position:absolute; left:0; right:0; bottom:-1px; height:2px;
  border-radius:2px; background:var(--accent);
  transform-origin:left; animation:glCargaBarra 1.1s var(--ease) infinite;
}
#shopVista .shop-head{ position:relative; }
@keyframes glCargaBarra{
  0%{ transform:scaleX(0); opacity:0 }
  25%{ opacity:1 }
  100%{ transform:scaleX(1); opacity:0 }
}

/* Sin movimiento si el sistema lo pide. La sustitución sigue ocurriendo
   —el catálogo no vuelve a recargarse—, pero nada se desplaza ni se
   desenfoca: se pierde la animación, no la velocidad. El JavaScript ni
   siquiera arranca la transición en ese caso; estas reglas son el cinturón
   por si el navegador la iniciara igual. */
@media (prefers-reduced-motion: reduce){
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*){ animation:none !important; }
  #shopVista.cargando .shop-head::after{ animation:none; transform:scaleX(1); opacity:.6 }
  #shopVista.cargando .product-grid, #shopVista.cargando .empty-state{ filter:none }
}
