:root {
  --red: #FF0211;
  --red-dark: #d40009;
  --red-tint: #fff0f0;
  --ink: #222222;
  --muted: #767676;
  --bg: #f5f5f5;
  --card: #ffffff;
  --border: #e8e8e8;
  --gold: #f5a623;
  --radius: 10px;
  --green: #0a8a3c;
  --green-tint: #e7f7ee;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: "Hiragino Sans", "Noto Sans", "Segoe UI", Arial, sans-serif;
  background: var(--bg);
  color: var(--ink);
  line-height: 1.55;
  /* Pie pegado abajo: en las vistas cortas (Inicio, Favoritos vacío, Mi
     cuenta) el pie quedaba a media pantalla con un vacío gris debajo, como
     si la página se hubiera cortado a la mitad al cargar. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* Transiciones suaves centralizadas: antes casi ninguna regla :hover tenía
   "transition" (el color cambiaba de golpe), así que se agrega acá una sola
   vez para todo lo interactivo en vez de repetirla en cada regla :hover de
   la hoja -- misma duración/curva en todos lados (principio de repetición). */
a, button, input, select,
.product-row, .category-card, .brand-card, .filter-option,
.cats span, .cat-label, .cat-submenu a, .page-btn, .sort-tab, .metro-tab,
.region-chip, .variant-pill, .related-item, .ranking-block-more {
  transition: border-color .15s ease, color .15s ease, background-color .15s ease,
    box-shadow .15s ease, opacity .15s ease;
}

/* Transición sutil de una vista a otra: como .hidden usa display:none, no se
   puede animar directamente, pero una animación (a diferencia de una
   transición) sí se vuelve a disparar cada vez que el elemento pasa de
   none a block, así que basta con esto -- sin tocar el JS de setActiveView. */
.view:not(.hidden) { animation: view-fade-in .22s ease; }
@keyframes view-fade-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .view:not(.hidden) { animation: none; }
  a, button, input, select,
  .product-row, .category-card, .brand-card, .filter-option,
  .cats span, .cat-label, .cat-submenu a, .page-btn, .sort-tab, .metro-tab,
  .region-chip, .variant-pill, .related-item, .ranking-block-more {
    transition: none;
  }
}

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

.container { max-width: 1120px; margin: 0 auto; padding: 0 16px; }

.hidden { display: none !important; }

.muted { color: var(--muted); }
.small { font-size: 12px; }

/* ---------- Header ---------- */
.topbar {
  background: var(--card);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 20;
}
.topbar-top { border-bottom: 1px solid var(--border); }
.topbar-inner {
  max-width: 1120px;
  margin: 0 auto;
  padding: 8px 16px;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
}
.brand-icon {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  flex-shrink: 0;
}
.brand-mark {
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -.5px;
  color: var(--red);
}
.brand-mx { color: var(--ink); }
.search-container {
  flex: 3 1 360px;
  min-width: 360px;
  position: relative;
}
/* En celulares angostos (iPhone SE/mini, ~375px), 360px de mínimo no deja
   nada de margen para el padding de .topbar-inner y desborda el ancho de
   la pantalla por unos pocos px -- a pedido del usuario, que reportó la
   barra superior rota en su teléfono. */
@media (max-width: 420px) {
  .search-container { min-width: 0; flex-basis: 100%; }
}
.search-wrap {
  display: flex;
  border: 2px solid var(--red);
  border-radius: 8px;
  overflow: hidden;
}
/* Sugerencias de categoría mientras se escribe en el buscador (ver
   buildSearchSuggestions en js/app.js): vive fuera de .search-wrap
   porque ese contenedor tiene overflow:hidden (para redondear las
   esquinas del input+botón) y recortaría el desplegable. */
.search-suggestions {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
  max-height: 340px;
  overflow-y: auto;
  z-index: 60;
}
.search-suggestion-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  font-size: 14px;
  color: var(--ink);
  cursor: pointer;
}
.search-suggestion-item:hover,
.search-suggestion-item.active { background: var(--bg); }
.search-suggestion-item .icon { width: 16px; height: 16px; color: var(--muted); flex-shrink: 0; }
.search-suggestion-cat { color: var(--muted); font-weight: 400; }
.search-wrap input {
  flex: 1;
  border: none;
  padding: 8px 14px;
  font-size: 15px;
  outline: none;
  color: var(--ink);
}
.search-wrap button {
  border: none;
  background: var(--red);
  color: #fff;
  padding: 0 22px;
  cursor: pointer;
  display: flex;
  align-items: center;
}
.search-wrap button:hover { background: var(--red-dark); }
/* Iconos de línea en vez de emoji: un emoji se dibuja distinto según el
   SO/navegador de quien mire (Windows/Mac/Android/fuente instalada), así
   que la barra superior nunca se ve igual dos veces; un SVG inline con
   stroke="currentColor" hereda el color del texto (incluido el hover) y
   se ve idéntico en cualquier lado. */
.nav-icon { width: 15px; height: 15px; flex-shrink: 0; }
.search-wrap button .nav-icon { width: 18px; height: 18px; }
.util-nav {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--muted);
}
.util-nav a { display: inline-flex; align-items: center; gap: 5px; }
.util-nav a:hover { color: var(--red); }
.account-nav-avatar {
  width: 16px; height: 16px; border-radius: 50%;
  object-fit: cover; flex-shrink: 0;
}
.ship-toggle {
  display: flex; align-items: center; gap: 6px;
  cursor: pointer; user-select: none;
  padding: 3px 8px; border-radius: 8px; border: 1px solid var(--border);
}
.ship-toggle:hover { border-color: var(--red); }
.ship-toggle input { accent-color: var(--red); width: 15px; height: 15px; }
.ship-toggle.active { color: var(--red); font-weight: 700; border-color: var(--red); background: var(--red-tint); }
.shipping-estimate-note {
  display: block; font-size: 11px; font-weight: 700; color: #a15c00;
  margin-top: 2px;
}

.topbar-cats { background: var(--card); position: relative; }
/* Con 18 categorías la barra ya no cabe en una fila: por default se
   recorta a una sola línea (46px, la altura de un span) y el botón de
   abajo la despliega. overflow:hidden en vez de max-height:0 para que,
   ya colapsada, la primera fila siga totalmente visible y clicable. */
.cats {
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 16px;
  display: flex;
  gap: 4px;
  font-size: 13px;
  flex-wrap: wrap;
  overflow: hidden;
  max-height: 34px;
  transition: max-height .22s ease;
}
.cats.expanded { max-height: 300px; }
.cat-nav-toggle {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 0 auto 4px;
  padding: 2px 12px;
  border: none;
  background: none;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.cat-nav-toggle:hover { color: var(--red); }
.cat-nav-toggle-arrow { transition: transform .18s ease; display: inline-block; }
.cat-nav-toggle[aria-expanded="true"] .cat-nav-toggle-arrow { transform: rotate(180deg); }
.cats span {
  cursor: pointer;
  padding: 7px 12px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  border-bottom: 3px solid transparent;
  white-space: nowrap;
}
.cats span:hover { color: var(--red); }
.cats span.active {
  color: var(--red);
  border-bottom-color: var(--red);
}
.cat-item { display: inline-flex; }
/* position:fixed (asignado por JS en mouseenter) para escapar del
   overflow:hidden de .cats -- ver comentario en renderCatNav(). */
.cat-submenu {
  display: none;
  position: fixed;
  z-index: 50;
  background: #fff;
  /* Diseño plano: el submenú se separa del fondo con un borde definido, no
     con una sombra de elevación (era la única sombra de profundidad que
     quedaba en la hoja). */
  border: 1px solid #bdbdbd;
  border-radius: 8px;
  padding: 6px;
  min-width: 200px;
  /* Alto tope de reserva; JS ajusta esto por instancia según el espacio
     real disponible arriba/abajo del item (ver renderCatNav()). */
  max-height: calc(100vh - 16px);
  overflow-y: auto;
}
/* El submenú vive en document.body (no es descendiente de .cat-item en el
   DOM), así que su visibilidad la controla JS con esta clase en vez de
   :hover en CSS. */
.cat-submenu.visible { display: block; }
.cat-submenu a {
  display: block;
  padding: 9px 12px;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  cursor: pointer;
}
.cat-submenu a:hover { background: #f7f7f7; color: var(--red); }
.cat-submenu a.active { color: var(--red); background: #fff0f0; }

/* ---------- Home / hero / rankings ---------- */
/* El body usa --bg (gris) como lienzo neutro para que las tarjetas blancas
   contrasten en el resto del sitio (listado, filtros, etc.), pero en Inicio
   ese gris se notaba detrás del texto del hero y del cuadro de categorías
   -- blanco liso solo aquí, sin tocar --bg global. */
#viewHome { background: var(--card); }
/* Encabezado de página en texto plano sobre el mismo fondo que el resto:
   nada de bloque de color ni degradado — lo único que debe llamar la
   atención en la página es el precio, no el título. */
.hero,
.page-band {
  padding: 10px 0 6px;
}
.hero {
  padding-top: 4px;
  display: flex;
  align-items: center;
  /* A pedido del usuario (con captura): el texto de apoyo quedaba pegado
     al borde derecho por el space-between -- flex-start lo deja
     inmediatamente después del título en vez de empujarlo al extremo. */
  justify-content: flex-start;
  gap: 32px;
  flex-wrap: wrap;
}
.hero h1,
.page-band h1 {
  margin: 0 0 6px;
  color: var(--ink);
  letter-spacing: -.01em;
}
/* Más salto tipográfico entre el título y el texto de apoyo (14px) del que
   había (26px, 1.9x) -- un h1 más grande se lee más "de portada" y ayuda a
   la jerarquía en el primer vistazo. */
.hero h1 { font-size: 28px; }
.page-band h1 { font-size: 22px; }
.hero p, .page-band p { margin: 0; font-size: 14px; line-height: 1.5; color: var(--muted); }
.hero p { max-width: 420px; }
/* Banner chico para elegir ubicación, en la esquina vacía de la derecha
   del hero (a pedido del usuario, con captura marcando ese espacio) --
   margin-left:auto lo empuja al extremo derecho de la fila flex sin
   afectar el empaquetado a la izquierda de h1+p. Mismo ícono/acción que
   el botón grande "Elegir mi ubicación" de la ficha de producto
   (abre el mismo mapModal), pero en formato banner chico para esta
   franja angosta. */
.hero-map-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-left: auto;
  background: var(--card);
  border: 1px solid var(--red);
  border-radius: var(--radius);
  padding: 8px 14px;
  cursor: pointer;
  text-align: left;
  font: inherit;
  flex-shrink: 0;
}
.hero-map-btn:hover { background: var(--red-tint); }
.hero-map-btn.is-set { border-color: var(--green); }
.hero-map-btn-icon {
  width: 32px; height: 32px;
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg);
  border-radius: 50%;
  color: var(--red);
  /* overflow:hidden como red de seguridad: si por lo que sea el ícono de
     adentro no toma su tamaño chico (ver comentario de más abajo), que
     quede recortado al círculo en vez de desbordar gigante por toda la
     página, como pasó -- reportado con captura por el usuario. */
  overflow: hidden;
}
.hero-map-btn.is-set .hero-map-btn-icon { color: var(--green); }
/* El <svg> también lleva width/height="16" como atributos HTML (ver
   index.html), no solo esta regla CSS -- así el tamaño correcto no
   depende de que esta clase le gane en cascada a ninguna otra regla
   .icon del archivo (la explicación más probable de por qué se vio
   gigante: alguna caché de service worker sirviendo una combinación
   vieja de CSS/HTML). Belt-and-suspenders. */
.hero-map-btn-icon .icon { width: 16px; height: 16px; }
.hero-map-btn-text { display: flex; flex-direction: column; gap: 1px; }
.hero-map-btn-text strong { font-size: 13px; color: var(--ink); }
.hero-map-btn-text span { font-size: 12px; color: var(--muted); }
.hero-map-btn.is-set .hero-map-btn-text span { color: var(--green); }
.hero h2,
.section-title {
  margin: 10px 0 8px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  color: var(--ink);
  font-size: 18px;
  letter-spacing: -.005em;
}
.page-band .breadcrumb { padding: 0 0 6px; }

/* Insignia de posición para las listas en modo ranking (ver .has-rank en
   .product-row, más abajo): número plano sin degradado, con medallero
   dorado/plata/bronce para el top 3. */
.rank-badge {
  width: 30px; height: 30px;
  border-radius: 50%;
  background: var(--muted);
  color: #fff;
  font-size: 13px;
  font-weight: 800;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}

/* ---------- List / search results ---------- */
.breadcrumb { font-size: 12px; color: var(--muted); padding: 14px 0 0; }
.breadcrumb a:hover { color: var(--red); }

.list-layout {
  display: grid;
  /* Ancho por proporción con un piso fijo en vez de un ancho fijo puro: el
     panel de filtros gana algo de aire en pantallas medianas/grandes (hasta
     los 1120px del .container) en vez de quedarse siempre en los mismos
     220px mientras el resto del layout sí respira más. */
  grid-template-columns: minmax(220px, 22%) 1fr;
  gap: 16px;
  padding-bottom: 24px;
}
@media (max-width: 760px) {
  /* minmax(0, 1fr) y no 1fr a secas: un grid item hereda min-width:auto por
     defecto, así que su contenido (aquí, una marca larga sin espacios en
     el filtro) puede forzar la columna más ancha que el viewport aunque
     "1fr" diga que ocupe el espacio disponible -- minmax(0, ...) anula ese
     mínimo implícito y deja que el contenido se ajuste/trunque en vez de
     desbordar la página. */
  .list-layout { grid-template-columns: minmax(0, 1fr); }
}

.filters {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
  align-self: start;
  margin-top: 10px;
  /* Con >1200 marcas distintas en el catálogo, el filtro de Marca por sí
     solo puede medir decenas de miles de px de alto -- sin position:sticky
     eso estira la fila del grid entera y, al bajar por la lista, tanto este
     panel como el de Categoría (columna derecha) quedan scrolleados fuera
     de vista mucho antes de llegar al final de los productos. Con sticky +
     su propio scroll interno, el panel se queda a la vista siempre. */
  position: sticky;
  top: 161px;
  max-height: calc(100vh - 175px);
  overflow-y: auto;
}
@media (max-width: 760px) {
  /* En una sola columna no hay nada al lado del panel de filtros con lo
     que compartir el alto de fila -- sticky solo tendría sentido para
     "flotar" sobre la lista, tapándola, así que se desactiva y el panel
     vuelve a fluir normal como cualquier bloque de la página. */
  .filters {
    position: static;
    max-height: none;
    overflow-y: visible;
  }
}
.filters h2 {
  position: relative;
  font-size: 17px;
  margin: -18px -18px 16px;
  padding: 13px 16px 13px 20px;
  border-radius: var(--radius) var(--radius) 0 0;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
.filters h2::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 4px;
  background: var(--red);
}
/* Flechita del toggle de todo el panel de Filtros -- solo tiene efecto
   visual/funcional en mobile (ver el bloque @media abajo); en desktop
   queda oculta porque ahí el panel siempre se ve entero. */
.filters-panel-toggle { display: none; }
@media (max-width: 760px) {
  /* A pedido del usuario: en mobile, Filtros arranca cerrado para que los
     productos se vean apenas se entra a una categoría, en vez de quedar
     varias pantallas más abajo del panel completo (Categoría + Precio +
     Marca con cientos de checkboxes + Calificación + Condición...). */
  .filters h2 {
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .filters-panel-toggle {
    display: inline-block;
    font-size: 12px;
    transition: transform 0.15s;
  }
  .filters.filters-open .filters-panel-toggle { transform: rotate(180deg); }
  .filters:not(.filters-open) > .filter-group { display: none; }
}

.filter-group { margin-bottom: 16px; padding-bottom: 16px; border-bottom: 1px solid var(--border); }
.filter-group:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: none; }
.filter-group h3 { font-size: 12px; color: var(--muted); margin: 0 0 10px; text-transform: uppercase; letter-spacing: .03em; }
.filter-group-collapsible h3 {
  cursor: pointer;
  user-select: none;
}
.filter-group-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.filter-group-desc {
  display: block;
  margin-top: 3px;
  font-size: 11px;
  text-transform: none;
  letter-spacing: normal;
  color: var(--muted);
  opacity: .75;
  font-weight: 400;
}
.filter-group-toggle { font-size: 10px; transition: transform .15s; }
.filter-group-collapsible.collapsed h3 { margin-bottom: 0; }
.filter-group-collapsible.collapsed .filter-group-toggle { transform: rotate(-90deg); }
/* Un solo selector genérico (todo hijo directo salvo el <h3>) en vez de
   listar cada tipo de contenido interno -- ahora que TODOS los grupos son
   colapsables (Precio, Marca, Calificación, Condición, MagSafe, Tamaño),
   cada uno esconde algo distinto (slider de precio, buscador de marca,
   lista de opciones), y un selector por tipo se habría ido desactualizando
   con cada grupo nuevo. */
.filter-group-collapsible.collapsed > *:not(h3) { display: none; }
.filter-search {
  width: 100%;
  box-sizing: border-box;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 7px 10px;
  font-size: 13px;
  margin-bottom: 8px;
  color: var(--ink);
  font-family: inherit;
}
.filter-search:focus { outline: none; border-color: var(--red); }
.filter-empty { font-size: 13px; color: var(--muted); padding: 6px 0; margin: 0; }
.filter-options { display: flex; flex-direction: column; gap: 4px; }

/* Reemplaza la vieja lista de rangos fijos (radio buttons "Menos de
   $5,000" / "$5,000 - $10,000" / ...) por un slider de dos manijas +
   inputs numéricos: monto libre en vez de casillas predefinidas, y ocupa
   mucho menos espacio vertical en el panel de filtros. Truco clásico de
   "doble range input superpuesto": los dos <input type=range> comparten
   la misma pista pero solo la perilla (::-webkit-slider-thumb /
   ::-moz-range-thumb) recibe clics -- el resto tiene pointer-events:none
   para no bloquearse una a la otra.  El de "máximo" queda siempre encima
   (z-index) porque en el uso normal casi nadie arrastra el mínimo más
   allá del máximo; el propio JS ya evita que se crucen. */
.price-range-slider { position: relative; height: 28px; margin: 4px 0 10px; }
.price-range-track {
  position: absolute; top: 50%; left: 0; right: 0; height: 4px;
  transform: translateY(-50%);
  background: var(--border); border-radius: 2px;
}
.price-range-fill {
  position: absolute; top: 50%; height: 4px; transform: translateY(-50%);
  background: var(--red); border-radius: 2px;
}
.price-range-input {
  position: absolute; top: 0; left: 0; width: 100%; height: 28px; margin: 0;
  background: transparent; -webkit-appearance: none; appearance: none;
  pointer-events: none;
}
.price-range-input-max { z-index: 3; }
.price-range-input-min { z-index: 2; }
.price-range-input::-webkit-slider-runnable-track { -webkit-appearance: none; background: transparent; height: 28px; }
.price-range-input::-moz-range-track { background: transparent; height: 28px; border: none; }
.price-range-input::-webkit-slider-thumb {
  -webkit-appearance: none; pointer-events: auto;
  width: 18px; height: 18px; border-radius: 50%; margin-top: 5px;
  background: var(--red); border: 2px solid #fff; box-shadow: 0 0 0 1px var(--red);
  cursor: pointer;
}
.price-range-input::-moz-range-thumb {
  pointer-events: auto; width: 18px; height: 18px; border-radius: 50%;
  background: var(--red); border: 2px solid #fff; box-shadow: 0 0 0 1px var(--red);
  cursor: pointer;
}
.price-range-inputs { display: flex; align-items: center; gap: 8px; }
.price-range-field {
  display: flex; align-items: center; gap: 3px; flex: 1;
  border: 1px solid var(--border); border-radius: 6px; padding: 6px 8px;
}
.price-range-field:focus-within { border-color: var(--red); }
.price-range-field span { color: var(--muted); font-size: 13px; }
.price-range-field input {
  border: none; outline: none; width: 100%; min-width: 0;
  font-size: 13px; font-family: inherit; color: var(--ink);
  -moz-appearance: textfield;
}
.price-range-field input::-webkit-outer-spin-button,
.price-range-field input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.price-range-sep { color: var(--muted); }
.filter-option {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  cursor: pointer;
  padding: 9px 8px;
  margin: 0 -8px;
  border-radius: 6px;
}
.filter-option:hover { background: var(--bg); }
.filter-option.active { color: var(--red); font-weight: 700; }
.filter-option input { accent-color: var(--red); width: 18px; height: 18px; }
/* Doble clase (más específico que .filter-option a secas) para que este
   margin-left le gane pase lo que pase al "margin: 0 -8px" de arriba, sin
   depender del orden de las reglas en la hoja de estilos -- ahí estaba el
   bug real: antes eran dos reglas de la misma especificidad y ganaba la que
   apareciera después en el archivo, anulando la sangría por completo. */
.filter-option.filter-suboption {
  margin-left: 20px;
  padding-left: 20px;
  font-size: 13px;
  color: var(--muted);
  border-left: 2px solid var(--border);
}
.filter-option.filter-suboption.active { color: var(--red); border-left-color: var(--red); }

.list-main { margin-top: 14px; }
.list-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 18px;
  flex-wrap: nowrap;
}
/* El título se achica con ellipsis en vez de forzar un salto de línea en
   toda la fila -- a pedido del usuario, con captura marcando cómo el
   selector de orden se caía a una segunda línea cuando el título era
   largo. min-width:0 es lo que permite que un item de flex se encoja
   por debajo de su ancho de contenido natural. */
.list-head h1 {
  font-size: 24px;
  margin: 0;
  letter-spacing: -.01em;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.list-head .ship-toggle,
.list-head #shipEstimateHint,
.list-head .sort-select {
  flex-shrink: 0;
}
.sort-select {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 9px 12px;
  font-size: 14px;
  background: var(--card);
  color: var(--ink);
}

.product-list { display: flex; flex-direction: column; gap: 10px; }
.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin: 16px 0;
}
.pagination .page-btn {
  padding: 8px 16px;
  border: 1px solid var(--border, #ddd);
  border-radius: 8px;
  background: #fff;
  font-weight: 700;
  font-size: 14px;
  cursor: pointer;
}
.pagination .page-btn:disabled { opacity: .4; cursor: default; }
.pagination .page-btn:not(:disabled):hover { border-color: var(--red); color: var(--red); }
.pagination .page-indicator { font-size: 13px; color: var(--muted); font-weight: 600; }
.product-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  cursor: pointer;
}
.product-row:hover { border-color: var(--red); }
.product-row .row-icon { position: relative; font-size: 40px; width: 56px; text-align: center; flex-shrink: 0; }
.product-row .row-info { flex: 1; min-width: 0; }
.product-row .row-brand { font-size: 12px; color: var(--muted); }
/* Algunos títulos reales (p. ej. filamentos/telas que listan cada
   combinación de color en el propio nombre, sin espacios entre
   "rosa-blanco/azul-morado/...") son una sola palabra kilométrica: sin
   límite, el navegador la corta letra por letra para no desbordar el
   ancho angosto de .row-info en mobile, e infla la fila a decenas de
   líneas -- con align-items:center en .product-row, eso deja el bloque
   de precio flotando a mitad de esa fila gigante, superpuesto con lo
   que venga alrededor. Se limita a 2 líneas con "…" en vez de dejar que
   crezca sin tope. */
.product-row .row-name {
  font-size: 15px;
  font-weight: 600;
  margin: 3px 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: break-word;
}
.product-row .row-stars { font-size: 13px; color: var(--gold); }
.row-colors { display: flex; align-items: center; gap: 5px; margin-top: 4px; }
.color-dot {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  display: inline-block;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.12);
}
.color-dot-extra { font-size: 11px; color: var(--muted); }
.product-row .row-priceblock { text-align: right; flex-shrink: 0; }
.product-row .row-from { font-size: 12px; color: var(--muted); }
.product-row .row-price { font-size: 21px; font-weight: 800; color: var(--red-dark); }
.product-row .row-stores { font-size: 12px; color: var(--muted); }
.product-row.has-rank .rank-badge { flex-shrink: 0; }
/* Antes esto usaba :nth-child (posición en el DOM), que con paginación
   dejó de coincidir con el puesto real del ranking (la página 2 también
   tiene una fila en la posición 2) -- ahora la clase la pone JS según el
   número de puesto real (ver rankOffset en renderProductListInto). */
.product-list .product-row.has-rank.rank-2 .rank-badge { background: #d9a300; color: #fff; }
.product-list .product-row.has-rank.rank-3 .rank-badge { background: #a8a8a8; color: #fff; }
.product-list .product-row.has-rank.rank-4 .rank-badge { background: #a9662f; color: #fff; }

/* ---------- Rankings de Inicio (general + top 3 categorías) ---------- */
.home-rankings {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 20px;
  padding-bottom: 32px;
}
.ranking-block {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px;
  display: flex;
  flex-direction: column;
}
.ranking-block-head {
  font-weight: 800;
  font-size: 14px;
  padding-bottom: 10px;
  margin-bottom: 10px;
  border-bottom: 2px solid var(--red);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ranking-block-list { display: flex; flex-direction: column; gap: 8px; }
/* Filas más chicas que en la lista completa: son una vitrina de 3, no la
   vista para comparar a fondo (esa sigue siendo la lista paginada). */
.ranking-block-list .product-row { padding: 8px 10px; gap: 10px; }
.ranking-block-list .row-icon { font-size: 26px; width: 40px; }
.ranking-block-list .row-icon.has-photo { width: 40px; height: 40px; }
/* Nombres/marcas largos (p.ej. "Mancuernillas Pegasus Acero Inoxidable
   316L") se cortaban en varias líneas y hacían que esta tarjeta creciera
   mucho más alto que las otras dos del mismo home-rankings, quedando
   despareja. Se fuerza una sola línea con "..." al final -- así las 3
   tarjetas del ranking miden siempre lo mismo sin importar qué tan largo
   sea el nombre del producto. */
.ranking-block-list .row-name {
  font-size: 13px;
  margin: 2px 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ranking-block-list .row-brand {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ranking-block-list .row-price { font-size: 16px; }
.ranking-block-list .rank-badge { width: 24px; height: 24px; font-size: 11px; }
.ranking-block-list .row-fav-btn { padding: 2px; }
.ranking-block-list .row-fav-btn .heart-icon { width: 15px; height: 15px; }
.ranking-block-more {
  align-self: flex-start;
  margin-top: 10px;
  border: none;
  background: none;
  color: var(--red-dark);
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
  padding: 0;
}
.ranking-block-more:hover { text-decoration: underline; }

/* ---------- Product detail ---------- */
.detail-head {
  display: flex;
  align-items: flex-start;
  gap: 20px;
  margin: 16px 0 22px;
}
/* flex-basis fijo (no flex:1) para que el bloque de texto no estire la
   fila a todo el ancho del contenedor -- con flex:1 el corazón de
   favoritos quedaba pegado al borde derecho, muy lejos del precio, con
   una franja vacía enorme en medio (a pedido del usuario, que la marcó
   como espacio desperdiciado). Así el corazón queda justo después del
   texto, y el hueco sobrante es simple margen de página, no una fila
   que se ve rota. */
.detail-headinfo { flex: 0 1 560px; min-width: 0; }
.detail-icon {
  position: relative;
  font-size: 96px;
  width: 170px; height: 170px;
  display: flex; align-items: center; justify-content: center;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  flex-shrink: 0;
}
.detail-headinfo h1 { margin: 2px 0 8px; font-size: 25px; }
.detail-rating { font-size: 14px; color: var(--gold); margin-bottom: 8px; }
.detail-rating .rc { color: var(--muted); margin-left: 4px; }
.detail-colors:empty { display: none; }
.detail-colors-inner { display: flex; align-items: center; gap: 7px; margin-bottom: 8px; font-size: 13px; color: var(--muted); flex-wrap: wrap; }
.color-dot-link { display: inline-flex; line-height: 0; }
.color-dot-link .color-dot { width: 18px; height: 18px; cursor: pointer; }
.color-dot-link .color-dot:hover { box-shadow: 0 0 0 2px var(--red); }
.detail-fromprice { margin: 0; font-size: 14px; color: var(--muted); }
/* El precio de la ficha es el punto de mayor énfasis de toda la página. */
.detail-fromprice strong { color: var(--red-dark); font-size: 22px; }

/* Tarjeta superior de la ficha (foto + nombre + top de tiendas en un solo
   bloque, en vez de repartir la foto arriba y la comparación de precios en
   un panel aparte más abajo) -- a pedido del usuario, con una captura de
   referencia estilo Kakaku.com donde ambas cosas viven juntas. La tabla
   completa (pestañas, existencia, puntos, calificación, banner de entrega)
   sigue en su propio panel debajo; esto es solo el resumen de un vistazo. */
.detail-hero-panel .detail-head { margin: 0 0 14px; }
.detail-top-offers { border-top: 1px solid var(--border); padding-top: 4px; }
.detail-top-offer-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 4px;
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}
.detail-top-offer-row:last-of-type { border-bottom: none; }
.detail-top-offer-store {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 180px;
  min-width: 0;
  font-size: 14px;
  font-weight: 600;
}
.detail-top-offer-storename { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.detail-top-offer-price {
  flex: 0 0 auto;
  font-size: 18px;
  font-weight: 700;
  color: var(--red-dark);
  white-space: nowrap;
}
.detail-top-offer-ship { flex: 0 0 auto; font-size: 13px; color: var(--muted); min-width: 120px; }
.detail-top-offer-btn { flex: 0 0 auto; margin-left: auto; padding: 9px 20px; }
.detail-top-offers-more {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 9px;
  border: none;
  background: none;
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  text-align: center;
}
.detail-top-offers-more:hover { color: var(--red); }
@media (max-width: 640px) {
  .detail-top-offer-row { gap: 8px 12px; }
  .detail-top-offer-btn { flex-basis: 100%; margin-left: 0; }
}

.panel {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 16px;
}
.panel h2 {
  position: relative;
  font-size: 17px;
  margin: -16px -16px 10px;
  padding: 10px 16px;
  border-radius: var(--radius) var(--radius) 0 0;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
.panel h2::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 4px;
  background: var(--red);
}
/* Texto secundario que va pegado al encabezado (ej. "últimos 30 días"). */
.panel h2 .h2-sub { font-size: 12px; font-weight: 400; color: var(--muted); margin-left: 6px; }

.compare-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
/* Este encabezado va a media altura del panel (debajo del banner de
   entrega), no pegado arriba: se queda como franja redondeada por los
   cuatro lados, sin los márgenes negativos de .panel h2. */
.compare-panel-head h2 {
  margin: 0;
  padding: 11px 18px 11px 20px;
  border-radius: 8px;
  border-bottom: none;
  flex: 1;
  min-width: 200px;
}

/* Banner de entrega: plano, con borde en vez de bloque de color, para que
   siga distinguiéndose como acción principal sin competir con el precio. */
.delivery-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--card);
  border: 1px solid var(--red);
  border-radius: var(--radius);
  padding: 10px 16px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}
.delivery-banner-icon {
  font-size: 22px;
  width: 38px; height: 38px;
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg);
  border-radius: 50%;
}
.delivery-banner-text {
  flex: 1;
  min-width: 180px;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.delivery-banner-text strong { font-size: 14px; color: var(--ink); }
.delivery-banner-text span { font-size: 12px; color: var(--muted); }
.delivery-banner.is-set {
  background: var(--green-tint);
  border-color: var(--green);
}
.delivery-banner.is-set .delivery-banner-text strong { color: var(--green); }

.location-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--red);
  border: 2px solid var(--red);
  color: #fff;
  border-radius: 20px;
  padding: 9px 18px;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  flex-shrink: 0;
}
.location-btn:hover { background: var(--red-dark); }
.location-btn.is-set {
  background: var(--card);
  border-color: var(--green);
  color: var(--green);
}
.location-btn.is-set:hover { background: var(--green-tint); }
.location-btn-icon { font-size: 15px; }

.sort-tabs {
  display: flex;
  gap: 8px;
  margin: 14px 0 6px;
  flex-wrap: wrap;
}
.sort-tab {
  border: 1px solid var(--border);
  background: var(--card);
  border-radius: 20px;
  padding: 8px 16px;
  font-size: 13px;
  cursor: pointer;
  color: var(--muted);
}
.sort-tab:hover { color: var(--ink); }
.sort-tab.active {
  border-color: var(--red);
  color: var(--red);
  font-weight: 700;
}

/* Map modal */
/* 32 estados no caben en una sola fila: la barra se vuelve horizontalmente
   desplazable (scroll táctil o con rueda) en vez de envolver en varias
   líneas o recortar pestañas. white-space:nowrap evita que un nombre largo
   ("Santiago de Querétaro") rompa la fila. */
.metro-tabs {
  display: flex;
  gap: 6px;
  margin-top: 12px;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  overflow-y: hidden;
  flex-wrap: nowrap;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}
.metro-tab {
  border: none;
  background: none;
  padding: 11px 16px;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  border-bottom: 3px solid transparent;
  margin-bottom: -1px;
  white-space: nowrap;
  flex-shrink: 0;
}
.metro-tab:hover { color: var(--ink); }
.metro-tab.active {
  color: var(--red);
  border-bottom-color: var(--red);
}

.map {
  /* Más alto que antes (280px) para que los pines más grandes y sus
     etiquetas permanentes (ver .region-marker-label) tengan aire, sobre
     todo en Ciudad de México con varias alcaldías juntas. */
  height: 340px;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid var(--border);
  margin-top: 10px;
}
/* Nombre del municipio/alcaldía, siempre visible arriba de su pin (ya no
   solo al pasar el mouse) -- en celular no hay "pasar el mouse", así que
   sin esto no había forma de saber qué pin era cuál antes de tocarlo a
   ciegas. Pill blanco para que se lea encima del mapa sin importar el
   color del terreno debajo. Selector combinado (.leaflet-tooltip Y esta
   clase, no solo esta clase sola) a propósito: el CSS de Leaflet se
   carga recién al abrir el mapa (ver ensureLeafletLoaded), es decir
   DESPUÉS de este stylesheet -- con la misma especificidad, su propia
   regla .leaflet-tooltip (fondo/borde/padding por defecto) ganaría por
   orden de aparición y pisaría esta personalización en silencio. */
.leaflet-tooltip.region-marker-label {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 2px 7px;
  font-size: 12px;
  font-weight: 700;
  color: var(--ink);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}

.region-chips {
  display: flex;
  gap: 10px;
  margin-top: 14px;
  flex-wrap: wrap;
}
.region-chip {
  border: 1px solid var(--border);
  background: var(--card);
  border-radius: 22px;
  padding: 10px 18px;
  font-size: 14px;
  cursor: pointer;
}
.region-chip:hover { border-color: var(--red); }
.region-chip.active {
  background: var(--red);
  border-color: var(--red);
  color: #fff;
  font-weight: 600;
}

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(20, 20, 20, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  z-index: 100;
}
.modal {
  background: var(--card);
  border-radius: var(--radius);
  padding: 22px;
  max-width: 540px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
}
.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: -22px -22px 4px;
  padding: 16px 20px;
  border-radius: var(--radius) var(--radius) 0 0;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
.modal-head h2 { margin: 0; font-size: 19px; color: var(--ink); }
.modal-close {
  border: none;
  background: none;
  border-radius: 50%;
  width: 32px; height: 32px;
  font-size: 18px;
  cursor: pointer;
  color: var(--muted);
  line-height: 1;
  padding: 0;
  flex-shrink: 0;
}
.modal-close:hover { color: var(--ink); background: var(--border); }

/* Compare table */
.compare-table { width: 100%; border-collapse: collapse; margin-top: 10px; }
.compare-table th {
  text-align: left;
  font-size: 13px;
  color: var(--muted);
  border-bottom: 2px solid var(--border);
  padding: 10px 8px;
}
.compare-table td {
  padding: 14px 10px;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
  vertical-align: middle;
}
.compare-table tbody tr:nth-child(even) { background: var(--bg); }
/* La fila más barata va resaltada con una barra roja a la izquierda, ya
   sin degradado de fondo: el precio y la etiqueta bastan para destacarla. */
.compare-table tr:first-child td:first-child {
  border-left: 3px solid var(--red);
  padding-left: 8px;
}
.store-badge {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-weight: 600;
}
/* Distingue cada fila cuando un producto fusionado por color (ver
   merge_color_variants.py) muestra sus N colores como N filas de la misma
   tienda -- sin esto todas dirían "Mercado Libre" sin explicar por qué
   hay varias. */
.store-color-label { font-weight: 400; color: var(--muted); }
/* Pastillas de variante (color/talla) bajo el nombre de la tienda: se ve
   de un vistazo que hay más opciones sin tener que salir de la tabla.
   "Esta" queda marcada activa porque es la fila que ya se está mostrando;
   las demás abren directo la variante correspondiente en el sitio real. */
.variant-pills {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  margin-top: 6px;
}
.variant-pills-label {
  font-size: 11px;
  color: var(--muted);
  font-weight: 600;
  margin-right: 2px;
}
.variant-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border: 1px solid var(--border);
  background: #fff;
  border-radius: 14px;
  padding: 2px 10px 2px 2px;
  font-size: 11px;
  color: var(--ink);
  cursor: pointer;
  transition: border-color .12s, background .12s;
  text-decoration: none;
}
.variant-pill img {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
  background: #f4f4f4;
}
.variant-pill:hover { border-color: var(--red); }
.variant-pill.active {
  border-color: var(--red);
  background: var(--red-tint, #fff0f0);
  color: var(--red-dark, var(--red));
  font-weight: 700;
}
/* Mismo aviso pero compacto, para las listas/rankings donde no hay tabla
   de tiendas: solo dice "hay más" sin abrir nada hasta entrar a la ficha. */
.variant-count-badge {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  color: var(--muted);
  background: #f4f4f4;
  border-radius: 10px;
  padding: 1px 7px;
  margin-left: 7px;
  vertical-align: middle;
}
/* Aviso "de un vistazo" de que el producto es usado/preowned (ej. The
   Luxury Closet), en tono ámbar para distinguirlo de la insignia gris de
   variantes y no confundirse con el rojo de descuento/error. */
.used-badge {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  color: #8a5a00;
  background: #fff2d9;
  border: 1px solid #ffdd99;
  border-radius: 10px;
  padding: 1px 7px;
  margin-left: 7px;
  vertical-align: middle;
}
/* Equipo de uso comercial/industrial (refrigeradores de restaurante,
   aspiradoras industriales, etc.): tono azul para no confundirse con el
   ámbar de "usado" ni el rojo de descuento. */
.commercial-badge {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  color: #1e4d8f;
  background: #e3edfb;
  border: 1px solid #a9c6ef;
  border-radius: 10px;
  padding: 1px 7px;
  margin-left: 7px;
  vertical-align: middle;
}
/* Aviso de posible pedido mínimo (MOQ) en ofertas de Alibaba: mismo tono de
   advertencia que used-badge, pero como bloque propio debajo del nombre de
   la tienda en vez de inline junto a texto (necesita más espacio). */
.wholesale-badge {
  display: block;
  width: fit-content;
  font-size: 11px;
  font-weight: 700;
  color: #8a5a00;
  background: #fff2d9;
  border: 1px solid #ffdd99;
  border-radius: 10px;
  padding: 2px 8px;
  margin-top: 5px;
}
/* Insignia de tienda: color plano de la marca con una letra, sin relieve
   ni brillo — solo sirve para identificar la tienda de un vistazo. */
.store-dot {
  width: 32px; height: 32px;
  border-radius: 8px;
  color: #fff;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: -.2px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
/* Cuando hay foto real del logo, el fondo pasa a blanco con un borde
   sutil: el logo trae su propio color, no necesita el fondo de marca. */
.store-dot.has-logo {
  background: #fff;
  border: 1px solid var(--border);
  padding: 4px;
  overflow: hidden;
}
.store-dot.has-logo img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.price-cell { vertical-align: top; }
/* El precio es el elemento con más peso visual de toda la tabla. */
.price-line { font-weight: 800; color: var(--red-dark); font-size: 20px; }
.list-price { font-size: 13px; color: var(--muted); text-decoration: line-through; margin-right: 4px; }
.discount-badge {
  display: inline-block;
  font-size: 12px;
  font-weight: 800;
  color: #fff;
  background: var(--red-dark);
  padding: 2px 7px;
  border-radius: 4px;
  margin-left: 4px;
  vertical-align: middle;
}
/* Sello sobre la esquina de la foto del producto (a pedido explícito del
   usuario, con captura marcando esa esquina): el -X% que ya va junto al
   precio (.discount-badge) es fácil de pasar por alto en una grilla de
   tarjetas -- este sello se ve antes de leer el precio, en la miniatura
   chica de una fila de lista y en la foto grande de "Más visto"/ficha de
   producto por igual. Offset no-negativo (no "colgado" del borde) para
   que no lo recorte el overflow:hidden que ya tienen esos contenedores. */
.discount-ribbon {
  position: absolute;
  top: 3px;
  left: 3px;
  z-index: 2;
  background: var(--red-dark);
  color: #fff;
  font-size: 10px;
  font-weight: 800;
  line-height: 1;
  padding: 3px 5px;
  border-radius: 4px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
  pointer-events: none;
}
/* Descuentos de 30% o menos, en rojo más pálido (misma base --red-dark,
   con transparencia) -- a pedido del usuario, para que el color del sello
   ya adelante qué tan grande es el descuento sin tener que leer el
   número. */
.discount-ribbon--low { background: rgba(212, 0, 9, 0.55); }
.most-viewed-icon .discount-ribbon,
.detail-icon .discount-ribbon {
  top: 8px;
  left: 8px;
  font-size: 13px;
  padding: 4px 8px;
}
/* En una fila de lista/ranking, sin embargo, el sello no va sobre la
   miniatura chica del ícono (.row-icon, 56px) sino colgado de la
   esquina de toda la tarjeta (.product-row) -- a pedido del usuario, en
   la miniatura pasaba desapercibido. .product-row sí puede "colgar" el
   sello fuera del borde (a diferencia de los contenedores de foto de
   arriba) porque no tiene overflow:hidden. */
.product-row > .discount-ribbon {
  top: -8px;
  left: -8px;
  font-size: 11px;
  padding: 4px 8px;
  border-radius: 5px;
}
.best-tag {
  display: inline-block;
  margin-left: 6px;
  font-size: 11px;
  font-weight: 700;
  background: var(--red);
  color: #fff;
  padding: 2px 7px;
  border-radius: 4px;
  vertical-align: middle;
}
/* Insignia "recomendado": deliberadamente NO roja como MÁS BARATO/MÁS
   RÁPIDO, para que se lea como una sugerencia distinta (mejor combinación
   de precio+calificación+stock) y no como una tercera copia del mismo
   mensaje de "más barato". */
.recommended-tag {
  background: var(--gold);
  color: #5c3d00;
}
/* Monto ahorrado en pesos, junto al % de descuento (ver bestSavingsAmount). */
.save-amount {
  display: inline-block;
  margin-left: 6px;
  font-size: 12px;
  font-weight: 700;
  color: var(--green);
  vertical-align: middle;
}
.delivery-sub { margin-top: 5px; font-size: 13px; color: var(--muted); }
.delivery-fast { color: var(--green); font-weight: 700; }
.stars-cell { color: var(--gold); white-space: nowrap; font-size: 14px; }
.stars-cell .rc { color: var(--muted); font-size: 12px; margin-left: 3px; }
.ship-badge {
  display: inline-block;
  font-size: 12px;
  font-weight: 700;
  color: var(--green);
  background: var(--green-tint);
  padding: 3px 10px;
  border-radius: 10px;
  white-space: nowrap;
  border: 1px solid #c9edd9;
}
/* Envío internacional (sin centro de distribución en México): tono neutro,
   ni verde (implicaría gratis) ni ámbar (implicaría estimado numérico) --
   es simplemente informativo. */
.ship-badge-intl {
  color: var(--muted);
  background: var(--bg);
  border-color: var(--border);
}
.est-badge {
  display: inline-block;
  margin-left: 6px;
  font-size: 11px;
  font-weight: 700;
  color: #a15c00;
  background: #fff8ec;
  padding: 2px 8px;
  border-radius: 10px;
  white-space: nowrap;
  cursor: help;
  border: 1px solid #f3ddb5;
}
.buy-btn {
  background: var(--red);
  color: #fff;
  border: none;
  padding: 11px 20px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
}
.buy-btn:hover { background: var(--red-dark); }
.buy-btn--outline {
  background: #fff;
  color: var(--red);
  border: 1px solid var(--red);
}
.buy-btn--outline:hover { background: #fff5f5; }
.google-signin-btn {
  width: 100%;
  display: flex; align-items: center; justify-content: center; gap: 10px;
  background: #fff;
  color: #1f1f1f;
  border: 1px solid var(--border);
  padding: 11px 20px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.google-signin-btn:hover { background: #f7f7f7; }
.google-icon { width: 18px; height: 18px; flex-shrink: 0; }
.account-divider {
  display: flex; align-items: center; gap: 10px;
  margin: 14px 0;
  color: var(--muted);
  font-size: 12px;
}
.account-divider::before, .account-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}
.account-auth-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.account-auth-error {
  margin-top: 10px;
  font-size: 12.5px;
  color: var(--red);
}
.account-profile-head {
  display: flex; align-items: center; gap: 12px;
  margin-bottom: 10px;
}
.account-avatar {
  width: 40px; height: 40px; border-radius: 50%;
  object-fit: cover; flex-shrink: 0;
  background: var(--border);
}
.account-email { margin: 0; }
.link-btn {
  display: inline-block;
  margin-top: 10px;
  background: none;
  border: none;
  padding: 0;
  color: var(--red);
  font-size: 12.5px;
  cursor: pointer;
  text-decoration: none;
}
.link-btn:hover { text-decoration: underline; }
/* Micro-copy de confianza pegado al botón: reduce el riesgo percibido de
   salir del sitio (aversión a la incertidumbre) sin prometer nada que no
   se cumpla — es un enlace directo a la tienda real, no un checkout propio. */
.buy-trust {
  margin-top: 6px;
  font-size: 10px;
  color: var(--muted);
  text-align: center;
  white-space: nowrap;
}
.disclaimer { font-size: 12px; color: var(--muted); margin-top: 12px; }

/* Spec table */
.spec-table { width: 100%; border-collapse: collapse; }
.spec-table tr { border-bottom: 1px solid var(--border); }
.spec-table tr:last-child { border-bottom: none; }
.spec-table th, .spec-table td {
  text-align: left;
  padding: 8px 4px;
  font-size: 13px;
  font-weight: 400;
}
.spec-table th { width: 40%; color: var(--muted); font-weight: 600; }

/* Calculadora de envío (#/envio y el widget compacto de la ficha de
   producto) -- comparten el markup de resultados (renderShippingResultsInto
   en js/app.js), así que las reglas de .shipping-calc-* aplican a ambos. */
.shipping-calc-panel { max-width: 720px; }
.shipping-calc-form { display: flex; flex-direction: column; gap: 14px; margin-bottom: 18px; }
.shipping-calc-row { display: flex; gap: 14px; flex-wrap: wrap; }
.shipping-calc-row label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
  color: var(--muted);
  flex: 1;
  min-width: 120px;
}
.shipping-calc-row input {
  border: 1px solid var(--border);
  border-radius: 7px;
  padding: 10px 12px;
  font-size: 14px;
  font-family: inherit;
  color: var(--ink);
  background: var(--card);
}
.shipping-calc-row input:focus {
  outline: none;
  border-color: var(--red);
}
.shipping-calc-hint { margin: 0; }
.shipping-calc-platform { margin-bottom: 18px; }
.shipping-calc-platform:last-child { margin-bottom: 0; }
.shipping-calc-platform h3 { font-size: 14px; margin: 0 0 8px; }
.shipping-calc-table { width: 100%; border-collapse: collapse; }
.shipping-calc-table th, .shipping-calc-table td {
  text-align: left;
  padding: 8px 6px;
  font-size: 13px;
  border-bottom: 1px solid var(--border);
}
.shipping-calc-table th { color: var(--muted); font-weight: 600; font-size: 12px; }
.shipping-calc-table tr:last-child td { border-bottom: none; }

/* Reviews */
.review-list { display: flex; flex-direction: column; gap: 14px; }
.review-item { border-bottom: 1px solid var(--border); padding-bottom: 14px; }
.review-item:last-child { border-bottom: none; padding-bottom: 0; }
/* Empujoncito para escribir la primera reseña (o no perder una a medio
   escribir) -- ver renderReviewNudge. Tres variantes de color según el
   mensaje, mismo layout base. */
.review-nudge {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 14px;
  padding: 12px 14px;
  border-radius: var(--radius);
  font-size: 13px;
  line-height: 1.4;
}
.review-nudge .icon { width: 20px; height: 20px; flex-shrink: 0; }
.review-nudge span { flex: 1; }
.review-nudge-first {
  background: #fff8e6;
  border: 1px solid #f2d488;
  color: #7a5a00;
}
.review-nudge-first .icon { color: var(--gold); }
.review-nudge-remind {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--ink);
}
.review-nudge-remind .icon { color: var(--muted); }
.review-nudge-draft {
  background: var(--red-tint);
  border: 1px solid #f3c8c8;
  color: var(--red-dark);
}
.review-nudge-draft .icon { color: var(--red); }
.review-nudge-dismiss {
  flex-shrink: 0;
  border: none;
  background: none;
  color: inherit;
  text-decoration: underline;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  padding: 0;
}
.review-stars { color: var(--gold); font-size: 13px; }
.review-meta { font-size: 12px; color: var(--muted); margin: 2px 0 6px; }
.review-comment { font-size: 13px; line-height: 1.5; margin: 0; }

/* Bloque "Productos relacionados" (páginas SEO estáticas) */
.related-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
}
.related-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 12px 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-align: center;
  color: var(--ink);
  font-size: 12px;
}
.related-item:hover { border-color: var(--red); }
.related-item .row-icon { font-size: 26px; }
.related-name { font-weight: 600; line-height: 1.3; }
.related-price { color: var(--red-dark); font-weight: 700; }

.site-footer {
  background: #2a2a2a;
  color: #bbb;
  font-size: 12px;
  padding: 20px 0;
  /* auto (no 20px) para que el pie caiga al fondo de la ventana cuando la
     vista es corta -- ver el display:flex del body. En vistas largas auto
     vale 0 y la separación la da el borde rojo + el fondo oscuro. */
  margin-top: auto;
  border-top: 3px solid var(--red);
}

.leaflet-popup-content { font-size: 13px; }

/* ---------- Favoritos (corazón) ---------- */
.fav-btn {
  border: 1px solid var(--border);
  background: var(--card);
  border-radius: 50%;
  width: 48px; height: 48px;
  cursor: pointer;
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--muted);
}
.fav-btn:active { transform: scale(0.92); }
.fav-btn:hover { border-color: var(--red); }
.fav-btn.is-fav {
  color: var(--red);
  border-color: var(--red);
  background: var(--red-tint);
}
.fav-btn .heart-icon { width: 21px; height: 21px; }

.row-fav-btn {
  border: none;
  background: none;
  cursor: pointer;
  color: var(--muted);
  padding: 6px;
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
}
.row-fav-btn.is-fav { color: var(--red); }
.row-fav-btn .heart-icon { width: 19px; height: 19px; }

/* ---------- Disponibilidad ---------- */
.stock-badge { font-size: 13px; font-weight: 600; white-space: nowrap; position: relative; padding-left: 14px; }
.stock-badge::before {
  content: "";
  position: absolute;
  left: 0; top: 50%;
  transform: translateY(-50%);
  width: 7px; height: 7px;
  border-radius: 50%;
  background: currentColor;
}
.stock-in { color: #0a8a3c; }
.stock-low { color: #b8860b; }
/* Un pequeño pulso en el punto de "últimas piezas" (no en el texto, que se
   queda quieto y legible): suficiente para llamar la atención sobre la
   escasez real (Cialdini) sin caer en una cuenta regresiva o un contador
   inventado. */
.stock-low::before { animation: stock-pulse 1.6s ease-in-out infinite; }
@keyframes stock-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(184, 134, 11, .45); }
  50% { box-shadow: 0 0 0 4px rgba(184, 134, 11, 0); }
}
.stock-back { color: var(--muted); }

/* ---------- Historial de precio ---------- */
.table-scroll { overflow-x: auto; }

/* ---------- Grupos de ofertas: verificado vs. referencia ---------- */
.offer-group { margin-top: 14px; }
.offer-group-head { margin-bottom: 4px; display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
/* La etiqueta sola ("Precios de referencia") no le dice nada a quien entra
   por primera vez; esta línea explica qué significa ahí mismo, sin mandarlo
   a la nota al pie. */
.offer-group-hint { font-size: 12px; color: var(--muted); line-height: 1.45; }
.offer-group-badge {
  display: inline-block;
  font-size: 13px;
  font-weight: 700;
  padding: 6px 13px;
  border-radius: 20px;
}
.offer-group-badge.verified { background: var(--green-tint); color: #0a8a3c; border: 1px solid #bfe9d3; }
.offer-group-badge.reference { background: #fff8ec; color: #a15c00; border: 1px solid #f3ddb5; }

.offer-group-verified {
  border: 2px solid #b7ecce;
  border-radius: var(--radius);
  padding: 10px 10px 4px;
  background: var(--card);
}
.offer-group-reference {
  opacity: 0.94;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 10px 10px 4px;
  background: var(--card);
}

.empty-note {
  font-size: 12px;
  color: var(--muted);
  padding: 10px 4px 4px;
}

/* Inicio: categorías a la izquierda (ocupa el espacio disponible) + panel
   de "Top 5 más visitadas" a la derecha, angosto y fijo -- las categorías
   quedan "empujadas" contra ese panel en vez de extenderse a todo el
   ancho. En pantallas angostas el panel baja debajo de las categorías. */
.home-categories-layout {
  display: flex;
  gap: 16px;
  align-items: flex-start;
}
.home-right-column {
  flex: 0 0 320px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
/* El panel de "Más visto" vive en la columna angosta (antes iba a todo el
   ancho, debajo del ranking) -- ver .most-viewed-card más abajo para el
   diseño vertical/grande que reemplaza al horizontal compacto que tenía
   cuando ocupaba todo el ancho de la página. */
/* Banner de afiliados (Admitad), debajo del Top 5 -- mismo marco que el
   resto de las tarjetas de la columna, con la etiqueta "Publicidad" para
   que se distinga con claridad de contenido propio del sitio. */
.home-ad-banner {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
  text-align: center;
}
.home-ad-label {
  display: block;
  font-size: 10px;
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 8px;
}
.home-ad-banner img { max-width: 100%; height: auto; border-radius: 6px; display: block; margin: 0 auto; }
@media (max-width: 720px) {
  .home-categories-layout { flex-direction: column; }
  .home-right-column { flex: 1 1 auto; width: 100%; box-sizing: border-box; }
}

/* "Visto recientemente" / "Recomendado para ti": solo con sesión iniciada
   (ver renderHomeAccountSections en js/app.js), reutiliza .product-list y
   .section-title tal cual, así que no hace falta mucho más que el margen
   entre los dos bloques. */
.home-account-block { margin-bottom: 8px; }

/* "Más visto en este navegador": misma idea que el Top 5 de categorías
   (clics reales de este navegador, ver trackProductView), pero mostrando
   la ficha del único producto más visto en vez de una lista -- una
   versión compacta de .detail-head (icono/nombre/rating/colores/precio)
   dentro de una tarjeta clicable que lleva a la ficha completa.
   Diseño vertical y grande (a pedido del usuario): antes esta tarjeta
   iba a todo el ancho de la página con el icono chico a un lado del
   texto; ahora vive apilada en la columna angosta debajo del banner de
   publicidad, así que el icono pasa a ocupar todo el ancho arriba, bien
   grande, con el texto debajo en vez de al lado. */
.most-viewed-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  cursor: pointer;
}
.most-viewed-card:hover { border-color: var(--red); }
.most-viewed-icon {
  position: relative;
  font-size: 72px;
  width: 100%; height: 180px;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  flex-shrink: 0;
  overflow: hidden;
}
/* Rótulo del panel, superpuesto en la esquina vacía de la foto (arriba a
   la derecha, opuesto al sello de descuento que va arriba a la
   izquierda) en vez de un <h2> aparte encima de la tarjeta -- a pedido
   del usuario, para no gastar esa línea extra de alto y que el bloque
   completo suba. Fondo semitransparente para que se lea encima de
   cualquier foto. */
.most-viewed-badge {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 3px;
  max-width: none;
  white-space: nowrap;
  background: rgba(255, 255, 255, 0.92);
  color: var(--ink);
  font-size: 10px;
  font-weight: 700;
  line-height: 1.25;
  padding: 4px 8px;
  border-radius: 999px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  pointer-events: none;
}
/* Selector más específico a propósito: el ícono del rótulo vive dentro de
   .most-viewed-icon (el marco de la foto), así que sin este extra nivel
   también calzaba con la regla genérica .most-viewed-icon .icon (64px,
   pensada para el emoji de reemplazo cuando no hay foto) -- con la misma
   especificidad de otro modo, esa regla venía después en el archivo y
   ganaba, dejando el ojo enorme en vez de chico junto al texto. */
.most-viewed-icon .most-viewed-badge .icon { width: 12px; height: 12px; flex-shrink: 0; color: var(--ink); }
/* Misma etiqueta pero como texto normal arriba de la tarjeta -- se usa
   solo en el estado vacío (sin producto visto todavía no hay foto donde
   superponerla). */
.most-viewed-heading {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 10px;
  font-size: 15px;
  font-weight: 800;
}
.most-viewed-heading .icon { width: 18px; height: 18px; flex-shrink: 0; }
.most-viewed-info { flex: 1; min-width: 0; }
.most-viewed-info h3 { margin: 2px 0 6px; font-size: 19px; }
.most-viewed-cta {
  margin-top: 10px;
  border: none;
  background: none;
  color: var(--red);
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
  padding: 0;
}
.most-viewed-empty { font-size: 13px; color: var(--muted); line-height: 1.6; margin: 0; }

/* ---------- Marcas y ofertas (catálogo de afiliados) ---------- */
.category-card-grid {
  flex: 1;
  min-width: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(108px, 1fr));
  gap: 8px;
  padding: 4px 0 18px;
}
/* Borde solo alrededor de las categorías de Inicio (no las de /marcas,
   que reusan la misma .category-card-grid) -- pegado a las tarjetas
   (padding chico), esquinas redondeadas como el resto de las tarjetas
   del sitio, y en un rojo más claro que --red, para que se note sin
   pesar tanto como el rojo de marca a pantalla completa. La pestaña
   roja "Categorías" que iba montada arriba del borde se quitó a
   pedido del usuario. */
.home-category-box {
  flex: 1;
  min-width: 0;
  padding: 6px;
  margin: 4px 0 18px;
  /* A pedido del usuario se quita el patrón de sarape (llegó a probarse
     en varias vueltas: rombos sutiles, sarape vívido multicolor, luego
     acotado a blanco/rosa/rojo -- commits 422af90ba..c5c5f907d) y se
     deja solo un borde, sin textura de fondo. El grosor del borde
     también se probó en ambos extremos a pedido (6px -> ahora 1px, el
     mínimo que un borde CSS puede tener y seguir siendo visible). */
  border: 1px solid #f7c3cd;
  border-radius: var(--radius);
  background-color: var(--card);
}
#homeCategoryGrid { padding: 0; margin: 0; }
.category-card {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 8px 6px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  text-align: center;
  font: inherit;
  color: var(--ink);
  /* Sin esto, el ítem de grid usa min-width:auto por default, que en la
     práctica es el min-content del contenido (el tamaño intrínseco de la
     foto ilustrativa de la categoría, aunque esté en width:100%) -- eso
     hacía que las 3 columnas de 1fr en mobile no repartieran el ancho en
     partes iguales sino según ese mínimo, desbordando la cuadrícula en
     pantallas angostas. */
  min-width: 0;
}
.category-card:hover { border-color: var(--red); }
.category-card.active { border-color: var(--red); color: var(--red); }
.category-card-icon {
  position: relative;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  font-size: 22px;
  flex-shrink: 0;
}
.category-card-icon img { width: 100%; height: 100%; object-fit: cover; }
/* Sello "Muchas ofertas" para las categorías del top 10 con más
   descuentos activos ahora mismo (ver topDiscountCategoryIds) -- a
   pedido del usuario, en la esquina de la tarjeta de categoría de
   Inicio. Colgado del borde superior-izquierdo de la tarjeta completa
   (no del marco de la foto, que tiene overflow:hidden y lo recortaba /
   lo dejaba tapando la foto) -- mismo criterio que el "corner flag" de
   .product-row > .discount-ribbon, para no cubrir la ilustración. */
.category-discount-badge {
  position: absolute;
  top: -6px;
  left: -6px;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 2px;
  max-width: calc(100% - 4px);
  background: var(--red-dark);
  color: #fff;
  font-size: 8px;
  font-weight: 800;
  line-height: 1.15;
  padding: 2px 4px;
  border-radius: 4px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  pointer-events: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Selector con un nivel extra de anidamiento (no solo .category-discount-badge
   .icon) a propósito: las reglas generales para .icon (24px/40px) dentro de
   .category-card-icon / .category-card-icon--photo aparecen más abajo en
   este archivo -- con la misma especificidad, ganarían por orden de
   aparición y dejarían la flamita gigante en vez de chica. El sello ya no
   vive dentro de .category-card-icon (ver comentario arriba), así que se
   ancla a .category-card, su nuevo contenedor. */
.category-card .category-discount-badge .icon { width: 8px; height: 8px; flex-shrink: 0; }
/* Las ilustraciones propias (Celulares/Tabletas, Laptops, Monitores) son
   panorámicas (~1024x559): un marco circular les recortaba los bordes.
   Este marco rectangular horizontal, con la misma proporción que las
   fotos, y object-fit: contain (en vez de cover) garantiza que se vea la
   ilustración completa sin recortes. Se agrandó (72x40 -> 92x52) para
   que ocupe más de la tarjeta, a pedido explícito ("más panzona"). */
.category-card-icon--photo {
  width: 92px;
  height: 52px;
  border-radius: var(--radius);
}
.category-card-icon--photo img { object-fit: contain; }
.category-card-name { font-size: 12px; font-weight: 700; line-height: 1.25; }
/* margin-top:auto empuja el conteo al borde inferior: los nombres largos
   ("Televisores y control remoto") ocupan dos renglones y antes recorrían
   el conteo hacia abajo tarjeta por tarjeta, así que la cuadrícula se veía
   desalineada en cada fila. Ahora el conteo queda a la misma altura en
   todas. */
.category-card-count { font-size: 10px; color: var(--muted); margin-top: auto; }
.category-card.active .category-card-count { color: var(--red); }
/* A pedido del usuario: en mobile, minmax(108px, 1fr) sólo alcanza para
   2 columnas (108px*3 + 2 gaps ya no entra en ~330px de ancho útil), así
   que se fuerzan 3 columnas parejas en vez de dejar que auto-fill decida
   -- las tarjetas se angostan un poco (ver ajustes de íconos abajo) pero
   caben sin desbordar. Este bloque tiene que ir DESPUÉS de las reglas base
   .category-card-icon/.category-card-icon--photo/.category-card-name de
   arriba: con la misma especificidad, gana la que aparece último en el
   archivo -- puesto antes (como estaba), esas reglas base "de escritorio"
   pisaban el override mobile en cualquier ancho de pantalla, dejando los
   íconos fotográficos en 92px fijos y desbordando las 3 columnas en
   teléfonos angostos (320-375px de ancho). */
@media (max-width: 480px) {
  .category-card-grid { grid-template-columns: repeat(3, 1fr); gap: 6px; }
  .category-card { padding: 6px 4px; }
  .category-card-icon { width: 36px; height: 36px; font-size: 18px; }
  .category-card-icon--photo { width: 100%; height: 40px; }
  .category-card-name { font-size: 11px; }
}

.brand-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 14px;
}
.brand-card {
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  color: var(--ink);
}
.brand-card:hover { border-color: var(--red); }
.brand-card-logo {
  height: 70px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
}
.brand-card-logo img { max-width: 100%; max-height: 100%; object-fit: contain; }
.brand-card-body { padding: 12px 14px; }
.brand-card-cat { font-size: 11px; color: var(--red); font-weight: 700; text-transform: uppercase; letter-spacing: .02em; }
.brand-card-name { font-size: 15px; font-weight: 700; margin: 3px 0 4px; }
.brand-card-desc { font-size: 12px; color: var(--muted); line-height: 1.4; margin: 0; }

/* ---------- Fotos de producto ----------
   Solo aparecen cuando una API conectada las provee (ver renderProductMedia
   en js/app.js); si no, el mismo contenedor sigue mostrando el emoji, así
   que estas reglas conviven con el diseño actual sin cambiarlo. */
.product-photo {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.row-icon.has-photo {
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: 8px;
  background: #fff;
}
.product-row .row-icon.has-photo {
  height: 56px;
  border: 1px solid var(--border);
}
.detail-icon.has-photo {
  padding: 8px;
  background: #fff;
  overflow: hidden;
}
.most-viewed-icon.has-photo {
  padding: 6px;
  background: #fff;
  overflow: hidden;
}

/* ---------- Iconos ilustrados (reemplazan a los emoji en todo el sitio) ----------
   Ver data/icons.json e icon()/svg_icon() en js/app.js y
   scripts/generate_seo_pages.py: mismo <svg fill="none"|"currentColor"
   stroke="currentColor"> en toda la interfaz, en vez de depender de la
   fuente de emoji de cada sistema operativo. stroke/fill="currentColor"
   hereda el color del texto que lo rodea (insignias, links activos), así
   que la mayoría de los contextos de abajo solo fijan tamaño, no color. */
.icon { flex-shrink: 0; }

/* Ilustración de categoría dentro del contenedor que antes mostraba el
   emoji de respaldo de un producto sin foto (ver renderProductMedia). */
.row-icon .icon { width: 26px; height: 26px; color: var(--muted); }
.ranking-block-list .row-icon .icon,
.related-item .row-icon .icon { width: 18px; height: 18px; }
.detail-icon .icon { width: 72px; height: 72px; color: var(--muted); }
.most-viewed-icon .icon { width: 64px; height: 64px; color: var(--muted); }
.category-card-icon .icon { width: 24px; height: 24px; color: var(--muted); }
.category-card-icon--photo .icon { width: 40px; height: 40px; }

/* Icono de categoría inline junto al nombre (nav superior, submenús,
   filtro "Categoría", listas de inicio, encabezados de ranking): mismo
   svg.icon con la clase cat-item-icon en cada uno de esos call sites, así
   que una sola regla cubre todos. Sin color propio -- hereda currentColor
   del texto que lo rodea (gris en reposo, rojo activo/hover). */
.cat-item-icon { width: 15px; height: 15px; vertical-align: -3px; margin-right: 1px; }

/* Encabezados de sección (Top 5, Ranking, Más visto, resultados en vivo). */
.section-title .icon,
.hero h2 .icon,
#homeTopCategories h2 .icon,
#liveSearchSection h2 .icon,
.list-head h1 .icon,
.ranking-block-head .icon {
  width: 18px; height: 18px;
  vertical-align: -4px;
  margin-right: 3px;
  color: var(--red);
}

.rank-badge .icon { width: 15px; height: 15px; color: #fff; }
.ranking-block-list .rank-badge .icon { width: 12px; height: 12px; }
.delivery-banner-icon .icon { width: 19px; height: 19px; color: var(--red); }
.location-btn-icon .icon { width: 14px; height: 14px; vertical-align: -2px; }

/* Iconos dentro de insignias/pills de texto pequeño: heredan el color de
   la insignia (currentColor), solo hace falta el tamaño. */
.used-badge .icon,
.commercial-badge .icon,
.variant-count-badge .icon,
.variant-pills-label .icon,
.est-badge .icon,
.wholesale-badge .icon,
.shipping-estimate-note .icon,
.ship-badge .icon,
.offer-group-badge .icon,
.best-tag .icon,
.buy-trust .icon {
  width: 12px; height: 12px;
  vertical-align: -2px;
  margin-right: 1px;
}
.offer-group-badge .icon { width: 15px; height: 15px; vertical-align: -3px; }

/* ---------- Formularios (reseña / perfil) ---------- */
.review-form { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
/* Reemplaza al formulario (ver el submit handler en js/app.js) justo
   después de publicar, para que quede claro que la reseña se envió de
   verdad -- en vez de solo vaciar el textarea y dejar el mismo formulario
   a la vista, como si nada hubiera pasado. */
.review-form-success {
  margin-top: 16px;
  padding: 20px 14px;
  border-top: 1px solid var(--border);
  text-align: center;
}
.review-form-success .icon { width: 34px; height: 34px; color: var(--green); margin-bottom: 8px; }
.review-form-success p { margin: 0 0 8px; font-weight: 700; color: var(--ink); }
.review-form h3 { font-size: 13px; margin: 0 0 4px; }
.review-form-row { display: flex; gap: 14px; flex-wrap: wrap; margin: 10px 0; }
.review-form label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
  color: var(--muted);
  flex: 1;
  min-width: 160px;
}
.review-form input[type="text"],
.review-form select,
.review-form textarea {
  border: 1px solid var(--border);
  border-radius: 7px;
  padding: 10px 12px;
  font-size: 14px;
  font-family: inherit;
  color: var(--ink);
  background: var(--card);
}
.review-form input[type="text"]:focus,
.review-form select:focus,
.review-form textarea:focus {
  outline: none;
  border-color: var(--red);
}
.review-form textarea { resize: vertical; margin-top: 10px; }
.review-form .buy-btn { margin-top: 10px; padding: 11px 20px; font-size: 14px; }

.empty-state {
  background: var(--card);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 30px;
  text-align: center;
  color: var(--muted);
  font-size: 13px;
}

/* ---------- Resultados en vivo (búsqueda abierta, fuera del catálogo) ---------- */
.live-search-section {
  margin-top: 24px;
  padding-top: 16px;
  border-top: 1px dashed var(--border);
}
.live-search-section h2 { font-size: 15px; margin: 0 0 2px; }
.product-row.is-external { border-style: dashed; }
.row-external-badge {
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
  margin-left: 4px;
}

/* ---------- Botón "Volver arriba" ---------- */
/* position:fixed (no absolute) para que quede anclado al viewport y no a
   la página: sin importar cuánto se baje, se queda siempre en la misma
   esquina de la pantalla en vez de desplazarse con el contenido. Solo
   aparece después de bajar un poco (ver SCROLL_TOP_THRESHOLD en app.js) --
   mostrarlo desde arriba del todo no tendría sentido, ya se está ahí. */
.back-to-top-btn {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 40;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: none;
  background: var(--red);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
  opacity: 1;
  transition: opacity 0.15s, transform 0.15s;
}
.back-to-top-btn:hover { transform: translateY(-2px); }
.back-to-top-btn svg { width: 22px; height: 22px; }
.back-to-top-btn.is-hidden {
  opacity: 0;
  pointer-events: none;
}
@media (max-width: 480px) {
  .back-to-top-btn { right: 16px; bottom: 16px; width: 44px; height: 44px; }
}
