/* ============================================================================
   RARA · catalog.css — catalog / search listing (filters, facets, sort, rail).
   Migrated from rara.css 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-search input{width:100%;max-width:520px;font-family:var(--mono);font-size:14px;padding:13px 16px;border:1px solid var(--line-2);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. */
@media (min-width:1001px){
  .cat-grid .grid{grid-template-columns:repeat(auto-fill,minmax(200px,1fr))}
}

/* filter rail */
.rail{position:sticky;top:88px;border-top:1px solid var(--burgundy);padding-top:6px}
.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-2);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 3 columnas que .facet-opt, menos la casilla: GLIFO ·
     etiqueta · cifra, con la columna del glifo siempre presente. */
  display:grid;grid-template-columns:14px 1fr auto;align-items:center;gap:9px;width:100%;
  padding:7px 8px;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:1;margin-right:0}
.facet-autocomplete .facet-suggestion>.facet-lbl{grid-column:2}
.facet-autocomplete .facet-suggestion>.facet-n{grid-column:3}
.facet-autocomplete .facet-suggestion:hover{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)}

/* 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-2);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 */
.cat-bar{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:18px}
.sort{display:flex;align-items:center;gap:9px}
.sort select{font-family:var(--mono);font-size:12px;padding:9px 12px;border:1px solid var(--line-2);background:var(--surface);cursor:pointer;min-height:40px}

@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.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-caret{margin-left:auto;font-size:10px;transition:transform var(--dur-fast,.2s) var(--ease-out,ease)}
  .rail-toggle[aria-expanded="true"] .rail-toggle-caret{transform:rotate(180deg)}
}
@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) both;
  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(--ink)}
.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 both;
    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. ---- */
.catalog .grid .card{transition:transform var(--dur-mid) var(--ease-out),box-shadow var(--dur-mid) var(--ease-out)}
.catalog .grid .card:hover{
  transform:perspective(900px) rotateX(1deg) rotateY(-1deg) translateY(-4px) scale(1.008);
}

/* ---- ambient: whisper-parallax on cover images (prototype Layer 11) — the ONE
   continuous/ambient motion in the grid viewport (no other continuous effect
   runs alongside it here). Scroll-linked per card via view(), uses only the
   already-rendered cover image, transform-only, ~6px of travel. ---- */
@supports (animation-timeline: view()){
  @keyframes coverParallax{from{transform:translateY(0)}to{transform:translateY(-6px)}}
  .catalog .grid .card .ph img{
    animation:coverParallax linear both;
    animation-timeline:view();
    animation-range:cover 0% cover 100%;
  }
}

/* ---- 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{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}}
