:root {
  --color-primary: #6d5efc;
  --color-secondary: #20c997;
  --color-accent: #f6b73c;
  --color-bg: #0b1020;
  --color-surface: #121b2d;
  --color-surface-alt: #1a2740;
  --color-text: #edf3ff;
  --color-muted: #aab9d1;
  --color-border: rgba(255, 255, 255, 0.12);
  --container-width: 1180px;
  --radius: 18px;
  --shadow: 0 30px 60px rgba(2, 6, 23, 0.35);
}

* {
  box-sizing: border-box;
}

/* Focus visible: ensure focus is perceivable on all interactive elements */
*:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

/* Reduced motion: la preferencia del sistema desactiva transiciones, animaciones y scroll suave */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0 !important;
    scroll-behavior: auto !important;
  }
}

body {
  margin: 0;
  font-family: Arial, Helvetica, sans-serif;
  /* R45: los dos stops intermedios eran negros escritos a mano que ignoraban la paleta y dejaban
     cualquier tema claro con fondo oscuro bajo texto oscuro (1,02:1). Los stops salen ahora de
     --color-bg / --color-surface / --color-surface-alt; la línea literal siguiente es el respaldo
     para navegadores sin color-mix (reproduce la paleta publicada). */
  background: linear-gradient(180deg, var(--color-bg), #0d1527 30%, #111b2e);
  background: linear-gradient(180deg, var(--color-bg), color-mix(in srgb, var(--color-bg), var(--color-surface)) 30%, color-mix(in srgb, var(--color-bg), var(--color-surface-alt)));
  color: var(--color-text);
  line-height: 1.6;
  /* Los textos de negocio llegan de la configuración (R10) y pueden traer palabras largas o sin
     espacios. Con `anywhere` el mínimo de la columna no crece, la palabra se parte solo cuando no
     cabe y la página no desborda en horizontal; con el valor por defecto, una palabra larga
     ensanchaba su columna de la rejilla y sacaba el contenido de la pantalla. */
  overflow-wrap: anywhere;
}

a {
  color: inherit;
  text-decoration: none;
}

img {
  display: block;
  max-width: 100%;
}

button,
input,
textarea,
select {
  font: inherit;
}

.container {
  width: min(var(--container-width), calc(100% - 2rem));
  margin: 0 auto;
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 90;
  backdrop-filter: blur(18px);
  /* R45: la marca y la navegación caen encima de esta cabecera, así que su fondo sale de
     --color-bg (80% alfa) y nunca de un negro fijo. El literal es el respaldo sin color-mix. */
  background: rgba(11, 16, 32, 0.8);
  background: color-mix(in srgb, var(--color-bg) 80%, transparent);
  border-bottom: 1px solid var(--color-border);
}

.nav-wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 78px;
  gap: 1rem;
}

.brand {
  font-size: 1.8rem;
  font-weight: 800;
  letter-spacing: -0.06em;
}

.brand span {
  color: var(--color-secondary);
}

.main-nav {
  display: flex;
  align-items: center;
  gap: 1.2rem;
  color: var(--color-muted);
  flex-wrap: wrap;
}

.main-nav a:hover,
.main-nav a:focus-visible {
  color: var(--color-text);
}

/* R17: señal visible de la categoría activa. aria-current="page" anuncia el enlace a las
   tecnologías de asistencia, pero por sí solo no se ve: aquí el enlace activo se distingue por
   color (el mismo que en hover, para que siga legible sobre la cabecera oscura), peso y subrayado
   con el color de acento del tema. La regla va después del hover para que el subrayado no se
   pierda al pasar el cursor por encima. */
.main-nav a.nav-active {
  color: var(--color-text);
  font-weight: 700;
  text-decoration: underline;
  text-decoration-color: var(--color-secondary);
  text-underline-offset: 0.3em;
}

.hero,
.section,
.site-footer {
  padding: 4.5rem 0;
}

.hero-grid {
  display: grid;
  grid-template-columns: 1.2fr 0.8fr;
  gap: 2rem;
  align-items: center;
}

.eyebrow {
  display: inline-block;
  margin-bottom: 0.8rem;
  padding: 0.5rem 0.8rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.03);
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.72rem;
}

h1,
h2,
h3,
p {
  margin-top: 0;
}

h1 {
  font-size: clamp(2.6rem, 5vw, 5rem);
  letter-spacing: -0.06em;
  line-height: 1;
  margin-bottom: 1.1rem;
}

h2 {
  font-size: clamp(2rem, 3vw, 3rem);
  letter-spacing: -0.04em;
  margin-bottom: 1rem;
}

.lead,
.site-footer,
.category-copy,
.product-card p,
.legal-body p {
  color: var(--color-muted);
}

.cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-top: 1.5rem;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: 0.85rem 1.4rem;
  border-radius: 999px;
  border: 1px solid transparent;
  cursor: pointer;
  font-weight: 700;
  transition: transform 0.2s ease, opacity 0.2s ease;
}

.button:hover:not(:disabled),
.button:focus-visible {
  transform: translateY(-1px);
}

/* Estado deshabilitado de la acción de reinicio mientras corre la carga (R23). El hover deja de
   elevar el botón, que es el gesto con el que el propio estilo declara el control disponible: sin
   este bloque, un botón deshabilitado seguiría pareciendo pulsable. */
.button:disabled {
  cursor: progress;
  opacity: 0.55;
  box-shadow: none;
  transform: none;
}

/* R45: el texto blanco (16px/700) necesita 4.5:1 sobre cada stop del gradiente. El segundo stop
   era un morado claro fijo que bajaba el contraste a 3.92:1, así que los dos stops se obtienen
   oscureciendo el primario contra el fondo del tema. La línea anterior es el respaldo para
   navegadores sin color-mix: fondo sólido con el primario (4.51:1 con el tema publicado). */
.button-primary {
  background: var(--color-primary);
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--color-primary) 95%, var(--color-bg)),
    color-mix(in srgb, var(--color-primary) 85%, var(--color-bg)));
  color: #fff;
  box-shadow: 0 18px 30px rgba(109, 94, 252, 0.32);
}

.button-secondary {
  background: transparent;
  border-color: var(--color-border);
  color: var(--color-text);
}

.hero-card {
  /* R45: la última parada del degradado es la que sostiene el texto de la tarjeta, así que sale
     de --color-surface (80% alfa) y no de un negro fijo. El primer stop es un velo blanco
     translúcido que solo aclara: en cualquier tema el texto sigue mandando --color-text. */
  background: linear-gradient(180deg, rgba(255,255,255,0.04), rgba(18,27,45,0.8));
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), color-mix(in srgb, var(--color-surface) 80%, transparent));
  border: 1px solid var(--color-border);
  border-radius: calc(var(--radius) + 12px);
  box-shadow: var(--shadow);
  padding: 1.5rem;
}

.hero-card-inner {
  background: rgba(255,255,255,0.02);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 1.5rem;
}

.card-pill {
  display: inline-flex;
  padding: 0.45rem 0.7rem;
  border-radius: 999px;
  /* R45: mismo criterio que .badge, con el fondo teñido por --color-secondary del tema. */
  background: rgba(32, 201, 151, 0.15);
  background: color-mix(in srgb, var(--color-secondary) 15%, transparent);
  color: #aef4d8;
  color: color-mix(in srgb, var(--color-secondary) 35%, var(--color-text));
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.hero-card ul {
  padding-left: 1.1rem;
  margin-bottom: 0;
  color: var(--color-muted);
}

.section-head {
  text-align: center;
  margin-bottom: 2rem;
}

.product-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.4rem;
}

.product-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--color-border);
  background: rgba(255,255,255,0.02);
  border-radius: var(--radius);
  overflow: hidden;
}

.product-card img {
  height: 220px;
  width: 100%;
  object-fit: cover;
}

.product-body {
  padding: 1.2rem;
}

.product-topline {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.8rem;
  margin-bottom: 0.8rem;
}

.badge {
  display: inline-flex;
  font-size: 0.7rem;
  padding: 0.38rem 0.6rem;
  border-radius: 999px;
  /* R45: la etiqueta lleva texto encima. El fondo se tiñe del primario del tema y el rótulo mezcla
     ese primario con --color-text (35/65), de modo que conserva el tinte sin salirse de la gama
     que el tema garantiza legible. Los literales son el respaldo sin color-mix. */
  background: rgba(109, 94, 252, 0.14);
  background: color-mix(in srgb, var(--color-primary) 14%, transparent);
  color: #d7d0ff;
  color: color-mix(in srgb, var(--color-primary) 35%, var(--color-text));
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.price {
  font-weight: 800;
  color: var(--color-text);
  font-size: 1.3rem;
}

/* R45: este aviso pequeño iba al 80% de opacidad, y ese alfa diluía --color-muted hacia el fondo
   hasta dejar el texto en 4,35:1 con un tema claro bien configurado (el par muted/fondo da 7,13:1
   al 100% de opacidad). La diferencia visual con el enlace la marcan ya el tamaño (0.75rem) y el
   peso, así que la transparencia se retira y el aviso hereda el contraste del resto de muted. */
.external-link-notice {
  color: var(--color-muted);
  font-size: 0.75rem;
  margin-left: 0.4rem;
  vertical-align: middle;
}

.product-body a {
  margin-top: 1rem;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--color-text);
  font-weight: 700;
}

.product-body a::after {
  content: '→';
}

.category-layout {
  display: grid;
  grid-template-columns: 1fr 1.2fr;
  gap: 2rem;
  align-items: start;
}

.category-feature {
  border-radius: 28px;
  overflow: hidden;
  border: 1px solid var(--color-border);
  background: rgba(255,255,255,0.02);
}

.category-feature img {
  width: 100%;
  height: 350px;
  object-fit: cover;
}

.category-copy {
  padding: 1.5rem;
}

.category-copy p {
  margin-bottom: 0;
}

.site-footer {
  padding-top: 2.5rem;
  padding-bottom: 3rem;
}

.footer-wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  border-top: 1px solid var(--color-border);
  padding-top: 1.5rem;
}

.footer-links {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

.cookie-banner {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 120;
  /* R45: el párrafo y los dos botones del aviso caen sobre este fondo, que por eso sale de
     --color-bg (96% alfa). El literal es el respaldo sin color-mix. */
  background: rgba(11, 16, 32, 0.96);
  background: color-mix(in srgb, var(--color-bg) 96%, transparent);
  border-top: 1px solid var(--color-border);
  box-shadow: var(--shadow);
}

.cookie-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 0;
}

.cookie-inner p {
  margin: 0;
  color: var(--color-muted);
  max-width: 760px;
}

.cookie-actions {
  display: flex;
  gap: 0.75rem;
}

.hidden {
  display: none !important;
}

/* El atributo hidden debe ganar siempre: .state y .product-grid declaran display y lo sobrescribirían. */
[hidden] {
  display: none !important;
}

/* ============================================================================
   Estados de la vista: cargando, vacío, error y disponibilidad (R20, R21, R23)
   ============================================================================ */

/* Los cuatro estados son excluyentes entre sí y cada uno se distingue a simple vista por tres vías
   redundantes: el acento (borde izquierdo y marcador), la forma del marcador y el rótulo con su
   texto. El acento admite --color-error para que un tema alternativo lo sustituya.

   Contraste con el tema por defecto, calculado sobre la superficie real del estado (#121a2b): el
   texto principal alcanza 15,6:1 y el secundario 8,7:1, de modo que ambos superan holgadamente el
   4,5:1 del texto normal. El acento, en cambio, no sirve para texto pequeño: --color-primary se
   queda en 3,9:1, así que los rótulos van siempre en --color-text y el acento se reserva para el
   borde y el marcador, que son elementos no textuales (T16 revisa el resto de contrastes). */
.state {
  --state-accent: var(--color-primary);
  display: grid;
  justify-items: center;
  gap: 0.75rem;
  padding: 2.4rem 1.5rem;
  text-align: center;
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--state-accent);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.02);
  color: var(--color-text);
}

.state-marker {
  width: 2.4rem;
  height: 2.4rem;
  border: 3px solid var(--state-accent);
  border-radius: 50%;
}

/* El marcador es decorativo (aria-hidden) y marca el ritmo de la espera sin animar nada: con
   prefers-reduced-motion no hay con qué chocaría, y un estado de carga que se mueve no informa más.

   Los dos selectores llevan la clase del contenedor porque la vista de categoría reutiliza
   .legal-body, cuya regla ".legal-body p" pone en apagado todos los párrafos de <main> y, con
   especificidad (0,1,1), ganaba a un ".state-title" suelto: los rótulos del estado se veían apagados
   solo en esa vista. Con ".state .state-title" la especificidad es (0,2,0) y el color es el mismo en
   las dos vistas. */
.state .state-title {
  margin: 0;
  font-size: 1.3rem;
  font-weight: 700;
  color: var(--color-text);
}

.state .state-text {
  margin: 0;
  max-width: 58ch;
  color: var(--color-muted);
}

.state-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
}

.state-loading {
  --state-accent: var(--color-muted);
}

.state-loading .state-marker {
  border-style: dashed;
}

.state-empty {
  --state-accent: var(--color-primary);
}

.state-error {
  --state-accent: var(--color-error, #ff9d9d);
}

/* El único rótulo que adopta el tono del estado es el del error. El rosa fijo sirve sobre la
   superficie oscura del tema publicado, pero cualquier tema claro lo deja en ~1,9:1: se mezcla
   50/50 con --color-text para conservar el tono rosado y poder leer el aviso en cualquier paleta.
   La línea literal es el respaldo para navegadores sin color-mix. El acento del borde y del
   marcador sigue siendo --color-error puro: son elementos no textuales. */
.state-error .state-title {
  color: var(--color-error, #ff9d9d);
  color: color-mix(in srgb, var(--color-error, #ff9d9d) 50%, var(--color-text));
}

.state-error .state-marker {
  border-radius: 0.35rem;
}

.state-unavailable {
  --state-accent: var(--color-accent);
}

.state-unavailable .state-marker {
  border-style: dotted;
}

/* El indicador de foco del reinicio vive aquí y no en el botón genérico: es el único control que
   aparece dentro de un estado, y aparece y desaparece con el repintado, así que su foco tiene que
   estar definido aunque todavía no exista el indicador global de T16. Con el botón deshabilitado
   durante la carga (R23) desaparece de la secuencia de tabulación, de modo que sin contorno la
   persona no tiene forma de saber que hay una acción en curso. */
.state .button:focus-visible {
  outline: 3px solid var(--color-text);
  outline-offset: 3px;
}

.legal-body {
  padding: 4rem 0 5rem;
}

.legal-card {
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 1.8rem;
}

.legal-card h1 {
  margin-bottom: 1rem;
}

.legal-card h2 {
  margin-top: 2rem;
  font-size: 1.5rem;
}

/* R21 / A11: los bloques de código del texto de categoría (spec 002) no envuelven su contenido
   (white-space: pre), así que una sola línea larga estiraba la página y producía desbordamiento
   horizontal en un viewport de 360 px. El bloque pasa a desplazarse en horizontal dentro de su
   propio contenedor: la página no se ensancha y el código sigue siendo legible. */
pre {
  max-width: 100%;
  overflow-x: auto;
}

@media (max-width: 780px) {
  .hero-grid,
  .product-grid,
  .category-layout {
    grid-template-columns: 1fr;
  }

  .cookie-inner,
  .footer-wrap {
    flex-direction: column;
    align-items: flex-start;
  }

  /* Navegación visible en rango móvil: mostrable y usable a 360 px */
  .main-nav {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-start;
  }
}
