  /* **La letra es Figtree** (18/10/2026, elegida entre cuatro): redondeada,
     cercana y muy legible en el móvil, con cifras claras para los precios.
     **Servida desde aquí** (`/fuentes/`, vista `fuente`) y no desde Google
     Fonts, que vería la IP de cada visita. Es variable —un fichero para todos
     los pesos— y va partida en latín y latín ampliado (`unicode-range`), así
     que una página en castellano baja solo los 20 KB del primero.
     `font-display: swap`: hasta que llega se lee con la del sistema, nunca en
     blanco. Licencia OFL en `fuentes/OFL-figtree.txt`. */
  @font-face {
    font-family: "Figtree"; font-style: normal; font-weight: 300 900; font-display: swap;
    src: url("/fuentes/figtree-5.2.10-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
      U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
      U+FEFF, U+FFFD;
  }
  @font-face {
    font-family: "Figtree"; font-style: normal; font-weight: 300 900; font-display: swap;
    src: url("/fuentes/figtree-5.2.10-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
      U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
      U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  /* **El gris y el verde, un pelo más oscuros que al principio** (17/10/2026,
     repaso de accesibilidad con axe y Lighthouse). Eran `#666f7d` y `#17803d`,
     y se quedaban justo por debajo del 4,5 de contraste que pide el texto
     normal en tres sitios: el gris sobre `--chip` («sin foto», 4,48), el verde
     sobre el fondo azulado (la bajada del informe, 4,49) y sobre su propio
     tinte (el aviso de bajadas de la lista, 3,97). Medido después: 4,8 y 5,4
     en esos fondos y 4,7 en el peor del verde. A la vista no se distingue. */
  :root {
    --bg: #f6f7f9; --card: #fff; --text: #14181f; --muted: #616a78;
    --line: #e2e6eb; --accent: #0b6bcb; --ok: #147236; --err: #b3261e;
    --best: #147236; --chip: #eef1f5; --up: #b3261e; --down: #147236;
    /* El ámbar del «solo tienda» del selector. Faltaba, así que esa marca
       llevaba su color escrito a mano y se quedaba fuera del tema. */
    --aviso: #a16207;
    /* **El marino de las bandas oscuras** —la barra de arriba, el pie y la
       portada de la bienvenida—. Sale de tomar como referencia tres webs con el
       mismo lenguaje (bandas de azul profundo a todo el ancho alternando con
       claras, titulares grandes, botones en pastilla): el azul de siempre sigue
       siendo el de los controles, y este es el fondo sobre el que se apoya. */
    --marino: #0a1a4a; --sobre-marino: #eef3ff; --sobre-marino-tenue: #a8b6dc;
    /* `#2c6bf0` y no `#3b7bff`: con el blanco de «Comparar mi lista» encima
       daba 3,84 de contraste, y a 14,5 px hacen falta 4,5. Ahora, 4,7. */
    --electrico: #2c6bf0;
    /* **El texto encima del azul de los controles.** Blanco en el tema claro;
       en el oscuro el azul se aclara (#6aa9f0) y el blanco encima se queda en
       2,5 de contraste —lo mínimo para leerse bien es 4,5—, así que allí va
       oscuro. Una variable y no un color escrito en cada botón: había nueve. */
    --sobre-accent: #fff;
    /* **El degradado de los botones** (18/10/2026): del azul de siempre a un
       azul más violáceo. Con el blanco encima, 5,3 y 6,1 de contraste. */
    /* Bastante distintos para que se vea en un móvil: del azul de siempre a
       violeta. Con el blanco encima, 5,3 y 7,6. */
    --boton-a: #0b6bcb; --boton-b: #5a3bd6; --boton-sombra: rgba(70, 70, 210, .5);
  }
  :root { --bg: #eef3fb; }
  @media (prefers-color-scheme: dark) {
    :root {
      --marino: #0b1433; --electrico: #6aa9f0;
      --bg: #14171c; --card: #1c2027; --text: #e7eaee; --muted: #9aa3b0;
      --line: #2b313a; --accent: #6aa9f0; --ok: #4ade80; --err: #f87171;
      --best: #4ade80; --chip: #252a32; --up: #f87171; --down: #4ade80;
      --aviso: #fbbf24;
      --sobre-accent: #0b1730;
      --boton-a: #6aa9f0; --boton-b: #b19cff; --boton-sombra: rgba(140, 150, 255, .35);
    }
  }
  /* **Las transiciones entre páginas las hace el navegador** (View Transitions).
     Cambiar de página era un parpadeo en blanco: la página entera desaparecía y
     la nueva se pintaba de golpe, cabecera incluida, aunque fuera la misma. Con
     `@view-transition` el navegador hace un fundido corto **y la cabecera, que
     lleva nombre propio, se queda quieta**: lo único que cambia es el contenido.

     Tres decisiones:
     - **Sin JavaScript para navegar.** Es una regla de CSS que un navegador que
       no la conoce se salta entera —el WebView de Chrome 74 de la aplicación
       incluido—: allí se navega como siempre. No hay nada que pueda romper la
       página, que es lo que importa en un sitio donde un `SyntaxError` la deja
       muerta (ver `test_js_viejo`).
     - **Corta: 0,15 s para salir y 0,22 para entrar.** Una transición que se nota
       como espera es peor que el parpadeo; esta se nota como suavidad.
     - **Quien pide menos movimiento no la tiene** (`prefers-reduced-motion`).
       Lo dice su sistema, y una animación que no ha pedido marea a algunas
       personas.
     - **Va a nivel raíz, fuera de cualquier `@media`.** Se escribió primero al
       lado del otro `prefers-reduced-motion` de este fichero, que vive **dentro
       del bloque del móvil** (el de 720 px), y la transición solo salía en el móvil:
       medido, a 720 px sí y a 721 no. Es el mismo tropiezo que tuvo el aviso de
       lo que se guarda. */
  @view-transition { navigation: auto; }
  body > header { view-transition-name: cabecera; }
  ::view-transition-old(root) { animation: 150ms ease-in both vt-salir; }
  ::view-transition-new(root) { animation: 220ms ease-out both vt-entrar; }
  @keyframes vt-salir { to { opacity: 0; } }
  @keyframes vt-entrar { from { opacity: 0; transform: translateY(8px); } }
  @media (prefers-reduced-motion: reduce) {
    @view-transition { navigation: none; }
    ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) {
      animation: none !important;
    }
  }

  /* ------------------------------------------------------------------ *
   * **El movimiento de todas las páginas**, pedido a la vista del de la
   * portada. Cinco piezas, todas con un motivo:
   *
   * - **Al entrar, el contenido sube escalonado** (los bloques de la página,
   *   uno detrás de otro). La portada lleva su propia entrada y se excluye.
   * - **Las tarjetas que llegan entran**: en Buscar, solo las nuevas de cada
   *   tienda que contesta (`.nueva`, la pone `pintarGrupo`); en Ofertas, las
   *   primeras de la rejilla al cargar.
   * - **Los desplegables y el menú se abren deslizando**, que dice de dónde
   *   sale lo que aparece.
   * - **Lo que se pulsa responde**: las tarjetas se levantan y los botones se
   *   hunden un poco al apretarlos.
   * - **La línea del histórico se dibuja** de izquierda a derecha, que es el
   *   sentido del tiempo.
   *
   * Tres reglas: va **todo dentro de `prefers-reduced-motion: no-preference`**
   * —quien pide menos movimiento no tiene nada de esto, sin tener que
   * anularlo pieza a pieza—; las entradas son **`backwards`, nunca `both`**:
   * un `transform` que se queda puesto al acabar convierte al elemento en
   * referencia de lo que va `position: fixed` dentro (el selector de producto,
   * la lupa) y los descoloca; y **nada depende de JavaScript para verse**: son
   * animaciones de CSS que terminan solas. A nivel raíz, fuera de cualquier
   * otro `@media`, por lo que costó la transición entre páginas. */
  @keyframes mv-subir { from { opacity: 0; transform: translateY(16px); } }
  @keyframes mv-tarjeta { from { opacity: 0; transform: translateY(18px) scale(.97); } }
  @keyframes mv-desplegar { from { opacity: 0; transform: translateY(-6px); } }
  @keyframes mv-menu { from { transform: translateX(-100%); } }
  @keyframes mv-velo { from { opacity: 0; } }
  @keyframes mv-trazar { from { stroke-dashoffset: 1; } }
  @media (prefers-reduced-motion: no-preference) {
    main.wrap > :not(.inicio):not(script):not(style) {
      animation: mv-subir .55s cubic-bezier(.2, .7, .2, 1) backwards; }
    main.wrap > :nth-child(2) { animation-delay: .06s; }
    main.wrap > :nth-child(3) { animation-delay: .12s; }
    main.wrap > :nth-child(4) { animation-delay: .18s; }
    main.wrap > :nth-child(5) { animation-delay: .24s; }
    main.wrap > :nth-child(n+6) { animation-delay: .3s; }

    article.nueva { animation: mv-tarjeta .5s cubic-bezier(.2, .7, .2, 1) backwards;
      animation-delay: calc(var(--i, 0) * 35ms); }
    /* Solo las de la rejilla (Buscar y Ofertas): las fichas de la lista llevan
       dentro paneles que se colocan a mano, y un `transform` puesto los
       descolocaría. Y solo con ratón: en táctil el `:hover` se queda pegado. */
    @media (hover: hover) {
      #grid article { transition: transform .2s ease, box-shadow .2s ease; }
      #grid article:hover { transform: translateY(-4px);
        box-shadow: 0 14px 30px -16px rgba(11, 40, 90, .45); }
    }

    details[open] > .ddpanel { animation: mv-desplegar .18s ease-out; }
    #menu:not([hidden]) { animation: mv-menu .26s cubic-bezier(.2, .7, .2, 1); }
    #menufondo:not([hidden]) { animation: mv-velo .26s ease-out; }
    #fotogrande:not([hidden]) { animation: mv-velo .2s ease-out; }
    #fotogrande img { animation: mv-tarjeta .28s cubic-bezier(.2, .7, .2, 1); }

    button, .chip-btn, .ir { transition: transform .12s ease, filter .15s ease,
      background-color .15s ease, border-color .15s ease; }
    button:active, .chip-btn:active, .ir:active { transform: scale(.96); }

    svg .trazo { stroke-dasharray: 1; animation: mv-trazar 1.1s cubic-bezier(.4, 0, .2, 1) backwards; }
  }
  * { box-sizing: border-box; }
  body {
    margin: 0; background: var(--bg); color: var(--text);
    font: 15px/1.5 "Figtree", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  }
  /* Los controles no heredan la letra por su cuenta: sin esto, los botones y
     los campos seguían con la del sistema. */
  button, input, select, textarea { font-family: inherit; }
  a { color: var(--accent); }
  /* Los rótulos van en minúscula y con peso, no en mayúsculas espaciadas: es
     el rasgo más repetido de las páginas hechas con plantilla, y en un nombre
     de tienda o de producto la mayúscula sostenida además se lee peor. */
  h1, h2, h3 { line-height: 1.2; letter-spacing: -0.01em; }
  /* El foco de teclado se ve siempre, en todo lo que se pulsa. */
  a:focus-visible, button:focus-visible, summary:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 2px; }
  /* `hidden` lo pone el navegador con `display: none` en su hoja de estilo, que
     pierde contra cualquier regla nuestra con `display:` — y basta un selector
     de id para ganarle. Sin esto, ocultar `#compra` o `#avisos` no hacía nada:
     se quedaban visibles debajo de lo que debía sustituirlos, y solo pasaba
     desapercibido cuando estaban vacíos y no ocupaban alto. */
  [hidden] { display: none !important; }
  /* **Solo para el lector de pantalla.** El título de una página que ya se ve
     por su cabecera —el «Buscar» de la barra, el campo con su ejemplo— pero a
     la que le faltaba un `<h1>`: quien navega por encabezados no tenía por
     dónde empezar. Es la técnica de siempre: fuera de la vista, no fuera del
     árbol de accesibilidad (`display: none` lo quitaría de los dos). */
  .oculto-visual {
    position: absolute !important; width: 1px; height: 1px; padding: 0;
    margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap;
    border: 0;
  }
  /* ------------------------------------------------------------------ *
   * El menú de secciones, a la izquierda.
   *
   * Antes eran enlaces sueltos en la barra: con seis ya se partía en dos
   * líneas en un móvil, y la barra es fija —o sea, cuarenta píxeles menos de
   * pantalla para siempre, justo en la que se usa dentro del súper—. Y las
   * opciones de cada sección no tenían dónde vivir salvo en filas de botones
   * que crecían sin parar.
   *
   * Ahora cada sección es un punto del menú y **cuelga de él lo suyo**.
   * ------------------------------------------------------------------ */
  /* El «— Sección» del título. Se calla si no hay nada que decir, y en una
     pantalla estrecha se recorta antes que el nombre del sitio. */
  .seccion-actual { color: var(--muted); font-weight: 600; font-size: 14px;
    margin-left: 4px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    min-width: 0; }
  #menubtn {
    background: none; border: 0; color: inherit; font-size: 22px;
    cursor: pointer; padding: 2px 8px; line-height: 1; order: -1;
  }
  /* **Ojo con `inset` y con `min()`: el WebView de la aplicación no los
     conoce.** Esto abría en cualquier navegador de escritorio y **no abría en
     la app**, que es donde más falta hace. El WebView del sistema puede ser
     Chrome 74 —el del emulador de Android 10 lo es— y ahí `inset` (Chrome 87)
     se ignora, así que el velo queda de 0×0; y `min()` (Chrome 79) invalida la
     declaración entera, así que el panel se queda sin ancho y encogido a su
     contenido. Se pulsaba el ☰, el panel se abría de verdad… y no se veía.
     Las dos se escriben a la antigua, que hace exactamente lo mismo. */
  #menufondo { position: fixed; top: 0; right: 0; bottom: 0; left: 0;
    background: rgba(0,0,0,.35); z-index: 60; }
  #menu {
    position: fixed; top: 0; left: 0; bottom: 0; width: 19rem; max-width: 86vw;
    background: var(--card); border-right: 1px solid var(--line);
    z-index: 61; overflow-y: auto; padding: 10px 0 24px;
    /* El panel ocupa la altura entera, así que su primera sección caería
       debajo de la barra de estado igual que el ☰. */
    padding-top: calc(10px + env(safe-area-inset-top, 0px));
    padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
    box-shadow: 2px 0 12px rgba(0,0,0,.12);
  }
  #menu .seccion {
    display: block; padding: 11px 16px; font-weight: 600; font-size: 15px;
    color: var(--text); text-decoration: none; border: 0; background: none;
    width: 100%; text-align: left; cursor: pointer; font-family: inherit;
  }
  #menu .seccion[aria-current="page"] { color: var(--accent); }
  /* Lo de la cuenta que en un ordenador está en la barra y en el móvil se muda
     aquí (ver la cabecera en una sola línea, en el bloque de 720 px). */
  #menu .solomovil { display: none; }
  /* Una sección que en vez de llevar a un sitio **abre lo suyo**: se pinta
     igual que las demás para que se lea como una más. */
  #menu details.seccion { padding: 0; }
  /* **Sin forma de botón.** `.dd > summary` la pinta como pastilla —borde,
     fondo y esquinas redondas— porque en las filas de la página eso es lo que
     hace falta para que se vea pulsable. En el menú no: ahí es **una sección
     más**, y con su recuadro se leía como si fuera de otra categoría que
     «Buscar» o «Mi lista». Lo que dice que se abre es el ▾, no una caja. */
  #menu details.seccion > summary {
    padding: 11px 16px; font-weight: 600; font-size: 15px; cursor: pointer;
    list-style: none; border: 0; background: none; border-radius: 0;
  }
  #menu details.seccion[open] > summary,
  #menu details.seccion > summary:hover { border: 0; }
  #menu details.seccion > summary::-webkit-details-marker { display: none; }
  #menu details.seccion > summary::after { content: " ▾"; color: var(--muted); }
  #menu details.seccion[open] > summary::after { content: " ▴"; }
  #menu details.seccion > summary:hover { background: var(--bg); }
  #menu details.seccion .ddpanel { position: static; box-shadow: none;
    border: 0; background: none; padding: 0 10px 8px 16px; }
  #menu .seccion:hover { background: var(--bg); }
  /* Lo de cada sección va debajo, sangrado: se lee como «dentro de». */
  #menu .menuops { padding: 0 10px 8px 16px; }
  #menu .menuops:empty { display: none; }
  #menu .menuops > * { display: block; width: 100%; margin: 3px 0;
    text-align: left; font-size: 13px; }
  /* **Lo de dentro de una sección tampoco lleva recuadro.** Los botones vienen
     de las filas de la página, donde la pastilla es lo que los hace verse
     pulsables; aquí son opciones de una lista y el borde solo mete ruido. Se
     quita en los dos huecos: el de una sección normal (`.menuops`) y el del
     desplegable de «La compra» (`.ddpanel`). */
  #menu .menuops > *, #menu .ddpanel > * {
    border: 0; background: none; border-radius: 6px; padding: 8px 6px;
    color: var(--text); font-weight: 600;
  }
  #menu .menuops > *:hover, #menu .ddpanel > *:hover {
    background: var(--bg); color: var(--accent);
  }
  /* **Y un desplegable de dentro tampoco.** Un `.dd` trae la pastilla de las
     filas de la página —borde, fondo y esquinas redondas—, que allí es lo que
     lo hace verse pulsable; en el menú queda como un botón suelto en medio de
     una lista de opciones. Lo que dice que se abre es el ▾, igual que en «La
     compra». Hoy no la usa nadie —la trajo «Mis listas», que ya no existe— y
     se queda porque la regla es de aquí: el día que `alMenu` mueva otro
     `<details>`, sin ella se vería como de otra categoría. */
  #menu .menuops .dd > summary {
    display: block; width: 100%; border: 0; background: none;
    border-radius: 6px; padding: 8px 6px; text-align: left;
    font-size: 13px; color: var(--text); font-weight: 600;
  }
  #menu .menuops .dd > summary:hover { background: var(--bg); color: var(--accent); }
  #menu .menuops .dd > summary::after { content: " ▾"; color: var(--muted); }
  #menu .menuops .dd[open] > summary::after { content: " ▴"; }
  /* Las filas de «Mi lista»: el nombre y, debajo, cuántos productos tiene.
     Las pinta `Precios.pintarMisListas`, que es hoy el único sitio que las
     enumera: «Mis listas» era el otro y se quitó. */
  #menu #menulistas .meta {
    display: block; font-weight: 600; font-size: 11.5px; color: var(--muted);
  }
  #menu .ddvacio { padding: 8px 8px 4px; color: var(--muted); font-size: 12.5px; }
  #menu .menuops .ddpanel {
    position: static; box-shadow: none; border: 0; background: none;
    min-width: 0; padding: 0 0 4px 10px; max-height: 40vh;
  }
  /* **Y el que está puesto se sigue viendo.** Lo marcaba
     `#modos button[aria-pressed="true"]` con el borde en azul; al traerse los
     dos modos al menú esa regla dejó de alcanzarlos y no había forma de saber
     cuál estaba elegido. Aquí se marca sin caja: color y peso. */
  #menu [aria-pressed="true"] { color: var(--accent); font-weight: 800; }
  #menu .sep { height: 1px; background: var(--line); margin: 8px 12px; }
  #menu .titulo { padding: 6px 16px 10px; font-weight: 700; font-size: 13px;
    color: var(--muted); }

  /* **El pie de una tarjeta de producto, y va aquí porque lo usan dos páginas.**
     Estuvo solo en la de búsqueda y en la de ofertas las tarjetas salían sin
     él: «📈 histórico» y «Abrir ↗» pegados, leyéndose como un solo enlace. Es
     el mismo caso que el CSS de los desplegables. */
  .foot { margin-top: auto; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
  /* La marca de «reparte aquí» en cada fila del selector, y el resumen del
     panel del código postal. El «no se sabe» no se pinta: un hueco no puede
     parecerse a un no. */
  /* **Y van con las variables, no con un color escrito a mano.** Estuvieron
     con `#15803d`, `#b91c1c` y `#a16207` —los verdes y rojos del tema claro—
     y **el tema oscuro no los tocaba**: medido sobre el fondo de verdad, el
     contraste era **3,26 · 2,53 · 3,32** donde a ese tamaño hacen falta 4,5.
     Un color fijo en una página que tiene dos temas solo puede estar bien en
     uno de los dos. De paso no eran ni los mismos verde y rojo que usa el
     resto (`--ok` es `#147236` y `--err` `#b3261e`): dos parejas casi iguales
     para decir lo mismo. */
  .ddrow .cob { margin-left: auto; font-size: 11px; font-weight: 700; }
  .ddrow .cob.si { color: var(--ok); }
  .ddrow .cob.no { color: var(--err); }
  .ddrow .cob.med { color: var(--aviso); }
  /* **Apagada con el gris del texto secundario, no con transparencia.** Con
     `opacity: .55` el nombre quedaba a 3,95 de contraste sobre el panel, y una
     tienda que no te reparte se sigue pudiendo marcar: hay que poder leerla. */
  .ddrow.nozona .nm { color: var(--muted); }
  #cpcob { margin: 6px 0 0; font-size: 12px; color: var(--muted, #666); }
  #cpcob .hecho { color: var(--ok); font-weight: 600; }
  .wrap { max-width: 1280px; margin: 0 auto; }

  /* --- cabecera y navegación --- */
  /* ------------------------------------------------------------------ *
   * **La franja de arriba tiene que dejar sitio a la barra de estado.**
   *
   * Dentro de la aplicación la página se dibuja **de borde a borde**: el
   * proyecto de Android apunta a `targetSdkVersion 35`, y desde Android 15 eso
   * ya no es opcional — el sistema deja la ventana debajo de la barra de
   * estado y de la de navegación. Como la cabecera es `sticky; top: 0`, el ☰
   * caía justo ahí: **se veía y no se podía pulsar**, porque ese trozo de
   * pantalla se lo queda el sistema. Desde un navegador no hay forma de verlo,
   * que es lo que hizo perder dos intentos.
   *
   * Se arregla con las variables del sistema (`env(safe-area-inset-*)`, de
   * Chrome 69) **y con `viewport-fit=cover` en el `viewport`**, sin el cual
   * valen cero y no hacen nada. El relleno normal va **antes**, así que donde
   * `env()` no se entienda la línea se ignora y queda el de siempre.
   * ------------------------------------------------------------------ */
  header {
    position: sticky; top: 0; z-index: 5; background: var(--card);
    border-bottom: 1px solid var(--line); padding: 0 20px;
    padding-left: calc(20px + env(safe-area-inset-left, 0px));
    padding-right: calc(20px + env(safe-area-inset-right, 0px));
  }
  nav { display: flex; align-items: baseline; gap: 18px; margin-bottom: 12px; }
  nav .brand { font-size: 17px; font-weight: 700; letter-spacing: -0.01em; text-decoration: none; color: var(--text); }
  /* **La barra va en marino, a todo el ancho, y los controles de cada página
     siguen en claro debajo.** El fondo se estira con una sombra recortada
     (`box-shadow` + `clip-path`) y no con márgenes negativos: así no mueve ni un
     píxel de la maquetación ni desborda la página por el ancho de la barra de
     desplazamiento, que es lo que hace `100vw`. Y los colores se cambian
     **redefiniendo las variables** dentro de la barra, no regla a regla: lo que
     cuelga de ella —enlaces, rótulo de la sección, el ☰— se pinta solo. */
  body > header nav {
    --text: var(--sobre-marino); --muted: var(--sobre-marino-tenue);
    --accent: #8fb8ff; --chip: rgba(255, 255, 255, .1);
    background: var(--marino); color: var(--text);
    box-shadow: 0 0 0 100vmax var(--marino); clip-path: inset(0 -100vmax);
    padding: 14px 0; padding-top: calc(14px + env(safe-area-inset-top, 0px));
    margin-bottom: 0;
  }
  body > header nav .links a.destacado {
    background: #fff; color: var(--marino); border-radius: 999px; }
  /* Lo que cada página pone en la cabecera (el buscador, la lista) va en su
     propia caja, y sin nada dentro desaparece: si no, quedaría una franja
     blanca vacía debajo de la barra. */
  .cabecera-extra { padding: 12px 0 16px; }
  .cabecera-extra:empty { display: none; }
  /* El logo, a la altura del texto. `vertical-align` y no un `flex` en el
     enlace: la marca envuelve en dos líneas en un móvil estrecho, y con `flex`
     el icono se quedaría arriba en vez de junto a la primera palabra. */
  nav .brand img { width: 22px; height: 22px; vertical-align: -4px; margin-right: 2px; }
  /* **Centrados, no estirados.** Sin esto los hijos se estiran a lo alto de la
     fila (`align-items: stretch`): el enlace del nombre quedaba de 33 px y el
     botón de «Salir», que va dentro de su `<form>`, de 27 y pegado arriba — y
     sus textos salían a distinta altura. Medido: 2 px en el móvil y 3 en el
     escritorio, lo justo para que se vea torcido y no se sepa por qué. */
  nav .links { display: flex; align-items: center; gap: 4px; margin-left: auto; }
  nav .links a {
    padding: 6px 12px; border-radius: 7px; text-decoration: none;
    color: var(--muted); font-size: 14px; font-weight: 600;
  }
  nav .links a:hover { background: var(--chip); color: var(--text); }
  nav .links a[aria-current="page"] { background: var(--chip); color: var(--text); }
  nav .links a.destacado { color: var(--accent); }
  /* En escritorio, al final de la barra; en móvil sube a la línea del título.
     Con `order` y no moviéndolo en el HTML: es el mismo botón y la misma lógica. */
  /* **El pie también en marino**, a todo el ancho con la misma sombra recortada
     que la barra, y con las variables redefinidas para que la tabla de lo que
     se guarda y los enlaces se lean sobre oscuro sin tocar sus reglas. */
  .pie {
    --text: var(--sobre-marino); --muted: var(--sobre-marino-tenue);
    --accent: #8fb8ff; --line: rgba(255, 255, 255, .14); --chip: rgba(255, 255, 255, .08);
    --card: rgba(255, 255, 255, .04);
    background: var(--marino);
    box-shadow: 0 0 0 100vmax var(--marino); clip-path: inset(0 -100vmax);
    margin: 40px auto 0; padding: 30px 20px 26px; border-top: 0;
    color: var(--muted); font-size: 12.5px; line-height: 1.55;
    /* Y abajo, la barra de navegación: sin esto el último renglón queda
       debajo de ella y no se lee. */
    padding-bottom: calc(26px + env(safe-area-inset-bottom, 0px));
  }
  .pie p { margin: 0 0 8px; max-width: 62rem; }
  .pie b { color: var(--text); font-weight: 600; }
  .pie .tenue { opacity: .8; }
  .pie .guiapie { font-size: 14px; margin-bottom: 12px; }
  /* En un ordenador el apartado se lee como antes: título en negrita y el
     texto a continuación, en el mismo párrafo. */
  .pie .apartado { margin: 0 0 8px; max-width: 62rem; }
  .pie .apartado > summary { display: inline; list-style: none; cursor: default; }
  .pie .apartado > summary::-webkit-details-marker { display: none; }
  .pie .apartado > p { display: inline; margin: 0; }
  @media screen and (max-width: 720px) {
    .pie .apartado { margin: 0; border-top: 1px solid var(--line); }
    .pie .apartado > summary { display: block; padding: 10px 0; cursor: pointer;
      font-size: 13.5px; }
    .pie .apartado > summary::after { content: " ▾"; color: var(--muted); }
    .pie .apartado[open] > summary::after { content: " ▴"; }
    .pie .apartado > p { display: block; margin: 0 0 10px; }
    .pie .apartado + :not(.apartado) { margin-top: 12px; }
  }
  nav .instalar { order: 2; }
  nav .instalar {
    padding: 6px 12px; border-radius: 7px; border: 1px solid var(--accent);
    background: none; color: var(--accent); font-size: 13.5px; font-weight: 600;
    cursor: pointer;
  }
  nav .instalar:hover { background: var(--accent); color: var(--sobre-accent); }
  nav a.quien { text-decoration: none; }
  nav .quien {
    padding: 6px 4px 6px 12px; color: var(--muted);
    font-weight: 600; border-left: 1px solid var(--line); margin-left: 6px;
  }
  /* **El tamaño de la pareja se escribe una vez.** El nombre y «Salir» son lo
     mismo —quién eres y cómo te vas— y estaban puestos por separado: el de
     `.quien` decía 13 px y **no se aplicaba**, porque `nav .links a` le gana
     por especificidad, así que salía a 14 junto a un «Salir» de 13. Con el
     selector de clase delante sí manda, y los dos van del mismo tamaño por
     construcción.

     **Y el `line-height` también va en la pareja**, que es la trampa de
     siempre en esta página: un `<button>` usa `line-height: normal` y un
     enlace hereda el de la página (1,5), así que con la misma letra y el mismo
     relleno salían cajas de 32 y de 27 px. Centrados ya se leían a la misma
     altura, pero el fondo del `:hover` delataba la diferencia. */
  nav .links a.quien,
  nav .salir button {
    /* **La misma letra, el mismo tamaño y el mismo interlineado.** Un
       `<button>` trae la tipografía del sistema si no se le dice otra cosa, y
       con eso quedaban 30 px contra 27 aun teniendo los dos 13 px y el mismo
       relleno: la letra del sistema mide distinto. */
    font-family: inherit; font-size: 13px; line-height: normal;
  }
  nav .salir { display: inline; }
  nav .salir button {
    background: none; border: 0; color: var(--muted);
    font-weight: 600; padding: 6px 8px; cursor: pointer;
  }
  nav .salir button:hover { color: var(--err); }

  /* --- formulario --- */
  form.search { display: flex; gap: 8px; flex-wrap: wrap; }
  input[type=search], select {
    padding: 11px 14px; font-size: 16px; border: 1px solid var(--line);
    border-radius: 8px; background: var(--bg); color: var(--text);
  }
  input[type=search] { flex: 1 1 260px; min-width: 0; border-radius: 999px;
    padding-left: 18px; background: var(--card); }
  input[type=search]:focus, select:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
  /* **Los botones, con degradado y un destello al pasar** (18/10/2026, elegido
     entre cuatro). Todo va en el `background` del propio botón —dos capas: el
     destello, una franja blanca en diagonal que espera fuera, y debajo el
     degradado—, así que **los que ya tienen su propio fondo no lo heredan**: la
     ✕, el plegador o las pastillas claras declaran `background` y lo pisan
     entero. Al pasar, la franja cruza el botón; con «reducir movimiento» no se
     mueve nada (se ilumina un poco, sin animación). */
  button {
    padding: 11px 20px; font-size: 15px; font-weight: 600; cursor: pointer;
    border: 0; border-radius: 999px; color: var(--sobre-accent);
    /* Tres capas: el destello que cruza, un brillo fijo en la mitad de arriba
       —lo que se ve en un móvil sin tocar nada, donde no hay «pasar»— y el
       degradado. Y un reflejo fino en el borde de arriba. */
    background: linear-gradient(115deg, rgba(255, 255, 255, 0) 40%, rgba(255, 255, 255, .38) 50%,
                  rgba(255, 255, 255, 0) 60%) no-repeat,
                linear-gradient(180deg, rgba(255, 255, 255, .2) 0%, rgba(255, 255, 255, .06) 48%,
                  rgba(255, 255, 255, 0) 52%) no-repeat,
                linear-gradient(135deg, var(--boton-a), var(--boton-b));
    background-size: 250% 100%, 100% 100%, 100% 100%;
    background-position: 130% 0, 0 0, 0 0;
  }
  /* **La sombra de color, solo en los principales.** Un fondo propio pisa el
     degradado, pero no la sombra: el «Ocultar el buscador», que es un texto,
     salía con un halo azul. Así que va en una lista: enviar un formulario,
     buscar y «Ver resultados». */
  button[type=submit], form.search > button, #run, #cajonfiltros .cajonpie .ver {
    box-shadow: 0 8px 18px -8px var(--boton-sombra), inset 0 1px 0 rgba(255, 255, 255, .35);
  }
  @media (prefers-reduced-motion: no-preference) {
    button { transition: background-position .65s ease, box-shadow .2s ease, transform .12s ease; }
  }
  @media (hover: hover) {
    button:hover:not(:disabled) { background-position: -30% 0, 0 0, 0 0; }
    button[type=submit]:hover:not(:disabled), form.search > button:hover:not(:disabled),
    #run:hover:not(:disabled) { box-shadow: 0 10px 22px -8px var(--boton-sombra),
      inset 0 1px 0 rgba(255, 255, 255, .35); }
  }
  /* **Y al tocarlo**, que en un móvil no hay «pasar»: el destello lo cruza al
     pulsar. */
  button:active:not(:disabled) { background-position: -30% 0, 0 0, 0 0; }
  button:active:not(:disabled) { transform: translateY(1px); }
  button:disabled { opacity: .55; cursor: progress; }
  /* **La pastilla vive aquí y no en la lista, que es de donde venía.** Estaba
     definida solo en `lista.html` y **`base.html` la usa**: el botón «Dejar
     solo las N que tengo en mi zona» lo crea el panel del código postal, que
     sale en las tres páginas. Resultado medido: en «Mi lista» era una pastilla
     de **27 px**, y en «Buscar» y en «Ofertas» —sin regla que lo alcanzara—
     caía al `button` de arriba y salía **azul sólido de 56 px**, en dos
     líneas, en mitad de un panel de pastillas discretas. Es el mismo caso que
     el CSS de los desplegables, que también estuvo en una sola página. */
  .chip-btn {
    padding: 5px 12px; font-size: 12.5px; border-radius: 999px;
    background: var(--card); color: var(--text); border: 1px solid var(--line);
    font-weight: 600;
  }
  .chip-btn:hover { border-color: var(--accent); color: var(--accent); }
  /* **Y la fila del campo y su botón se dicen la altura, para los dos.** Es la
     misma pelea que la fila de añadir de la lista, que ya la tenía resuelta, y
     esta página nunca recibió la regla: el campo mide 42 px por su relleno y
     su tipografía de 16 —que no se baja, porque por debajo iOS hace zoom al
     escribir— y el `<button>`, con `line-height: normal` y la tipografía del
     sistema, se quedaba en **36**. Seis píxeles de escalón en una fila de dos
     cosas. */
  form.search > button, form.search > input[type=search] {
    height: 42px; box-sizing: border-box;
  }
  /* **La fila de controles va a una sola altura, y en las tres páginas.** La
     regla vivía en `lista.html`, así que la misma pastilla —«Supermercados: N
     de 21»— medía **31 px en la lista y 36 en Buscar y en Ofertas**.
     Y el `> #cp >` no es un adorno: el desplegable del código postal cuelga de
     un `<span id="cp">` con `display: contents`, que lo **saca de la
     maquetación pero no del parentesco que miran los selectores**, así que
     `#shops > .dd > summary` no lo alcanzaba nunca. En escritorio coincidía
     por casualidad y en un móvil se veía: 36 contra 31, desalineado con sus
     cinco vecinas. */
  #shops > button,
  #shops > .dd > summary,
  #shops > #cp > .dd > summary,
  #shops > #filtrar > .dd > summary,
  #shops > #filtrar > button {
    height: 31px; display: inline-flex; align-items: center;
    box-sizing: border-box; font-weight: inherit;
  }

  /* --- tarjetas --- */
  main { padding: 20px; }
  /* `.rejilla` es lo mismo que `#grid` y existe porque la página de búsqueda
     puede pintar **varias**, una por producto buscado. Comparten regla para que
     no se separen: dos rejillas con tamaños distintos serían dos diseños. */
  #grid, .rejilla { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
  article {
    background: var(--card); border: 1px solid var(--line); border-radius: 10px;
    padding: 12px; display: flex; flex-direction: column; gap: 8px;
  }
  /* `aspect-ratio` es de Chrome 88 y el WebView de la app puede ser muy
     anterior: sin alto el hueco de «sin foto» se queda plano. El relleno
     porcentual da el cuadrado en cualquiera, y donde sí se conoce manda
     `aspect-ratio`, que además no descuadra si el contenido crece. */
  .ph { padding-top: 100%; position: relative;
    display: grid; place-items: center; }
  .ph > * { position: absolute; top: 0; right: 0; bottom: 0; left: 0;
    margin: auto; }
  @supports (aspect-ratio: 1) {
    .ph { padding-top: 0; aspect-ratio: 1; }
    .ph > * { position: static; }
  }
  .ph img { max-width: 100%; max-height: 100%; object-fit: contain; }
  .ph .none { color: var(--muted); font-size: 12px; }
  /* Sin foto: el logo de la tienda, apagado, y debajo «sin foto». Van dentro
     de un `.hueco`, que es el hijo de `.ph` y se coloca como cualquier otro
     (también en el WebView viejo, sin `aspect-ratio`). */
  /* Las acciones de una tarjeta (📈 y «＋ lista») y el panel de destino: aquí y
     no en Buscar porque las tarjetas de Ofertas llevan las mismas. */
  .hist {
    font-size: 12px; text-decoration: none; color: var(--muted);
    border: 1px solid var(--line); border-radius: 6px; padding: 2px 7px;
    margin-left: auto;
  }
  .hist:hover { color: var(--accent); border-color: var(--accent); }
  .alista {
    font-size: 12px; background: none; color: var(--muted); cursor: pointer;
    border: 1px solid var(--line); border-radius: 6px; padding: 2px 7px;
    margin-left: auto;
  }
  /* Los dos van juntos a la derecha del €/unidad, en su propia caja. */
  .acciones { display: inline-flex; gap: 6px; margin-left: auto; align-items: center; }
  .acciones .hist, .acciones .alista { margin-left: 0; }
  .alista:hover { color: var(--accent); border-color: var(--accent); }
  .alista.hecho { color: var(--ok); border-color: var(--ok); cursor: default; }
  .alista.hecho.mal { color: var(--err); border-color: var(--err); }
  .hist + .alista { margin-left: 6px; }
  /* El panel de destino va `fixed` y colocado a mano, como la lupa y el selector
     de producto de la lista: dentro de la tarjeta lo recortaría su `overflow`. */
  .destino {
    position: fixed; z-index: 40; background: var(--card); color: var(--text);
    border: 1px solid var(--line); border-radius: 10px; padding: 8px;
    box-shadow: 0 8px 24px rgba(0,0,0,.18); min-width: 220px; max-width: 280px;
  }
  .destino .tit { margin: 0 0 6px; font-size: 12px; color: var(--muted);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .destino .nota { margin: 4px 0 0; font-size: 12px; color: var(--muted); }
  .destino button {
    display: block; width: 100%; text-align: left; background: none;
    color: inherit; border: 0; border-radius: 6px; padding: 6px 8px;
    font-size: 13px; cursor: pointer;
  }
  .destino button:hover { background: var(--chip); }
  .ph.sinfoto { background: var(--chip); border-radius: 8px; }
  .ph .hueco { display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: 8px; width: 100%; height: 100%; }
  .ph img.logohueco { width: 58%; max-height: 34%; object-fit: contain;
    opacity: .5; filter: grayscale(.4); border-radius: 6px; cursor: default; }
  .ph.sinfoto .none { font-size: 11px; }
  /* Los logos son capturas con fondo blanco: en el tema claro `multiply` se
     come ese blanco contra el gris del hueco. En el oscuro no, que ahí la caja
     blanca es lo que deja ver un logo negro. */
  .ph img.logohueco { mix-blend-mode: multiply; }
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .ph img.logohueco { mix-blend-mode: normal; }
  }
  :root[data-theme="dark"] .ph img.logohueco { mix-blend-mode: normal; }
  /* **La foto de una tarjeta se amplía al tocarla** (`#fotogrande`). En un
     móvil la tarjeta es de media pantalla y muchos productos solo se
     distinguen por el envase. Va a pantalla completa y no como la `#lupa` de
     la lista, que se coloca al lado de una línea: aquí no hay línea al lado,
     y en un teléfono no cabría. Se cierra tocando en cualquier sitio, con la ✕
     o con Escape. */
  article .ph img:not(.logohueco) { cursor: zoom-in; }
  #fotogrande {
    position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 70;
    background: rgba(0,0,0,.82); display: flex; flex-direction: column;
    align-items: center; justify-content: center; padding: 16px;
    cursor: zoom-out;
  }
  #fotogrande img {
    /* Ancho fijo y no el natural: las miniaturas de varias tiendas miden
       300 px, y a su tamaño la «ampliación» apenas crece en un móvil. */
    width: 100%; max-width: 600px; height: auto; max-height: 80vh;
    object-fit: contain; box-sizing: border-box;
    background: #fff; border-radius: 10px; padding: 8px;
  }
  #fotogrande p {
    color: #fff; font-size: 14px; line-height: 1.35; text-align: center;
    margin: 12px 0 0; max-width: 40em;
  }
  #fotogrande button {
    position: absolute; top: 12px; right: 12px; width: 40px; height: 40px;
    border-radius: 50%; border: 0; background: rgba(255,255,255,.9);
    color: #000; font-size: 20px; line-height: 1; cursor: pointer; padding: 0;
  }
  .shop {
    align-self: flex-start; font-size: 12px; font-weight: 700;
    color: var(--muted); display: inline-flex; align-items: center; gap: 6px;
  }
  /* El logo va sobre blanco: son capturas de su web con fondo claro, y en el
     tema oscuro sin esa caja varios desaparecían. */
  .shop .shoplogo { height: 18px; width: auto; max-width: 64px; object-fit: contain;
    background: #fff; border-radius: 4px; padding: 1px 3px; }
  .name { font-size: 13.5px; line-height: 1.35; }
  .name a { color: inherit; text-decoration: none; }
  .name a:hover { text-decoration: underline; }
  .price { font-size: 19px; font-weight: 700; font-variant-numeric: tabular-nums; }
  .price.na { font-size: 14px; font-weight: 400; color: var(--muted); }
  .meta { font-size: 12px; color: var(--muted); }
  .unit { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
  #msg { color: var(--muted); padding: 32px 0; text-align: center; }
  #bar {
    display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
    margin-bottom: 14px; color: var(--muted); font-size: 14px;
  }
  /* **«Ordenar», con la forma de las pastillas de al lado.** Con el aspecto
     del navegador era el único control cuadrado de la página, justo debajo de
     «Supermercados» y el 📍. La flecha se dibuja aparte porque `appearance:
     none` se lleva la del sistema; va en un gris que se lee en los dos temas,
     que dentro de un `url()` no se puede usar una variable. */
  #bar select { padding: 6px 30px 6px 13px; font-size: 13px; background-color: var(--card);
    border-radius: 999px; -webkit-appearance: none; appearance: none; cursor: pointer;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238a94a3' stroke-width='1.6'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 12px center; }
  #bar select:hover { border-color: var(--muted); }
  .spin {
    width: 10px; height: 10px; border: 2px solid var(--muted);
    border-top-color: transparent; border-radius: 50%;
    animation: spin .7s linear infinite; display: inline-block;
  }
  @keyframes spin { to { transform: rotate(360deg); } }

  /* Desplegables. <details> se encarga solo de abrir y cerrar. */
  .dd { position: relative; }
  .dd > summary {
    list-style: none; cursor: pointer; display: inline-flex; align-items: center;
    gap: 6px; padding: 5px 12px; border-radius: 999px; font-size: 12.5px;
    border: 1px solid var(--line); background: var(--card); user-select: none;
  }
  .dd > summary::-webkit-details-marker { display: none; }
  .dd > summary:hover { border-color: var(--muted); }
  .dd[open] > summary { border-color: var(--accent); }
  .dd > summary b { font-variant-numeric: tabular-nums; }
  .ddpanel {
    position: absolute; z-index: 20; top: calc(100% + 6px); left: 0;
    min-width: 268px; max-height: 62vh; overflow: auto; padding: 6px;
    background: var(--card); border: 1px solid var(--line); border-radius: 10px;
    box-shadow: 0 8px 28px rgba(0,0,0,.16);
  }
  /* **La cabecera del panel: de quién es y cómo se cierra.** Va pegada arriba
     (`sticky`) porque el panel se desplaza por dentro —caben veinte productos—
     y una ✕ que se fuera con el desplazamiento no serviría de nada justo
     cuando más falta hace. El fondo es obligatorio: sin él, lo que pasa por
     debajo se lee a través. */
  .ddcab {
    position: sticky; top: -6px; z-index: 1;
    display: flex; align-items: center; gap: 8px;
    margin: -6px -6px 4px; padding: 7px 8px;
    background: var(--card); border-bottom: 1px solid var(--line);
  }
  .ddquien { font-size: 12.5px; font-weight: 700; color: var(--muted);
             flex: 1 1 auto; min-width: 0;
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* 28×28: por debajo de eso no se acierta con el dedo, que es el aparato
     donde esta ✕ existe. */
  .ddcerrar {
    flex: none; width: 28px; height: 28px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 15px; line-height: 1;
    background: var(--chip); color: var(--text);
    border: 1px solid var(--line); border-radius: 8px;
  }
  .ddcerrar:hover { border-color: var(--muted); }
  .ddrow {
    display: flex; align-items: center; gap: 9px; padding: 7px 9px;
    border-radius: 7px; cursor: pointer; font-size: 13.5px;
  }
  .ddrow:hover { background: var(--chip); }
  .ddrow input { margin: 0; cursor: pointer; accent-color: var(--accent); }
  .ddrow .nm { flex: 1; }
  /* El asa de arrastrar. **`touch-action: none` no es cosmético**: sin ella el
     navegador se queda el gesto para hacer scroll y en un móvil la fila no se
     mueve — el panel se desplaza bajo el dedo y no pasa nada más. */
  .ddrow .asa {
    cursor: grab; color: var(--muted); font-size: 15px; line-height: 1;
    padding: 2px 1px; touch-action: none; user-select: none;
  }
  .ddrow.arrastrando { background: var(--chip); opacity: .75; }
  /* **Los filtros de una búsqueda** (`Precios.montarFiltros`). Con alguno
     puesto la pastilla va en el azul de lo abierto: escondido sin que se vea
     que hay algo escondiendo es la peor forma de filtrar. */
  .filtrosdd.activo > summary { border-color: var(--accent); color: var(--accent); font-weight: 600; }
  /* **Los filtros salen en un cajón desde la izquierda** (`Precios.cajonFiltros`),
     pedido así: todos a la vista a la vez y **sin mover lo de debajo**. Era un
     desplegable que en el móvil empujaba la página —abrirlo mandaba los
     resultados media pantalla abajo— y en el ordenador flotaba donde cupiera.
     El cajón va por encima, con su velo, como el menú ☰; y a la antigua
     (`top/right/bottom/left`, `width` + `max-width`) por el WebView de la app. */
  #cajonfondo { position: fixed; top: 0; right: 0; bottom: 0; left: 0;
    background: rgba(0,0,0,.35); z-index: 62; }
  #cajonfiltros {
    position: fixed; top: 0; left: 0; bottom: 0; width: 24rem; max-width: 90vw;
    background: var(--card); border-right: 1px solid var(--line); z-index: 63;
    display: flex; flex-direction: column; box-shadow: 2px 0 14px rgba(0,0,0,.16);
    padding-top: env(safe-area-inset-top, 0px);
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  #cajonfiltros .cajoncab { display: flex; align-items: center; gap: 8px;
    padding: 12px 14px; border-bottom: 1px solid var(--line); flex: none; }
  #cajonfiltros .cajoncab h2 { margin: 0; font-size: 17px; flex: 1 1 auto; }
  /* Lo de dentro se desplaza solo, y sin arrastrar a la página de detrás. */
  #cajonfiltros .cajoncuerpo { flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain;
    padding: 12px 14px 16px; display: flex; flex-direction: column; gap: 14px; }
  #cajonfiltros .cajonpie { flex: none; display: flex; gap: 8px; padding: 10px 14px;
    border-top: 1px solid var(--line); }
  #cajonfiltros .cajonpie .ver { flex: 1 1 auto; }
  /* Dentro del cajón el panel deja de flotar: es el cuerpo. */
  #cajonfiltros .ddpanel.filtros { position: static; width: auto; max-width: none;
    max-height: none; overflow: visible; padding: 0; border: 0; box-shadow: none;
    background: none; display: flex; flex-direction: column; gap: 12px; animation: none; }
  /* Una sección por filtro (Ropa), con su título y sus opciones abiertas. */
  #cajonfiltros .fgrupo { border-bottom: 1px solid var(--line); padding-bottom: 12px; }
  #cajonfiltros .fgrupo:last-child { border-bottom: 0; }
  #cajonfiltros .fgrupo > h3 { margin: 0 0 6px; font-size: 14px; }
  #cajonfiltros .fgrupo > h3 .cuantos { color: var(--accent); font-weight: 600; }
  /* Una lista larga (marcas, tipos) se desplaza dentro de su sección: así las
     demás siguen a la vista en vez de quedar treinta pantallas más abajo. */
  #cajonfiltros .fgrupo .flista2 { max-height: 15rem; overflow-y: auto; }
  /* Lo que se trae de la página (`seccion`): un control por línea. */
  #cajonfiltros .fmovidos { display: flex; flex-direction: column; gap: 8px; font-size: 13.5px; }
  #cajonfiltros .fmovidos > label { display: flex; align-items: center; gap: 8px;
    flex-wrap: wrap; color: var(--text); }
  #cajonfiltros .fmovidos select { padding: 6px 8px; font-size: 14px; border: 1px solid var(--line);
    border-radius: 8px; background: var(--card); color: var(--text); flex: 1 1 10em; }
  @media (prefers-reduced-motion: no-preference) {
    #cajonfiltros:not([hidden]) { animation: mv-menu .26s cubic-bezier(.2, .7, .2, 1); }
    #cajonfondo:not([hidden]) { animation: mv-velo .26s ease-out; }
  }
  @media print { #cajonfiltros, #cajonfondo { display: none !important; } }
  .filtros .frango { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 13px; }
  .filtros .ftit { font-weight: 600; color: var(--text); }
  .filtros .fcampo { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--muted); }
  .filtros .fcampo input { padding: 5px 8px; font-size: 14px; border: 1px solid var(--line);
    border-radius: 8px; background: var(--card); color: var(--text); width: 5.5em; }
  .filtros .fsin { flex-direction: column; align-items: stretch; gap: 4px; }
  .filtros .fsin input { width: auto; }
  /* Las listas de varias opciones: título, buscador si son muchas y casillas
     que se desplazan dentro de su caja. */
  .filtros .fmulti { display: flex; flex-direction: column; gap: 6px; font-size: 13px; }
  .filtros .fmulti .flista2 { max-height: 15rem; overflow-y: auto; border: 1px solid var(--line);
    border-radius: 8px; padding: 2px 6px; }
  /* Con su altura, que si no hereda la del buscador grande de la página. */
  #cajonfiltros .filtros .fmulti .buscafiltro { height: 34px; min-height: 0; flex: none;
    box-sizing: border-box; padding: 5px 8px; font-size: 14px; border: 1px solid var(--line);
    border-radius: 8px; background: var(--card); color: var(--text); }
  .filtros .flista select { padding: 5px 8px; font-size: 14px; border: 1px solid var(--line);
    border-radius: 8px; background: var(--card); color: var(--text); max-width: 14em; }
  .filtros .ddrow { padding: 4px 2px; font-size: 13px; }
  .filtros .fquitar { align-self: flex-start; }
  .ocultos { color: var(--muted); }
  #msg .chip-btn { margin-left: 8px; }
  .ddrow.arrastrando .asa { cursor: grabbing; }

  /* **Plegar los controles en el móvil.** La pantalla de un teléfono se la come
     el buscador: el formulario, el selector de supermercados, el código postal
     y sus botones ocupan media pantalla **antes** del primer producto, y eso
     es justo lo que se mira. Se puede plegar para dejarles sitio.

     El botón **solo existe en móvil**, y `.plegado` también: en un ordenador
     sobra el espacio y esconder los controles sería quitar por nada. Por eso
     la regla vive dentro del `@media` — así un estado guardado en el móvil no
     deja medio escritorio en blanco. */
  .plegador {
    display: none; align-items: center; gap: 6px; margin: 0 0 8px;
    background: none; border: 0; padding: 4px 0; color: var(--muted);
    font: inherit; font-size: 13px; cursor: pointer;
  }
  .plegador:hover { color: var(--accent); }
  /* La fila del plegador: él a la izquierda y lo que se queda a la vista al
     plegar —«Buscar precios»— **pegado a la derecha**, que es donde no estorba
     y donde cae el pulgar. Es una fila propia y no el flujo del bloque porque
     así se alinean de verdad: sueltos, uno es inline y el otro un botón, y
     coincidían de pura casualidad. Vacía no ocupa nada. */
  .filaplegador { display: flex; align-items: center; gap: 8px; }
  /* **Del tamaño del plegador, no del suyo.** En la fila de añadir es la acción
     principal y se pinta grande (42 px); aquí es el compañero de un rótulo de
     13 px, y con su tamaño de siempre quedaba una mancha azul del doble de
     alto que el texto de al lado. */
  .filaplegador > .plegasiempre {
    margin-left: auto; margin-bottom: 8px;
    padding: 0 12px; font-size: 13px; border-radius: 8px; width: auto;
  }
  /* **La altura se escribe, y para los dos.** Es la misma pelea que la fila de
     añadir: un `<button>` usa `line-height: normal` y la tipografía del
     sistema, y el plegador hereda las de la página, así que con el mismo
     relleno salen 28 y 25 — perseguirlo con `padding` no converge. Se dice una
     vez para ambos y se acabó. */
  .filaplegador > .plegador,
  .filaplegador > .plegasiempre {
    height: 28px; min-height: 0; box-sizing: border-box; line-height: 1;
  }
  /* La pastilla «Filtros» también se queda a la vista al plegar: es un
     `<details>`, así que lo que mide 28 px es su `<summary>` y no la caja. */
  .filaplegador > .plegasiempre ~ .plegasiempre { margin-left: 0; }
  .filaplegador > .plegador { flex: none; white-space: nowrap; }
  .filaplegador > #filtrar > .dd:first-child { margin-left: auto; margin-bottom: 8px; }
  .filaplegador > .dd { flex: 0 0 auto; }
  .filaplegador > .dd.plegasiempre { height: auto; padding: 0; border-radius: 0; }
  .filaplegador > .dd.plegasiempre > summary { height: 28px; box-sizing: border-box;
    display: inline-flex; align-items: center; font-size: 13px; }
  .plegador .flecha { transition: transform .15s; }
  .plegador[aria-expanded="false"] .flecha { transform: rotate(-90deg); }

  /* La ✕ de cerrar un aviso. Va dentro de la caja y arriba a la derecha, que es
     donde se busca, y con hueco reservado para que no se monte sobre el texto. */
  .concerrar { position: relative; padding-right: 30px !important; }
  .cerrar-aviso {
    position: absolute; top: 4px; right: 4px; width: 24px; height: 24px;
    display: inline-flex; align-items: center; justify-content: center;
    background: none; border: 0; border-radius: 6px; padding: 0;
    color: var(--muted); font-size: 16px; line-height: 1; cursor: pointer;
  }
  .cerrar-aviso:hover { color: var(--err); background: var(--bg); }
  #cpdd .ddpanel { width: 20rem; padding: .6rem .7rem; }
  #cpdd .cpfila { display: flex; gap: .4rem; }
  #cpdd input {
    flex: 1; min-width: 0; padding: .35rem .5rem; font: inherit;
    font-variant-numeric: tabular-nums;
    border: 1px solid var(--line); border-radius: 6px;
  }
  #cpdd p { margin: .5rem 0 0; font-size: .82rem; color: var(--muted); line-height: 1.4; }
  #cpdd p.mal { color: var(--bad); }
  .ddrow .n { color: var(--muted); font-variant-numeric: tabular-nums; font-size: 12px; }
  .ddrow .slow { color: var(--muted); font-size: 11px; }
  /* Cuánto tardó esa tienda, o «de hoy» si salió de lo ya buscado. Vivía en
     el chip de la búsqueda; al quitarlos se mudó aquí. */
  .ddrow .t { color: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
  .ddrow .t[title] { cursor: help; border-bottom: 1px dotted var(--line); }
  .ddrow.err .n { color: var(--err); }
  .ddsep { height: 1px; background: var(--line); margin: 5px 3px; }

  /* --- móvil ------------------------------------------------------------
     Lo que cambia no es solo el tamaño: en una pantalla estrecha un panel
     flotante se sale por el borde y no hay forma de leerlo entero, así que
     los desplegables de cabecera pasan a ocupar su sitio en el flujo. */
  /* **El aviso de lo que se guarda.** Abajo y fijo, que es donde se busca y
     donde no tapa lo que se viene a hacer. Nada de `inset` ni de `min()`: el
     WebView que puede llevar un teléfono no los entiende y la regla entera se
     ignora — con `inset` el velo del menú se quedó de 0×0 y nadie lo vio. */
  #galletas {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
    background: var(--card); border-top: 1px solid var(--line);
    box-shadow: 0 -6px 24px rgba(0,0,0,.12);
    padding: 12px 16px;
    /* El relleno normal va **antes** del `calc()`: donde `env()` no se
       entienda, la línea se ignora y queda el de siempre. Sin esto, en la
       aplicación el botón cae debajo de la barra del sistema. */
    padding-bottom: 12px;
    padding-bottom: calc(12px + env(safe-area-inset-bottom));
    display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  }
  #galletas p { margin: 0; flex: 1 1 320px; font-size: 12.5px; color: var(--muted); }
  #galletas p b { color: var(--text); }
  #galletas .botones { display: flex; align-items: center; gap: 10px; margin-left: auto; }
  #galletas a { font-size: 12.5px; color: var(--accent); }
  #galletas button { white-space: nowrap; }
  #quesequeda { margin-top: 10px; }
  /* El relleno le da 24 px de alto, lo mínimo para acertar con el dedo: sin
     él medía 19 y quedaba pegado al apartado de encima (Lighthouse,
     «target-size», en todas las páginas del móvil). */
  #quesequeda > summary { cursor: pointer; font-size: 12.5px; color: var(--accent);
    padding: 3px 0; }
  #quesequeda table { border-collapse: collapse; margin: 10px 0 0; font-size: 11.5px; }
  #quesequeda th, #quesequeda td {
    text-align: left; vertical-align: top; padding: 5px 10px 5px 0;
    border-bottom: 1px solid var(--line); color: var(--muted);
  }
  #quesequeda th { color: var(--text); font-weight: 600; }
  #quesequeda code { font-size: 11px; color: var(--text); }
  /* **Y no se imprime.** El bloque de impresión de la lista solo cubre esa
     página, y esto sale en todas: sin la regla aquí, la lista de la compra
     saldría con el aviso pegado abajo. Al súper se lleva la lista, no esto. */
  @media print { #galletas { display: none !important; } }
  /* **«Hay una versión nueva»**: abajo, en una pastilla, sin tapar nada más
     que su sitio. Solo sale cuando el servidor sirve otra versión que la de
     esta pestaña (ver su script). */
  #versionnueva { position: fixed; left: 0; right: 0; bottom: 16px; z-index: 55;
    margin: 0 auto; width: -webkit-fit-content; width: fit-content;
    max-width: calc(100% - 24px); box-sizing: border-box;
    display: flex; align-items: center; gap: 10px;
    padding: 8px 8px 8px 16px; border-radius: 999px; background: var(--marino);
    color: var(--sobre-marino); box-shadow: 0 8px 24px rgba(0,0,0,.25);
    font-size: 13.5px; }
  /* Con el aviso de lo que se guarda abierto, por encima de él. */
  #galletas:not([hidden]) ~ #versionnueva { bottom: 100px; }
  #versionnueva button { padding: 7px 14px; font-size: 13px; white-space: nowrap; }
  #versionnueva .x { background: none; color: var(--sobre-marino-tenue); padding: 4px 8px; }
  @media print { #versionnueva { display: none !important; } }

  @media (max-width: 720px) {
    header { padding: 0 12px; }
    body > header nav { padding: 10px 0; padding-top: calc(10px + env(safe-area-inset-top, 0px)); }
    .cabecera-extra { padding: 10px 0 12px; }
    nav { flex-wrap: wrap; gap: 8px 12px; margin-bottom: 10px; }
    nav .brand { font-size: 16px; }
    nav .brand img { width: 20px; height: 20px; vertical-align: -4px; }
    #galletas { padding: 8px 12px; flex-wrap: nowrap; gap: 10px; }
    #galletas p { flex: 1 1 auto; font-size: 12px; line-height: 1.35; }
    #galletas button { padding: 8px 14px; }
    nav .links { flex-wrap: wrap; gap: 2px; }
    nav .links a { padding: 6px 9px; font-size: 13px; }
    /* **La cabecera, en una sola línea.** «Entrar · Crear cuenta» se iban a una
       segunda fila y eran cuarenta píxeles menos de pantalla en todas las
       páginas, y la barra es fija. Se queda lo que dice quién eres —«Entrar» o
       tu nombre— y «Crear cuenta» y «Salir» se mudan al menú (`.solomovil`),
       que es donde ya está todo lo demás. Si no cabe, se recorta el rótulo de
       la sección, no la cuenta. */
    header nav { flex-wrap: nowrap; margin-bottom: 0; }
    nav .brand { flex: 0 1 auto; min-width: 0; white-space: nowrap; overflow: hidden;
      text-overflow: ellipsis; }
    nav .seccion-actual { flex: 0 1 auto; }
    /* **Con una sección delante, la marca es el logo.** En Ofertas se cortaban
       los dos —«Comparador de⋯» y «Ofertas y fo⋯»— y ninguno se leía. El logo
       ya dice qué sitio es y lleva a la portada; lo que hace falta leer entero
       es dónde estás. El nombre no se quita del documento, se oculta a la
       vista: el `alt` del logo va vacío y sin él el enlace se quedaría sin
       nombre para un lector de pantalla. En la portada no hay sección, así que
       allí el nombre se queda. */
    nav .brand.solologo { flex: 0 0 auto; }
    nav .brand.solologo .nombresitio { position: absolute; width: 1px; height: 1px;
      overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
    nav .brand.solologo img { width: 26px; height: 26px; vertical-align: -7px; margin-right: 0; }
    nav .brand.solologo + .seccion-actual { font-size: 16px; color: inherit; margin-left: 2px; }
    nav .links { flex: 0 0 auto; flex-wrap: nowrap; width: auto; }
    nav .links a.destacado, nav .links .salir { display: none; }
    #menu .solomovil { display: block; }
    nav .links a.quien { max-width: 7.5rem; overflow: hidden; text-overflow: ellipsis;
      white-space: nowrap; }
    /* El tamaño lo pone la regla de la pareja, arriba: aquí decía 12 px y
       **nunca se aplicó** —`nav .links a` le ganaba—, así que era un número
       escrito que no describía la pantalla.

       **Y sin la raya de la izquierda.** Separa la zona de la cuenta de los
       enlaces de la barra, y aquí no hay nada que separar: los enlaces se
       envuelven a su propia línea, así que el nombre es lo primero de la fila
       y la raya queda pegada al margen izquierdo como un palito suelto. En un
       ordenador sí va detrás de ellos y hace su trabajo. */
    nav .quien { padding-left: 9px; margin-left: 3px; border-left: 0; }
    /* A la derecha del título, que es donde sobra sitio: en la fila de enlaces
       no cabía sin empujar «Crear cuenta» a otra línea. */
    nav .instalar { order: 0; margin-left: auto; padding: 5px 10px; font-size: 12.5px; }
    main { padding: 14px 12px; }
    form.search { gap: 6px; }
    /* El buscador en su propia línea: partido con el botón queda ilegible. */
    input[type=search] { flex: 1 1 100%; }
    button { padding: 10px 16px; font-size: 14.5px; }
    #bar { gap: 10px; font-size: 13px; margin-bottom: 12px; }
    #grid, .rejilla { gap: 9px; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); }
    article { padding: 10px; gap: 6px; }
    .price { font-size: 17px; }
    .name { font-size: 13px; }

    /* **Los paneles de «Añadir de otra forma», que son de las dos páginas.**
     Estaban en `lista.html` hasta que el botón se pidió también en Buscar: el
     CSS se queda donde vive lo que estila, que es la misma regla que ya trajo
     aquí `.dd`, `.chip-btn` y los ayudantes de precio — allí el selector de la
     lista salía sin estilar, con el triángulo del navegador. */
  #revision, #dictado {
    background: var(--card); border: 1px solid var(--accent); border-radius: 12px;
    padding: 16px 18px; margin-bottom: 16px;
    /* Que al traerlo a la vista no quede debajo de la cabecera fija, que con
       el buscador plegado mide unos 110 px. */
    scroll-margin-top: 130px;
  }
  #revision h2, #dictado h2 { margin: 0 0 4px; font-size: 16px; }
  #revision .sub, #dictado .sub {
    margin: 0 0 12px; color: var(--muted); font-size: 13px;
  }
  #revlineas { display: grid; gap: 6px; margin-bottom: 14px; }
  .revline { display: flex; align-items: center; gap: 10px; }
  .revline input[type=checkbox] { accent-color: var(--accent); cursor: pointer; }
  .revline input[type=text] {
    flex: 1; min-width: 0; padding: 7px 10px; font-size: 14px;
    border: 1px solid var(--line); border-radius: 7px;
    background: var(--bg); color: var(--text);
  }
  .revline .crudo { font-size: 11px; color: var(--muted); white-space: nowrap; }
  /* Sigla ampliada: es una propuesta nuestra, así que se ve y se puede deshacer. */
  .revline .sigla {
    background: none; border: 1px solid var(--line); color: var(--muted);
    font-size: 11px; padding: 3px 7px; border-radius: 6px; cursor: pointer;
    white-space: nowrap;
  }
  .revline .sigla:hover { border-color: var(--accent); color: var(--accent); }
  .revline input.ampliado { border-color: var(--accent); }
  #revision .sugs { display: inline-flex; gap: 4px; flex-wrap: wrap; }
  #revision .sug {
    padding: 2px 8px; font-size: 11.5px; cursor: pointer;
    border: 1px dashed var(--line); border-radius: 999px;
    background: var(--bg); color: var(--muted);
  }
  #revision .sug:hover { border-style: solid; border-color: var(--accent); color: var(--accent); }
  #revision .acciones, #dictado .acciones {
    display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  }
  #revision .cancelar, #dictado .cancelar {
    background: none; border: 1px solid var(--line); color: var(--muted);
  }

  /* Dictado: lo que se va oyendo, antes de pasar a la revisión. */
  #dictlineas { display: grid; gap: 6px; margin-bottom: 10px; }
  .dictline {
    display: flex; align-items: center; gap: 10px; padding: 7px 10px;
    border: 1px solid var(--line); border-radius: 7px; background: var(--bg);
    font-size: 14px;
  }
  .dictline .quitar {
    margin-left: auto; background: none; border: 0; cursor: pointer;
    color: var(--muted); font-size: 15px; line-height: 1; padding: 2px 4px;
  }
  .dictline .quitar:hover { color: var(--err); }
  /* El punto rojo que dice que el micrófono está abierto: sin él no se sabe si
     te está oyendo o se ha parado sola. */
  #dictestado { display: flex; align-items: center; gap: 8px; margin: 0 0 10px;
                font-size: 13.5px; color: var(--muted); min-height: 20px; }
  #dictestado .punto {
    width: 9px; height: 9px; border-radius: 50%; background: var(--err); flex: none;
    animation: latido 1.2s ease-in-out infinite;
  }
  #dictestado.parado .punto { background: var(--line); animation: none; }
  #dictparcial { font-style: italic; }
  @keyframes latido { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
  @media (prefers-reduced-motion: reduce) {
    #dictestado .punto { animation: none; }
  }

    .revline { flex-wrap: wrap; }
    .revline input[type=text] { flex: 1 1 100%; order: 3; }
    .revline .crudo { white-space: normal; font-size: 10.5px; }

  /* Los desplegables de cabecera dejan de flotar y empujan el contenido:
       con `absolute` se salían por la derecha en cuanto el botón no estaba
       pegado al margen izquierdo. El de producto (`prodpick`) no entra aquí,
       que ese lo coloca `colocarPanel()` a mano. */
    .dd:not(.prodpick) { flex: 1 1 100%; }
    .dd:not(.prodpick) > .ddpanel {
      position: static; width: auto; min-width: 0; margin-top: 6px;
      max-height: 50vh; box-shadow: none;
    }
    .dd > summary { padding: 7px 13px; font-size: 13px; }
    .plegador { display: inline-flex; }
    .plegado { display: none !important; }
    /* **El hueco de la fila del plegador, guardado desde el principio.** El
       JavaScript va al final y en una conexión lenta llega tarde: la fila
       (28 px del botón y 8 de margen) entraba cuando la página ya estaba
       pintada y empujaba todo 36 px hacia abajo — Lighthouse lo contaba como
       CLS en la lista, Buscar y Ofertas. Solo con `con-js`, que lo pone el
       script de la cabecera: sin JavaScript no hay fila y no se reserva nada. */
    html.con-js .filaplegador[data-reservada]:empty { height: 36px; }
    .ddrow { padding: 9px; font-size: 14px; }
    /* `#cpdd .ddpanel` lleva id y le gana por especificidad a la regla de
       arriba: sin repetirlo aquí, sus 20rem se salían de una pantalla de 360px. */
    #cpdd .ddpanel { width: auto; }
    /* Cerrado, «Filtros» mide lo suyo al lado de «Ordenar»; abierto ocupa la
       fila, que su panel empuja y a media anchura no se lee. */
    #bar .dd { flex: 0 1 auto; }
    #bar .dd[open] { flex: 1 1 100%; }
    .filtros .fcampo input { font-size: 16px; }
    /* Los numéricos, más estrechos: «Capacidad desde … hasta … kg» cabe así en
       una línea a 390 px. Solo los de las filas de rango, no «Sin estas
       palabras», que tiene que ocupar el ancho. */
    .filtros .frango .fcampo input { width: 4.2em; }
  }

  /* El 🔔 de las secciones de catálogo (`Precios.catalogo.botonSeguir`):
     pequeño y en el color apagado hasta que se pulsa, que es una opción de la
     tarjeta y no lo que se viene a hacer. */
  .seguir { align-self: flex-start; white-space: nowrap; font-size: 12px; background: none; color: var(--muted);
    border: 1px solid var(--line); border-radius: 999px; padding: 3px 10px; cursor: pointer; }
  .seguir:hover { color: var(--accent); border-color: var(--accent); }
  .seguir[aria-pressed="true"] { color: var(--ok); border-color: var(--ok); font-weight: 600; }
  .seguir-nota { font-size: 12px; color: var(--muted); }
  .seguir-nota a { color: var(--accent); }

  @media (max-width: 400px) {
    #grid, .rejilla { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }
  }
