/* MonkeyCams — Layout: hero, max-width, noticias */

/* ============================================
           CABECERA
        ============================================ */
.app-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding-bottom: 20px;
    margin-bottom: 20px;
    border-bottom: 1px solid var(--border-subtle);
    flex-wrap: wrap;
}

.app-brand {
    display: flex;
    align-items: center;
    gap: 12px;
}

.brand-icon {
    width: 36px;
    height: 36px;
    background: var(--accent-dim);
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--accent);
    flex-shrink: 0;
}

.brand-icon svg {
    width: var(--ac-icon-lg);
    height: var(--ac-icon-lg);
}

.brand-text h1 {
    font-size: 1.25rem;
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: -0.02em;
    text-wrap: balance;
    display: flex;
    align-items: center;
    gap: 8px;
}
.brand-emoji {
    font-size: 1.2rem;
    line-height: 1;
}

.brand-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 5px;
}

.brand-sub {
    font-size: 0.72rem;
    color: var(--text-faint);
}

.count-pill {
    font-family: "DM Mono", monospace;
    font-size: 0.68rem;
    color: var(--accent);
    background: var(--accent-dim);
    padding: 1px 7px;
    border-radius: var(--radius-pill);
}
.count-pill:empty {
    display: none;
}


.header-right {
    display: flex;
    gap: 6px;
    align-items: center;
}

/* ── Search highlight ────────────────────────────────────────────────────── */
mark {
    background: rgba(255, 214, 10, 0.32);
    color: inherit;
    border-radius: 2px;
    padding: 0 1px;
}

/* ============================================================
   ESTILO APPLE — apariencia y sensación de apple.com
   (aplicado de forma permanente a toda la interfaz)
============================================================ */

/* ── Hero centrado (compacto) ── */
.apple-hero {
    text-align: center;
    padding: 26px 22px 18px;
}
.apple-hero h1 {
    font-size: 2.1rem;
    font-weight: 600;
    letter-spacing: -0.03em;
    line-height: 1.05;
    margin: 0;
    color: var(--text-main);
}
.collection-count {
    margin: 8px 0 0;
    font-family: "DM Mono", monospace;
    font-size: 0.78rem;
    color: var(--text-muted);
    letter-spacing: 0.01em;
}
.collection-count:empty {
    display: none;
}
/* Móviles estrechos: el hero unificado reserva hueco para herramientas del editor */
@media (max-width: 420px) {
    #panel-news .apple-hero,
    #panel-recipes .apple-hero {
        padding-left: 52px;
        padding-right: 52px;
    }
    .inventory-hero {
        padding-left: 16px;
        padding-right: 16px;
    }
}

/* ── Inventario: hero unificado (título + contador + editor) ── */
.inventory-hero {
    display: grid;
    /* Tres columnas con una vacía a la izquierda que hace de contrapeso: las
       dos laterales son 1fr y se reparten el hueco por igual, así que la
       columna del título queda centrada respecto al hero pase lo que pase.
       Con las dos columnas de antes ("1fr auto") el título se centraba en el
       espacio SOBRANTE, así que su posición dependía de cuántos botones
       hubiera a la derecha: se desplazaba 18px a la izquierda al iniciar
       sesión (aparece el lápiz) y otros 18px al haber cambios pendientes.
       La columna de los botones lleva max-content como mínimo para que, si
       algún día el título no cupiera, se estreche antes la columna vacía que
       los propios botones. */
    grid-template-columns: minmax(0, 1fr) auto minmax(max-content, 1fr);
    grid-template-areas: ". center tools";
    align-items: start;
    gap: 12px 16px;
    position: relative;
    text-align: center;
    /* Sin padding lateral y a 1024px: los botones de engranaje/editar
       alinean con el borde derecho de la columna de contenido (KPIs,
       gráfico, toolbar, listas) — a 1100px+22px sobresalían 16px. */
    padding: 26px 0 18px;
    max-width: var(--content-max-table);
    margin: 0 auto;
}
.inventory-hero-center {
    grid-area: center;
    min-width: 0;
}
.inventory-hero-tools {
    grid-area: tools;
    position: static;
    top: auto;
    right: auto;
    align-self: start;
    justify-self: end;
    flex-shrink: 0;
}
@media (max-width: 540px) {
    .inventory-hero {
        grid-template-columns: 1fr;
        grid-template-areas:
            "tools"
            "center";
        padding-top: 18px;
    }
    .inventory-hero-tools {
        justify-self: end;
    }
}

/* Legacy: .app-header ya no se usa en inventario; reglas conservadas por si
   algún fragmento antiguo las referencia en caché del SW. */
.app-header {
    border: none;
    padding: 0;
    margin: 0 0 6px;
    justify-content: flex-end;
}
.app-brand { display: none; }
/* ── Ancho máximo uniforme ── */
.recipes-page, .news-sidebar-inner { max-width: var(--content-max); margin: 0 auto; }

/* ── Hero uniforme en los tres paneles ── */
/* Recetas y Noticias usan el mismo .apple-hero que Inventario */
.recipes-page .apple-hero,
.news-sidebar-inner .apple-hero {
    padding: 26px 22px 18px;
}

/* ── Noticias — estilo Apple ── */
/* Sobreescribir display:flex del news.css original (era un sidebar estrecho)
   ahora es un panel de ancho completo → display:block */
.news-sidebar-inner {
    display: block !important;
    padding: 0 0 40px;
}
/* quitar el viejo header sticky */
.news-sidebar-inner > .news-sidebar-header { display: none; }
.news-sidebar-inner > .filter-bar {
    background: var(--bg-surface);
    border-radius: var(--radius-lg);
    padding: 10px 16px;
    border: none;
    margin: 0 auto 14px;
    max-width: var(--content-max);
}
/* Restaurar label "MARCA" — visible y compacto */
.news-sidebar-inner > .filter-bar .filter-label {
    display: inline-block;
    font-size: 0.65rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-faint);
    flex-shrink: 0;
}
.news-card {
    background: var(--bg-surface);
    border: none !important;
    border-radius: var(--radius-lg);
    transition: transform 0.18s var(--ease-out), box-shadow 0.18s var(--ease-out);
}
@media (hover: hover) {
    .news-card:hover {
        transform: translateY(-2px) !important;
        box-shadow: 0 10px 28px rgba(0,0,0,.45) !important;
        border-color: transparent !important;
    }
}
.news-grid {
    gap: 10px;
}

/* Quitar header viejo de noticias cuando está dentro de news-sidebar-inner */
.news-sidebar-header { display: none; }
/* ── Noticias: layout buscador (apariencia en .ac-search-input) ── */
.news-search-box {
    min-width: 0;
}
.news-count {
    max-width: var(--content-max);
    margin: 0 auto 10px;
    font-family: "DM Mono", monospace;
    font-size: 0.75rem;
    color: var(--text-muted);
    text-align: center;
}

/* Escritorio: buscador arriba, marcas abajo en fila limpia */
@media (min-width: 1081px) {
    .news-sidebar-inner > .filter-bar {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        grid-template-rows: auto auto;
        gap: 12px 20px;
        padding: 14px 18px;
        align-items: center;
    }
    .news-sidebar-inner > .filter-bar .news-search-box {
        grid-column: 1;
        grid-row: 1;
        width: 100%;
    }
    .news-sidebar-inner > .filter-bar .update-stamp {
        grid-column: 2;
        grid-row: 1;
        margin-left: 0;
        white-space: nowrap;
        align-self: center;
    }
    .news-sidebar-inner > .filter-bar .news-filter-brands {
        grid-column: 1 / -1;
        grid-row: 2;
        display: flex;
        align-items: center;
        gap: 10px 12px;
        flex-wrap: wrap;
        min-width: 0;
    }
    .news-sidebar-inner > .filter-bar .news-filter-brands .filter-label {
        margin: 0;
        line-height: 1;
    }
    .news-sidebar-inner > .filter-bar .news-filter-brands .brand-chips {
        flex: 1;
        min-width: 0;
    }
}

.news-sidebar-inner > .filter-bar {
    flex-wrap: wrap;
    gap: 8px;
}

/* ── Noticias móvil: buscador arriba, marcas en dropdown ── */
.news-brand-select {
    display: none;
    width: 100%;
    box-sizing: border-box;
}
@media (max-width: 1080px) {
    .news-sidebar-inner {
        padding-left: 16px;
        padding-right: 16px;
    }
    .news-sidebar-inner > .filter-bar {
        display: flex;
        flex-direction: column;
        align-items: stretch;
        gap: 10px;
        padding: 12px 14px;
    }
    .news-sidebar-inner > .filter-bar .news-filter-brands {
        display: contents;
    }
    .news-sidebar-inner > .filter-bar .news-search-box {
        order: -2;
        width: 100%;
        flex: none;
        box-sizing: border-box;
    }
    /* El rótulo "Marca" queda huérfano al aplanar .news-filter-brands con
       display:contents (el select salta con order:-1 y el rótulo no le
       sigue). El select ya se autodescribe ("Todas las marcas") y conserva
       su aria-label, así que en móvil el rótulo visual sobra. */
    .news-sidebar-inner > .filter-bar .filter-label {
        display: none;
    }
    .news-brand-select {
        display: block;
        order: -1;
    }
    .news-sidebar-inner > .filter-bar .brand-chips {
        display: none;
    }
    .news-sidebar-inner > .filter-bar .update-stamp {
        width: 100%;
        margin: 0;
        text-align: left;
        font-size: 0.65rem;
    }
}


/* ============================================
   CATÁLOGOS RECORRIBLES — Películas y Cámaras
   Solo COLOCACIÓN. Los filtros son `.ac-pill` (el componente de filtro del
   sistema), la banda de filtro activo es `.filter-active-banner` (la del
   inventario), el botón de filtros de móvil es `.filter-sheet-btn` y la
   cabecera es `.apple-hero`, como Recetas y Noticias. Aquí no se decide ningún
   aspecto.

   Estas clases se llamaban `.films-*` y las usaba solo Películas. Al llegar
   Cámaras (2026-08-06) resultó que la pantalla es la misma de arriba abajo
   —entradilla, chips de faceta, banda, rejilla de tarjetas— y solo cambia lo
   que va DENTRO de cada tarjeta. Duplicarlas con otro prefijo habría dejado dos
   maquetaciones que envejecen por separado, así que la capa común pasó a
   `.catalog-*` y cada catálogo se queda solo con lo suyo (más abajo).
============================================ */
.catalog-page {
    max-width: var(--content-max);
    margin: 0 auto;
    padding: 0 16px 48px;
}
.catalog-intro {
    max-width: 46em;
    margin: 0 0 22px;
    color: var(--text-muted);
    font-size: 0.9rem;
    line-height: 1.55;
    text-wrap: pretty;
}
.catalog-intro p {
    margin: 0 0 8px;
}
.catalog-intro p:last-child {
    margin-bottom: 0;
}
.catalog-intro strong {
    color: var(--text-main);
    font-weight: 600;
}
.catalog-intro-more > p {
    margin: 8px 0 0;
}
.catalog-filters-btn {
    /* En escritorio no existe: hereda el display:none de .filter-sheet-btn. */
    align-self: flex-start;
}
/* Disclosure de filtros, solo en escritorio — pedido el 2026-08-08: «que
   salgan colapsados por defecto». En móvil no existe (`display:none`
   heredado de aquí abajo): ahí los filtros ya viven en el sheet, con su
   propio botón (`.catalog-filters-btn`), y `#camsFiltersHost`/
   `#filmsFiltersHost` no pintan nada que plegar. */
.catalog-filters-toggle {
    display: none;
    align-items: center;
    gap: 6px;
    margin-bottom: 14px;
    padding: 6px 2px;
    background: none;
    border: none;
    color: var(--text-main);
    font: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
}
.catalog-filters-toggle svg {
    width: var(--ac-icon-sm);
    height: var(--ac-icon-sm);
    color: var(--text-faint);
    transition: transform 0.2s;
}
.catalog-filters-toggle[aria-expanded="true"] svg {
    transform: rotate(180deg);
}
@media (hover: hover) {
    .catalog-filters-toggle:hover svg {
        color: var(--text-muted);
    }
}
.catalog-filters-toggle:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 4px;
}
@media (min-width: 1081px) {
    .catalog-filters-toggle {
        display: flex;
    }
}
.catalog-filters {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 20px;
}
/* Rótulo SIEMPRE encima de sus chips (ver la nota de más abajo). */
.catalog-filter-group {
    /* Rótulo SIEMPRE encima de sus chips. Antes era una fila con el rótulo al
       lado y `flex-wrap`: los grupos de muchos chips envolvían y dejaban el
       rótulo arriba, y los de dos (Tipo, Tuyas) lo dejaban al lado. Dos
       alineaciones distintas en la misma columna, y parecía un fallo. */
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
}
.catalog-filter-label {
    flex: 0 0 auto;
    font-size: 0.7rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    font-weight: 600;
    color: var(--text-faint);
}
.catalog-filter-chips {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}
/* La banda de filtro activo es la MISMA del inventario
   (`.filter-active-banner`, inventory.css): dice qué se está filtrando, cuántas
   quedan y trae dentro su «Quitar filtros». Aquí solo se corrige el ancho, que
   allí está atado al de la tabla. */
.catalog-banner.filter-active-banner {
    max-width: none;
    margin-left: 0;
    margin-right: 0;
}
.catalog-list {
    display: grid;
    /* Una columna en móvil y dos en cuanto caben: la ficha lleva párrafos de
       texto y estirarla más allá de ~34em la vuelve incómoda de leer. */
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 30em), 1fr));
    gap: 14px;
    /* Sin esto el grid estira cada tarjeta a la altura de la fila (el
       comportamiento por defecto): con las tarjetas-acordeón, una plegada
       junto a una desplegada se estiraba igual, dejando un hueco vacío
       debajo de su cabecera. Cada una mide ahora solo lo que pide su propio
       contenido. */
    align-items: start;
}
.catalog-card {
    background: var(--bg-surface);
    border-radius: var(--radius-lg);
    padding: 18px 20px;
}
.catalog-card-head {
    margin-bottom: 10px;
}
.catalog-card-brand {
    display: block;
    font-size: 0.7rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-muted);
}
.catalog-card-name {
    font-size: 1.15rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    margin: 2px 0 0;
}
/* La ficha viene de `.ac-roll-film-info`, pensada para vivir dentro de un
   formulario estrecho: aquí no necesita ni su fondo ni su margen. */
.catalog-empty {
    color: var(--text-muted);
    font-style: italic;
}

/* ── Vista: lista / por marca, y tarjetas-acordeón ────────────────────────────
   Nace el 2026-08-07: el usuario pidió que las tarjetas de Cámaras y Películas
   se leyeran como las de Inventario en su vista por marca —acordeones cerrados
   por defecto— y poder agrupar por marca, con las cámaras ordenadas por fecha
   dentro de cada grupo (`Cat.sortByYear`, en cameras-view.js). El interruptor
   reutiliza el `.btn-group-views`/`.icon-btn` de Inventario (inventory.css) tal
   cual, con los MISMOS iconos y el mismo significado: rejilla = lista plana,
   capas = agrupado.

   El agrupado por marca NO reutiliza la línea de tiempo con puntos de color de
   Inventario (`.brand-panel`, inventory-timeline.css): esa decoración está
   pensada para una sola columna en móvil, y aquí la rejilla tiene dos columnas
   en cuanto cabe. Sí reutiliza el lenguaje de esa vista —cabecera con nombre,
   recuento y «mostrar/ocultar»— y la misma `.catalog-list` de siempre para las
   tarjetas de dentro. */
.catalog-view-toolbar {
    display: flex;
    /* Buscador a la izquierda, interruptor de vista a la derecha. Con el
       buscador dentro (2026-08-10) la barra dejó de ser solo los dos botones:
       `space-between` los mantiene en su sitio y `gap` evita que se toquen
       cuando el campo llega a su ancho máximo. */
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    margin-bottom: 14px;
}
/* Solo colocación: el aspecto del campo es el `.ac-search-input` de
   components.css, el mismo de Inventario, Recetas y Noticias. Ancho tope para
   que en escritorio no se estire hasta el interruptor de vista; en móvil se
   queda con el ancho que le deje la fila. */
.catalog-search {
    flex: 1 1 auto;
    max-width: 26em;
}
/* El selector «Ordenar» (2026-08-16) solo existe en Cámaras —Películas
   comparte `.catalog-view-toolbar` pero no tiene un tercer elemento que
   meter en la fila—, así que el `flex-wrap` que necesita va con el
   selector de ID por delante y no en la clase compartida: ponerlo en
   `.catalog-view-toolbar` a secas hizo que el buscador de Películas, con
   `max-width: 26em` más ancho que la pantalla en móvil, pasara a ocupar su
   propia línea entera en vez de encogerse junto al interruptor de vista
   como venía haciendo — dos filas donde antes había una, la primera
   película empujada fuera de la pantalla inicial. Detectado por el smoke
   de Películas (Flujo 24 y 27), no a ojo. */
#main-content-cameras .catalog-view-toolbar {
    flex-wrap: wrap;
}
/* Ancho de contenido, nunca se aplasta —lo pequeño que puede llegar a estar
   ya lo decide `.select-pill`—, y `sincronizarOrden()` lo esconde entero
   con la vista agrupada activa. */
#camsSortSelect {
    flex-shrink: 0;
}
.catalog-groups {
    display: flex;
    flex-direction: column;
    gap: 28px;
}
.cat-group-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding-bottom: 8px;
    margin-bottom: 14px;
    border-bottom: 1px solid var(--border-subtle);
}
.cat-group-name {
    font-size: 0.95rem;
    font-weight: 700;
}
.cat-group-count {
    font-size: 0.68rem;
    font-weight: 700;
    color: var(--text-faint);
    background: var(--bg-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-pill);
    padding: 0 7px;
    line-height: 18px;
}
.cat-group-toggle {
    margin-left: auto;
    background: none;
    border: none;
    color: var(--text-faint);
    font-size: 0.7rem;
    font-family: inherit;
    font-weight: 600;
    cursor: pointer;
    padding: 2px 6px;
    border-radius: 4px;
    position: relative;
}
.cat-group-toggle::after {
    content: "";
    position: absolute;
    /* -14px y no -11px, igual que su gemelo `.sec-toggle` de Inventario: el
       botón mide 18px de alto y con 11 el área táctil se quedaba en 40, por
       debajo del mínimo de 44 de esta app. */
    inset: -14px -8px;
}
@media (hover: hover) {
    .cat-group-toggle:hover {
        background: var(--bg-hover);
        color: var(--text-muted);
    }
}
.catalog-groups .catalog-list.is-collapsed {
    display: none;
}

/* Tarjeta-acordeón: cabecera y resumen SIEMPRE visibles (marca, nombre, y en
   Cámaras las píldoras/«ya la tienes», en Películas la ficha en una línea),
   cuerpo plegado por defecto con el resto — párrafos largos y ficha técnica.
   No reutiliza el DOM de `.cam-card` de Inventario: una ficha de catálogo no
   es una unidad tuya, no lleva foto ni nº de serie, y su contenido no cabe en
   una fila. Sí reutiliza el patrón de interacción (chevron que gira 180°,
   cerrado por defecto) y los tokens de espaciado/color del resto de la app. */
.cat-card-head-row {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin-bottom: 10px;
}
.cat-card-head-row .catalog-card-head {
    flex: 1;
    min-width: 0;
    margin-bottom: 0;
}
.cat-card-toggle {
    flex-shrink: 0;
    background: transparent;
    border: none;
    color: var(--text-faint);
    font-size: 0.75rem;
    line-height: 1;
    cursor: pointer;
    padding: 4px;
    border-radius: 4px;
    position: relative;
    transition: transform 0.2s;
}
.cat-card-toggle::after {
    content: "";
    position: absolute;
    inset: -14px;
}
.cat-card-toggle:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.cat-card-toggle[aria-expanded="true"] {
    transform: rotate(180deg);
}
.cat-card-body {
    margin-top: 10px;
    padding-top: 12px;
    border-top: 1px solid var(--border-subtle);
}
/* Resumen técnico de una línea en Películas («ISO 400 · Color · grano
   medio»), el equivalente de las píldoras de Cámaras: lo que permite comparar
   dos tarjetas sin abrir ninguna. */
.catalog-card-summary {
    margin: 0;
    font-size: 0.78rem;
    color: var(--text-muted);
}

/* ── Lo propio de Cámaras ─────────────────────────────────────────────────────
   La ficha técnica NO tiene estilos aquí: son `.cam-fgrid` / `.cam-field` /
   `.cam-k` / `.cam-v`, las mismas tarjetas de la ficha lateral del inventario
   (inventory.css). Una cámara del catálogo y una cámara tuya se leen igual, que
   es lo que uno quiere al comparar la que tiene con la que se piensa comprar. */
/* El nombre y «Ya la tienes», en la misma línea (2026-08-18). Gemela de
   `.films-name-row`, que hace lo mismo con el icono de blanco y negro.
   `wrap` porque el nombre más largo del catálogo («OM-D E-M10 Mark IV») más
   la pill no caben en una tarjeta de móvil: sin él, la pill empujaría el
   nombre y lo cortaría. `center` y no `baseline` como en Películas porque
   aquí lo de al lado no es un icono sino una píldora con fondo y borde:
   alineada por la base del texto se ve descolgada hacia abajo. */
.cams-name-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 8px;
    margin-top: 2px;
}
.cams-name-row .catalog-card-name {
    margin-top: 0;
}

/* Arriba a la derecha (2026-08-16), no debajo del nombre: hermano de `.cat-
   card-toggle` dentro de `.cat-card-head-row` (`align-items: flex-start`),
   así que no hace falta `position: absolute` para que caiga en la esquina. */
.cams-card-years {
    display: block;
    flex-shrink: 0;
    padding-top: 3px;
    font-family: "DM Mono", monospace;
    font-size: 0.7rem;
    color: var(--text-faint);
    letter-spacing: -0.01em;
    white-space: nowrap;
}
/* El resumen de un vistazo: tipo, formato y precio. Es lo que permite comparar
   dos tarjetas sin llegar a leerlas. */
.cams-badges {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: 10px;
}
.cams-badges--uses {
    margin-top: 12px;
}
/* La píldora de disponibilidad se distingue de las tres de al lado: no
   describe la cámara, avisa de dónde vas a poder comprarla. */
.cams-badge-avail {
    border-color: var(--accent-dim);
    color: var(--accent);
}

/* Ya la tienes: es la MISMA pill de estado que «Funcional» en Inventario
   (`.ac-pill--ok`, verde con su check) — el color y la forma vienen de ahí.
   `.cams-card-mine` ya no lleva estilo propio (vivió con `margin-top` cuando
   colgaba suelta debajo de `.cams-badges`; el 2026-08-16 pasó a ser una
   píldora más DENTRO de esa fila, así que el `gap` del flex ya la separa) —
   se queda como clase solo para que el smoke test la encuentre. */
.cams-card-character {
    margin: 12px 0 0;
    font-size: 0.88rem;
    line-height: 1.55;
    color: var(--text-main);
    text-wrap: pretty;
}
.cams-specs {
    margin-top: 14px;
}
.cams-card-when,
.cams-card-watch {
    margin: 12px 0 0;
    font-size: 0.85rem;
    line-height: 1.5;
    color: var(--text-muted);
    text-wrap: pretty;
}
.cams-card-when .cam-k,
.cams-card-watch .cam-k {
    display: inline;
    margin-right: 6px;
}
/* «Qué mirar al comprarla» lleva barra de aviso porque es lo único de la ficha
   que te puede ahorrar dinero, y en un bloque de tres párrafos seguidos se
   perdería. Con `--err-color` no: no es un error, es una precaución. */
.cams-card-watch {
    border-left: 2px solid var(--border);
    padding-left: 10px;
}
.cams-card-watch .cam-k {
    color: var(--text-main);
}
.cams-related {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px;
    margin-top: 14px;
    padding-top: 12px;
    border-top: 1px solid var(--border-subtle);
}
/* La arista de vuelta del grafo: quién te manda AQUÍ, en vez de adónde ir.
   Va pegada a «Mira también» y más apagada porque es información secundaria
   —con el mismo filete a 14px por encima parecían dos secciones del mismo
   peso, y no lo son—. El trazo discontinuo la marca como la vuelta de la
   misma relación y no como un apartado nuevo. */
.cams-related--inbound {
    margin-top: 8px;
    padding-top: 8px;
    border-top-style: dashed;
}
.cams-related--inbound .cam-k {
    color: var(--text-muted);
}

/* El enlace de referencia es su propia línea, no una más de «Mira también»:
   no es otra ficha del catálogo, es salir de la app. Mismo `.ac-btn-inline`
   que ese botón, para no inventar un tercer estilo de acción en texto. */
.cams-card-link {
    display: inline-block;
    margin-top: 12px;
    text-decoration: none;
}
/* «Dónde comprarla» (2026-08-17): agrupa oficial/Amazon/Foto K/Foto Ruano
   Pro en una sola fila, gemela de `.cams-related` en vez de dejar cada
   enlace suelto con su propio `margin-top` — con hasta cuatro seguidos,
   sin `flex-wrap` se habrían quedado pegados unos a otros (`.cams-card-link`
   es `inline-block` sin espacio entre elementos creados con `appendChild`). */
.cams-card-buy {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px 14px;
    margin-top: 12px;
}
.cams-card-buy .cams-card-link {
    margin-top: 0;
}
/* Con el dedo, cada tienda es un CHIP y no un enlace de texto. Tres intentos
   antes de esto, los tres reportados por el usuario, y cada uno enseñó una
   parte del problema:

   1. En fila de texto, las zonas pulsables se SOLAPABAN un píxel —la primera
      fila acababa en y=2283 y la segunda empezaba en 2282—, así que apuntar a
      «Amazon.es» podía abrir «Tienda oficial».
   2. Dándoles 44px reales de caja se quitó el solape, pero el texto quedaba
      flotando con mucho aire, y el rótulo —un `.cam-k` en `display:block`
      dentro del flex— compartía línea con el primer enlace: se leía «Dónde
      comprarla: Tienda oficial» y las otras tres como otra cosa.
   3. En columna de cuatro filas de 44px con filete de 1px, el `border-radius`
      que `.ac-btn-inline` ya trae curvaba las puntas de cada filete y salían
      arcos flotando; y 197px de alto no es «compacto».

   Lo que fallaba en los tres es lo mismo: **una zona pulsable de 44px alrededor
   de un texto de 17px es aire**, y el aire hay que llenarlo o disimularlo. Un
   chip lo llena — la altura pasa a ser la del propio botón, que se ve, y deja
   de leerse como separación. Y para no pagarla dos veces, la zona pulsable se
   amplía por pseudoelemento (mismo patrón que `.sec-toggle` y `.cam-chevron-btn`)
   en vez de con más padding: el chip mide 32px, la zona 44, y la maqueta no se
   entera. Dos filas de dos, 78px en vez de 197.

   En escritorio no cambia nada: allí siguen siendo cuatro enlaces en línea. */
@media (pointer: coarse) {
    .cams-card-buy {
        /* Las filas separadas 14px y no 4: es lo que hace que las zonas
           pulsables ampliadas (5px por arriba y por abajo de cada chip) no
           lleguen a tocarse. Medido con `elementFromPoint`, no a ojo. */
        gap: 14px 8px;
    }
    .cams-card-buy .cam-k {
        /* El rótulo, a su propia línea: es el título del grupo y no el primero
           de los enlaces. Ver el intento 2 de arriba. */
        flex: 0 0 100%;
        margin-bottom: 0;
    }
    .cams-card-buy .cams-card-link {
        position: relative;
        display: inline-flex;
        align-items: center;
        padding: 9px 12px;
        margin: 0;
        border-radius: var(--radius-pill);
        background: rgba(var(--ac-overlay-white), 0.07);
    }
    .cams-card-buy .cams-card-link::after {
        content: "";
        position: absolute;
        inset: -5px -2px;
    }
}
/* Dentro de la tabla de la comparativa, el `margin-top` de los enlaces sobra:
   ahí no van detrás de un párrafo (como en la tarjeta) sino solos en su
   celda, así que el margen los dejaba descolgados respecto al rótulo de su
   fila. Lo reportó el usuario el 2026-08-18 en «Dónde comprarla», pero
   medido resultó que las descolgadas eran TODAS las filas de enlaces (23px)
   y la de compra solo 11 — o sea que quitarlo solo ahí habría dejado dos
   desfases distintos en la misma tabla. Se quita en las dos. */
.cams-compare-table .cams-card-buy,
.cams-compare-table .cams-card-link {
    margin-top: 0;
}
/* La tarjeta destacada al llegar desde un «Mira también». Dura 1,6 s: lo
   justo para encontrarla con la vista después del scroll. */
.cams-card--destacada {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.cams-card:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}
.cams-missing {
    margin-top: 24px;
    padding-top: 16px;
    border-top: 1px solid var(--border-subtle);
    color: var(--text-faint);
    font-size: 0.8rem;
    line-height: 1.5;
    text-wrap: pretty;
}
.cams-missing p {
    margin: 0;
}

/* ── Comparativa ───────────────────────────────────────────────────────────
   Pedido el 2026-08-08: «un portal donde poder elegir las cámaras y ver los
   valores técnicos comparados, y algo que no sea totalmente técnico». Mismo
   patrón de bandeja + modal que ya existe en Recetas (`recipes/compare.js`,
   `recipes.css`) — de ahí salen los tokens `--z-overlay`/`--z-compare-modal`,
   ya pensados como genéricos («bandeja comparar», no «bandeja recetas») — pero
   con CSS propio en vez de compartir las reglas: `recipes.css` solo se carga
   bajo demanda en escritorio (ver el comentario de service-worker.js), y
   Cámaras existe también en móvil, así que las clases no podían vivir allí.

   El modal reutiliza a propósito el ARMAZÓN de `.filter-sheet` (mismo patrón
   que `crearSheet()` en overlay-utils.js: trampa de foco, Escape, clic en el
   fondo, bloqueo de scroll) en vez de reescribirlo — solo se le da otro
   tamaño y lo centra en vez de anclarlo abajo. */
.cams-compare-btn:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}
/* Puntero táctil: es el único control de verdad pulsable en la tira de
   píldoras de CADA tarjeta —hasta 165 veces en la misma pantalla, el que
   más se toca de toda Cámaras— así que pide el mismo mínimo de 44px que el
   botón de filtros o el interruptor de vista, no los 28px por defecto de
   `.ac-icon-btn--s` (pensados para acciones sueltas de baja frecuencia,
   como quitar una etiqueta o cerrar un modal ya centrado bajo el dedo). */
@media (pointer: coarse) {
    .cams-compare-btn {
        width: 44px;
        height: 44px;
        min-width: 44px;
        min-height: 44px;
    }
}
/* Puntero táctil: es el único control de verdad pulsable en la tira de
   píldoras de CADA tarjeta —hasta 165 veces en la misma pantalla, el que
   más se toca de toda Cámaras— así que pide el mismo mínimo de 44px que el
   botón de filtros o el interruptor de vista, no los 28px por defecto de
   `.ac-icon-btn--s` (pensados para acciones sueltas de baja frecuencia,
   como quitar una etiqueta o cerrar un modal ya centrado bajo el dedo). */
.cams-compare-tray {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: var(--z-overlay);
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    padding: 10px 20px calc(14px + env(safe-area-inset-bottom, 0px));
    background: var(--bg-surface);
    border-top: 1px solid var(--accent-dim);
    box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.3);
}
.cams-compare-tray[hidden] {
    display: none;
}
@media (prefers-reduced-motion: no-preference) {
    .cams-compare-tray {
        animation: camsCompareTrayIn 0.2s var(--ease-out);
    }
    @keyframes camsCompareTrayIn {
        from { transform: translateY(100%); opacity: 0.6; }
        to { transform: translateY(0); opacity: 1; }
    }
}
.cams-compare-tray-header {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}
.cams-compare-tray-title {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-faint);
}
.cams-compare-tray-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    flex: 1;
    min-width: 0;
}
.cams-compare-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    background: rgba(var(--accent-rgb), 0.14);
    border-radius: var(--radius-pill);
    padding: 4px 6px 4px 10px;
    max-width: 200px;
}
.cams-compare-chip-label {
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--accent);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.cams-compare-chip-remove {
    font-size: 0.85rem;
    line-height: 1;
    padding: 2px 3px;
}
.cams-compare-open-btn {
    flex-shrink: 0;
    white-space: nowrap;
}

/* Reportado en un móvil real (Brave/Android, 390px): con tres o más fichas
   elegidas, los chips ocupaban dos líneas dentro de `.cams-compare-tray-chips`
   sin que la fila EXTERIOR llegara a enterarse —seguía cabiendo en una sola
   línea de flexbox, `min-width: 0` se lo permitía— así que el botón «Ver
   comparativa» quedaba centrado verticalmente contra esas dos líneas en vez
   de debajo, tapando el chip de en medio. Se arregló apilando la bandeja en
   tres franjas —cabecera, chips, botón— cada una en su propia fila.

   Primer intento de la fila de chips: una tira que se desliza en horizontal
   sin envolver nunca. Segundo reporte, con 4 elegidas: «ya no las puedo
   ver» — el deslizamiento funcionaba, pero sin ninguna pista visual (barra
   de scroll, difuminado en el borde) parecía que el resto de chips
   simplemente no estaban. Ahora que la fila de chips vive SOLA en su propia
   franja —ya no compite por sitio con el botón, que está en la suya debajo—
   envolver a varias líneas es seguro otra vez: la bandeja crece hacia
   arriba lo que haga falta y las cuatro se ven de un vistazo, sin gesto
   alguno. */
@media (max-width: 700px) {
    .cams-compare-tray {
        flex-direction: column;
        align-items: stretch;
        gap: 8px;
        padding: 10px 16px calc(12px + env(safe-area-inset-bottom, 0px));
    }
    .cams-compare-open-btn {
        width: 100%;
    }
}

/* El modal: `.filter-sheet`/`.filter-sheet-backdrop`/`.filter-sheet-panel` dan
   la carcasa (ver inventory.css); aquí solo lo que cambia frente a un sheet
   normal — centrado en vez de anclado abajo, y bastante más ancho, porque
   lleva una ficha técnica entera por columna en vez de cinco `<select>`. */
.cams-compare-modal {
    align-items: center;
    padding: 16px;
    z-index: var(--z-compare-modal);
}
.cams-compare-panel {
    width: 100%;
    max-width: 1180px;
    max-height: 90vh;
    border-radius: 14px;
    border-bottom: 1px solid var(--border);
    /* Recorta a la curva del panel. Sin esto el redondeo existe pero no se
       ve por abajo: la primera columna de la tabla es `position: sticky` con
       fondo opaco propio, así que la última fila pintaba su rectángulo por
       encima de la esquina inferior izquierda y la dejaba en pico —
       reportado por el usuario con captura el 2026-08-18. No rompe el
       sticky: su contenedor de scroll es `.cams-compare-table-wrap`, que
       está por dentro de este. */
    overflow: hidden;
    animation: acConfirmIn 0.24s var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
    .cams-compare-panel {
        animation: none;
    }
}
.cams-compare-modal-header {
    display: flex;
    /* `flex-start` y no `center`: el bloque del título lleva ahora dos líneas
       (título + qué significan las estrellas) y la X debe quedarse arriba,
       alineada con el título, no centrada contra las dos. */
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 16px 12px;
    border-bottom: 1px solid var(--border-subtle);
    flex-shrink: 0;
}
.cams-compare-modal-heading {
    min-width: 0;
}
.cams-compare-modal-header h2 {
    font-size: 1.05rem;
    font-weight: 700;
}
.cams-compare-modal-note {
    margin-top: 3px;
    font-size: 0.78rem;
    line-height: 1.35;
    color: var(--text-muted);
    text-wrap: pretty;
}
.cams-compare-columns {
    display: flex;
    overflow-x: auto;
    overflow-y: auto;
    flex: 1;
    min-height: 0;
    -webkit-overflow-scrolling: touch;
}
.cams-compare-col {
    flex: 1;
    min-width: 280px;
    display: flex;
    flex-direction: column;
    border-right: 1px solid var(--border-subtle);
}
.cams-compare-col:last-child {
    border-right: none;
}
.cams-compare-col-head {
    padding: 14px 16px 12px;
    border-bottom: 1px solid var(--border-subtle);
    flex-shrink: 0;
}
.cams-compare-col-head-top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 8px;
}
.cams-compare-col-name {
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    margin: 2px 0 0;
}
.cams-compare-col-badges {
    margin-top: 8px;
}
/* Las estrellas de móvil: mismo `.cam-fgrid` que el resto de la ficha técnica,
   con un borde de acento para que se note que es el resumen «de un vistazo»
   antes de leer nada más. Solo aparece si hay al menos un dato con el que
   rankear — ver `columnaComparativa()`. */
.cams-compare-col-stars {
    padding: 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}
/* `minmax(0, 1fr)` y no el `1fr 1fr` heredado de `.cam-fgrid`: por defecto
   una celda de rejilla tiene `min-width: auto`, o sea que NO baja de su
   contenido mínimo — y rótulos como «Sensibilidad ★ = más ISO nativo» son
   más anchos que media columna de móvil. Resultado medido a 375px: la
   columna se desbordaba 87px por la derecha. Es anterior a «En qué gana»
   (comprobado midiendo en el commit de antes), solo que esa fila, al ocupar
   el ancho entero, lo hacía más visible. Va acotado a la comparativa: el
   mismo `.cam-fgrid` lo usa la ficha lateral de Inventario, que cabe de
   sobra y no hay por qué tocar. */
.cams-compare-col .cam-fgrid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}
.cams-compare-col .cam-k,
.cams-compare-col .cam-v {
    min-width: 0;
    overflow-wrap: anywhere;
}
.cams-compare-col-body {
    padding: 14px 16px calc(18px + env(safe-area-inset-bottom, 0px));
    display: flex;
    flex-direction: column;
}
/* En una columna las tres franjas de texto largo (cómo es de usar / cuándo
   elegirla / qué mirar) pesan más que en la tarjeta normal —no hay acordeón
   que las esconda— así que aquí sí llevan su propio espaciado en vez de
   heredar el `margin-top: 12px` pensado para ir una detrás de otra en una
   ficha ya abierta. */
.cams-compare-col-body > * {
    margin-top: 14px;
}
.cams-compare-col-body > *:first-child {
    margin-top: 0;
}
.cams-compare-tabs {
    display: none; /* de sitio en el DOM siempre; visible solo en móvil */
}
/* Reportado en un móvil real (Brave/Android): deslizar en horizontal entre
   columnas de 82vw dejaba SIEMPRE un trozo de dos fichas a la vez a medio
   camino, sin nada que dijera «estás viendo la 2 de 3» ni dónde parar. Por
   debajo de 700px se cambia el mecanismo entero: una sola columna a pantalla
   completa, y unas pestañas arriba —mismo patrón que ya usaba Recetas para
   cambiar de foto en su propia comparativa, aquí para cambiar de cámara— en
   vez de esperar a que el dedo encuentre el sitio exacto donde soltar. */
@media (max-width: 700px) {
    .cams-compare-tabs {
        display: flex;
        gap: 6px;
        overflow-x: auto;
        padding: 10px 16px;
        border-bottom: 1px solid var(--border-subtle);
        flex-shrink: 0;
        -webkit-overflow-scrolling: touch;
    }
    .cams-compare-tab {
        flex-shrink: 0;
    }
    .cams-compare-columns {
        overflow-x: hidden; /* ya no se desliza: las pestañas cambian de cámara */
    }
    .cams-compare-col {
        min-width: 100%;
        border-right: none;
    }
    .cams-compare-col.is-inactive-movil {
        display: none;
    }
}

/* ── La tabla de escritorio ────────────────────────────────────────────────
   Pedido el 2026-08-08: «con 4 cámaras (el máximo) sale mal la tabla...
   quiero algo más atractivo». Las cuatro fichas en columna de la vuelta
   anterior (`.cams-compare-col`, pensada primero para el modo pestañas de
   móvil) no cabían: a 280px mínimo cada una, cuatro pasaban de sobra el
   ancho del panel y la última se cortaba contra el borde sin ninguna pista
   de que hubiera más. Aquí no hay ese problema: cada valor es una línea o
   dos, no una ficha entera, así que caben las cuatro sin desbordar. Solo
   existe en escritorio (`.cams-compare-columns`/`.cams-compare-tabs` se
   apagan ahí abajo); en móvil sigue el mecanismo de pestañas de la sección
   de arriba, que ya funciona bien. */
.cams-compare-table-wrap {
    display: none;
}
@media (min-width: 701px) {
    .cams-compare-columns,
    .cams-compare-tabs {
        display: none;
    }
    .cams-compare-table-wrap {
        display: block;
        flex: 1;
        min-height: 0;
        overflow: auto;
        -webkit-overflow-scrolling: touch;
    }
}
.cams-compare-table {
    width: 100%;
    border-collapse: collapse;
    table-layout: fixed;
    /* `table { white-space: nowrap; }` (inventory.css, para SU tabla) se
       hereda en cualquier tabla de la app, esta incluida — sin este reset
       explícito, cada `td` de aquí abajo peleaba esa herencia por su cuenta
       con `text-wrap: pretty`, y esa pelea (invalidar solo la sub-propiedad
       de ajuste de línea sobre una base heredada en nowrap, no partir de un
       `white-space: normal` limpio) es lo que dejaba medidas de línea mal
       calculadas — reportado por el usuario como texto de dos columnas
       superpuesto, sin que las celdas mismas estuvieran mal posicionadas
       (medido con getBoundingClientRect(): sin solape en la caja, sí en lo
       pintado). Cortar la herencia aquí, en la tabla, deja que `text-wrap:
       pretty` de cada celda parta de una base limpia. */
    white-space: normal;
}
.cams-compare-table thead th:first-child,
.cams-compare-table tbody th[scope="row"] {
    width: 168px;
}
.cams-compare-table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--bg-surface);
    padding: 14px;
    text-align: left;
    vertical-align: top;
    border-bottom: 1px solid var(--border);
}
.cams-compare-table thead th:first-child {
    left: 0;
    z-index: 3;
}
.cct-head-top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 8px;
}
.cct-head-name {
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    margin: 2px 0 0;
}
.cct-head-badges {
    margin-top: 8px;
}
.cams-compare-table tbody th[scope="row"] {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--bg-surface);
    text-align: left;
    vertical-align: top;
    padding: 10px 14px;
    font-size: 0.68rem;
    font-weight: 700;
    color: var(--text-faint);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    border-right: 1px solid var(--border-subtle);
    border-bottom: 1px solid var(--border-subtle);
}
.cams-compare-table td {
    padding: 10px 14px;
    vertical-align: top;
    font-size: 0.85rem;
    line-height: 1.55;
    color: var(--text-main);
    border-bottom: 1px solid var(--border-subtle);
    text-wrap: pretty;
}
.cct-section-row th {
    position: sticky;
    left: 0;
    background: var(--bg-raised);
    color: var(--text-faint);
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    padding: 8px 14px;
    text-align: left;
}
/* La fila de sección lleva su propio fondo detrás de TODA la fila (no solo
   la celda pegajosa), para que no parezca que el rótulo flota suelto al
   deslizar en horizontal. */
.cct-section-row {
    background: var(--bg-raised);
}
/* Las estrellas y, al lado, el dato real que las genera («★★★★★ Barata»):
   sin el dato el ranking hay que creérselo, y encima la dirección —si más
   estrellas es más barata o más cara— no se deduce de mirar. */
.cams-compare-stars {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 2px 7px;
}
.cams-compare-stars-glifos {
    font-size: 0.95rem;
    letter-spacing: 1px;
    color: var(--warn-color);
    white-space: nowrap;
}
.cams-compare-stars-valor {
    font-size: 0.85rem;
    color: var(--text-muted);
}
/* En el rótulo de la fila, debajo del nombre: «★ = más barata». Es la mitad
   del arreglo — el dato de al lado dice cuánto, y esto dice hacia dónde.
   
   SIN `nowrap` (2026-08-18, reportado por el usuario: «en Autonomía aparece
   más disparos por carga…»). Lo llevaba, y mientras la columna podía
   ensancharse a su medida no se notaba — pero ensanchándose era justo lo
   que desbordaba la columna 87px en móvil. Al acotarla con `minmax(0, 1fr)`
   el texto dejó de caber y, al no poder partir línea, se cortaba: el
   arreglo de un síntoma destapó el otro. El rótulo más largo («★ = más
   disparos por carga») necesita 144px y la columna da 108, así que en móvil
   TIENE que poder ocupar dos líneas. */
.cams-compare-stars-dir {
    display: block;
    margin-top: 2px;
    font-size: 0.68rem;
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
    color: var(--text-faint);
    text-wrap: pretty;
}
.cams-compare-stars--sin-dato {
    display: block;
    color: var(--text-faint);
    font-size: 0.85rem;
    letter-spacing: normal;
}

/* ── Lo propio de Películas ───────────────────────────────────────────────── */
/* Icono junto al nombre para distinguir blanco y negro de color sin leer
   nada. Pidió el usuario el 2026-08-07 «algún icono o algo». */
.films-name-row {
    display: flex;
    align-items: baseline;
    gap: 6px;
    margin-top: 2px;
}
.films-name-row .catalog-card-name {
    margin-top: 0;
}
.films-type-icon {
    display: inline-flex;
    flex-shrink: 0;
}
.films-type-icon svg {
    width: 13px;
    height: 13px;
}
/* Contraste, saturación, tono y precio: el resumen de un vistazo, gemelo de
   `.cams-badges`. Nace el 2026-08-07, a petición del usuario. */
.films-badges {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 8px;
}
/* Misma distinción que `.cams-badge-avail`: no describe la película, avisa
   de dónde vas a poder comprarla. */
.films-badge-avail {
    border-color: var(--accent-dim);
    color: var(--accent);
}
.films-card-character {
    margin: 12px 0 0;
    font-size: 0.88rem;
    line-height: 1.55;
    color: var(--text-main);
    text-wrap: pretty;
}
.films-card-sheet.ac-roll-film-info {
    background: none;
    padding: 0;
    margin: 0;
}
/* Lo que has disparado tú. Va con el acento porque es el dato que convierte el
   catálogo en TU catálogo; el «todavía no la has probado» se atenúa, que es una
   ausencia y no un logro. */
.films-card-mine {
    margin: 10px 0 0;
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--accent);
}
.films-card-mine--nueva {
    font-weight: 400;
    color: var(--text-faint);
}
.films-card-latitude {
    margin: 10px 0 0;
    font-size: 0.8rem;
    color: var(--text-muted);
}
.films-card-latitude .cam-k {
    display: inline;
    margin-right: 6px;
}

/* ── Reciprocidad ────────────────────────────────────────────────────────────
   El último bloque de la ficha (2026-08-26), y el más técnico: cuánto hay que
   alargar de verdad una exposición larga con ESTA película. Se separa con una
   línea porque cambia de registro —hasta aquí la tarjeta habla de cómo se ve,
   y esto son números que se copian al cronómetro—, con el mismo `border-top`
   fino que ya separa la guía de situaciones dentro de una ficha de cámara.
   Los chips reusan `.cam-sit-num` (inventory.css): mismo dato, misma forma. */
.films-card-recip {
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px solid var(--border-subtle);
}
.films-recip-lead,
.films-recip-note,
.films-recip-nodata {
    margin: 6px 0 0;
    max-width: 78ch;
    line-height: 1.5;
    text-wrap: pretty;
}
.films-recip-lead,
.films-recip-nodata {
    font-size: 0.8rem;
    color: var(--text-muted);
}
.films-recip-note {
    font-size: 0.78rem;
    color: var(--text-faint);
}
/* En fila y con salto: son siete pares cortos, y en lista vertical ocuparían
   media tarjeta para decir lo mismo. */
.films-recip-list {
    display: flex;
    flex-wrap: wrap;
    gap: 5px 6px;
    list-style: none;
    margin: 8px 0 0;
    padding: 0;
}
/* La fuente, en la letra más pequeña de la ficha: se lee cuando se busca, no
   mientras se mira la tabla. Pero se lee — es un número con el que se sale de
   noche, y en un `title` no existiría en el móvil. */
.films-recip-src {
    margin: 8px 0 0;
    font-size: 0.7rem;
    line-height: 1.45;
    color: var(--text-faint);
    text-wrap: pretty;
}


/* ── Los catálogos en móvil ──────────────────────────────────────────────────
   Medido en un 390×844 antes de tocar nada: en Películas la entradilla ocupaba
   231px y el bloque de filtros 460px, así que la primera película empezaba en
   el píxel 824 — una pantalla entera de preámbulo. Los filtros pasan a
   plegarse y las explicaciones largas también. En Cámaras el problema sería
   peor (seis grupos de chips en vez de cuatro), así que nace ya con el sheet. */
@media (max-width: 1080px) {
    /* El botón de filtros es el del inventario (`.filter-sheet-btn`, redondo
       de 44px con su insignia); allí `display:none` en escritorio y `flex` en
       móvil lo decide inventory.css, y aquí vale igual. Los chips se van al
       sheet, así que su hueco en la página no debe ocupar nada. */
    .catalog-filters-btn {
        display: inline-flex;
        margin-bottom: 18px;
    }
    #filmsFiltersHost,
    #camsFiltersHost {
        display: none;
    }
    .catalog-page {
        padding-bottom: 32px;
    }
}

/* ── Gráfica año × precio de Cámaras ──────────────────────────────────────────
   Pedida el 2026-08-09. Puntos colocados en porcentaje sobre un lienzo, no
   SVG: es la técnica que ya usa el gráfico de Inventario. El eje Y son las
   cinco bandas de precio —el catálogo no guarda euros— y por eso las etiquetas
   van con su tramo en el `title`, igual que los chips del filtro. */
#camsChartHost {
    /* La mitad de `.cams-chart-dot` (10px). En una variable porque lo usan
       dos reglas —el lienzo y el eje X— y tienen que moverse juntas: si el
       eje X no reservara lo mismo, la última etiqueta de año se separaría
       del punto al que corresponde. */
    --cams-chart-radio-punto: 5px;
    /* Reportado por el usuario el 2026-08-15: sin este margen, el último
       elemento pintado dentro (la leyenda, o el aviso de «sin puntos») toca
       directamente `.catalog-view-toolbar` — medido en el navegador, el
       hueco real era 0px. Va en el host y no en `.cams-chart-legend` porque
       el hueco hace falta también cuando lo último pintado es el estado
       vacío (`.catalog-empty`, compartida con otras pantallas, sin margen
       propio a propósito). */
    margin-bottom: 14px;
}
.cams-chart-intro {
    font-size: 0.82rem;
    color: var(--text-muted);
    margin-bottom: 10px;
    text-wrap: pretty;
}
.cams-chart {
    display: flex;
    gap: 10px;
    height: 260px;
    /* Sitio para la media bola que sobresale por la derecha. Los puntos se
       colocan en porcentaje sobre el lienzo y van centrados sobre su
       coordenada (`translate(-50%, 50%)`), así que los del año más nuevo
       caen en `left: 100%` y se salen justo su radio — medido a 375px: 5px
       de desbordamiento en `#camsChartHost`, los de 2026. No se arregla con
       `overflow: hidden` porque eso los cortaría por la mitad en vez de
       dejarles sitio, ni moviendo la escala, que mentiría sobre el año. */
    padding-right: var(--cams-chart-radio-punto);
}
.cams-chart-y {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    flex-shrink: 0;
    /* Las etiquetas marcan la línea, no el hueco: media línea de desplazamiento
       las deja centradas sobre su banda en vez de sobre el espacio de al lado. */
    margin: -0.5em 0;
    text-align: right;
}
.cams-chart-y-label {
    font-size: 0.68rem;
    line-height: 1;
    color: var(--text-faint);
    white-space: nowrap;
}
.cams-chart-plot {
    position: relative;
    flex: 1;
    min-width: 0;
    border-left: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
}
.cams-chart-grid {
    position: absolute;
    left: 0;
    right: 0;
    height: 1px;
    background: var(--border-subtle);
}
.cams-chart-dot {
    position: absolute;
    width: 10px;
    height: 10px;
    padding: 0;
    border: none;
    border-radius: 50%;
    /* Colocado por su centro: sin esto los puntos de los extremos se salen
       media bola por el borde del lienzo. */
    transform: translate(-50%, 50%);
    cursor: pointer;
    background: var(--accent);
    opacity: 0.75;
    transition: transform 0.12s, opacity 0.12s;
}
.cams-chart-dot.is-digital {
    background: var(--accent);
}
.cams-chart-dot.is-analogica {
    background: var(--warn-color);
}
/* Las tuyas: el mismo verde que «Ya la tienes» en la tarjeta, y con anillo
   para que no dependan solo del color. */
.cams-chart-dot.is-tuya {
    background: var(--ok-color);
    opacity: 1;
    box-shadow: 0 0 0 2px var(--bg-surface), 0 0 0 3.5px var(--ok-color);
}
.cams-chart-dot:hover,
.cams-chart-dot:focus-visible {
    transform: translate(-50%, 50%) scale(1.6);
    opacity: 1;
    z-index: 2;
}
.cams-chart-dot:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.cams-chart-x {
    display: flex;
    justify-content: space-between;
    font-size: 0.68rem;
    color: var(--text-faint);
    margin-top: 4px;
    /* Alineado con el lienzo, no con la caja entera: a la izquierda está el
       eje Y, que no forma parte de la escala de años; a la derecha, el mismo
       hueco que `.cams-chart` le deja a la media bola del punto más nuevo. */
    padding-left: calc(4.5em + 10px);
    padding-right: var(--cams-chart-radio-punto);
}
.cams-chart-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 16px;
    margin-top: 10px;
    font-size: 0.75rem;
    color: var(--text-muted);
}
.cams-chart-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 12px;
}
/* En la leyenda los puntos no están colocados sobre un lienzo: se les quita
   el posicionamiento absoluto y la corrección de centro. */
.cams-chart-legend .cams-chart-dot {
    position: static;
    transform: none;
    cursor: default;
    flex-shrink: 0;
}

/* «En qué gana» cierra la sección «De un vistazo» de la comparativa
   (2026-08-18): resume las filas de estrellas de encima diciendo en qué
   categorías se lleva la máxima cada cámara. Se separa con una línea porque
   no es una fila más — es la lectura de las anteriores. */
.cams-compare-victorias > * {
    border-top: 1px solid var(--border-subtle);
    padding-top: 8px;
}
.cams-compare-victorias .cam-v,
.cams-compare-victorias td {
    color: var(--ok-tx);
}

/* «Solo las diferencias» (2026-08-18). Comparando dos cámaras parecidas casi
   la mitad de las filas dicen lo mismo en todas (medido: 9 de 23 entre la
   Lumix S1RII y la S1II), y lo que se busca al comparar son las otras.
   Esconder por CSS y no repintando: las filas iguales ya vienen marcadas
   desde `pintarTablaComparativa()`. */
.cams-compare-solo-dif {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
    font-size: 0.78rem;
    color: var(--text-muted);
    cursor: pointer;
    user-select: none;
}
.cams-compare-solo-dif input {
    cursor: pointer;
    accent-color: var(--accent);
}
.cams-compare-table-wrap.solo-diferencias tr.is-igual {
    display: none;
}
/* Solo tiene sentido donde se ven las columnas a la vez. Por debajo de
   701px la comparativa va por pestañas —una ficha cada vez— y esconder
   filas ahí quitaría información sin nada con lo que contrastarla. */
@media (max-width: 700px) {
    .cams-compare-solo-dif {
        display: none;
    }
}
