/* ============================================================================
 * SISTEMA DE DISEÑO — Clean Step & Hair Cuts
 * ----------------------------------------------------------------------------
 * Monocromo: obsidiana → platino, con luz LED blanca fría.
 * El color solo aparece cuando comunica un estado (pagado, pendiente,
 * cancelado); nunca como decoración. Eso es lo que lo hace verse caro.
 *
 * Para revender a otra barbería basta cambiar los tokens de :root.
 * ========================================================================== */

:root {
  /* Superficies, de más profunda a más cercana */
  --ink:        #06070A;
  --ink-soft:   #0A0C10;
  --panel:      #0F1216;
  --card:       #13171C;
  --raised:     #191E25;

  /* Acento = luz. Platino con un matiz frío apenas perceptible. */
  --accent:     #E8EEF5;
  --accent-lit: #FFFFFF;
  --accent-dim: #A9B6C4;
  --accent-rgb: 232, 238, 245;

  /* Tinte frío exclusivo para los halos LED */
  --led-cool:   180, 205, 230;

  /* Texto */
  --text:       #EEF2F7;
  --text-soft:  #97A2AF;
  --text-faint: #5C6774;

  /* Líneas */
  --line:       rgba(255, 255, 255, 0.07);
  --line-lit:   rgba(255, 255, 255, 0.16);

  /* Estados: el único color del sitio, y solo para informar */
  --ok:      #4ADE9B;
  --warn:    #E9BC63;
  --danger:  #F08792;
  /* Reagendada. Naranja de verdad, no el dorado de --warn: los dos avisos
     conviven en la misma lista y si se parecen, no distinguen nada. */
  --move:    #F2914F;

  /* Ritmo vertical */
  --gap-xs: .5rem;
  --gap-sm: .875rem;
  --gap:    1.5rem;
  --gap-lg: 2.75rem;
  --gap-xl: 5rem;

  --radius:    14px;
  --radius-lg: 22px;

  /* Vidrio esmerillado. En móvil se baja el radio: desenfocar es lo más caro
     que hace la GPU y ahí no se nota la diferencia.
     El coste de `backdrop-filter` sube con el radio, y se paga entero en cada
     frame del scroll: lo que hay detrás cambia de sitio, así que el navegador
     no puede reaprovechar el desenfoque del frame anterior. De 22 px a 14 px
     no se distingue —encima va el suelo oscuro de `--vidrio`, que es lo que
     de verdad tapa el muro— y es casi la mitad de trabajo.
     Se cayó también el `saturate`: era una segunda pasada de filtro sobre un
     fondo que ya se publica en blanco y negro. */
  --glass-blur: blur(14px);

  /* Suelo oscuro del vidrio. Desde que hay una foto de grafiti detras de toda
     la pagina por debajo del hero, transparentar sin mas deja el texto sobre
     el muro y lo que se lee es el muro. Esta capa va DEBAJO del brillo blanco
     de cada tarjeta y es la que garantiza el contraste, tenga o no el
     navegador `backdrop-filter`. */
  --vidrio:      rgba(8, 10, 14, .56);
  --vidrio-soft: rgba(8, 10, 14, .42);

  color-scheme: dark;
}

/* En movil el desenfoque baja a la mitad (es lo mas caro que hace la GPU),
   asi que el suelo oscuro tiene que subir para compensar: lo que alli separa
   el texto del grafiti es la opacidad, no el difuminado. */
@media (max-width: 640px) {
  :root {
    --glass-blur: blur(9px);
    --vidrio:      rgba(8, 10, 14, .70);
    --vidrio-soft: rgba(8, 10, 14, .58);
  }
}

/* ---------------------------------------------------------------- base ---- */
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* La página sólo se desplaza en vertical. `clip` es lo correcto aquí: corta
   lo que sobresale sin convertir la raíz en un contenedor con scroll propio,
   así los carruseles internos (el riel de reseñas) siguen desplazándose.
   `hidden` queda de respaldo para iOS anterior a 16. */
html { overflow-x: hidden; }
@supports (overflow: clip) { html { overflow-x: clip; } }

/* Recortar lo que sobresale evita la barra, pero en el teléfono el dedo aún
   podía arrastrar la página de lado. `pan-y` le dice al navegador que ese
   gesto aquí no existe. `pinch-zoom` se conserva para no quitarle el zoom a
   quien lo necesita. */
html, body { touch-action: pan-y pinch-zoom; }

/* Las excepciones: lo que SÍ se desliza de lado a propósito — el riel de
   reseñas y la barra de pestañas del panel del barbero. Recuperan el gesto,
   pero `contain` impide que al llegar al tope se lo pasen a la página. */
.review-rail, .overflow-x-auto {
  touch-action: pan-x pan-y pinch-zoom;
  overscroll-behavior-x: contain;
}

body {
  background: var(--ink);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
@supports (overflow: clip) { body { overflow-x: clip; } }

::selection { background: var(--accent); color: #06070A; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--ink); }
::-webkit-scrollbar-thumb { background: #222831; border-radius: 99px; }
::-webkit-scrollbar-thumb:hover { background: #2C333D; }

/* Grano sutil: textura sin ensuciar. Va por ENCIMA de todo (como el grano de
   una película) pero sin capturar el puntero. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: 200; pointer-events: none; opacity: .03;
  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='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* --------------------------------------------------------- tipografía ---- */
.h-hero { font-size: clamp(2.5rem, 8.5vw, 5.25rem); line-height: .96; letter-spacing: -.038em; }
.h-sec  { font-size: clamp(1.85rem, 5vw, 3rem);     line-height: 1.06; letter-spacing: -.03em; }
.h-sub  { font-size: clamp(1.05rem, 2.5vw, 1.3rem); line-height: 1.32; letter-spacing: -.015em; }
.lead   { font-size: clamp(.95rem, 2.1vw, 1.05rem); line-height: 1.68; color: var(--text-soft); }

/* Los titulos de seccion y sus bajadas van sueltos sobre el fondo, sin tarjeta
   que los sostenga. La sombra no se ve como sombra: hace de separacion entre
   la letra y el grafiti para que el contorno no se pierda contra un trazo
   blanco. El titulo del hero se queda fuera: ya tiene su propio halo. */
.h-sec, .h-sub, .lead, .eyebrow { text-shadow: 0 2px 20px rgba(6, 7, 10, .9); }

/* Y lo mismo, mas ceñido, para los dos grises del sistema. Son los que peor lo
   pasan sobre el muro: `--text-faint` es gris medio a 11-13 px, y encima de un
   trazo blanco del grafiti pierde el contorno. En las zonas negras de la
   pagina (que son casi todas) esta sombra no se ve.
   Ninguna de las dos clases se usa nunca sobre fondo claro, asi que no hay
   que hacer excepciones: los elementos con fondo blanco de este sitio (boton
   principal, dia elegido, hora elegida, palomita) pintan su color por CSS y
   no por estas utilidades. */
.text-soft, .text-faint { text-shadow: 0 1px 10px rgba(6, 7, 10, .85); }

.eyebrow {
  display: inline-flex; align-items: center; gap: .65rem;
  font-size: .6875rem; font-weight: 600; letter-spacing: .3em; text-transform: uppercase;
  color: var(--text-soft);
}
.eyebrow::before {
  content: ""; width: 28px; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(var(--accent-rgb), .85));
}

/* Texto con luz: el degradado imita el reflejo de un LED sobre metal */
.grad-text {
  background: linear-gradient(104deg, #FFFFFF 0%, #E8EEF5 38%, #8E9BA9 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.text-lit { text-shadow: 0 0 26px rgba(var(--accent-rgb), .38); }

/* ------------------------------------------------------- luz ambiental ---- */
.led { position: absolute; border-radius: 9999px; pointer-events: none; z-index: 0; filter: blur(120px); }
.led-warm { background: rgba(var(--accent-rgb), .10); }
.led-cool { background: rgba(var(--led-cool), .085); }

/* LOS HALOS NO SE MUEVEN, Y ES A PROPOSITO
   Hubo una deriva lenta (32 px en 19 s, sobre un circulo con 110 px de
   desenfoque: literalmente invisible). Se quito por lo mismo que la del muro:
   son las capas que estan DETRAS de todo el vidrio de la pagina, y
   `backdrop-filter` no puede reaprovechar su resultado si lo que tiene detras
   cambia. Mientras derivaban, cada tarjeta rehacia su desenfoque en cada
   frame, tambien con la pagina quieta. La luz sigue estando; lo que se fue es
   un movimiento que nadie veia y que cobraba siempre. */

/* Tira LED: separa secciones sin una línea dura */
.led-strip {
  height: 1px; border: 0;
  background: linear-gradient(90deg, transparent, var(--line-lit) 20%, rgba(var(--accent-rgb), .5) 50%, var(--line-lit) 80%, transparent);
}

/* Barra LED vertical, para acentuar bordes de tarjetas protagonistas */
.led-edge { position: relative; overflow: hidden; }
.led-edge::after {
  content: ""; position: absolute; left: 0; top: 12%; bottom: 12%; width: 2px;
  background: linear-gradient(180deg, transparent, rgba(var(--accent-rgb), .85), transparent);
  box-shadow: 0 0 18px rgba(var(--accent-rgb), .55);
}

/* --------------------------------------------------------- superficies ----
   Vidrio esmerillado: la tarjeta no tiene color propio, difumina lo que pasa
   por detrás (los halos LED y la capa ambiental). El brillo interior de arriba
   y la sombra interior de abajo son lo que le da grosor de cristal. */
.surface, .surface-soft {
  position: relative;
  background:
    linear-gradient(158deg, rgba(255,255,255,.075), rgba(255,255,255,.022) 46%, rgba(255,255,255,.006)),
    var(--vidrio);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid rgba(255,255,255,.09);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.16),
    inset 0 -1px 0 rgba(0,0,0,.45),
    0 30px 68px -42px rgba(0,0,0,.92);
}
.surface      { border-radius: var(--radius-lg); }
.surface-soft {
  border-radius: var(--radius);
  background: linear-gradient(158deg, rgba(255,255,255,.05), rgba(255,255,255,.014)), var(--vidrio-soft);
}

/* Vidrio dentro de vidrio: el de dentro desenfoca un fondo que el de fuera ya
   dejo liso, asi que no cambia nada en pantalla y cuesta lo mismo que el
   primero. Pasa con las formas de pago y los bloques informativos del
   formulario de la cita, y con todo lo que va dentro de los modales. */
.surface .pick, .surface .surface-soft, .surface .review,
.surface-soft .pick, .surface-soft .review {
  -webkit-backdrop-filter: none; backdrop-filter: none;
}

/* Sin backdrop-filter el vidrio se vería como un rectángulo casi invisible:
   ahí sí se pinta una superficie sólida. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .surface      { background: var(--card); }
  .surface-soft { background: var(--panel); }
}

/* Borde con luz + destello que gira lentamente por el canto de la tarjeta.
   Las dos capas van en el mismo pseudo-elemento para que el recorte de 1px
   (mask-composite) las deje a las dos como puro borde. */
.edge-lit { position: relative; }
.edge-lit::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px;
  background:
    conic-gradient(from var(--ang), transparent 0 56%,
                   rgba(255,255,255,.85) 71%, rgba(var(--led-cool), .8) 79%, transparent 90%),
    linear-gradient(150deg, rgba(255,255,255,.34), rgba(255,255,255,.04) 45%, rgba(var(--led-cool), .30));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  pointer-events: none; z-index: 3;
  animation: ang-spin 9s linear infinite;
}
@property --ang { syntax: "<angle>"; initial-value: 0deg; inherits: false; }
@keyframes ang-spin { to { --ang: 360deg; } }
.modal.is-hidden .edge-lit::before { animation-play-state: paused; }

/* Reflejo que sigue al cursor: la luz de la sala moviéndose sobre el cristal.
   fx.js sólo escribe --mx / --my / --lume; el pintado es todo CSS. */
.surface::after, .surface-soft::after, .pick::after, .review::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none; z-index: 2;
  opacity: var(--lume, 0); transition: opacity .4s ease;
  background: radial-gradient(240px circle at var(--mx, 50%) var(--my, 0%),
              rgba(255,255,255,.11), rgba(var(--led-cool), .05) 42%, transparent 70%);
}

.glow-lit {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.18),
    inset 0 -1px 0 rgba(0,0,0,.45),
    0 0 0 1px rgba(var(--accent-rgb), .10),
    0 22px 70px -30px rgba(var(--accent-rgb), .34);
}
.glow-soft { box-shadow: 0 26px 80px -34px rgba(0,0,0,.95); }

/* --------------------------------------------------------------- botones -- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .55rem;
  font-weight: 600; letter-spacing: .01em; border-radius: 12px;
  padding: .95rem 1.6rem; font-size: .9rem; text-align: center; cursor: pointer;
  border: 1px solid transparent;
  transition: transform .18s cubic-bezier(.2,.8,.2,1), box-shadow .25s, background .2s, border-color .2s, color .2s;
}
.btn:disabled, .btn[data-busy="true"] { opacity: .45; pointer-events: none; }

/* El destello es una segunda capa del propio fondo, no un elemento encima.
   Así nunca hace falta recortar el botón: con `overflow: hidden` bastaba que
   el botón se encogiera un pixel para que se comiera la palabra. */
.btn-primary {
  background-image:
    linear-gradient(100deg, transparent 32%, rgba(255,255,255,.95) 45%, rgba(140,156,172,.45) 55%, transparent 68%),
    linear-gradient(135deg, #FFFFFF, #E8EEF5 48%, #BDC8D4);
  background-size: 250% 100%, 100% 100%;
  background-position: 100% 0, 0 0;
  background-repeat: no-repeat;
  color: #06070A;
  box-shadow: 0 12px 34px -16px rgba(var(--accent-rgb), .7);
  transition: transform .18s cubic-bezier(.2,.8,.2,1), box-shadow .25s,
              background-position .6s cubic-bezier(.2,.8,.2,1), color .2s;
}
.btn-primary:hover { background-position: 0% 0, 0 0; }
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 20px 48px -18px rgba(var(--accent-rgb), .85); }
.btn-primary:active { transform: translateY(0); }

/* El boton fantasma tambien es vidrio. Con el `rgba(255,255,255,.02)` que
   tenia antes sobre el grafiti no se veia el boton: se veia el muro con unas
   letras encima y un borde suelto alrededor. */
.btn-ghost {
  border-color: var(--line-lit); color: var(--text);
  background: linear-gradient(180deg, rgba(255,255,255,.07), rgba(255,255,255,.02)), var(--vidrio);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.14), 0 14px 34px -22px rgba(0,0,0,.9);
}
.btn-ghost:hover {
  border-color: rgba(var(--accent-rgb), .7);
  background: linear-gradient(180deg, rgba(255,255,255,.12), rgba(255,255,255,.05)), var(--vidrio);
}

/* El discreto se queda como estaba, sin fondo: todos los que hay en la pagina
   (Cambiar, Copiar, Ver productos) viven DENTRO de una tarjeta de vidrio, que
   ya les pone el contraste. Rellenarlos los convertiria en pastillas solidas
   dentro de un cuadro que precisamente busca lo contrario. */
.btn-quiet { color: var(--text-soft); }
.btn-quiet:hover { color: var(--text); background: rgba(255,255,255,.06); }

.btn-danger { border-color: rgba(240,135,146,.32); color: var(--danger); background: rgba(240,135,146,.08); }
.btn-danger:hover { background: rgba(240,135,146,.16); }

@media (hover: none) { .btn:hover { transform: none; } }

/* --------------------------------------------- tarjetas seleccionables ---- */
.pick {
  position: relative; display: block; cursor: pointer;
  background: linear-gradient(158deg, rgba(255,255,255,.06), rgba(255,255,255,.016)), var(--vidrio);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid rgba(255,255,255,.085); border-radius: var(--radius);
  padding: 1.15rem 1.25rem;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.12), 0 18px 44px -34px rgba(0,0,0,.9);
  transition: border-color .22s, background .22s, transform .22s cubic-bezier(.2,.8,.2,1), box-shadow .25s;
}
.pick:hover { border-color: var(--line-lit); transform: translateY(-2px); }
.pick input { position: absolute; opacity: 0; pointer-events: none; }

.pick[data-on="true"] {
  border-color: rgba(var(--accent-rgb), .55);
  background: linear-gradient(180deg, rgba(255,255,255,.12), rgba(255,255,255,.035)), rgba(8,10,14,.66);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.22),
    0 0 0 1px rgba(var(--accent-rgb), .18),
    0 18px 44px -26px rgba(var(--accent-rgb), .55);
}

.pick .tick {
  position: absolute; top: .95rem; right: .95rem;
  width: 22px; height: 22px; border-radius: 999px;
  border: 1px solid var(--line-lit); display: grid; place-items: center;
  transition: all .2s; color: transparent; font-size: .7rem;
}
.pick[data-on="true"] .tick { background: var(--accent); border-color: var(--accent); color: #06070A; }

/* --------------------------------------------------------------- pasos ---- */
.step { transition: opacity .5s ease, filter .5s ease; }
.step[data-locked="true"] { opacity: .3; pointer-events: none; filter: grayscale(.4); }
.step[data-locked="true"] .step-body { display: none; }

.step-num {
  flex: none; width: 34px; height: 34px; border-radius: 999px;
  display: grid; place-items: center; font-size: .8rem; font-weight: 700;
  border: 1px solid var(--line-lit); color: var(--text-faint); background: var(--ink-soft);
  transition: all .3s;
}
.step[data-locked="false"] .step-num {
  border-color: rgba(var(--accent-rgb), .5); color: var(--text);
  box-shadow: 0 0 22px -6px rgba(var(--accent-rgb), .45);
}
.step[data-done="true"] .step-num { background: var(--accent); border-color: var(--accent); color: #06070A; }

/* ----------------------------------------------------------- calendario --- */
.cal-day {
  aspect-ratio: 1; border-radius: 11px; font-size: .875rem; font-weight: 500;
  display: grid; place-items: center; position: relative;
  background: rgba(255,255,255,.045); color: var(--text);
  border: 1px solid rgba(255,255,255,.05); transition: all .18s;
}
.cal-day:hover:not(:disabled) { background: rgba(255,255,255,.11); }
.cal-day[data-on="true"] {
  background: var(--accent); color: #06070A; font-weight: 700;
  box-shadow: 0 0 0 1px rgba(var(--accent-rgb), .45), 0 12px 30px -14px rgba(var(--accent-rgb), .9);
}
.cal-day:disabled { background: transparent; color: var(--text-faint); opacity: .34; cursor: not-allowed; }
.cal-day[data-blocked="true"] { background: rgba(240,135,146,.09); color: rgba(240,135,146,.55); }
.cal-day .dot { position: absolute; bottom: 6px; width: 4px; height: 4px; border-radius: 999px; background: var(--text-soft); }
.cal-day[data-on="true"] .dot { background: #06070A; }

.slot {
  padding: .8rem .5rem; border-radius: 11px; font-size: .875rem; font-weight: 500;
  border: 1px solid rgba(255,255,255,.1); color: var(--text);
  background: linear-gradient(180deg, rgba(255,255,255,.07), rgba(255,255,255,.02)), var(--vidrio);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.1);
  transition: all .18s;
}
.slot:hover:not(:disabled) {
  border-color: rgba(var(--accent-rgb), .6);
  background: linear-gradient(180deg, rgba(255,255,255,.13), rgba(255,255,255,.05)), var(--vidrio);
}
.slot[data-on="true"] {
  background: var(--accent); border-color: var(--accent); color: #06070A; font-weight: 700;
  box-shadow: 0 12px 30px -16px rgba(var(--accent-rgb), .9);
}
.slot:disabled { opacity: .42; text-decoration: line-through; cursor: not-allowed; }

/* --------------------------------------------------------------- campos --- */
.field {
  width: 100%; background: rgba(255,255,255,.045); color: var(--text);
  border: 1px solid rgba(255,255,255,.09); border-radius: 12px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.07);
  padding: .95rem 1.05rem; font-size: .95rem;
  transition: border-color .2s, box-shadow .2s, background .2s;
}
.field::placeholder { color: var(--text-faint); }
.field:focus {
  outline: none; border-color: rgba(var(--accent-rgb), .8);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), .12);
  background: rgba(255,255,255,.06);
}
.field.invalid { border-color: rgba(240,135,146,.65); box-shadow: 0 0 0 3px rgba(240,135,146,.12); }
.field.valid   { border-color: rgba(var(--accent-rgb), .34); }

.label { display: block; font-size: .8rem; font-weight: 500; color: var(--text-soft); margin-bottom: .45rem; }
.req { color: var(--text); }

.field-error { display: none; margin-top: .4rem; font-size: .75rem; color: var(--danger); }
.field-error.show { display: block; }

/* --------------------------------------------------------------- chips ---- */
.chip {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .3rem .7rem; border-radius: 999px;
  font-size: .7rem; font-weight: 600; letter-spacing: .045em; text-transform: uppercase;
  border: 1px solid var(--line-lit); color: var(--text-soft);
  background: var(--vidrio-soft);
}
.chip-ok     { background: rgba(74,222,155,.12);  border-color: rgba(74,222,155,.28);  color: var(--ok); }
.chip-warn   { background: rgba(233,188,99,.12);  border-color: rgba(233,188,99,.28);  color: var(--warn); }
.chip-danger { background: rgba(240,135,146,.12); border-color: rgba(240,135,146,.28); color: var(--danger); }
.chip-move   { background: rgba(242,145,79,.13);  border-color: rgba(242,145,79,.30);  color: var(--move); }
.chip-lit    { background: rgba(255,255,255,.07); border-color: rgba(var(--accent-rgb), .3); color: var(--text); }

/* ------------------------------------------------------------ reseñas ----- */
.stars { display: inline-flex; gap: 2px; color: var(--accent); }
.stars svg { width: 15px; height: 15px; }
.star-off { color: var(--text-faint); }

.review {
  position: relative;
  background: linear-gradient(158deg, rgba(255,255,255,.07), rgba(255,255,255,.018)), var(--vidrio);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid rgba(255,255,255,.09); border-radius: var(--radius);
  padding: 1.6rem; display: flex; flex-direction: column; gap: .9rem;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.14), 0 26px 60px -42px rgba(0,0,0,.92);
  transition: border-color .25s, transform .25s, box-shadow .25s;
}
.review:hover {
  border-color: var(--line-lit); transform: translateY(-3px);
  box-shadow: 0 22px 60px -34px rgba(var(--accent-rgb), .3);
}
.review q { quotes: none; color: var(--text); line-height: 1.68; font-size: .95rem; }

.review-rail {
  display: flex; gap: var(--gap); overflow-x: auto; scroll-snap-type: x mandatory;
  padding-bottom: .5rem; scrollbar-width: none;
  touch-action: pan-x pan-y pinch-zoom; overscroll-behavior-x: contain;
}
.review-rail::-webkit-scrollbar { display: none; }
.review-rail > * { scroll-snap-align: start; flex: 0 0 min(86vw, 350px); }
@media (min-width: 900px) {
  .review-rail { display: grid; grid-template-columns: repeat(3, 1fr); overflow: visible; }
  .review-rail > * { flex: initial; }
}

/* --------------------------------------------------------------- modal ----
   El estado abierto se declara de forma explícita: si la animación no llega a
   ejecutarse (navegador con movimiento reducido, pestaña en segundo plano),
   el modal igual se ve. Antes dependía de la transición y podía quedarse
   invisible. */
.modal { opacity: 1; visibility: visible; transition: opacity .28s ease, visibility 0s; }
.modal-panel { opacity: 1; transform: none; transition: transform .38s cubic-bezier(.2,.8,.2,1), opacity .28s; }
/* `visibility: hidden` y no sólo `opacity: 0`.
   Los cinco modales son `fixed inset-0` con `backdrop-blur`, y dentro llevan
   otra tarjeta de vidrio: nueve capas del tamaño de la pantalla que desenfocan
   la página entera. Con opacidad cero el navegador las sigue teniendo en el
   árbol y puede seguir pagándolas; con `visibility: hidden` las salta.
   El retardo de .28 s es lo que deja que la salida se siga viendo: la
   visibilidad no cambia hasta que la opacidad terminó de irse. Al abrir no hay
   retardo (lo pone la regla de arriba), así que aparece al instante. */
.modal.is-hidden {
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .28s ease, visibility 0s linear .28s;
}
.modal.is-hidden .modal-panel { transform: translateY(20px) scale(.97); opacity: 0; }

/* ------------------------------------------------------------- revelado --- */
.reveal { opacity: 0; transform: translateY(26px); transition: opacity .8s ease, transform .8s cubic-bezier(.2,.8,.2,1); }
.reveal.seen { opacity: 1; transform: none; }

/* --------------------------------------------------------- Mercado Pago --- */
.mp-host {
  border-radius: var(--radius); background: rgba(255,255,255,.025);
  border: 1px solid var(--line); padding: .85rem; min-height: 56px;
}
.mp-host:empty { display: none; }
.mp-host iframe { max-width: 100% !important; }

.spinner {
  width: 17px; height: 17px; border-radius: 999px;
  border: 2px solid rgba(var(--accent-rgb), .22); border-top-color: var(--accent);
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ------------------------------------------------------------- utilidad --- */
.hide { display: none !important; }
.safe-b { padding-bottom: env(safe-area-inset-bottom); }
.tabular { font-variant-numeric: tabular-nums; }
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { scrollbar-width: none; }

/* ============================================================================
 * CAPA VISUAL — profundidad, luz y parallax
 * ----------------------------------------------------------------------------
 * Todo lo de aquí abajo es decoración: si el navegador no lo soporta, o si el
 * usuario pidió menos movimiento, la página sigue funcionando igual.
 * Regla de oro: sólo se anima `transform` y `opacity`. Nada que obligue al
 * navegador a recalcular el layout mientras haces scroll.
 * ========================================================================== */

/* ------------------------------------------------------------- la barra --- *
 * El fondo de la barra aparece al bajar 24 px; app.js solo pone y quita la
 * clase, los valores estan aqui para que la media query los alcance.
 *
 * En movil el desenfoque baja de 18 px a 8 y la opacidad del fondo sube de
 * .78 a .92 para compensar. Es la misma regla que ya siguen las tarjetas de
 * vidrio (ver `--glass-blur`), y aqui pesa mas que en ninguna otra parte: la
 * barra va FIJA sobre un hero que se desplaza con el parallax, asi que lo que
 * tiene detras cambia en cada frame y `backdrop-filter` no puede reaprovechar
 * el desenfoque del frame anterior. Es el unico cristal de la pagina que
 * rehace su desenfoque en todo el ancho de la pantalla mientras haces scroll.
 * Lo que separa el texto del fondo alli es la opacidad, no el difuminado. */
#nav { border-bottom: 1px solid transparent; }
#nav.nav-solido {
  background: rgba(6, 7, 10, .78);
  -webkit-backdrop-filter: blur(18px);
          backdrop-filter: blur(18px);
  border-bottom-color: rgba(255, 255, 255, .06);
}
@media (max-width: 640px) {
  #nav.nav-solido {
    background: rgba(6, 7, 10, .92);
    -webkit-backdrop-filter: blur(8px);
            backdrop-filter: blur(8px);
  }
}

/* ------------------------------------------------------ segundo fondo ---- *
 * El collage de grafiti. Es una capa FIJA, no una imagen dentro de una
 * seccion, y ahi esta el truco: el hero es opaco de arriba abajo, asi que
 * mientras estas en la primera pantalla tapa esta capa por completo. En
 * cuanto el hero empieza a subir, el grafiti aparece por debajo como una
 * cortina que se descorre, y de ahi hasta el pie ya es el fondo de todo.
 * Cero JavaScript: es geometria, no un efecto de scroll.
 *
 * Va antes que `.ambient` en el HTML a proposito: asi los halos LED y la
 * reticula quedan por ENCIMA del muro, y el muro por detras de todo.
 *
 * POR QUE NO SE VE COMO UNA FOTO
 * El collage es blanco y negro a maximo contraste: puesto tal cual, cualquier
 * texto encima compite con un trazo blanco y pierde. Lo que se publica es la
 * foto en gris, bajada de brillo y al 48 %, con un velo encima. Lo que queda
 * no es una foto de fondo: es textura de muro. Eso es lo que se quiere.       */
.telon2 {
  position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden;
  contain: layout paint style;
}

/* EL MURO NO SE MUEVE, Y ES A PROPOSITO
   Llego a tener una deriva lenta de 64 s. Se quito porque era la causa
   principal de que la pagina se sintiera pesada al escribir: `backdrop-filter`
   no puede reutilizar su resultado si lo que tiene detras cambia, asi que
   mientras el muro se movia, TODOS los cuadros de vidrio de la pagina rehacian
   su desenfoque en cada fotograma. Quieto, cada uno lo calcula una vez.
   El 6 % de margen se queda: recorta el borde de la foto en pantallas muy
   anchas, donde el `cover` la estira. */
/* EL GRIS Y LA PENUMBRA VAN EN EL ARCHIVO, NO EN EL CSS
   Antes esto llevaba `filter: grayscale(1) contrast(1.06) brightness(.60)` y
   `opacity: .48`. Las dos cosas obligan al navegador a montar una superficie
   de render aparte: una capa fija a pantalla completa que en escritorio se
   rasteriza a ~1434x2534 px, se filtra entera y despues se compone. Se pagaba
   al abrir la pagina y cada vez que cambiaba el tamanio de la ventana.

   El webp que se publica ya viene con esa cadena aplicada y ya viene fundido
   al 48 % sobre `--ink`, que es lo unico que tenia detras. El resultado en
   pantalla es el mismo pixel por pixel; lo que desaparece es la superficie.
   De paso el archivo bajo de 103 KB a 79 KB: una foto oscura y sin color
   comprime mucho mejor que una a todo color.

   SI ALGUN DIA CAMBIAS ESTA FOTO: no basta con reemplazar el archivo, hay que
   volver a hornearle el gris. Ver el bloque de `.telon2` en el registro. */
.telon2 i {
  position: absolute; inset: -6%; display: block;
  background: url("https://img-servicios.prismstudio.com.mx/barber/clean-step-hair-cuts/imagenes/fondo-2.8b83609e.webp") center / cover no-repeat;
}

/* El velo: vinieta al centro para que el contenido caiga sobre la zona mas
   oscura, y un degradado vertical que apaga los extremos. Sin esto el texto
   suelto (titulos de seccion, precios) se lee a ratos y a ratos no, segun que
   trozo del muro le toque detras. */
.telon2::after {
  content: ""; position: absolute; inset: 0;
  background:
    radial-gradient(ellipse 78% 58% at 50% 46%, rgba(6,7,10,.26), rgba(6,7,10,.84) 100%),
    linear-gradient(180deg, rgba(6,7,10,.58), rgba(6,7,10,.21) 34%, rgba(6,7,10,.21) 66%, rgba(6,7,10,.58));
}

/* ------------------------------------------------------------ ambiente ---- *
 * Capa fija detrás de todo el contenido. Existe para que el vidrio tenga algo
 * que difuminar: sin ella las tarjetas transparentes se ven sobre negro plano
 * y el esmerilado no se nota.                                                */
.ambient {
  position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden;
  contain: layout paint style;
}
.ambient i {
  position: absolute; display: block; border-radius: 9999px;
  filter: blur(110px); opacity: .75;
}
.ambient .a1 { width: 60vw; height: 60vw; top: -18vw; left: -12vw;  background: radial-gradient(circle, rgba(var(--accent-rgb), .09), transparent 68%); }
.ambient .a2 { width: 55vw; height: 55vw; top:  38vh; right: -16vw; background: radial-gradient(circle, rgba(var(--led-cool), .085), transparent 68%); }
.ambient .a3 { width: 48vw; height: 48vw; bottom: -14vw; left: 22vw; background: radial-gradient(circle, rgba(var(--accent-rgb), .06), transparent 70%); }

/* Malla de líneas: da escala y hace visible el desenfoque del vidrio. */
.ambient::after {
  content: ""; position: absolute; inset: -10%;
  background-image:
    linear-gradient(rgba(255,255,255,.028) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.028) 1px, transparent 1px);
  background-size: 74px 74px;
  -webkit-mask-image: radial-gradient(ellipse 80% 60% at 50% 35%, #000, transparent 78%);
          mask-image: radial-gradient(ellipse 80% 60% at 50% 35%, #000, transparent 78%);
}

/* ------------------------------------------------------------ parallax ---- *
 * fx.js escribe el desplazamiento en `transform`. Los halos LED conservan su
 * vaivén en la propiedad `translate`, que se compone con `transform` en vez de
 * pisarlo: así una capa flota y la otra responde al scroll, sin pelearse.    */
[data-par] { will-change: transform; }
.par-img   { transform-origin: 50% 50%; }

/* ---------------------------------------------------------------- hero ---- */
.hero-vignette {
  position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(ellipse 78% 70% at 42% 45%, transparent 42%, rgba(6,7,10,.72) 100%);
}

/* Haces de luz cruzando el encuadre, como el reflejo de un tubo LED. */
.hero-rays {
  position: absolute; inset: -30% -10%; pointer-events: none; opacity: .5;
  background: repeating-linear-gradient(102deg,
    transparent 0 90px,
    rgba(var(--accent-rgb), .045) 90px 92px,
    transparent 92px 210px);
  -webkit-mask-image: radial-gradient(ellipse 60% 70% at 70% 20%, #000, transparent 72%);
          mask-image: radial-gradient(ellipse 60% 70% at 70% 20%, #000, transparent 72%);
  animation: rays 26s ease-in-out infinite alternate;
}
@keyframes rays { from { transform: translate3d(-3%, 0, 0) } to { transform: translate3d(3%, 2%, 0) } }

/* Una sola pasada de luz cruza el hero al cargar y no vuelve. Es el gesto que
   hace que la primera pantalla se sienta encendida en vez de puesta ahí. Corre
   una vez: no deja coste permanente. */
.hero-sweep {
  position: absolute; top: -30%; bottom: -30%; left: 0; width: 45%;
  pointer-events: none; z-index: 1;
  background: linear-gradient(90deg, transparent,
              rgba(255,255,255,.075) 45%, rgba(var(--led-cool), .1) 58%, transparent);
  transform: translateX(-160%) skewX(-14deg);
  animation: hero-sweep 2.4s cubic-bezier(.3,.7,.2,1) .4s 1 both;
}
@keyframes hero-sweep {
  from { transform: translateX(-160%) skewX(-14deg); }
  to   { transform: translateX(340%)  skewX(-14deg); }
}

/* ------------------------------------------------- señal de bajar --------- *
 * Una gota de luz cae por una vía fina. Dice «hay más abajo» sin el rebote
 * genérico de flecha que trae cualquier plantilla.                          */
.scroll-cue {
  position: absolute; bottom: 24px; left: 50%; margin-left: -11px;
  width: 22px; height: 46px; z-index: 10;
}
.scroll-cue::before {
  content: ""; position: absolute; left: 50%; top: 0; width: 1px; height: 100%;
  background: linear-gradient(180deg, transparent, var(--line-lit) 32%, transparent);
}
.scroll-cue::after {
  content: ""; position: absolute; left: 50%; top: 0;
  width: 3px; height: 3px; margin-left: -1.5px; border-radius: 999px;
  background: var(--accent-lit);
  box-shadow: 0 0 9px 1px rgba(var(--accent-rgb), .9);
  animation: cue-fall 2.6s cubic-bezier(.55,0,.45,1) infinite;
}
@keyframes cue-fall {
  0%       { transform: translateY(2px);  opacity: 0; }
  22%, 68% { opacity: 1; }
  100%     { transform: translateY(40px); opacity: 0; }
}

/* El título recibe un barrido de luz cada tanto, no todo el tiempo. */
.grad-text { background-size: 200% 100%; }
.shine { animation: shine 7s ease-in-out infinite; }
@keyframes shine {
  0%, 62%, 100% { background-position: 0% 0; }
  82%           { background-position: 100% 0; }
}

/* ------------------------------------------------------ barra de scroll --- */
#progress {
  position: fixed; top: 0; left: 0; height: 2px; z-index: 45;
  width: 100%; transform-origin: 0 50%; transform: scaleX(0);
  background: linear-gradient(90deg, rgba(var(--led-cool), .7), var(--accent-lit));
  box-shadow: 0 0 14px rgba(var(--accent-rgb), .65);
  pointer-events: none;
}

/* --------------------------------------------------------- botón: brillo --
 * El barrido del botón principal vive en `background-position` (arriba, junto
 * a su degradado). Aquí sólo queda el del botón fantasma, que se enciende por
 * dentro: su capa mide exactamente el botón, así que no hay nada que recortar
 * y el texto nunca corre riesgo de quedarse cortado.                         */
.btn-ghost { position: relative; }
.btn-ghost::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none; opacity: 0; transition: opacity .3s;
  background: radial-gradient(120px circle at var(--mx, 50%) var(--my, 50%), rgba(var(--accent-rgb), .16), transparent 65%);
}
.btn-ghost:hover::after { opacity: 1; }

/* ------------------------------------------- el boton que hay que ver ----
 * Aqui habia dos luces recorriendo el contorno: un `conic-gradient` movido
 * por `--ang`, recortado a 2 px con `mask-composite` y con un `drop-shadow`
 * encima. Se veia muy bien y costaba carisimo. Un gradiente conico no tiene
 * camino rapido en el compositor: cambiarle el angulo obliga a repintarlo
 * entero, y las tres pasadas —gradiente, mascara, desenfoque— se rehacian en
 * cada frame y en el hilo principal, que es el mismo que atiende al dedo.
 *
 * Lo que hay ahora anima UNICAMENTE `transform` y `opacity`. Son las dos
 * propiedades que el compositor mueve por su cuenta sin volver a pintar: la
 * capa se rasteriza una vez y a partir de ahi la GPU la escala y la
 * desvanece. Cuesta practicamente lo mismo quieta que corriendo, y por eso
 * esta puede quedarse encendida tambien en el telefono —que es donde la gente
 * reserva— en vez de pararse como el resto.
 *
 * Son dos capas y ninguna llega a taparle la cara al boton:
 *
 *   ::before  el halo que respira. Su `box-shadow` es FIJO; lo que sube y
 *             baja es la opacidad de la capa entera. Una sombra exterior se
 *             dibuja por definicion fuera de la caja, asi que nunca pasa por
 *             encima del texto.
 *
 *   ::after   la onda que sale y se apaga. Es un anillo hueco: como no tiene
 *             fondo, al crecer no cubre nada.
 *
 * POR QUE `--onda` Y NO UN VALOR FIJO
 * `scale()` es proporcional, y estos dos botones no miden lo mismo: el del
 * hero anda por los 164 px y el de confirmar ocupa el ancho de su tarjeta.
 * El mismo factor daria 10 px de crecimiento en uno y 55 en el otro, y ese
 * segundo se saldria de los 28 px de margen de la tarjeta. Con la variable
 * cada boton pide el suyo y los dos crecen mas o menos lo mismo en pixeles. */
.btn-beam {
  position: relative;
  --onda: 1.14;
  box-shadow:
    0 0 28px -6px rgba(var(--accent-rgb), .5),
    0 14px 42px -16px rgba(var(--accent-rgb), .85);
}
/* El de confirmar ocupa todo el ancho de la tarjeta: mismo crecimiento en
   pixeles, factor mucho mas pequenio. */
#submitBtn.btn-beam { --onda: 1.045; }

.btn-beam::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none;
  box-shadow:
    0 0 0 3px rgba(var(--accent-rgb), .22),
    0 0 38px 8px rgba(var(--accent-rgb), .45);
  animation: btn-respirar 2.8s ease-in-out infinite;
  will-change: opacity;
}
@keyframes btn-respirar {
  0%, 100% { opacity: .3; }
  50%      { opacity: 1; }
}

.btn-beam::after {
  content: ""; position: absolute; inset: -3px; border-radius: 15px;
  pointer-events: none;
  border: 2px solid rgba(var(--accent-rgb), .75);
  opacity: 0;
  animation: btn-onda 2.8s cubic-bezier(.22,.61,.36,1) infinite;
  will-change: transform, opacity;
}
@keyframes btn-onda {
  0%       { transform: scale(1);           opacity: .8; }
  55%, 100% { transform: scale(var(--onda)); opacity: 0; }
}

/* Fuera del encuadre no se mueve nada: no tiene sentido pagar repintado por
   algo que nadie está viendo. fx.js pone y quita la clase, y la usa para las
   tres cosas que se animan solas — el anillo del botón principal, el borde
   giratorio de las tarjetas y la cinta de cortes. */
.fuera-de-vista::after,
.fuera-de-vista::before { animation-play-state: paused; }
.fuera-de-vista .cinta-pista { animation-play-state: paused; }

/* Y mientras hay un campo con el cursor dentro, tampoco. Son repintados en el
   hilo principal, el mismo que responde al teclado: pararlos es la diferencia
   entre que la letra salga al instante y que salga un pelo despues. En cuanto
   sales del campo, todo vuelve a moverse.

   El boton de agendar ya no esta aqui. Estuvo mientras su anillo era un
   `conic-gradient` que se repintaba entero en cada frame; ahora que solo anima
   `transform` y `opacity` no le quita nada al teclado, y pararlo era peor que
   dejarlo: `animation-play-state: paused` lo congela en la opacidad que
   llevara en ese instante, asi que al tocar un campo el halo se quedaba a
   medio encender y parecia un fallo. */
.edge-lit:focus-within::before { animation-play-state: paused; }

/* ------------------------------------- en el telefono, sin repintado ------ *
 * Dos efectos de esta pagina NO los puede mover el compositor:
 *
 *   `.shine`  anima `background-position` sobre un texto recortado con
 *             `background-clip: text`. Cada frame hay que volver a rasterizar
 *             los glifos del titular, y a 84 px eso no es poco.
 *
 *   `--ang`   alimenta el `conic-gradient` del borde de las tarjetas. Un
 *             gradiente conico no tiene camino rapido: cambiarle el angulo
 *             obliga a repintarlo entero. Y encima lleva una mascara de dos
 *             capas con `mask-composite`, que se recompone en la misma pasada.
 *
 * Son diez elementos (2 titulares + 8 tarjetas) repintandose sesenta veces
 * por segundo mientras se ven. En un monitor no se nota. En un telefono es el
 * tiron del scroll en la primera pantalla.
 *
 * SE PARAN, NO SE QUITAN. El gradiente sigue pintado: el borde de las
 * tarjetas conserva su brillo, sencillamente quieto. Lo que se va es el
 * movimiento, que era lo unico que cobraba por frame.
 *
 * El boton de agendar NO esta en esta lista, y es a proposito: desde que su
 * animacion es solo `transform` y `opacity` la mueve el compositor y no
 * cuesta repintado, asi que puede seguir llamando la atencion justo donde
 * mas falta hace. Ver `.btn-beam`.
 *
 * `pointer: coarse` y no un ancho de pantalla: lo que decide no es el tamanio
 * sino que se toca con el dedo. Ahi estan las GPU flojas, y ahi estos brillos
 * acompanan a un hover que no existe. */
@media (pointer: coarse) {
  .shine { animation: none; }
  .edge-lit::before { animation: none; }
}

/* ----------------------------------------------------------- los pasos ---- *
 * Un riel de luz une los cinco números. Se ilumina de arriba hacia abajo
 * conforme el paso queda desbloqueado.                                       */
@media (min-width: 640px) {
  .step { position: relative; }
  .step::before {
    content: ""; position: absolute; left: 16px; top: 42px; bottom: calc(var(--gap-lg) * -1);
    width: 1px; pointer-events: none;
    background: linear-gradient(180deg, rgba(var(--accent-rgb), .35), rgba(255,255,255,.05));
    opacity: 0; transition: opacity .5s ease;
  }
  .step[data-locked="false"]::before { opacity: 1; }
  .step:last-child::before { display: none; }
}

/* El número del paso activo respira. */
.step[data-locked="false"][data-done="false"] .step-num { animation: pulse-num 3.2s ease-in-out infinite; }
@keyframes pulse-num {
  0%, 100% { box-shadow: 0 0 22px -6px rgba(var(--accent-rgb), .45); }
  50%      { box-shadow: 0 0 30px -2px rgba(var(--accent-rgb), .75); }
}

/* Mientras escribes en ese paso, deja de respirar. Anima `box-shadow`, que el
   compositor no sabe mover: se repinta en el hilo principal, el mismo que
   atiende al teclado, y late a un palmo del campo.
   Repite los dos atributos de la regla de arriba a propósito. Sin ellos pesa
   menos que ella y pierde, esté donde esté en el archivo: los selectores no se
   resuelven por orden mientras uno sea más específico que el otro. */
.step[data-locked="false"][data-done="false"]:focus-within .step-num {
  animation-play-state: paused;
}

/* -------------------------------------------------------- revelado v2 ----- */
.reveal { opacity: 0; transform: translate3d(0, 30px, 0); filter: blur(6px);
          transition: opacity .85s ease, transform .85s cubic-bezier(.2,.8,.2,1), filter .85s ease;
          transition-delay: var(--delay, 0s); }
.reveal.seen { opacity: 1; transform: none; filter: none; }

/* ------------------------------------------------------------- separador -- */
.led-strip { position: relative; }
.led-strip::after {
  content: ""; position: absolute; top: -1px; left: 0; width: 22%; height: 3px;
  border-radius: 99px;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.9), transparent);
  filter: blur(1.5px);
  animation: strip-run 9s cubic-bezier(.55,0,.45,1) infinite;
}
/* El recorrido termina justo en el borde derecho de la línea y no un pixel
   más: 354% de un elemento que mide 22% son exactamente los 78% que faltan.
   Con el 490% anterior la luz sobresalía 112 px del ancho de la pantalla y
   eso era lo que dejaba deslizar la página de lado en el teléfono. */
@keyframes strip-run {
  0%       { transform: translateX(-100%); opacity: 0; }
  15%, 85% { opacity: .85; }
  100%     { transform: translateX(354%); opacity: 0; }
}

/* ------------------------------------------------------ cinta de cortes --- *
 * Carrusel infinito, sin libreria y sin JavaScript.
 *
 * COMO CIERRA EL BUCLE
 * En el HTML el mismo grupo de tres fotos esta cuatro veces seguidas. La cinta
 * se desplaza exactamente un 25 % de su propio ancho y vuelve a empezar: como
 * en ese punto lo que se ve es el grupo siguiente, pixel por pixel identico al
 * anterior, el salto no existe. Las tres imagenes se descargan una sola vez;
 * las otras nueve etiquetas salen de la cache.
 *
 * LA SEPARACION VA EN `margin-right`, NO EN `gap`
 * Y es la linea de la que depende todo. Con `gap` habria 11 huecos para 12
 * tarjetas, asi que un 25 % del ancho total NO caeria sobre el inicio del
 * segundo grupo, y en cada vuelta la cinta daria un tiron. Con el margen
 * dentro de cada tarjeta, las 12 miden lo mismo y el 25 % es exacto.         */
.cinta {
  position: relative; overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}
/* Sin `will-change`: la capa mide casi 4000 px de ancho y el `translate3d` de
   los fotogramas ya la promociona mientras corre. Declararlo la dejaria
   reservada en memoria de video tambien cuando esta pausada fuera del
   encuadre, que es justo lo que se quiere evitar. */
/* CUANTOS GRUPOS HACEN FALTA DEPENDE DEL ANCHO
 * Para que el bucle no se note, la cinta tiene que seguir tapando la pantalla
 * despues de correrse un grupo entero. O sea: grupos x ancho_de_grupo menos la
 * pantalla tiene que ser al menos un grupo mas. Un grupo son tres tarjetas de
 * 292 px con 24 de separacion = 948 px, asi que con dos grupos se cubre hasta
 * 948 px de pantalla, con tres hasta 1896, y de ahi para arriba hacen falta
 * cuatro.
 *
 * En el HTML estan los cuatro siempre; aqui se apagan los que sobran. Y apagar
 * de verdad importa: una foto `loading="lazy"` dentro de un `display:none` no
 * se descarga, no se decodifica y no se rasteriza. En un telefono eso es la
 * mitad de la cinta que deja de existir.
 *
 * LA VUELTA SE AJUSTA AL NUMERO DE GRUPOS
 * Correrse un grupo es 100/grupos por ciento del ancho total. Y como el ancho
 * total encoge en la misma proporcion, la distancia recorrida en pixeles sale
 * igual en los tres casos: 948 px en 30 s. La cinta va exactamente a la misma
 * velocidad en el telefono que en el monitor, que es lo que se quiere. */
.cinta-pista {
  display: flex; width: max-content;
  --vuelta: -25%;
  animation: cinta-correr 30s linear infinite;
}
@media (max-width: 1896px) {
  .cinta-pista { --vuelta: -33.3333%; }
  .cinta-pista > .corte:nth-child(n+10) { display: none; }
}
@media (max-width: 948px) {
  .cinta-pista { --vuelta: -50%; }
  .cinta-pista > .corte:nth-child(n+7) { display: none; }
}
@keyframes cinta-correr {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(var(--vuelta), 0, 0); }
}
/* Al pasar el puntero la cinta se detiene: si no, mirar una foto con calma es
   perseguirla. En el telefono no hay puntero, y ahi se arrastra con el dedo. */
.cinta:hover .cinta-pista,
.cinta:focus-within .cinta-pista { animation-play-state: paused; }

.corte {
  position: relative; flex: 0 0 auto;
  width: clamp(198px, 46vw, 292px);
  aspect-ratio: 2 / 3;
  margin: 0 var(--gap) 0 0;
  border-radius: var(--radius-lg); overflow: hidden;
  border: 1px solid rgba(255,255,255,.11);
  background: var(--vidrio);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.18),
    0 34px 74px -40px rgba(0,0,0,.95);
}

/* En reposo van medio desaturadas, en un sitio que es monocromo a proposito.
   El color entra solo en la que estas mirando: es lo que hace que el mechon
   rosa o el rojo del cuello se lean como el remate del corte y no como ruido.

   EL FILTRO SE QUEDA AQUI, Y NO EN `.cinta`
   Da la tentacion de subirlo al contenedor y pasar de doce filtros a uno. Es
   peor: `.cinta` esta quieta y lo que lleva dentro se mueve, asi que su
   superficie tendria contenido nuevo en cada frame y habria que rehacer la
   pasada de filtro sesenta veces por segundo. Aqui abajo el filtro es
   ESTATICO respecto de la pista: se rasteriza una vez y a partir de ahi solo
   viaja. No cuesta por frame; cuesta al montarse. Por eso lo que se recorta
   no es el filtro sino el numero de tarjetas que hay que montar. */
.corte img {
  width: 100%; height: 100%; display: block;
  object-fit: cover; object-position: 50% 26%;
  filter: grayscale(.55) contrast(1.06) brightness(.92);
  transition: filter .55s ease, transform .8s cubic-bezier(.2,.8,.2,1);
}
.corte:hover img { filter: grayscale(0) contrast(1.04) brightness(1); transform: scale(1.07); }

/* Reflejo de cristal en el canto superior, el mismo lenguaje que las tarjetas. */
.corte::before {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  border-radius: inherit;
  background: linear-gradient(158deg, rgba(255,255,255,.14), transparent 34%);
}

/* ---------------------------------------------------------------- mapa ---- */
.map-frame { position: relative; }
.map-frame::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; border-radius: inherit;
  box-shadow: inset 0 0 90px 20px rgba(6,7,10,.75), inset 0 1px 0 rgba(255,255,255,.12);
}

/* --------------------------------------------------- menos movimiento ----- */
@media (prefers-reduced-motion: reduce) {
  .hero-rays, .hero-sweep, .scroll-cue::after, .led-strip::after, .shine, .edge-lit::before,
  .btn-beam::after, .btn-beam::before,
  .step[data-locked="false"][data-done="false"] .step-num { animation: none; }
  [data-par] { transform: none !important; }
  .reveal { filter: none; }

  /* La cinta deja de correr sola y se convierte en un riel que se arrastra.
     Las fotos siguen estando: lo que se quita es el movimiento, no el
     contenido. */
  .cinta-pista { animation: none; }
  .cinta {
    overflow-x: auto; scrollbar-width: none;
    touch-action: pan-x pan-y pinch-zoom; overscroll-behavior-x: contain;
  }
  .cinta::-webkit-scrollbar { display: none; }
}

/* ---------------------------------------------------------------- galeria --
   Mosaico editorial. Seis rectangulos iguales en fila parecen un catalogo de
   miniaturas; lo que separa una galeria de una cuadricula es que las piezas no
   midan lo mismo.

   POR QUE TRES ALTAS Y NO DOS
   Con dos, las seis piezas suman 2x2 + 4x1 = 8 unidades de alto, y ocho no se
   reparte entre tres columnas: la ultima queda corta y abre un hueco al final.
   Con tres altas son 3x2 + 3x1 = 9, o sea tres por columna, y el bloque cierra
   recto abajo. La colocacion va explicita por eso: dejarsela al navegador
   vuelve a abrir el hueco en cuanto cambia el orden.

   `content-visibility` deja que se salte maquetar y pintar lo que no se ve, y
   `contain-intrinsic-size` adelanta cuanto ocupara para que la barra de scroll
   no pegue saltos al entrar en la seccion. */
.gal-mosaico {
  display: grid;
  gap: .7rem;
  grid-template-columns: repeat(2, 1fr);
  grid-auto-rows: clamp(9rem, 26vw, 12rem);
}

.gal-foto {
  position: relative;
  margin: 0;
  overflow: hidden;
  border-radius: 1.1rem;
  background: rgba(255,255,255,.04);
  border: 1px solid rgba(255,255,255,.08);
  content-visibility: auto;
  contain-intrinsic-size: 300px;
}

.gal-foto img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .6s cubic-bezier(.22,.61,.36,1), filter .4s;
  /* Bajar un punto la saturacion las hermana: vienen de camaras y luces
     distintas, y en mosaico ese salto de tono es lo que canta. */
  filter: saturate(.9);
}

/* El velo de abajo evita que una foto clara choque de golpe contra el fondo
   oscuro de la pagina. */
.gal-foto::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 40%;
  background: linear-gradient(to top, rgba(8,9,12,.5), transparent);
  pointer-events: none;
}

@media (hover: hover) {
  .gal-foto:hover img { transform: scale(1.05); filter: saturate(1.05); }
}

/* --- Movil: dos columnas, con la primera y la ultima a lo ancho ---------
   Cinco piezas sueltas en dos columnas dejan a la ultima sola, con un hueco al
   lado. Poniendo ancha tambien la de abajo, el bloque abre y cierra igual y no
   sobra ningun sitio. */
@media (max-width: 759px) {
  .gal-mosaico { grid-auto-rows: clamp(9.5rem, 30vw, 12rem); }
  .gal-foto:nth-child(1) { grid-column: span 2; grid-row: span 2; }
  .gal-foto:nth-child(6) { grid-column: span 2; }
}

/* --- Escritorio: tres columnas, tres altas, cierre recto ---------------- */
@media (min-width: 760px) {
  .gal-mosaico {
    gap: .9rem;
    grid-template-columns: repeat(3, 1fr);
    grid-auto-rows: clamp(10rem, 13vw, 15rem);
  }
  .gal-foto:nth-child(1) { grid-column: 1; grid-row: 1 / span 2; }
  .gal-foto:nth-child(2) { grid-column: 2; grid-row: 1; }
  .gal-foto:nth-child(3) { grid-column: 3; grid-row: 1 / span 2; }
  .gal-foto:nth-child(4) { grid-column: 2; grid-row: 2 / span 2; }
  .gal-foto:nth-child(5) { grid-column: 1; grid-row: 3; }
  .gal-foto:nth-child(6) { grid-column: 3; grid-row: 3; }
}

/* ------------------------------------------------------------- mayoristas -- */
.may-carrusel {
  display: flex;
  gap: .8rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding-bottom: .3rem;
}
.may-carrusel::-webkit-scrollbar { display: none; }
.may-carrusel figure {
  margin: 0;
  flex: 0 0 clamp(190px, 62vw, 260px);
  scroll-snap-align: center;
  border-radius: 1rem;
  overflow: hidden;
  background: rgba(255,255,255,.04);
  border: 1px solid rgba(255,255,255,.07);
}
.may-carrusel img {
  display: block; width: 100%; aspect-ratio: 3 / 4; object-fit: cover;
}

/* Escalon activo: el que corresponde a la cantidad elegida. */
.may-escalon {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: .75rem; padding: .5rem .75rem; border-radius: .6rem;
  border: 1px solid transparent; transition: background .2s, border-color .2s;
}
.may-escalon[data-activo="true"] {
  background: rgba(var(--accent-rgb), .1);
  border-color: rgba(var(--accent-rgb), .35);
}
