/* ============================================================================
   Augurea · catalog.css — catalog / search listing (filters, facets, sort, rail).
   Migrated from the legacy stylesheet with the Slice-4 palette (gold breadcrumbs → warm-grey,
   focus rings → burgundy). Product grid + card + chips are shared (components.css).
   ============================================================================ */

/* ---- folio head (owner-flagged parity 2026-07-19): the page eyebrow (.cat-crumb,
   already mono/small/muted — functions as the eyebrow) + h1 now close on a
   hairline rule and a REAL mono result count, instead of silently carrying an
   unused `total` context var. `total` is page.paginator.count from the Django
   view (catalog_list/search_page) — server-rendered, correct on first paint
   with zero JS, honest at 0 results too. The Alpine .cat-bar count below is
   left exactly as shipped (it re-fires on client-side filter/sort/search and
   was never part of this section head). ---- */
.cat-folio-rule{height:1px;background:var(--line-2);margin:18px 0 12px}
.cat-folio-count{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--warm-grey)}
.cat-folio-n{color:var(--ink);font-weight:500}
.cat-head{padding-block:var(--page-top) 8px}
.cat-crumb{font-size:11px;letter-spacing:.04em;color:var(--warm-grey)}
.cat-title{margin:14px 0 6px}
.cat-more{text-align:center;margin-top:40px}
.cat-more-btn{padding:14px 28px;cursor:pointer}

.cat-search input{width:100%;max-width:520px;font-family:var(--mono);font-size:14px;padding:13px 16px;border:1px solid var(--line-ui);background:var(--surface);margin-bottom:18px}
.cat-search input:focus{outline:2px solid var(--burgundy);outline-offset:2px}

.cat-grid{display:grid;grid-template-columns:248px 1fr;gap:40px;align-items:start}

/* Results grid density: the catalog results column is narrower than the home grid (the 248px facet
   rail eats width), so the shared .grid's 4 fixed columns cramp cards to ~146px at 1024 and wrap the
   2 note-chips. Floor the card width at 200px (rail-present desktop only) so columns auto-reduce to 3
   in the ~1001-1180 band and 4 only when there's room; keeps 2 chips on one row + uncramps name/image.
   Below 1001px the rail is a drawer and the components.css .grid 3/2-col rules apply unchanged.
   Upper-bounded at 1599px (see the >=1600 override below) — this rule used to have NO ceiling, which
   silently out-specificitied components.css's own >=1600 wide-screen rule (`.cat-grid .grid` beats
   bare `.grid` regardless of source order), so /catalogo/ never actually got the 290px floor the
   2026-07-25 perf wave documented verifying — it kept rendering 200px/13-column cards at 3840px. */
@media (min-width:1001px) and (max-width:1179px){.cat-grid .grid{grid-template-columns:repeat(auto-fill,minmax(200px,1fr))}}
@media (min-width:1180px) and (max-width:1599px){.cat-grid .grid{grid-template-columns:repeat(auto-fill,minmax(240px,1fr))}}

/* F2-catalogo-css-mas-especifico-neutraliza-290px (ATLAS-FIX-03, 2026-07-26): the ONE >=1600px rule
   that governs BOTH /catalogo/ and every other .grid page — same var(--grid-wide-floor) (tokens.css)
   and same auto-fit (F1-auto-fill-huecos-vacios) as components.css's own >=1600 rule, so a filtered
   result with few real cards never opens the same empty-track gap here that home's grid had. */
@media (min-width:1600px){
  .cat-grid .grid{grid-template-columns:repeat(auto-fit,minmax(var(--grid-wide-floor),1fr))}
}

/* Región viva de las facetas (QA-F4, 2026-09-06; movida fuera del cajón por
   C-F4-AX el mismo día). Misma receta que .soon-sr-only (static/css/soon.css):
   fuera de la vista pero NO fuera del árbol de accesibilidad. La posición
   absoluta es lo que la hace viable como región permanente — un hijo posicionado
   en absoluto no es un ítem de rejilla ni abre hueco donde se monte. Ahora cuelga
   de .cat-facets-alert, entre el botón FILTRAR y la rejilla, y no de .rail: un
   ancestro en display:none (el cajón cerrado en móvil, regla .rail más abajo) se
   lleva por delante a sus descendientes por muy `clip`ados que estén. */
.cat-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0}
.cat-facets-alert{position:relative}
/* El aviso VISIBLE de facetas es sólo de móvil: en escritorio el rail está siempre
   desplegado y su propio panel .facet ya lo dice, y dos avisos serían redundantes.
   display:none base + display:flex en el tramo del cajón; x-show sigue mandando
   por encima con su display:none en línea mientras facetsError sea falso. */
.cat-facets-note{display:none}

/* filter rail */
.rail{position:sticky;top:88px;border-top:1px solid var(--burgundy);padding-top:6px;max-height:calc(100vh - 104px);overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--line-2) transparent}
.rail-h{font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink);padding:12px 0}
.facet{border-top:1px solid var(--line);padding:14px 0}
.facet-h{font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--warm-grey);margin-bottom:10px}
/* Layer explainer (owner walkthrough 2026-07-23, R3) — quiet register, no new
   colors: reuses --warm-grey, sits between the (uppercase, tracked) title and
   the option list, margin pulled up so it reads as one caption block. */
.facet-sub{font-size:11px;letter-spacing:.01em;color:var(--warm-grey);margin:-6px 0 10px;line-height:1.35}
/* 4 columnas desde el herbario del panel (2026-07-24): casilla · GLIFO · etiqueta ·
   cifra. La columna del glifo es de ancho FIJO y existe siempre, tenga figura la fila
   o no — así las etiquetas arrancan todas en la misma vertical y la ausencia de una
   figura curada (ley de honestidad) no descuadra la lista. Antes eran 3 columnas y el
   <svg> extra empujaba la cifra a una segunda línea: cazado mirando la captura, no el
   markup. */
.facet-opt{display:grid;grid-template-columns:auto 14px 1fr auto;align-items:center;gap:9px;padding:6px 0;cursor:pointer;font-size:13px;min-height:32px}
.facet-opt>.facet-glifo{grid-column:2;margin-right:0}
.facet-opt>.facet-lbl{grid-column:3}
.facet-opt>.facet-n{grid-column:4}
.facet-opt input{width:15px;height:15px;accent-color:var(--burgundy);cursor:pointer}
.facet-lbl{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.facet-n{font-size:11px;color:var(--warm-grey)}
/* show-more/less toggle for long facets (House at 85 options over-ran the rail) */
.facet-more{display:inline-flex;align-items:center;gap:5px;margin-top:6px;padding:5px 0;
  background:none;border:0;cursor:pointer;font-family:var(--mono);font-size:11px;
  letter-spacing:.06em;text-transform:uppercase;color:var(--burgundy);min-height:32px}
.facet-more:hover{color:var(--burgundy-deep);text-decoration:underline}

/* NOTAS facet search-within-facet box (owner ruling 2026-07-19b: note-level
   filtering). font-size is 16px, NOT the 14px .cat-search uses — below 16px iOS
   Safari zooms the viewport on focus (the owner tests on a phone; that zoom-trap
   is exactly the kind of mobile friction this facet exists to remove). */
.facet-search{width:100%;font-family:var(--mono);font-size:16px;padding:9px 10px;
  border:1px solid var(--line-ui);background:var(--surface);margin-bottom:8px;min-height:38px}
.facet-search:focus{outline:2px solid var(--burgundy);outline-offset:2px}
.facet-empty{padding:6px 0;font-size:11px;color:var(--warm-grey)}

/* Autocomplete facets (Nota / Casa — catalog-filters IA reorg, 2026-07-22
   audit): the search box above renders a dropdown of clickable suggestion
   rows instead of a permanently-visible checkbox wall. `.facet-suggestion`
   is a plain button (not a checkbox) so a very long token list (~400 notes,
   88 houses) is only ever a few rows tall at once — selection state reads
   from `.is-sel`, not a checked input. */
.facet-autocomplete .facet-suggestion{
  /* Misma retícula de cuatro columnas que .facet-opt: hueco de casilla,
     glifo, etiqueta y cifra; las etiquetas comparten el mismo eje. */
  display:grid;grid-template-columns:15px 14px 1fr auto;align-items:center;gap:9px;width:100%;
  padding:7px 8px 7px 0;margin:0 0 2px;border:1px solid transparent;background:none;
  cursor:pointer;font-family:var(--sans);font-size:13px;text-align:left;min-height:32px}
.facet-autocomplete .facet-suggestion>.facet-glifo{grid-column:2;margin-right:0}
.facet-autocomplete .facet-suggestion>.facet-lbl{grid-column:3}
.facet-autocomplete .facet-suggestion>.facet-n{grid-column:4}
.facet-autocomplete .facet-suggestion:hover,.facet-autocomplete .facet-suggestion:focus-visible{background:var(--cream-2,#EBEBEB)}
.facet-autocomplete .facet-suggestion.is-sel{border-color:var(--burgundy)}
.facet-autocomplete .facet-suggestion.is-sel .facet-lbl{color:var(--burgundy);font-weight:500}
.facet-autocomplete .facet-suggestion.is-sel .facet-n{color:var(--burgundy)}

/* "Más filtros" secondary section (Casa autocomplete + País compact + Género —
   refinement, not discovery; see views._secondary_facet_groups). Same visual
   weight as .rail-h, set slightly apart with its own top rule. */
.rail-more-h{font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink);
  padding:16px 0 4px;margin-top:8px;border-top:1px solid var(--burgundy)}

.rail-more-h:not(:has(~ .facet:not([style*="display: none"]))){display:none}

/* Momento sugerido (estación/día·noche) editorial tag — audit: these are
   editorial estimates (season_*/day_share ficha fields), never a raw vote or
   a hard measurement, so the facet says so beside its own title. */
.facet-editorial-tag{font-size:9px;letter-spacing:.08em;color:var(--warm-grey);
  border:1px solid var(--line-2);border-radius:2px;padding:2px 5px;vertical-align:middle}

/* price range */
.price-row{display:flex;align-items:center;gap:8px}
.price-row input{width:74px;font-family:var(--mono);font-size:13px;padding:8px 9px;border:1px solid var(--line-ui);background:var(--surface);min-height:38px}
.price-sep{color:var(--warm-grey);font-size:12px}

/* mobile filters toggle: a collapsible rail drawer. Desktop keeps the sticky rail
   always visible, so the toggle is hidden there (shown only <=1000px below). */
.rail-toggle{display:none}

/* results bar + sort — flex-wrap + min-width:0 so the sort control drops to
   its own row instead of pushing the page past the viewport on narrow phones
   (the select's min-content width overflowed 11px at 320). */
.cat-bar{display:flex;align-items:center;justify-content:space-between;gap:10px 16px;margin-bottom:18px;flex-wrap:wrap}
.sort{display:flex;align-items:center;gap:9px;min-width:0;max-width:100%}
.sort>span{font-size:12px;color:var(--muted);letter-spacing:.06em;text-transform:uppercase;white-space:nowrap}
.sort select{font-family:var(--mono);font-size:12px;padding:9px 12px;border:1px solid var(--line-ui);background:var(--surface);cursor:pointer;min-height:40px;min-width:0;max-width:100%;color:var(--ink);transition:border-color var(--dur-fast) var(--ease-out)}

.sort select:hover,.sort select:focus-visible{border-color:var(--warm-grey)}

@media(max-width:1000px){
  .cat-grid{grid-template-columns:1fr}
  /* Collapsible drawer: hidden until the shopper opens it via .rail-toggle.
     visibility (not display:none) so the fade/slide can actually transition on
     open+close; visibility flips only after the fade-out finishes on close (delay
     matches --dur-mid), and instantly on open — same trick keeps closed facet
     inputs out of the tab order (no FX-6.x regression). */
  /* CAJÓN CERRADO = CERO ESPACIO (reparación 2026-07-24, owner: "la organización y
     armonización fue destruida"). Antes se cerraba con visibility:hidden, que oculta
     pero NO saca del flujo: en un teléfono quedaban ~1.200 px de vacío entre el botón
     FILTRAR y el primer producto — medido en captura real a 390 px. display:none
     colapsa el hueco Y saca los campos del orden de tabulación (mejor accesibilidad
     que visibility). Se pierde el desvanecido al abrir; la composición correcta vale
     más que la transición. */
  .rail{position:static;display:none;grid-template-columns:repeat(2,1fr);gap:0 28px}
  .rail{max-height:none;overflow:visible}
  .facet-opt{min-height:44px}
  .rail.is-open{display:grid}
  .rail-h{grid-column:1/-1}
  .rail-toggle{display:flex;align-items:center;gap:10px;width:100%;justify-content:flex-start;
    padding:13px 16px;border:1px solid var(--line-2);background:var(--surface);cursor:pointer;
    font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink);
    min-height:48px;margin-bottom:16px}
  .rail-toggle-n{color:var(--burgundy)}
  .rail-toggle[aria-expanded="true"]{position:sticky;top:73px;z-index:6;box-shadow:0 1px 0 var(--line-2)}
  .rail-toggle-caret{margin-left:auto;font-size:0;transition:transform var(--dur-fast,.2s) var(--ease-out,ease);width:7px;height:7px;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:translateY(-2px) rotate(45deg)}
  .rail-toggle[aria-expanded="true"] .rail-toggle-caret{transform:translateY(2px) rotate(-135deg)}
  .cat-facets-note{display:flex;flex-wrap:wrap;align-items:center;gap:8px 10px;
    margin:-4px 0 16px;padding:11px 14px;border:1px solid var(--burgundy);
    background:var(--surface);font-size:12px;line-height:1.5;color:var(--ink)}
  .cat-facets-note-t{letter-spacing:.08em;text-transform:uppercase;color:var(--burgundy)}
  .cat-facets-note-b{color:var(--warm-grey)}
}
@media(max-width:600px){.rail.is-open{grid-template-columns:1fr}}
@media(max-width:560px){
  /* Una sola columna: a 390 px dos columnas dejaban cada opción en ~160 px, y la
     retícula de la fila (casilla · glifo · etiqueta · cifra) ya no cabía — ese era
     el desborde de 4 px que se veía en la captura. */
  .rail{grid-template-columns:1fr}
  .facet-opt{gap:8px}
  .facet-opt>.facet-lbl{min-width:0}
}
@media(prefers-reduced-motion:reduce){.rail-toggle-caret{transition:none}.rail{transition:none}}

/* ============================================================================
   Slice 4d beautification — catalog cards, grid entrance, skeletons, states,
   filter/chip polish. Consumes tokens only; single accent = --burgundy; gold
   lives ONLY on the .card-badge (Colección tier). All motion is transform/
   opacity and is disabled under prefers-reduced-motion (bottom of file).
   ============================================================================ */

/* ---- card hover: lift + shadow only (motion.css .card/.card .ph rules), no image
   zoom here. The zoom used to double up with the lift on this page while home cards
   (catalog.css not loaded there) only got the lift — two different hover languages
   on the same card component. Restrained-motion pass: ONE consistent card-hover
   effect across home + catalog, so the lift now carries the whole affordance. ---- */

/* ---- Colección tier badge (the only gold on the card). Its positioning moved to
   components.css (.card .card-tags stacking) in Slice 5 so it holds on the PDP /
   home too, where catalog.css is not loaded; only the gold look stays in the badge. ---- */

/* ---- name / notes / price hierarchy ---- */
.card .name{margin-bottom:6px}
.card .price{color:var(--warm-grey)}
.card-price{font-weight:500;color:var(--ink)}

/* ---- grid entrance: CSS-only staggered mount (Alpine/fetch-injected nodes the
   IntersectionObserver can't hook). Replays whenever results change; keyed items
   that persist across a sort don't re-fire. Stagger caps at the 12th card. ---- */
@keyframes cardIn{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
.catalog .grid .card:not(.skeleton){
  animation:cardIn var(--dur-mid) var(--ease-out) backwards;
  animation-delay:calc(min(var(--i,0),11) * 45ms);
}

/* ---- skeletons: complete the card shape (shimmer, not a spinner) ---- */
.catalog .skeleton .house,
.catalog .skeleton .name,
.catalog .skeleton .price{
  background:linear-gradient(100deg,var(--cream-2) 30%,var(--sk-1) 50%,var(--cream-2) 70%);
  background-size:200% 100%;animation:sh 1.2s infinite;border-radius:2px;
}
.catalog .skeleton .house{height:9px;width:38%;margin:12px 0 8px}
.catalog .skeleton .name{height:20px;width:72%;margin:0 0 10px}
.catalog .skeleton .price{height:12px;width:46%;border-top:0;padding:0;margin-top:8px}

/* ---- designed empty / error states (composed, on-brand, not bare text) ---- */
.state-msg{padding:76px 0}
.state-mark{width:64px;height:64px;margin:0 auto 22px;display:grid;place-items:center;
  font-family:var(--serif);font-size:32px;line-height:1;color:var(--burgundy);
  background:var(--surface);border:1px solid var(--line-2);border-radius:50%}
.state-msg p{max-width:40ch;margin-left:auto;margin-right:auto}
.state-cta{margin-top:22px}

/* ---- applied-filter chips: clearer remove affordance + active count ---- */
.chip .chip-x{font-size:12px;line-height:1;color:var(--warm-grey);transition:color var(--dur-fast)}
.chip:hover .chip-x{color:var(--burgundy)}

/* ---- filter rail: selected rows read on the single accent ---- */
.facet-opt:hover .facet-lbl{color:var(--burgundy)}
.facet-opt:has(input:checked) .facet-lbl{color:var(--burgundy);font-weight:500}
.facet-opt:has(input:checked) .facet-n{color:var(--burgundy)}
.rail-count{color:var(--warm-grey);font-weight:400}

/* ============================================================================
   p10 luxe port — owner-picked catalog treatment (prototypes/p10-catalogo-v2),
   ported onto the real SSR + Alpine-hydrated grid. Every rule below targets
   .catalog .grid .card, which both the SSR baseline (_card.html) and the
   Alpine template (list.html x-for) render, so nothing here forks behavior
   between the two. transform/opacity only; all killed under reduced motion
   at the bottom of this file. Skipped from the prototype: the living-cover
   hover cross-fade (needs a second "cover-b" image per product; the real
   card only ever renders one p.image) and the cinemagraph Ken-Burns band
   (needs a dedicated hero image not already rendered on this page).
   ============================================================================ */

/* ---- intro entrance: breadcrumb + title stagger-rise once on load (prototype
   Layer 1). Small, one-shot, page-load only — not the grid's scroll reveal. ---- */
@keyframes catIntroRise{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
.cat-crumb,.cat-title{animation:catIntroRise 500ms var(--ease-out) both}
.cat-title{animation-delay:90ms}

/* ---- grid entrance, scroll-driven (prototype Layer 2): progressive enhancement
   over the onload cardIn stagger above. Where the browser supports scroll-driven
   animations, each card instead reveals as it crosses into the viewport (true
   staggered reveal while scrolling a long results list), with a slight
   per-card offset over the first 12 cards mirroring the prototype's stagger.
   Browsers without support keep the plain onload stagger already defined above. ---- */
@supports (animation-timeline: view()){
  .catalog .grid .card:not(.skeleton){
    animation:cardIn linear backwards;
    animation-timeline:view();
    animation-range:entry 0% entry 40%;
    animation-delay:0ms;
  }
  .catalog .grid .card:nth-child(1):not(.skeleton){animation-range:entry 0% entry 32%}
  .catalog .grid .card:nth-child(2):not(.skeleton){animation-range:entry 2% entry 34%}
  .catalog .grid .card:nth-child(3):not(.skeleton){animation-range:entry 4% entry 36%}
  .catalog .grid .card:nth-child(4):not(.skeleton){animation-range:entry 6% entry 38%}
  .catalog .grid .card:nth-child(5):not(.skeleton){animation-range:entry 8% entry 40%}
  .catalog .grid .card:nth-child(6):not(.skeleton){animation-range:entry 10% entry 42%}
  .catalog .grid .card:nth-child(7):not(.skeleton){animation-range:entry 12% entry 44%}
  .catalog .grid .card:nth-child(8):not(.skeleton){animation-range:entry 14% entry 46%}
  .catalog .grid .card:nth-child(9):not(.skeleton){animation-range:entry 16% entry 48%}
  .catalog .grid .card:nth-child(10):not(.skeleton){animation-range:entry 18% entry 50%}
  .catalog .grid .card:nth-child(11):not(.skeleton){animation-range:entry 20% entry 52%}
  .catalog .grid .card:nth-child(12):not(.skeleton){animation-range:entry 22% entry 54%}
}

/* ---- vitrine framing (owner-flagged parity 2026-07-19): the grid's image
   plates read as bare photo tiles while the PDP speaks engraved instruments —
   a restrained mounted-specimen treatment closes that gap. Hairline outline
   (outline, not border: no box-size shift, still paints under overflow:hidden)
   + four corner ticks, at the SAME oxblood-hairline opacities pdp.css already
   uses for .plate's border/.plate-ticks (rgba(74,22,34,.16 / .3)) — structural
   furniture, not a data mark, so it deliberately stays off the accord-hue
   register (DESIGN LAW: hue only on data marks). Static (no motion of its
   own); composes UNDER the existing sheen sweep (.ph::after, unchanged) and
   hover tilt/lift below — ticks paint first, the sheen still sweeps on top on
   hover. Gated on an explicit `.vitrine` marker class on `.ph` (_card.html +
   list.html's Alpine card template, both render it) so the treatment is a
   testable DOM fact, not CSS-selector-only; scoped under .catalog .grid .card
   too so home cards (catalog.css not loaded there) and the PDP's own .plate
   suite (pdp.css) stay untouched either way. ---- */
.catalog .grid .card .ph.vitrine{outline:1px solid rgba(74,22,34,.16);outline-offset:-1px}
.catalog .grid .card .ph.vitrine::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  background-image:
    linear-gradient(to right,rgba(74,22,34,.3) 0 8px,transparent 0),
    linear-gradient(to bottom,rgba(74,22,34,.3) 0 8px,transparent 0),
    linear-gradient(to left,rgba(74,22,34,.3) 0 8px,transparent 0),
    linear-gradient(to bottom,rgba(74,22,34,.3) 0 8px,transparent 0),
    linear-gradient(to right,rgba(74,22,34,.3) 0 8px,transparent 0),
    linear-gradient(to top,rgba(74,22,34,.3) 0 8px,transparent 0),
    linear-gradient(to left,rgba(74,22,34,.3) 0 8px,transparent 0),
    linear-gradient(to top,rgba(74,22,34,.3) 0 8px,transparent 0);
  background-repeat:no-repeat;
  background-size:8px 1px,1px 8px,8px 1px,1px 8px,8px 1px,1px 8px,8px 1px,1px 8px;
  background-position:
    top 8px left 8px, top 8px left 8px,
    top 8px right 8px, top 8px right 8px,
    bottom 8px left 8px, bottom 8px left 8px,
    bottom 8px right 8px, bottom 8px right 8px;
}

/* ---- specular sheen sweep on cover hover (prototype Layer 8). Silver/cream
   highlight only — never gold. Lives on .ph (already position:relative +
   overflow:hidden) so it never escapes the card's cover frame. ---- */
.card .ph{position:relative}
.card .ph::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(115deg,transparent 42%,rgba(245,245,245,.5) 48%,rgba(245,245,245,.72) 50%,rgba(245,245,245,.5) 52%,transparent 58%);
  transform:translateX(-120%);
  transition:transform 700ms var(--ease-out);
}
.card:hover .ph::after,.card:focus-visible .ph::after{transform:translateX(120%)}

/* ---- subtle depth-tilt on hover (prototype Layer 9), layered over the shared
   lift from motion.css's .card:hover. Scoped to .catalog .grid .card (higher
   specificity than motion.css's .card:hover) so home cards elsewhere keep the
   plain lift-only hover language from the restrained-motion pass untouched.

   animacion-8 correccion (R19 entrega 2): this scoping is exactly what kept the
   catalog grid — the 24-tile surface the brief is about — out of the parity pass
   landed in motion.css. `.catalog .grid .card:hover` is (0,4,0); motion.css's
   `.card:focus-visible` and `.card:active` are (0,2,0), so here the mouse kept
   winning: with Tab the catalog card got the bare 4px lift and none of the tilt
   (while the mouse got both), and the press NEVER painted, because :hover and
   :active are true together on every mouse click (and on iOS Safari, which
   applies :hover from touchstart). The cure is to spell the two missing states
   at this same specificity: `:focus-visible` shares the hover body verbatim, and
   `:active` re-declares motion.css's press so it is the SAME settle everywhere
   on the site — the tilt collapsing into the press is the card's echo of
   `.btn:active`, which also drops the button's hover lift. The press must stay
   BELOW the hover rule: both are (0,4,0), so source order decides. Its
   `transition-duration` is re-declared too, because `.catalog .grid .card`
   above is (0,3,0) and would otherwise hold the press to the hover cadence. ---- */
.catalog .grid .card{transition:transform var(--dur-mid) var(--ease-out),box-shadow var(--dur-mid) var(--ease-out)}
.catalog .grid .card:hover,.catalog .grid .card:focus-visible{
  transform:perspective(900px) rotateX(1deg) rotateY(-1deg) translateY(-4px) scale(1.008);
}
.catalog .grid .card:active{transform:translateY(-1px) scale(.995);transition-duration:var(--dur-fast)}

.catalog .grid .card .ph.vitrine{background:#FFFFFF}

/* ---- reduced motion: no mount travel (card hover lift is handled globally by
   motion.css's reduce block), and every p10-port addition above killed too. ---- */
@media(prefers-reduced-motion:reduce){
  .catalog .grid .card:not(.skeleton){animation:none}
  .cat-crumb,.cat-title{animation:none;opacity:1;transform:none}
  .card .ph::after{transition:none;transform:none}
  .catalog .grid .card{transition:none}
  .catalog .grid .card:hover,.catalog .grid .card:focus-visible,.catalog .grid .card:active{transform:none}
  .catalog .grid .card .ph img{animation:none}
}

/* ============================================================================
   THE COMPASS (Catálogo #1) — the 13 top-level olfactive families as an
   engraved instrument row above the catalog grid (catalog/_compass.html).
   Plain server-rendered <a> studs, zero new JS; a tap is a normal GET to the
   existing ?family= exact-in filter (filters.py CharInFilter). WCAG: family
   hue lives ONLY on .compass-fill (the data mark) — .compass-label always
   stays --ink, same split the home familias index uses for .home-fam-dot /
   .home-fam-name. One flex row, wraps freely on narrow viewports.
   ============================================================================ */
.compass{padding-block:2px 26px}
.compass-h{font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--warm-grey);margin-bottom:14px}
.compass-row{display:flex;flex-wrap:wrap;gap:4px 6px}
.compass-stud{display:flex;flex-direction:column;align-items:center;gap:6px;
  width:76px;padding:10px 4px 8px;text-decoration:none;border-radius:2px;
  border:1px solid transparent;transition:background-color var(--dur-fast,.2s),border-color var(--dur-fast,.2s)}
.compass-stud:hover{background:var(--cream)}
.compass-stud:focus-visible{outline:2px solid var(--burgundy);outline-offset:2px}
.compass-mark{width:30px;height:30px;border-radius:50%;flex:0 0 auto;
  border:1px solid var(--burgundy-deep);display:grid;place-items:center;background:var(--surface)}
.compass-fill{width:14px;height:14px;border-radius:50%;border:1px solid rgba(25,22,20,.16)}
.compass-label{font-size:12.5px;line-height:1.2;color:var(--ink);text-align:center}
.compass-n{font-size:10.5px;color:var(--warm-grey)}

/* active = the stud whose member compounds overlap the current ?family=
   selection (catalog_list()'s compass_families). Border-color only (never
   border-WIDTH) so the ring brightens with zero layout shift; the extra
   box-shadow ring reads as "engaged" without resizing the dial. */
.compass-stud.is-active{background:var(--cream);border-color:var(--line-2)}
.compass-stud.is-active .compass-mark{border-color:var(--burgundy);box-shadow:0 0 0 1.5px var(--burgundy)}
.compass-stud.is-active .compass-label{color:var(--burgundy);font-weight:500}
.compass-stud.is-active .compass-n{color:var(--burgundy)}

@media(prefers-reduced-motion:reduce){.compass-stud{transition:none}}
