/* Estilos de la página de catálogo. Extraído del <style> inline de
   catalog.html (plan CWV A.4) a estático cacheable. */

/* ── Catalog-specific styles (Plum Blossom tokens) ── */
.catalog-layout {
  display: flex;
  flex-direction: column;
  gap: 24px;
  align-items: flex-start;
  position: relative;
  min-height: 60vh;
}@media (min-width: 1024px) {
  .catalog-layout { flex-direction: row; }
  /* En desktop el sidebar de filtros (alto, sticky) ya da presencia vertical a
     la fila aun con grid corto. El `min-height:60vh` sólo añadía una banda
     vacía entre el grid y el footer. Lo soltamos a `auto` en desktop; en móvil
     se conserva para que el empty-state no colapse. */
  .catalog-layout { min-height: auto; }}

/* FAB de WhatsApp (global, bottom-right ~80px) solapaba la columna derecha del
   grid a ≥1280px. Reservamos un gutter derecho para que la última columna de
   cards no quede bajo el botón. No tocamos el FAB (vive en base_landing). */
@media (min-width: 1280px) {
  .catalog-layout { padding-right: 72px; }
}

/* Full-bleed: el catálogo ocupa TODO el ancho de la pantalla (sin el cap de
   1280px de .pl-landing-container). Padding lateral fluido con la misma escala
   que la convención `cat-wide` del home, para mantener aire on-brand en los
   bordes sin centrar el contenido en pantallas anchas. */
.pl-landing-container.cat-wide {
  max-width: none;
  padding-left: clamp(1.25rem, 4vw, 4.5rem);
  padding-right: clamp(1.25rem, 4vw, 4.5rem);
}

/* ── Filter panel: bottom sheet en móvil, columna fija en desktop ──
   Móvil (<1024px): hoja inferior con grabber, cabecera fija y barra de acción
   fija (ver bloque "Bottom-sheet chrome" más abajo). Desktop (≥1024px): sidebar
   sticky de siempre. El motor de filtrado (client-side) no cambia. */
.cat-sidebar {
  display: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  /* Por encima de la barra de navegación, que va a 1000. A 70 el panel se
     abría DEBAJO de ella: la cabecera con «Filtros» y la ✕ quedaban tapadas y no
     había forma visible de cerrar. Un panel a pantalla completa que no cubre el
     encabezado no es a pantalla completa. */
  z-index: 1100;
  width: 100%;
  max-width: 100%;
  /* PANTALLA COMPLETA (decisión del dueño, patrón de Amazon y Best Buy). Al 88%
     el panel dejaba 101px de hueco arriba —donde se veía la página por detrás—
     mientras abajo el contenido quedaba cortado: 152px de cromo fijo (cabecera
     79 + barra 73) sobre 743 de alto dejaban 591 útiles para 973 de contenido, y
     sólo entraban 2 de los 4 grupos. A pantalla completa son 692 útiles, un 17%
     más, sin regalar nada.
     `dvh` y no `vh`: en un teléfono la barra del navegador aparece y desaparece,
     y `vh` se queda con la medida grande — el panel sobresaldría por abajo justo
     donde vive el botón de acción. */
  height: 100dvh;
  max-height: 100dvh;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-bottom: none;
  /* Sin esquinas redondeadas: ya no es una hoja asomando sobre la página, es la
     pantalla entera. Un radio arriba con nada detrás se lee como un recorte. */
  border-radius: 0;
  padding: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  transform: translateY(100%);
  /* Salida 240ms / entrada 340ms: el usuario decide despacio, el sistema
     responde rápido. */
  transition: transform .24s var(--aurora-ease);
  box-shadow: 0 -12px 48px rgba(45,31,61,.22);
}
.cat-sidebar.open {
  display: block;
  transform: translateY(0);
  transition-duration: .34s;
}@media (min-width: 1024px) {
  .cat-sidebar {
    display: block;
    position: sticky;
    top: 7rem;
    left: auto;
    right: auto;
    bottom: auto;
    width: 272px;
    max-width: none;
    max-height: none;
    flex-shrink: 0;
    padding: 20px;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: none;
    transform: none;
    height: fit-content;
    /* La columna mide 1.018px de contenido. Pegada con `top:7rem` y sin techo,
       en 1366x768 —la pantalla más común del mercado— sus últimos 362px caen
       bajo el borde inferior y NO HAY SCROLL QUE LOS ALCANCE: la barra se clava
       en 112 y no vuelve a moverse. Ahí dentro quedaban el grupo Condición
       entero (la puerta de entrada por precio), dos marcas y la tarjeta
       «¿No ves lo que buscas?».
       La trampa que lo esconde: sólo pasa cuando el sidebar es MÁS ALTO que la
       rejilla. Al filtrar a pocos productos se alcanza todo y el defecto parece
       no existir. Por eso la comprobación va con sonda a 1366 y catálogo
       completo, no a ojo.
       El techo es el alto de pantalla menos el `top` y un respiro de 24px. */
    max-height: calc(100vh - 7rem - 24px);
    overflow-y: auto;
    overflow-x: hidden;
    /* Que el scroll de la columna no arrastre la página cuando se acaba. */
    overscroll-behavior: contain;
    /* Barra fina y visible: es la única pista de que aquí dentro hay más. En
       macOS las barras flotantes se esconden solas y la columna se leería como
       cortada. `stable` reserva su carril para que el contenido no salte al
       aparecer. */
    scrollbar-width: thin;
    scrollbar-color: var(--border) transparent;
    scrollbar-gutter: stable;
    z-index: auto;
  }
  .cat-sidebar::-webkit-scrollbar { width: 8px; }
  .cat-sidebar::-webkit-scrollbar-track { background: transparent; }
  .cat-sidebar::-webkit-scrollbar-thumb {
    background: var(--border);
    border-radius: 999px;
    border: 2px solid transparent;
    background-clip: content-box;
  }
  .cat-sidebar:hover::-webkit-scrollbar-thumb { background: var(--ink-soft); background-clip: content-box; }}


.cat-sidebar-overlay {
  display: block;
  position: fixed;
  inset: 0;
  /* Justo por debajo del panel y por encima del nav (1000), para que el velo
     también cubra el encabezado. */
  z-index: 1090;
  background: var(--overlay);
  opacity: 0;
  visibility: hidden;
  transition: opacity .24s var(--aurora-ease), visibility .24s var(--aurora-ease);
}
.cat-sidebar-overlay.open { opacity: 1; visibility: visible; }@media (min-width: 1024px) {
  .cat-sidebar-overlay { display: none !important; }}


/* ── Bottom-sheet chrome (solo móvil ≤1023px) ──
   Grabber (::before de la cabecera), cabecera fija arriba y barra de acción fija
   abajo con "Limpiar" + "Ver N productos" (conteo en vivo). En desktop la hoja
   es una columna estática y esta cromática se oculta. */
.cat-sheet-footer { display: none; }

/* ── «¿No ves lo que buscas?» (MIGRACIÓN KAN-24) ──
   Cierra la columna de filtros con la salida que el catálogo no puede dar por
   sí mismo. Sólo escritorio: en móvil este mismo <aside> ES la hoja de filtros
   y la tarjeta empujaría la barra de "Ver productos" fuera de pantalla. */
.cat-ask { display: none; }@media (min-width: 1024px) {
  .cat-ask {
    display: flex;
    flex-direction: column;
    gap: 7px;
    margin-top: 18px;
    padding: 16px;
    border-radius: var(--radius-lg);
    background: var(--pl-surface-2, var(--surface-2));
  }
  .cat-ask__h {
    margin: 0;
    font-size: .87rem;
    font-weight: 700;
    color: var(--pl-ink, var(--ink));
  }
  .cat-ask__p {
    margin: 0;
    font-size: .78rem;
    line-height: 1.5;
    color: var(--pl-ink-soft, var(--ink-soft));
  }
  .cat-ask__a {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: .8rem;
    font-weight: 700;
    /* --brand-hover y no --brand: a .8rem esto es texto normal y necesita
       4.5:1, que el morado claro no da sobre la superficie tintada. */
    color: var(--pl-brand-hover, var(--brand-hover));
    text-decoration: none;
    min-height: 32px;
  }
  .cat-ask__a:hover { text-decoration: underline; }
  .cat-ask__a:focus-visible {
    outline: 2px solid var(--pl-brand, var(--brand));
    outline-offset: 2px;
    border-radius: 3px;
  }
  .cat-ask__a i { font-size: .68rem; }}
@media (max-width: 1023px) {
  .cat-sidebar > .filter-header {
    position: sticky;
    top: 0;
    z-index: 4;
    background: var(--surface-1);
    padding: 22px 20px 12px;
    margin-bottom: 0;
  }
  .cat-sidebar > .filter-header::before {
    content: "";
    position: absolute;
    top: 9px;
    left: 50%;
    width: 40px;
    height: 4px;
    transform: translateX(-50%);
    border-radius: var(--radius-pill);
    background: var(--border-strong);
  }
  /* La cabecera "Limpiar" se muda a la barra de acción inferior en móvil. */
  #clearFiltersBtn { display: none !important; }
  /* El panel ya no tiene padding lateral (0): lo trasladamos a cada bloque
     scrolleable para que cabecera y footer puedan ir a sangre completa. */
  .cat-sidebar > .sidebar-search,
  .cat-sidebar > .filter-section,
  .cat-sidebar > #filterDynamic,
  .cat-sidebar > #sidebarLoader {
    padding-left: 20px;
    padding-right: 20px;
  }

  /* El grabber sobra a pantalla completa: no hay nada que arrastrar hacia
     arriba. La cabecera se queda con su sitio para el título y la ✕. */
  .cat-sidebar > .filter-header { padding-top: 14px; }
  .cat-sidebar > .filter-header::before { display: none; }

  .cat-sheet-footer {
    /* `sticky`, no `fixed`. Lo probé fija —para que el contenido nunca pasara por
       detrás— y en la inspección visual la barra DESAPARECÍA del panel: con un
       ancestro transformado el bloque contenedor cambia y la barra dejaba de
       pintarse donde debía. Pegajosa funciona, y el contenido queda despejado
       igual gracias al colchón del contenedor. */
    position: sticky;
    bottom: 0;
    z-index: 4;
    display: flex;
    gap: 10px;
    align-items: center;
    margin-top: 8px;
    padding: 12px 20px calc(12px + env(safe-area-inset-bottom));
    /* OPACA, no translúcida. Con 92% + desenfoque, una fila que pasaba por
       detrás se veía a medias y parecía cortada por la mitad —«Reacondicionado
       (3)» en la captura—. Detrás de una barra opaca el contenido está
       escondido, que es distinto de estar roto. */
    background: var(--surface-1);
    border-top: 1px solid var(--border);
  }
  /* Sin colchón de relleno: la barra de acción es `sticky`, no `fixed`, así que
     al llegar al final se coloca DESPUÉS del contenido y no tapa nada — medido,
     cero filas debajo con el panel al fondo. Un `padding-bottom` sobre la última
     sección abría además un vacío de 84px en medio, porque `#filterDynamic` es
     un contenedor vacío mientras no hay categoría elegida.
     Lo que sí hace falta es que el foco por teclado no aterrice bajo la barra. */
  /* SIN colchón de relleno. La barra es `sticky`: al llegar al final se coloca
     DESPUÉS del contenido, así que la última fila queda despejada sola. Un
     `padding-bottom` en el contenedor añadía 84px de vacío POR DEBAJO de la
     barra — visto en captura. `scroll-padding` sí se queda: es sólo para que el
     foco por teclado no aterrice pegado al borde. */
  .cat-sidebar { scroll-padding-bottom: 84px; }
  .cat-sheet-clear {
    flex: 0 0 auto;
    min-height: 48px;
    padding: 0 18px;
    border-radius: var(--radius-lg);
    border: 1px solid var(--border-strong);
    background: transparent;
    color: var(--ink-soft);
    font-weight: 700;
    font-size: .875rem;
    cursor: pointer;
    transition: background .2s, color .2s, border-color .2s, transform .1s;
  }
  .cat-sheet-clear:hover { background: var(--surface-2); color: var(--ink); }
  .cat-sheet-clear:active { transform: scale(.97); }
  .cat-sheet-apply {
    flex: 1 1 auto;
    min-height: 48px;
    padding: 0 20px;
    border-radius: var(--radius-lg);
    border: none;
    background: var(--brand);
    color: #fff;
    font-weight: 800;
    font-size: .9375rem;
    letter-spacing: -.01em;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    box-shadow: 0 10px 24px -8px rgba(124,58,237,.55);
    transition: background .2s, transform .1s, box-shadow .2s;
  }
  .cat-sheet-apply:hover { background: var(--brand-hover); }
  .cat-sheet-apply:active { transform: scale(.98); }
  .cat-sheet-clear:focus-visible,
  .cat-sheet-apply:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }}
@media (prefers-reduced-motion: reduce) {
  .cat-sidebar,
  .cat-sidebar-overlay { transition: none; }
  /* El feedback de pulsación es movimiento: se retira, pero el color y el foco
     (que sí comunican) se mantienen. */
  .cat-pill:active, .subcat-pill:active, .filter-pill:active, .filter-chip-x:active,
  .cat-tile:active { transform: none; }}


/* ── Filter section ── */
.filter-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 12px;
}

.filter-section { border-bottom: 1px solid var(--border); padding-bottom: 12px; margin-bottom: 4px; }
.filter-section:last-child { border-bottom: none; }

.filter-toggle-btn {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  padding: 8px 0;
  min-height: 44px;
  cursor: pointer;
  background: none;
  border: none;
  color: var(--ink);
}
.filter-toggle-btn i.fa-chevron-down { transition: transform .2s var(--aurora-ease); font-size: .625rem; color: var(--ink-soft); }
.filter-section.open .filter-toggle-btn i.fa-chevron-down { transform: rotate(180deg); }

.filter-body { display: none; padding-top: 4px; }
.filter-section.open .filter-body { display: block; }

/* ── Header row: toggle (crece) + botón de fijar (fijo) ── */
.filter-head { display: flex; align-items: center; gap: 2px; }
.filter-head .filter-toggle-btn { flex: 1 1 auto; }
/* Contador de opciones del grupo, junto al título. */
.filter-opt-count { font-weight: 600; color: var(--ink-soft); font-size: .72rem; margin-left: 6px; }
/* Conteo por opción (cuántos productos la cumplen dado el resto de filtros). */
.opt-count { color: var(--ink-soft); font-size: .72rem; margin-left: 4px; }
.filter-pill .opt-count { opacity: .85; }
.filter-pill.active .opt-count { color: inherit; opacity: .9; }
/* Fijar grupo: lo mantiene desplegado y persiste (localStorage). */
.filter-pin {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; min-width: 36px;
  background: none; border: none; cursor: pointer;
  color: var(--ink-soft); border-radius: 8px;
  transition: color .2s, background .2s, transform .2s;
}
.filter-pin i { font-size: .8rem; transition: transform .2s; }
.filter-pin:hover { background: var(--surface-2, rgba(124,58,237,.08)); color: var(--ink); }
.filter-pin:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.filter-pin.pinned { color: var(--brand); }
.filter-pin.pinned i { transform: rotate(-30deg); }@media (hover: none) {
  .filter-pin { width: 44px; height: 44px; min-width: 44px; }}

/* En móvil (bottom sheet) "fijar" no aplica —la hoja es transitoria— y la
   chincheta añade una segunda acción confusa por fila. Se oculta; el chevron
   sigue expandiendo/colapsando cada grupo. */
@media (max-width: 1023px) {
  .filter-pin { display: none; }
}

/* ── Filter pills ── */
/* Geometria del sistema (2026-08-18): las pildoras de filtro y las de familia
   usaban --radius-pill (999px) y --radius-lg (16px). Dos formas para dos
   controles que hacen lo mismo, y ninguna igual al boton. */
.filter-pill {
  display: inline-flex;
  align-items: center;
  min-height: var(--cta-alto-sm,36px);
  padding: 0 12px;
  border-radius: var(--cta-radio,10px);
  font-size: .75rem;
  font-weight: 700;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background .18s var(--aurora-ease), color .18s var(--aurora-ease),
              border-color .18s var(--aurora-ease), transform .12s var(--aurora-ease);
}
.filter-pill:active { transform: scale(.97); }@media (hover: hover) and (pointer: fine) {
  .filter-pill:hover { border-color: var(--brand); color: var(--brand); }}

.filter-pill.active { background: var(--brand); color: #fff; border-color: var(--brand); }

/* ── Filter checkbox ── */
.filter-check {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
  min-height: 36px;
  cursor: pointer;
  font-size: .75rem;
  color: var(--ink-soft);
  transition: color .2s;
}
.filter-check:hover { color: var(--brand); }
/* `[hidden]` NO gana por sí solo contra el `display:flex` de arriba: una
   declaración de autor vence al `display:none` del user-agent. Es la trampa
   que este repo ya se ha comido cinco veces. */
.filter-check[hidden] { display: none !important; }
/* La regla del umbral escrita, no adivinada: «Últimas unidades» sin decir
   cuántas son deja al visitante suponiendo. */
.filter-nota {
  margin: 2px 0 0;
  font-size: .72rem;
  line-height: 1.45;
  color: var(--ink-soft);
}
/* 16px y no el 13 de fábrica: en el MISMO panel, Marca usa píldoras de 33px y
   Condición/Disponibilidad usaban la casilla nativa del sistema. Mismo trabajo,
   dos pesos visuales que no se parecen en nada. El control no cambia —una lista
   de 2-4 opciones se lee mejor como casilla que como píldora—, sólo su talla. */
/* `radio` TAMBIÉN, no sólo `checkbox`: el grupo de Categoría usa radios y salía
   con el azul del sistema (`accent-color: auto`) al lado de casillas moradas, en
   el mismo panel. Visto en captura, no en las cifras. */
.filter-check input[type="checkbox"],
.filter-check input[type="radio"] { accent-color: var(--brand); width: 16px; height: 16px; flex: none; }

/* Touch targets ≥44px en breakpoints táctiles (≤1023px). Los pills (32px),
   checks (36px) y chips-x (22px) eran sub-44px y fallaban WCAG táctil. */
@media (max-width: 1023px) {
  .filter-pill { min-height: var(--cta-alto,44px); padding: 0 16px; }
  .filter-check { min-height: 44px; padding: 10px 0; }
  .filter-check input[type="checkbox"] { width: 20px; height: 20px; }
}

/* ── Price range ── */
.price-inputs { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.price-input {
  width: 46%;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 6px 8px;
  font-size: .8125rem;
  font-weight: 700;
  color: var(--ink);
  text-align: right;
}
.price-input:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft); }

.range-wrap { position: relative; height: 24px; margin: 12px 0; }
.range-track {
  position: absolute; top: 50%; left: 0; right: 0; height: 4px;
  background: var(--border); border-radius: var(--radius-pill);
  transform: translateY(-50%);
}
.range-progress {
  position: absolute; top: 50%; height: 4px;
  background: var(--brand-grad); border-radius: var(--radius-pill);
  transform: translateY(-50%); z-index: 1;
}
.range-input {
  position: absolute; top: 50%; left: 0; right: 0; width: 100%;
  height: 24px; transform: translateY(-50%);
  -webkit-appearance: none; appearance: none;
  pointer-events: none; background: none; z-index: 2; margin: 0;
}
.range-input::-webkit-slider-thumb {
  -webkit-appearance: none; pointer-events: auto;
  width: 20px; height: 20px; border-radius: 50%;
  background: #fff; border: 2px solid var(--brand);
  box-shadow: 0 4px 8px rgba(0,0,0,.1); cursor: pointer;
  transition: transform .2s;
}
.range-input::-webkit-slider-thumb:hover { transform: scale(1.15); }
.range-input::-moz-range-thumb {
  pointer-events: auto; width: 20px; height: 20px;
  border-radius: 50%; background: #fff; border: 2px solid var(--brand);
  box-shadow: 0 4px 8px rgba(0,0,0,.1); cursor: pointer;
}

/* ── Trust strip ── */
/* Prueba de confianza on-page: los tres hechos que cierran la venta (garantía,
   factura fiscal, técnicos propios) vivían sólo en el <meta description> y nunca
   se renderizaban. Una banda delgada y sobria bajo el toolbar, sobre la rejilla.
   No es un banner: hairline arriba/abajo, sin fondo llamativo, icono en púrpura
   disciplinado y texto ≥4.5:1. Una línea en desktop; envuelve en móvil. */
.cat-trust {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 28px;
  padding: 12px 4px;
  margin-bottom: 24px;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.cat-trust-item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: .8125rem;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.3;
}
.cat-trust-item i {
  color: var(--brand);
  font-size: .875rem;
  width: 16px;
  text-align: center;
  flex-shrink: 0;
}

/* La cabecera del catálogo (migaja + título grande + bajada) se retiró con sus
   reglas: ocupaba 218px en móvil y dejaba el precio y los botones de la primera
   tarjeta FUERA del primer pantallazo (medido: se veía el 36% de la tarjeta;
   ahora el 89%). El <h1> sigue vivo pero `sr-only`, así que no necesita forma.
   Si alguna vez vuelve, vuelve entera: dejar aquí `.cat-head__h` sin markup que
   la use es la pista falsa que hace pensar que el título sigue ahí. */

/* ── Toolbar ── */
.cat-toolbar {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 8px;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  margin-bottom: 24px;
  /* Fija bajo el nav: en móvil el botón Filtros y el orden se perdían al primer
     scroll (la página mide ~4700px), dejando al usuario sin forma de acotar sin
     volver arriba. Patrón de Amazon / Mercado Libre / Booking. */
  position: sticky;
  /* La isla del nav es fixed y ocupa de 8px a 67px; 76px la libera con holgura. */
  top: var(--cat-toolbar-top, 76px);
  z-index: 30;
}@media (min-width: 1024px) { .cat-toolbar { position: static; }}


.cat-pill-bar {
  display: flex;
  align-items: center;
  gap: 4px;
  overflow-x: auto;
  -ms-overflow-style: none;
  scrollbar-width: none;
  flex: 1;
  min-width: 0;
}
.cat-pill-bar::-webkit-scrollbar { display: none; }

/* ── Pista de que hay más al lado (carriles que scrollean en horizontal) ──
   Los dos carriles esconden su barra de scroll, así que sin esto no queda ni
   una señal de que sigue habiendo contenido. Medido en la vitrina de
   categorías: a 390px se ven 3 de 5 tarjetas y a 360px sólo 2, con 240px
   fuera y CERO indicios.
   El degradado lo pone el JS (`marcaDesborde`) según el estado REAL del
   scroll, no siempre: una máscara fija difumina la última píldora incluso
   cuando no hay nada más que ver, que es mentir en la otra dirección. */
.cat-tiles__rail[data-mas="derecha"],
.cat-pill-bar[data-mas="derecha"] {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent 100%);
  mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent 100%);
}
.cat-tiles__rail[data-mas="izquierda"],
.cat-pill-bar[data-mas="izquierda"] {
  -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 32px), transparent 100%);
  mask-image: linear-gradient(to left, #000 calc(100% - 32px), transparent 100%);
}
.cat-tiles__rail[data-mas="ambos"],
.cat-pill-bar[data-mas="ambos"] {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 32px, #000 calc(100% - 32px), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 32px, #000 calc(100% - 32px), transparent 100%);
}

/* ── El 2º nivel, ENTERO y a la vista (móvil y tableta) ───────────────────
   Medido a 390px: la fila útil son 317px y las subcategorías de Laptops piden
   843 — 2,7 pantallas. Ni con etiquetas cortas, sin conteos y sin el reset
   bajan de 545. Seis píldoras en 317px salen a 49px cada una: descontando
   relleno quedan ~20px de texto, CUATRO letras. «Gaming» no cabe. Una sola
   línea sin desplazamiento sólo existe escondiendo subcategorías.

   Así que envuelve en vez de desplazarse. Medido tras quitar el reset y los
   conteos: DOS filas como techo en todo el catálogo (94px), una en
   Televisores. Nada cortado, nada detrás de un scroll lateral que no se ve.

   Lo que se va de la fila y por qué:
   · «Todo» y «Ofertas» — 166px, el 52% de la fila, gastados por dos botones
     que no son del 2º nivel. Sin ellos las subcategorías empiezan en el borde.
     «Todo» tiene tres salidas más, comprobadas: el chip «Laptops ×» de filtros
     activos, el segundo toque en el azulejo de la vitrina y «Limpiar todo».
     «Ofertas» renace en `.cat-rapidos` como conmutador que SE SUMA a la
     categoría (aquí era pseudo-categoría: la borraba).
   · «Todos los tipos» — el segundo toque en la propia píldora ya deselecciona
     (comprobado: 34 resultados vuelven a 59) y el chip activo lleva su ×.
   · Los conteos — pagan una fila entera de alto. Siguen en el panel, en
     «Tipo», que es donde se afina.
   Todo esto es SÓLO móvil: en escritorio la fila cabe y se queda como está. */
@media (max-width: 1023px) {
  .cat-pill-bar {
    flex-wrap: wrap;
    overflow-x: visible;
    row-gap: 6px;
  }
  /* Sin desbordamiento no hay nada que difuminar, y el degradado dejaría la
     última píldora de cada fila medio borrada mintiendo que hay más al lado. */
  .cat-pill-bar[data-mas="derecha"],
  .cat-pill-bar[data-mas="izquierda"],
  .cat-pill-bar[data-mas="ambos"] {
    -webkit-mask-image: none;
    mask-image: none;
  }
  /* Con el ancestro delante A PROPÓSITO. `.subcat-pill` se declara 200 líneas
     más abajo con `display:inline-flex` y, a igual especificidad, gana la
     última: medido, el reset seguía en pantalla y la fila se iba a 3 renglones.
     Una media query no añade especificidad — la trampa que este archivo ya
     documenta en otro sitio. */
  .cat-pill-bar .cat-pill--raiz,
  .cat-pill-bar .cat-pill--reset,
  .cat-pill-bar .cat-pill-sep { display: none; }
  .cat-pill-bar .cat-pill .opt-count { display: none; }
}
/* Tableta (640-1023): la barra compartía fila con Filtros y el orden, así que
   de 768px le tocaban ~350 y envolvía en TRES renglones. Aquí se reparte igual
   que en móvil —[Filtros] … [Ordenar] arriba, el 2º nivel a lo ancho debajo—
   pero en flex, porque a este ancho el orden es el `<select>` nativo.
   `display:contents` saca a los hijos de `.toolbar-lead` al flex del toolbar
   sin tocar el DOM; `order` los recoloca porque en el marcado la barra viene
   ANTES del select, y sin eso el select se descolgaba a una tercera línea.
   Con el padre delante para ganarle a las utilidades de flex del marcado. */
@media (min-width: 640px) and (max-width: 1023px) {
  .cat-toolbar > .toolbar-lead { display: contents; }
  /* Empuja el orden a la derecha: con `space-between` y tres piezas sueltas, el
     rótulo «Ordenar:» se separaba de su propio desplegable. */
  .cat-toolbar #mobileFilterBtn { margin-right: auto; }
  .cat-toolbar .sort-label,
  .cat-toolbar .sort-select { order: 1; }
  .cat-toolbar .cat-pill-bar { order: 2; flex: 1 0 100%; }
}

/* El contenedor del scroller de categorías. min-width:0 deja que el
   flex hijo scrollee horizontalmente sin desbordar la toolbar (antes se
   forzaba overflow:hidden en el padre, lo que truncaba el contador). */
.toolbar-lead { min-width: 0; }
.toolbar-count { flex-shrink: 0; }

/* Móvil: el contador compite por espacio con el botón Filtros y la barra de
   categorías scrollable, truncándose a "47 D…". Le damos su propia fila para
   que se lea completo. */
/* Barra de herramientas móvil (grid): [Filtros] [Ordenar] en la primera fila,
   contador y categorías a lo ancho debajo. display:contents deja que los hijos
   de .toolbar-lead se coloquen en el grid del toolbar junto al <select> de
   orden (que es su hermano), sin tocar el DOM ni el layout de desktop. */
@media (max-width: 639px) {
  .cat-toolbar {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: center;
    gap: 10px;
    padding: 10px;
    /* 24px separaban la barra de la primera fila de chips: con el 2º nivel ya
       envolviendo dentro de la barra, ese aire sólo empujaba la rejilla. */
    margin-bottom: 14px;
  }
  .cat-toolbar > .toolbar-lead { display: contents; }
  #mobileFilterBtn { grid-column: 1; grid-row: 1; width: 100%; justify-content: center; }
  .sort-select { grid-column: 2; grid-row: 1; width: 100%; }
  .toolbar-count { grid-column: 1 / -1; grid-row: 2; width: 100%; padding-left: 2px; }
  /* El contador salió del flujo (vive ahora en la línea de chips), así que su
     renglón queda vacío: dejar la barra en el 3 cobraba 10px de hueco por una
     fila que no existe. */
  .cat-pill-bar { grid-column: 1 / -1; grid-row: 2; width: 100%; }
}

/* Acolchado vertical a 0 A PROPOSITO: `min-height` sube el alto pero no lo
   baja, asi que con padding vertical el chip se pasaba del token (medido:
   filter-pill 33, pd-landings__chip 42, faq-tab 44). El alto lo fija
   --cta-alto-sm y el centrado, `align-items:center`. */
.cat-pill {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0 16px;
  min-height: var(--cta-alto-sm,36px);
  border-radius: var(--cta-radio,10px);
  font-size: .75rem;
  font-weight: 700;
  white-space: nowrap;
  border: none;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background .18s var(--aurora-ease), color .18s var(--aurora-ease),
              box-shadow .18s var(--aurora-ease), transform .12s var(--aurora-ease);
  min-height: 36px;
}
.cat-pill:active { transform: scale(.97); }@media (hover: hover) and (pointer: fine) {
  .cat-pill:hover { background: var(--surface-2); }}

.cat-pill.active { background: var(--brand); color: #fff; box-shadow: var(--shadow-sm); }

/* La salida cuando la búsqueda no devuelve nada. Va DEBAJO del botón de
   reiniciar: primero se intenta seguir en la tienda, y sólo si eso no sirve
   se pide. */
.cat-empty-pedir {
  margin: 18px auto 0;
  max-width: 44ch;
  font-size: .85rem;
  line-height: 1.55;
  color: var(--ink-soft);
}
.cat-empty-pedir a {
  color: var(--brand-hover);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.cat-empty-pedir a:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: 3px;
}

/* ── «¿No está lo que buscas?» al final de la rejilla ──
   Banda, no tarjeta: la de la columna de filtros mide 272px y ésta cruza el
   ancho de la rejilla, así que aunque digan lo mismo no se leen como la misma
   pieza repetida. Superficie tintada y un solo botón: es la última acción de
   la página antes de la prosa, y compite con nada. */
.cat-pedido {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  margin: 8px 0 4px;
  padding: 20px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
}
.cat-pedido__txt { min-width: 0; }
.cat-pedido__h {
  margin: 0 0 4px;
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink);
}
.cat-pedido__p {
  margin: 0;
  font-size: .85rem;
  line-height: 1.55;
  color: var(--ink-soft);
  max-width: 56ch;
}
.cat-pedido__cta { flex: none; min-height: 44px; }
.cat-pedido__cta i { font-size: .75em; margin-left: 6px; }
@media (min-width: 720px) {
  .cat-pedido {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    padding: 22px 24px;
  }
}

/* Filete entre el 1er nivel (Todo/Ofertas) y el 2º (subcategorías). Los dos
   resets vivían pegados en la misma fila sin nada que dijera que mandan sobre
   alcances distintos. Decorativo puro: `aria-hidden` en el marcado. */
.cat-pill-sep {
  flex: none;
  align-self: center;
  width: 1px;
  height: 20px;
  margin: 0 6px;
  background: var(--border);
}
/* Las subcategorías comparten fila con "Todo"/"Ofertas" desde que las raíces se
   mudaron a la vitrina. Igualamos su métrica a la de .cat-pill (venían con
   min-height 34 y quedaban 10px más bajas), y les dejamos borde para que se
   distingan como 2º nivel dentro de la misma barra. */
#categoryBar .subcat-pill {
  min-height: 36px;
  padding: 8px 14px;
  font-size: .75rem;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: var(--surface-0);
}
#categoryBar .subcat-pill.active {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
  box-shadow: var(--shadow-sm);
}@media (hover: none) { #categoryBar .subcat-pill { min-height: 40px; }}


/* «Ordenar:» delante del selector, como la comp. En móvil el toolbar es una
   rejilla de dos columnas con posiciones explícitas, así que la etiqueta se
   saca del flujo (absolute + clip) en vez de ocultarse: sigue asociada al
   <select> por `for`, sigue siendo su nombre accesible, y no reclama celda. */
.sort-label {
  font-size: .78rem;
  color: var(--pl-ink-soft, var(--ink-soft));
  white-space: nowrap;
  flex-shrink: 0;
}@media (max-width: 639px) {
  .sort-label {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }}

/* El matiz «todos disponibles» compite con el conteo en la fila estrecha de
   móvil, donde ya se trunca; ahí se cae y el número se lee entero. */
.toolbar-count__todos { display: none; }@media (min-width: 640px) { .toolbar-count__todos { display: inline; }}
/* El `hidden` que pone el JS cuando hay 0 resultados NO oculta por sí solo: la
   regla de arriba declara `display:inline` y una declaración de autor vence al
   `display:none` del user-agent. Es la misma trampa que ya mordió cuatro veces
   en este repo (insignia de descuento, flechas del hero, toggle de specs,
   campos de envío del checkout): se mide el `display` CALCULADO, no el
   atributo. */
.toolbar-count__todos[hidden] { display: none !important; }


.sort-select {
  appearance: none;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  font-size: .75rem;
  font-weight: 700;
  color: var(--ink);
  padding: 8px 32px 8px 12px;
  min-height: 36px;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23554B6B' viewBox='0 0 16 16'%3E%3Cpath d='M4.5 6l3.5 4 3.5-4z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
}
.sort-select:focus { outline: none; border-color: var(--brand); }

/* ── A11y: foco de teclado visible + touch targets ≥44px en móvil ── */
.filter-check input[type="checkbox"]:focus-visible,
.cat-pill:focus-visible,
.sort-select:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}@media (max-width: 1023px) {
  /* Objetivo táctil de 44px para TODAS las píldoras de la barra, incluidas las
     subcategorías: comparten fila y con 37px quedaban desalineadas y por debajo
     del mínimo táctil. */
  .cat-pill,
  #categoryBar .subcat-pill { min-height: 44px; }
  /* Glifo de orden (↕) a la izquierda para que "Destacados" se lea como un
     control de ORDEN, no como una etiqueta. El caret de la derecha se conserva. */
  .sort-select {
    min-height: 44px;
    padding-left: 34px;
    background-image:
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='13' fill='none' stroke='%23554B6B' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 24 24'%3E%3Cpath d='M7 4v16M7 4l-3 3M7 4l3 3M17 20V4M17 20l-3-3M17 20l3-3'/%3E%3C/svg%3E"),
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23554B6B' viewBox='0 0 16 16'%3E%3Cpath d='M4.5 6l3.5 4 3.5-4z'/%3E%3C/svg%3E");
    background-repeat: no-repeat, no-repeat;
    background-position: left 12px center, right 10px center;
  }
  .active-filters-clear { min-height: 44px; display: inline-flex; align-items: center; }}


/* ── Subcategoría (2º nivel): aparece tras elegir una categoría principal ──
   Las categorías principales se leen como TABS (.cat-pill); las subcategorías,
   un peldaño abajo en la jerarquía, se leen como CHIPS de filtro. Fila propia
   bajo el toolbar, scrollable en móvil (mismo fade-mask que la barra de
   categorías). Visible siempre que la categoría activa exponga subtipos. */
.subcat-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex-shrink: 0;
  white-space: nowrap;
  padding: 6px 14px;
  min-height: 34px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--ink-soft);
  font-size: .75rem;
  font-weight: 700;
  cursor: pointer;
  transition: background .18s var(--aurora-ease), color .18s var(--aurora-ease),
              border-color .18s var(--aurora-ease), transform .12s var(--aurora-ease);
}
.subcat-pill:active { transform: scale(.97); }@media (hover: hover) and (pointer: fine) {
  .subcat-pill:hover { border-color: var(--brand); color: var(--brand); }}

.subcat-pill.active { background: var(--brand); color: #fff; border-color: var(--brand); }
.subcat-pill:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.subcat-pill .opt-count { color: inherit; opacity: .7; margin-left: 2px; }
.subcat-pill.active .opt-count { opacity: .85; }@media (hover: none) { .subcat-pill { min-height: 44px; padding: 8px 16px; }}



/* ── Product grid ── */
.product-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
}
/* Móvil (≥360px): 2 columnas compactas para densificar y permitir comparar
   dos equipos a la vez sin scroll interminable. El umbral anterior (420px)
   dejaba 1-col en pantallas de 390px (iPhone 12/13/14), produciendo una torre
   de ~16.7k px. Bajarlo a 360px materializa el 2-col en el móvil real. */
@media (min-width: 360px) { .product-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; } }@media (min-width: 640px) { .product-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }}

/* Densidad: a 1440px la rejilla daba 3 columnas de 302px con cards de 659px de
   alto → solo 3 productos en el primer pantallazo. ML muestra 4-5 a ese ancho.
   Con 4 columnas la card baja a ~250px y entran 8 productos sin scroll. */
@media (min-width: 1180px) { .product-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }@media (min-width: 1360px) { .product-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }}

/* Pantallas anchas: al ir full-bleed el área del grid crece mucho; pasamos a
   4 columnas para conservar la densidad de card (~300-380px) en vez de estirar
   3 cards a lo ancho de un monitor grande. */
@media (min-width: 1600px) { .product-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); } }


/* ── [hidden] gana siempre dentro del catálogo ──
   `display:none` de [hidden] vive en la hoja del agente de usuario: CUALQUIER
   regla de autor con display lo pisa. Ya nos mordió dos veces —el mensaje
   "Cargando más productos…" y el botón "Cargar más", que seguía visible con 0
   resultados— y volverá a morder cada vez que se añada un control con display
   propio. Esto ataca el patrón, no el caso. */
#catalog [hidden],
#catScrollFoot [hidden],
.cat-tiles[hidden],
.cat-toolbar [hidden] { display: none !important; }

/* La salida quirúrgica va ANTES que la nuclear y con menos peso visual: es la
   que casi siempre quiere el usuario, pero no debe gritar. */
.cat-empty-escape { margin-bottom: 12px; }
.cat-empty-escape[hidden] { display: none; }

/* Nota cuando la categoría elegida no ofrece ninguna faceta útil. Explica el
   hueco en vez de dejar el panel mudo (se leía como "los filtros no cargan"). */
.filter-empty-note {
  border-top: 1px solid var(--border);
  padding: 14px 0 4px;
  font-size: .8125rem;
  line-height: 1.45;
  color: var(--ink-soft);
}

/* ── Vitrina de categorías raíz ────────────────────────────────────────────
   Sólo visible sin filtros aplicados. En móvil es un carril con arrastre; en
   desktop una rejilla con tope de 6 columnas: sin tope, `auto-fit` metía 11
   tarjetas por fila cuando el catálogo pase de 3 a ~13 raíces. */
/* El aire superior lo daba la fila del enlace "Ver todo el catálogo"; al
   retirarla las tarjetas quedaron a 26px de la isla del nav, que es fija y
   proyecta sombra. El respiro pasa a ser propiedad de la vitrina, no un efecto
   secundario de un elemento que ya no existe. */
.cat-tiles { margin: 0 0 20px; padding-top: clamp(20px, 3vw, 32px); }
.cat-tiles[hidden] { display: none; }
.cat-tiles__rail {
  display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px;
  scroll-snap-type: x mandatory; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  /* `safe` evita el bug clásico de centrar en un contenedor con scroll: al
     desbordar, un `center` normal recorta la primera tarjeta y la deja
     inalcanzable. Con pocas categorías quedan centradas; con muchas, el carril
     se comporta como un carril normal. */
  justify-content: safe center;
}
.cat-tiles__rail::-webkit-scrollbar { display: none; }
/* Desktop: flex con wrap centrado en vez de grid. Con 3 categorías la rejilla
   de 6 columnas las dejaba pegadas a la izquierda y con dos huecos muertos;
   así nacen centradas y siguen centradas al crecer a 13. */
@media (min-width: 768px) {
  /* CENTRADA. Decisión del dueño (2026-08-09), reafirmada después de que una
     revisión automática pidiera alinearla a la izquierda «para que comparta
     arista con la columna y la rejilla». Se probó alineada y se revirtió: la
     vitrina es una banda de entrada, no una fila más de la retícula, y centrada
     es como se pidió desde que existe. No volver a alinearla por un hallazgo de
     auditoría — es criterio, no defecto.
     `safe` conserva su papel original: al desbordar, un `center` normal recorta
     la primera tarjeta y la deja inalcanzable. */
  .cat-tiles__rail { flex-wrap: wrap; overflow: visible; row-gap: 12px;
                     justify-content: safe center; }
}


.cat-tile {
  scroll-snap-align: start; flex: 0 0 132px;
  display: flex; flex-direction: column; gap: 6px; padding: 10px;
  background: var(--surface-0); border: 1px solid var(--border);
  border-radius: var(--radius-md); cursor: pointer; text-align: center;
  transition: border-color .15s ease, box-shadow .15s ease, transform .1s ease,
              background-color .15s ease;
}
/* Categoría activa: la vitrina se queda en pantalla al filtrar, así que la
   tarjeta tiene que decir dónde está parado el usuario. */
.cat-tile[aria-pressed="true"] {
  border-color: var(--brand);
  background: var(--brand-soft);
  box-shadow: inset 0 0 0 1px var(--brand);
}
.cat-tile[aria-pressed="true"] .cat-tile__name { color: var(--brand); }
/* Las fotos de producto traen fondo blanco propio y sobre el malva de la
   tarjeta activa se recortaban como un rectángulo. `multiply` funde ese blanco
   con el fondo (blanco × color = color) y deja solo el producto. Solo en tema
   claro: en oscuro multiply apagaría la foto entera. */
:root[data-theme="light"] .cat-tile[aria-pressed="true"] .cat-tile__img img,
:root:not([data-theme="dark"]) .cat-tile[aria-pressed="true"] .cat-tile__img img {
  mix-blend-mode: multiply;
}@media (min-width: 768px) { .cat-tile { flex: 0 1 168px; }}
@media (hover: hover) and (pointer: fine) {
  .cat-tile:hover { border-color: var(--brand-vivid); box-shadow: var(--shadow-sm); }}

.cat-tile:active { transform: scale(.98); }
.cat-tile:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
/* Sin caja de color detrás de la foto: las fotos de producto son apaisadas y
   dentro de un rectángulo más ancho dejaban dos bandas laterales visibles.
   Recortar (`cover`) decapitaba las laptops, así que se mantiene `contain` y se
   quita el contraste — el aire alrededor pasa a ser el fondo de la tarjeta. */
.cat-tile__img {
  /* 88px, no 64. La foto es 210x165 —vertical— dentro de una caja de 152 de
     ancho, así que con `contain` el dibujo lo limita el ALTO y no el ancho: a
     64px de caja el arte medía 81x64 y dejaba 71px de banda muerta a los
     lados. Cada píxel de alto compra 1,27 de ancho hasta los 120px, donde el
     arte por fin toca los bordes; subir a 88 lleva el dibujo a 112x88 —un 38%
     más grande de lado— sin tocar el ancho del azulejo ni el rótulo.
     Pedido del dueño el 2026-08-21: «¿pueden ser más grandes?». */
  height: 88px; background: transparent;
  display: grid; place-items: center; overflow: hidden;
}

/* ── Pantalla BAJA: la vitrina se compacta ──
   El defecto es de presupuesto vertical, así que la condición es la ALTURA, no
   el ancho. En un portátil 1366x768 real quedan ~629px de lienzo y la primera
   pantalla no llega a enseñar UN precio: el primero cierra 160px por debajo.
   Aquí la vitrina cede alto sin desaparecer —el dueño la pidió con foto, y con
   foto se queda—: media foto y sin el conteo.
   VA DESPUÉS de las bases de `.cat-tile` y `.cat-tile__img` a propósito. Una
   media query NO suma especificidad: colocada antes, la base posterior la pisa
   y no pasa nada (medido: el bloque seguía en 144px). Es la misma trampa que
   ya está anotada para el botón compacto de la tarjeta.
   No cierra sola los 160px: lo que falta está DENTRO de la tarjeta (a 205px de
   ancho el bloque de chips ocupa 3-4 filas) y recortarlo cambia la densidad de
   la rejilla, que es decisión del dueño. */
@media (min-width: 768px) and (max-height: 820px) {
  .cat-tile { padding: 7px; gap: 3px; }
  /* 52px, no 34. El techo de este bloque lo pone el pliegue, no el gusto, así
     que se midió: a 1366x768 el primer precio cierra en y=711 con 34px de
     foto, o sea 57px de margen antes del corte. Los 18px que cuesta subir a
     52 lo dejan en ~729, todavía dentro. Ese margen no existía antes del
     2026-08-21: lo liberó retirar los cinco puntos clave del tope, y es lo
     que hace que esto quepa hoy y no ayer.
     El arte pasa de 43x34 a 66x52 — la mitad más de lado, más del doble de
     superficie. Si alguna vez se añade algo encima de la rejilla, vuelve a
     medir el primer precio ANTES de subir este número. */
  .cat-tile__img { height: 52px; }
  .cat-tile__count { display: none; }
  #catTiles { margin-bottom: 10px; }
}
/* La imagen COMPLETA, siempre. Medido: la caja media 57x34 y el <img> se
   renderizaba 57x64 dentro de un `overflow:hidden` — se perdian 30px por arriba
   y por abajo. `max-width/max-height:100%` no bastaba porque los atributos
   `width`/`height` del <img> imponian su propia relacion de aspecto. Con
   `width:100%; height:100%` mas `contain`, la imagen se ajusta al hueco entera y
   deja aire a los lados si sobra: no hay recorte posible. */
.cat-tile__img picture,
.cat-tile__img img {
  display: block;
  width: 100%;
  /* `inherit` y NO `100%`: la caja es una rejilla con `place-items:center`, y
     contra ese área el porcentaje NO resuelve — medido, el <img> salía 45px de
     alto dentro de una caja de 30 y `overflow:hidden` se comía el resto.
     Heredar el alto sí funciona, porque el de la caja es una longitud (la
     `clamp` de abajo en móvil, 64px en escritorio). */
  height: inherit;
  object-fit: contain;
}
.cat-tile__more {
  font-size: 1.25rem; font-weight: 800; color: var(--brand);
  background: var(--brand-soft); border-radius: var(--radius-sm);
  width: 100%; height: 100%; display: grid; place-items: center;
}
/* El rotulo va DEBAJO de la imagen y en UNA sola linea. `nowrap` + elipsis es
   la red: si algun dia no cupiera ni el nombre corto, se vera que falta texto en
   vez de partirse a media palabra. */
.cat-tile__name {
  font-size: .8125rem; font-weight: 600; line-height: 1.25; color: var(--ink);
  display: block; width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* En escritorio manda el nombre entero; en telefono, el corto. Los dos estan en
   el arbol y sólo cambia cuál se pinta, así que girar el teléfono no obliga a
   reconstruir nada. */
.cat-tile__name--corto { display: none; }
@media (max-width: 767px) {
  .cat-tile__name--largo { display: none; }
  .cat-tile__name--corto { display: inline; }
}
.cat-tile__count { font-size: .75rem; color: var(--ink-soft); }

/* Móvil: versión compacta. Medido — con la tarjeta de escritorio la vitrina
   ocupaba 183px y empujaba el primer producto de y=364 a y=599, justo lo que
   este rediseño vino a corregir. A 44px de imagen baja a ~120px. */
@media (max-width: 767px) {
  .cat-tiles { margin-bottom: 14px; }
  /* ── TODAS las categorías en UNA fila (pedido del dueño) ──
     Antes era un carril que deslizaba: a 390px se veían 3 de 5 y a 360 sólo 2,
     con 240px fuera de pantalla. Deslizar esconde opciones, y en un teléfono la
     que no se ve no existe.
     `grid-auto-flow: column` + `grid-auto-columns: 1fr` mete a TODOS los hijos
     en una sola fila repartiéndose el ancho a partes iguales, sea cual sea su
     número — se adapta solo si el catálogo crece de 5 a 6 categorías, o si
     aparece el azulejo «+N Ver todas». `min-width: 0` es imprescindible: sin él
     un hijo de rejilla no baja de su tamaño de contenido y la fila desborda. */
  .cat-tiles__rail {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: 6px;
    overflow: visible;
    scroll-snap-type: none;
  }
  /* `container-type: inline-size` convierte cada azulejo en su propia unidad de
     medida: la foto y la letra se calculan sobre el ancho QUE LE TOCÓ, no sobre
     el de la pantalla. Así la fila queda bien con 4, 5 o 6 categorías sin una
     sola cifra fija que mantener — con 4 el azulejo es ancho y la foto crece,
     con 6 se estrecha y todo encoge a la vez. */
  .cat-tile {
    min-width: 0;
    container-type: inline-size;
    /* Sin costado horizontal: el rótulo va centrado y necesita hasta el último
       píxel para caber en una línea cuando hay seis categorías. La imagen no lo
       nota (ya es `width:100%`) y el filete de la tarjeta sigue dando el marco. */
    padding: 7px 0;
    gap: 4px;
    scroll-snap-align: none;
  }
  /* 68cqw, subido de 52cqw el 2026-08-21. La cuenta no es de gusto: con
     `contain` sobre una foto de 210x165, el ancho del dibujo es su alto por
     1,27. En un teléfono de 375 con cinco categorías el azulejo mide 62px, así
     que para que el dibujo llegue a ocupar el ancho del azulejo hace falta
     62/1,27 = 49px de alto, o sea ~79cqw. A 68cqw se queda en ~42px y el arte
     en 53x42: casi todo el ancho, con 3-4px de aire a cada lado para no pegar
     la foto al filete de la tarjeta. A 52cqw eran 32px y el dibujo salía 41
     de ancho dentro de una caja de 62 — un tercio del azulejo desperdiciado.
     `cqw` y no píxeles porque el azulejo se estrecha solo si el catálogo crece
     a seis categorías, y entonces la foto encoge con él sin tocar nada.
     El techo de 58px protege el caso contrario, 3 o 4 categorías: ahí el
     azulejo se ensancha y sin tope la foto se comería la fila entera. */
  .cat-tile__img { height: clamp(28px, 68cqw, 58px); }
  /* El nombre completo, en dos líneas si hace falta: «Componentes y partes»
     entero vale más que un «Componen…» que no dice qué hay dentro. */
  .cat-tile__name {
    /* Misma idea para la letra: 17cqw es lo que hace que un nombre de once
       caracteres («Componentes», «Televisores») quepa entero en una línea.
       Con techo de 12px para que con 4 categorías no se vuelva un titular. */
    font-size: clamp(.5rem, 15cqw, .75rem);
    /* Apretar la letra un pelo a tamaños diminutos es tipografía normal, y es
       lo que hace que «Componentes» y «Televisores» —los nombres más largos—
       entren enteros en vez de acabar en puntos suspensivos. */
    letter-spacing: -.015em;
    line-height: 1.2;
  }
  .cat-tile__count { display: none; }
  .cat-tile__more { font-size: 1rem; }
}

/* ── Empty state ── */
.cat-empty {
  padding: 80px 24px;
  text-align: center;
}
.cat-empty-icon {
  width: 80px; height: 80px;
  background: var(--brand-soft);
  border-radius: var(--radius-xl);
  display: flex; align-items: center; justify-content: center;
  margin: 0 auto 24px;
  font-size: 1.5rem;
  color: var(--brand);
}

/* ── Pagination ── */
.cat-pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  /* Reducido de 40px: junto con el padding-bottom menor del contenedor, cierra
     la banda vacía entre paginación y footer en desktop. */
  margin-top: 32px;
}
.cat-page-btn {
  width: 44px; height: 44px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  color: var(--ink);
  display: flex; align-items: center; justify-content: center;
  background: transparent;
  cursor: pointer;
  transition: background .2s, border-color .2s, color .2s;
}
.cat-page-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.cat-page-btn:hover { background: var(--surface-2); border-color: var(--brand); }
.cat-page-btn:disabled { opacity: .4; cursor: not-allowed; }

/* ── Active filter chips ── */
.active-filters {
  display: none;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 20px;
}
.active-filters.has-items { display: flex; }

/* ── El total, donde el ojo ya está ──────────────────────────────────────
   En móvil el contador se llevaba una fila entera de la barra de herramientas
   para decir «59 resultados». Aquí ocupa cero filas nuevas: encabeza la línea
   de los chips activos, que es donde se mira mientras se filtra, y hace de
   rótulo de esa línea (por eso «Filtros activos:» se retira, eran ~100px que
   no dicen nada). El original sigue en el DOM, sólo para lectores de pantalla:
   es quien lleva `role="status"`, y borrarlo dejaría a quien navega a ciegas
   sin enterarse de cuántos resultados quedan al filtrar. */
.cat-resumen { display: none; }
@media (max-width: 1023px) {
  .toolbar-count {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .active-filters.has-count { display: flex; margin-bottom: 12px; }
  .active-filters-label { display: none; }
  .cat-resumen {
    display: inline-flex;
    align-items: center;
    /* Fuera del carril (ver `.active-filters__scroll`): no se mueve, no tapa
       nada y no necesita fondo. */
    flex: 0 0 auto;
    padding-right: 8px;
    font-size: .75rem;
    font-weight: 700;
    color: var(--ink);
    white-space: nowrap;
  }
}
/* Móvil: fila única scrolleable (en vez de envolver) para no empujar la rejilla
   hacia abajo. Mismo fade-mask que las barras de categorías.
   El scroll vive en un envoltorio interno, NO en la fila: el total tiene que
   quedarse quieto a la izquierda mientras los chips corren por debajo, y
   pegarlo con `sticky` obligaba a pintarle un fondo opaco — imposible de
   cuadrar sobre `body.aurora-bg`, que es un degradado radial: medido, dejaba un
   recuadro blanco visible alrededor de la cifra. En escritorio el envoltorio es
   `display:contents` y la fila se comporta exactamente como antes. */
.active-filters__scroll { display: contents; }
@media (max-width: 639px) {
  .active-filters.has-items { flex-wrap: nowrap; }
  .active-filters__scroll {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1 1 auto;
    min-width: 0;
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
    padding-bottom: 2px;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 20px), transparent 100%);
    mask-image: linear-gradient(to right, #000 calc(100% - 20px), transparent 100%);
  }
  .active-filters__scroll::-webkit-scrollbar { display: none; }
  .active-filters__scroll > * { flex: 0 0 auto; }
}
.active-filters-label {
  font-size: .75rem;
  font-weight: 700;
  color: var(--ink-soft);
  margin-right: 2px;
}
.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 6px 6px 12px;
  min-height: 32px;
  border-radius: var(--radius-pill);
  background: var(--brand-soft);
  color: var(--brand);
  border: 1px solid transparent;
  font-size: .75rem;
  font-weight: 700;
  line-height: 1;
  max-width: 220px;
}
.filter-chip > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.filter-chip-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--brand);
  cursor: pointer;
  flex-shrink: 0;
  transition: background .18s var(--aurora-ease), color .18s var(--aurora-ease), transform .12s var(--aurora-ease);
}
.filter-chip-x:active { transform: scale(.9); }@media (hover: hover) and (pointer: fine) {
  .filter-chip-x:hover { background: var(--brand); color: #fff; }}

.filter-chip-x:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
/* Área táctil de 44px sin agrandar el glifo: el ::after extiende el objetivo
   más allá del botón visible (22px). Quitar un filtro es de las acciones que
   más se repiten en móvil y fallaba el mínimo táctil por la mitad. */
@media (hover: none) {
  .filter-chip-x { position: relative; }
  .filter-chip-x::after {
    content: "";
    position: absolute;
    inset: -11px;
    border-radius: inherit;
  }
}

/* Touch: el botón "quitar" del chip activo era 22px. Subimos chip y x a un
   área táctil ≥44px en ≤1023px sin agrandar el icono visual. */
@media (max-width: 1023px) {
  .filter-chip { min-height: 44px; padding: 8px 8px 8px 14px; }
  .filter-chip-x { width: 32px; height: 32px; }
}
.active-filters-clear {
  font-size: .75rem;
  font-weight: 700;
  color: var(--ink-soft);
  background: none;
  border: none;
  cursor: pointer;
  text-decoration: underline;
  padding: 4px;
}
.active-filters-clear:hover { color: var(--brand); }

/* ── Filtros rápidos sobre la rejilla (sólo móvil) ────────────────────────
   Aquí aterriza «Ofertas» al salir de la fila de categorías, y ya no como
   pseudo-categoría: es un conmutador que SE SUMA a lo elegido, así que
   «laptops en oferta» —el cruce comercial más caliente de la tienda— por fin
   se puede pedir sin abrir el panel. Son los MISMOS interruptores del panel,
   no un estado paralelo: pulsar aquí marca la casilla de allá y al revés.
   `hidden` con `!important` a propósito: declarar `display` en la clase deja
   sin efecto el atributo, y esa trampa ya mordió cuatro veces en este repo. */
.cat-rapidos { display: none; }
@media (max-width: 1023px) {
  .cat-rapidos {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 0 0 10px;
  }
}
.cat-rapidos[hidden] { display: none !important; }
/* Solo se ve en movil: por eso escapo al primer barrido, que fue a 1280px. */
.cat-rapido {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 16px;
  min-height: var(--cta-alto,44px);
  border-radius: var(--cta-radio,10px);
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--ink-soft);
  font-size: .75rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: background .18s var(--aurora-ease), color .18s var(--aurora-ease),
              border-color .18s var(--aurora-ease), transform .12s var(--aurora-ease);
}
.cat-rapido:active { transform: scale(.97); }
.cat-rapido.active {
  background: var(--brand);
  color: #fff;
  border-color: var(--brand);
}
.cat-rapido:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.cat-rapido .opt-count { color: inherit; opacity: .7; }
.cat-rapido.active .opt-count { opacity: .85; }
@media (hover: hover) and (pointer: fine) {
  .cat-rapido:hover { border-color: var(--brand); color: var(--brand); }
  .cat-rapido.active:hover { color: #fff; }
}

/* Count badge on the filter buttons (mobile toolbar + FAB) */
.filter-count-badge {
  display: none;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  margin-left: 4px;
  border-radius: var(--radius-pill);
  background: #fff;
  color: var(--brand);
  font-size: .6875rem;
  font-weight: 800;
  line-height: 1;
}
.filter-count-badge.show { display: inline-flex; }

/* (FAB de filtros eliminado — el panel de filtros es ahora una bottom sheet con
   barra de acción propia; el botón "Filtros" del toolbar la abre.) */




/* ── Loader ── */
.cat-loader {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 80px 0;
  gap: 16px;
}

/* ── Notifications ── */
.notif-area {
  position: fixed; top: 96px; right: 24px; z-index: 60;
  display: flex; flex-direction: column; gap: 8px;
  pointer-events: none;
}
.notif-item {
  background: var(--surface-1);
  color: var(--ink);
  padding: 12px 16px;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 280px;
  pointer-events: auto;
  border: 1px solid var(--border);
  animation: pl-fadeInUp .3s ease forwards;
}

/* ── Mobile body lock ── */
body.filters-open {
  overflow: hidden;
  position: fixed;
  width: 100%;
  height: 100%;
  overscroll-behavior: none;
  touch-action: none;
}

/* ── Search input in sidebar ── */
.sidebar-search {
  position: relative;
  margin-bottom: 12px;
}
.sidebar-search i {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--ink-soft);
  font-size: .875rem;
}
.sidebar-search input {
  width: 100%;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 10px 16px 10px 36px;
  font-size: .875rem;
  color: var(--ink);
}
.sidebar-search input:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-soft);
}

/* ── Mobile filter close ── */
.sidebar-close-mobile {
  display: none;
}@media (max-width: 1023px) {
  .sidebar-close-mobile { display: flex; }}



/* Salida del buscador del catálogo a la búsqueda del servidor (KAN-12).
   `[hidden]` explícito: esta regla es de autor y ganaría al display:none de la
   hoja del navegador — la trampa de cascada que ya mordió cuatro veces aquí. */
.cat-busca-todo{margin:.5rem 0 0;font-size:.78rem}
.cat-busca-todo[hidden]{display:none}
.cat-busca-todo a{display:inline-flex;align-items:center;gap:.35rem;font-weight:700;
                  color:var(--brand-hover);text-decoration:none;min-height:32px}
.cat-busca-todo a:hover{text-decoration:underline;text-underline-offset:3px}
.cat-busca-todo a:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:3px}
.cat-busca-todo i{font-size:.62rem}


/* ── Prosa del catálogo (debajo de la rejilla) ──────────────────────────────
   Texto de cierre: cubre el vocabulario de la cola larga y responde las dos
   preguntas que tenían impresiones y ninguna página («cuánto cuesta una laptop
   en república dominicana», «dónde comprar laptop en santo domingo»). Va al
   final de todo, así que no le quita ni un píxel al producto — que es la razón
   por la que la cabecera de arriba se retiró. Medida de línea contenida: es
   para leerse, no para rellenar. */
.cat-prosa {
  margin-top: clamp(28px, 4vw, 44px);
  padding-top: clamp(20px, 3vw, 28px);
  border-top: 1px solid var(--border);
}
.cat-prosa h2 {
  margin: 0 0 10px;
  font-family: var(--font-display, inherit);
  font-size: clamp(1.05rem, .95rem + .5vw, 1.25rem);
  font-weight: 700;
  letter-spacing: -.015em;
  color: var(--ink);
}
.cat-prosa p {
  margin: 0 0 10px;
  max-width: 68ch;
  font-size: .9rem;
  line-height: 1.65;
  color: var(--ink-soft);
  text-wrap: pretty;
}
.cat-prosa p:last-child { margin-bottom: 0; }
.cat-prosa strong { color: var(--ink); font-weight: 600; }

/* La reserva de altura de la rejilla y de la columna vive en el <style> en
   línea de catalog.html, NO aquí: esta hoja carga asíncrona y la reserva tiene
   que existir en el primer pintado o no sirve de nada. */

/* ── Orden en móvil: botón + hoja ──────────────────────────────────────
   El `<select>` nativo necesita 255px y en un teléfono de 390 tiene 159, con
   `text-overflow: clip`: se leía «Destacados: en» cortado a media letra y sin
   ninguna señal de que faltara texto. Trunca por debajo de 600px (medido a
   360/390/480/560); se usa el corte de 640 que ya rige la barra móvil, para no
   inventar un breakpoint nuevo.
   Amazon, Target, Walmart y Best Buy no ponen un select ahí: abren una hoja con
   las opciones como lista y una marca en la activa. Esto es eso. */
#mobileSortBtn { display: none; }
@media (max-width: 639px) {
  .cat-toolbar > .sort-label { display: none; }
  .cat-toolbar > .sort-select { display: none; }
  #mobileSortBtn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    grid-column: 2;
    grid-row: 1;
    width: 100%;
    min-height: 44px;
  }
  /* El texto se recorta CON puntos suspensivos: si algún día no cabe, al menos
     se ve que falta. Es justo lo que el select no hacía. */
  .sort-btn__txt {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

.cat-sort-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: var(--overlay);
  opacity: 0;
  transition: opacity .24s var(--aurora-ease);
}
.cat-sort-overlay.open { opacity: 1; }
.cat-sort-overlay[hidden] { display: none !important; }

.cat-sort-sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 70;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  box-shadow: 0 -12px 48px rgba(45, 31, 61, .22);
  transform: translateY(100%);
  transition: transform .28s var(--aurora-ease);
  padding-bottom: env(safe-area-inset-bottom, 0);
  max-height: 80dvh;
  overflow-y: auto;
}
.cat-sort-sheet.open { transform: translateY(0); }
/* La misma trampa de siempre: `[hidden]` no gana contra el `display` de autor
   de arriba, así que hay que declararlo. */
.cat-sort-sheet[hidden] { display: none !important; }

.cat-sort-sheet__head {
  position: sticky;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 20px 20px 10px;
  background: var(--surface-1);
}
/* El grabber de la hoja de filtros, para que las dos se lean como la misma
   pieza del sistema. */
.cat-sort-sheet__head::before {
  content: "";
  position: absolute;
  top: 9px;
  left: 50%;
  width: 40px;
  height: 4px;
  transform: translateX(-50%);
  border-radius: var(--radius-pill);
  background: var(--border-strong);
}
.cat-sort-sheet__h {
  margin: 0;
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink);
}
.cat-sort-sheet__x {
  flex: none;
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  border-radius: var(--radius-md);
}
.cat-sort-sheet__x:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.cat-sort-opt {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  min-height: 52px;
  padding: 12px 20px;
  border: none;
  background: none;
  font: inherit;
  font-size: .9375rem;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}
.cat-sort-opt:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.cat-sort-opt.activa { font-weight: 700; color: var(--brand-hover); }
.cat-sort-opt__tick { opacity: 0; font-size: .85rem; color: var(--brand); }
.cat-sort-opt.activa .cat-sort-opt__tick { opacity: 1; }
@media (hover: hover) and (pointer: fine) {
  .cat-sort-opt:hover { background: var(--surface-2); }
}
@media (prefers-reduced-motion: reduce) {
  .cat-sort-overlay, .cat-sort-sheet { transition: none; }
}

/* «Ver N marcas más» / «Ver menos». Enlace, no botón con relleno: es un control
   de la lista, no una acción del filtro, y compitiendo con las píldoras leería
   como una marca más. */
.filter-mas {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  margin-top: 6px;
  padding: 0 2px;
  border: none;
  background: none;
  font: inherit;
  font-size: .78rem;
  font-weight: 700;
  color: var(--brand-hover);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.filter-mas[hidden] { display: none !important; }
.filter-mas:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 3px; }

/* Resumen de lo elegido en la cabecera del grupo. Nace de colapsar al elegir
   (sólo móvil): un grupo que se cierra sin decir QUÉ elegiste no resume, esconde.
   Sólo se pinta con el grupo cerrado — abierto, las opciones marcadas ya se ven
   y repetirlas arriba sería ruido. */
.filter-sel {
  display: none;
  margin-left: 6px;
  font-size: .72rem;
  font-weight: 600;
  color: var(--brand-hover);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 45%;
}
.filter-section:not(.open) .filter-sel { display: inline-block; vertical-align: middle; }
/* Con resumen a la vista, el conteo de opciones del grupo sobra: son dos cifras
   compitiendo en un renglón y la que importa es la elegida. */
.filter-section:not(.open) .filter-sel + .filter-opt-count,
.filter-section:not(.open) .filter-opt-count:has(+ .filter-sel) { display: none; }
