/* ============================================================
   NASTASI FOODS — catalog.css
   ------------------------------------------------------------
   Correction #12. The page used to open on a dark green band with
   diagonal hatching across it. There is no opening band here at
   all now: the catalog begins on the same paper as the rest of the
   page, the way aoiofficial.com/collections/all begins on white.

   The layout is that reference's, read at 1280 and rebuilt in our
   vocabulary:

     masthead     title + manifest left, the nine departments right
                  as a rack of label spines that widen on hover
                  (their 84px collection slats, width .5s ease-in-out)
     control rail one ~80px sticky line: search left, the department
                  chips centred-ish, the live count right — chips with
                  no pill and no border, only weight and colour
     grid         three columns, 50px between them, 60px between rows,
                  cards with no box: photo, then type on the paper

   Everything catalog.js queries — #productGrid, #filterRow,
   #subcatRow, #catalogSearch, #catalogMeta, #catalogMore,
   #catalogSentinel — keeps its id and its place in the markup. This
   sheet only restyles.
   ============================================================ */

/* ============================================================
   1. The masthead
   ============================================================ */
.cat-masthead {
  position: relative;
  padding-top: calc(var(--header-h) + clamp(1.75rem, 1rem + 3vw, 3.75rem));
  padding-bottom: clamp(1.5rem, 1.1rem + 1.2vw, 2rem);
}
.cat-masthead__inner {
  display: grid;
  grid-template-columns: minmax(0, 47.5fr) minmax(0, 52.5fr);
  /* the type column is the taller of the two, so the rack stretches to
     meet it rather than adding height of its own */
  align-items: stretch;
  gap: clamp(1.5rem, 0.8rem + 3vw, 4rem);
}
.cat-masthead .breadcrumb { margin-bottom: 0.8rem; }
.cat-masthead .eyebrow { color: var(--stamp); margin-bottom: 0.7rem; }

/* The reference's h1 is 24px — small on purpose, so the products are
   the event and the title is only a label. Ours keeps the Fraunces
   voice but drops to about half of --fs-h1 for the same reason. */
.cat-masthead h1 {
  font-size: clamp(1.9rem, 1.35rem + 1.7vw, 2.4rem);
  line-height: var(--lh-snug);
  letter-spacing: -0.015em;
  max-width: 12ch;
}
/* The standfirst. The h1 is deliberately small (a label, per the
   reference), so this is the line that actually reads as the headline. */
.cat-masthead__claim {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.15rem, 0.9rem + 0.8vw, 1.5rem);
  line-height: var(--lh-snug);
  letter-spacing: -0.01em;
  color: var(--ink);
  max-width: 30ch;
  margin-top: 0.55rem;
}
.cat-masthead__lede {
  color: var(--text-muted);
  font-size: var(--fs-body);
  max-width: 46ch;
  margin-top: 0.9rem;
}
/* Three word-pairs instead of three numbers. A flex line broke a pair
   across two rows once the values stopped being "396"; a grid keeps
   label and value together and reflows whole cells. */
.cat-manifest--claims {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: 0.9rem clamp(1rem, 0.6rem + 1vw, 1.75rem);
}
.cat-manifest--claims dt { margin-bottom: 0.2rem; }
.cat-manifest--claims dd { font-size: 1.02rem; letter-spacing: -0.005em; }
.cat-masthead__talk { margin-top: clamp(0.9rem, 0.7rem + 0.6vw, 1.3rem); }

/* the customs-docket line under the lede */
.cat-manifest {
  display: flex; flex-wrap: wrap; gap: 0 clamp(1.25rem, 0.6rem + 1.6vw, 2.5rem);
  margin-top: clamp(1rem, 0.7rem + 0.9vw, 1.5rem);
  padding-top: 0.75rem;
  border-top: 1px solid var(--line);
}
.cat-manifest dt {
  font-family: var(--font-mono); font-size: 0.6rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--text-faint);
}
.cat-manifest dd {
  font-family: var(--font-display); font-weight: 600; font-size: 1.15rem;
  line-height: 1.25; color: var(--ink); font-variant-numeric: tabular-nums;
  margin: 0;
}

/* ---------- the department rack ----------
   aoiofficial fills the right 52.5% of its collection masthead with a
   sideways rack of 84px collection slats that transition
   `width .5s ease-in-out` when one is hovered. Same mechanic here, with
   the labels turned on their side: nine spines out of a filing rack,
   each washed in its own department colour.

   Real <a href="?department=…"> links, exactly like the reference's
   /collections/<name> links — catalog.js reads ?department= on load, so
   the deep link lands filtered. The chips in the rail below are the
   in-place filter; these are the way in. */
.dept-rack {
  display: flex;
  min-height: clamp(240px, min(30vw, 40vh), 400px);
  overflow: hidden;
  background: var(--bone-deep);
  /* runs past the text column to the edge of the page, the way the
     reference's carousel runs off the right of the viewport. No vw
     units: at <=1280 the wrap is full width, so this IS the edge, and
     nothing can overflow the document. */
  margin-right: calc(var(--gutter) * -1);
  box-shadow: 0 18px 40px rgba(22, 20, 15, 0.14);
}
.dept-slat {
  position: relative;
  flex: 0 0 auto;
  width: calc(100% / 9);
  min-width: 0;
  overflow: hidden;
  background: var(--slat, var(--olive-600));
  transition: width 0.5s ease-in-out;
}
/* a bone hairline between spines instead of a gap, so the widths can
   stay in whole percentages and always total 100 */
.dept-slat + .dept-slat { box-shadow: inset 1px 0 0 color-mix(in srgb, var(--bone-pure) 60%, transparent); }

.dept-slat__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 44%;
  opacity: 0.86;
  transition: opacity var(--dur-slow) ease, transform 0.7s var(--ease-out);
}
/* the department colour, pooled at the foot of the spine so the label
   always has something to sit on */
.dept-slat::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--slat) 26%, transparent) 0%,
    color-mix(in srgb, var(--slat) 6%, transparent) 30%,
    color-mix(in srgb, var(--slat) 88%, #14120D) 100%);
  transition: opacity var(--dur-mid) ease;
}
.dept-slat__n {
  position: absolute; top: 0.75rem; left: 0; right: 0; z-index: 2;
  text-align: center;
  font-family: var(--font-mono); font-size: 0.56rem; letter-spacing: 0.12em;
  color: color-mix(in srgb, var(--bone-pure) 76%, transparent);
  text-shadow: 0 1px 8px rgba(8, 10, 6, 0.5);
}
.dept-slat__name {
  position: absolute; left: 50%; bottom: 1rem; z-index: 2;
  writing-mode: vertical-rl;
  transform: translateX(-50%) rotate(180deg);
  font-family: var(--font-body); font-weight: 600;
  font-size: 0.76rem; letter-spacing: 0.13em; text-transform: uppercase;
  white-space: nowrap; line-height: 1;
  color: var(--bone-pure);
  text-shadow: 0 1px 12px rgba(8, 10, 6, 0.62);
}

/* The accordion. Order matters: the widening rule has the same
   specificity as the shrinking one and has to come after it. */
@media (hover: hover) {
  .dept-rack:hover .dept-slat { width: 9.5%; }
  .dept-rack .dept-slat:hover { width: 24%; }
  .dept-rack .dept-slat:hover .dept-slat__img { opacity: 1; transform: scale(1.05); }
  .dept-rack .dept-slat:hover::after { opacity: 0.82; }
}
/* The widths have to keep totalling 100 whichever way focus arrived, so
   the expanding rule is :focus, not :focus-visible — a mouse click sets
   focus on the slat but not :focus-visible, and pairing a shrink rule
   with a :focus-visible widen rule shrinks all nine and leaves a 14.5%
   hole. Only the ring is reserved for keyboard focus.

   Both rules are ALSO prefixed with the identical :has() condition, and
   that is load-bearing, not tidiness. :has() takes the specificity of
   its most specific argument, so `.dept-rack:has(.dept-slat:focus)
   .dept-slat` scores (0,4,0) while a bare `.dept-rack .dept-slat:focus`
   scores only (0,3,0) — the shrink rule outranks the widen rule and
   re-opens the same 14.5% hole from the other side. Written this way the
   widen rule is the shrink rule plus one compound, so it is strictly
   more specific and cannot lose. Where :has() is missing both are
   dropped together and the rack simply stops reacting to focus, which is
   safe; it is the mismatched pair that leaves a hole. */
.dept-rack:has(.dept-slat:focus) .dept-slat { width: 9.5%; }
.dept-rack:has(.dept-slat:focus) .dept-slat:focus { width: 24%; }
.dept-rack:has(.dept-slat:focus) .dept-slat:focus .dept-slat__img { opacity: 1; }
.dept-rack .dept-slat:focus-visible { outline: 2px solid var(--saffron); outline-offset: -2px; }

/* the masthead already carries the air above the rail */
.cat-masthead + .section { padding-top: 0; }

/* ============================================================
   2. The control rail
   ------------------------------------------------------------
   The reference's strip is 80px, sticky under the header, with no
   border and no fill of its own: `Filters` left, the collection chips
   centred, `Sort by` right. Ours has to hold a search box and ten
   department names instead of nine one-word collections, so it takes
   two thin lines to the reference's one — and lands on the same 80px.
   ============================================================ */
.catalog-toolbar {
  position: sticky; top: var(--header-h); z-index: var(--z-sticky);
  display: grid; gap: 0.3rem;
  background: color-mix(in srgb, var(--bone) 92%, transparent);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  padding-block: 0.35rem 0.4rem;
  margin-bottom: clamp(1rem, 0.8rem + 1.1vw, 1.5rem);
  border-bottom: 1px solid var(--line);
}
.catalog-bar { display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap; }

/* ---------- search: a ruled line, not a box ---------- */
.search-field {
  position: relative; flex: 1 1 260px; max-width: 340px; min-width: 0;
  display: flex; align-items: center;
}
.search-field__icon {
  position: absolute; left: 0; display: grid; place-items: center;
  color: var(--text-faint); pointer-events: none;
  transition: color var(--dur-fast);
}
.search-field__icon svg { width: 17px; height: 17px; }
.search-field__input {
  width: 100%;
  font-family: var(--font-body); font-size: 0.95rem; color: var(--ink);
  background: none;
  border: 0; border-bottom: 1.5px solid var(--line-strong); border-radius: 0;
  padding: 0.34rem 1.9rem 0.34rem 1.6rem;
  transition: border-color var(--dur-fast), border-width var(--dur-fast);
}
.search-field__input::placeholder { color: var(--text-faint); }
.search-field__input:hover { border-bottom-color: var(--olive-600); }
.search-field__input:focus {
  outline: none;
  border-bottom-width: 2.5px;
  border-bottom-color: var(--stamp);
  padding-bottom: calc(0.34rem - 1px);
}
.search-field:focus-within .search-field__icon { color: var(--stamp); }
/* the browser's own clear button would sit on top of ours */
.search-field__input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.search-field__clear {
  position: absolute; right: 0; width: 28px; height: 28px; border: 0; border-radius: 50%;
  background: none; color: var(--text-muted); display: grid; place-items: center; cursor: pointer;
  transition: background var(--dur-fast), color var(--dur-fast);
}
/* a class-level `display` outranks the UA rule for [hidden] */
.search-field__clear[hidden], .subcat-strip[hidden] { display: none; }
.search-field__clear:hover { background: var(--bone-deep); color: var(--stamp); }
.search-field__clear svg { width: 15px; height: 15px; }

.catalog-meta {
  font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--text-faint);
  margin-left: auto; white-space: nowrap;
}

/* ---------- the chips: no pill, no border, only weight and colour ----------
   The reference's chips carry no background and no outline at all; the
   active one is simply full-black where the rest are black/50, over a
   0.3s cubic-bezier(.4,0,.2,1). Same mechanic, plus the one thing that
   is ours: the rule under the active chip is that department's own
   colour, so the rail is nine colours and not one green. */
.dept-strip, .subcat-strip {
  display: flex; align-items: center;
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none; -ms-overflow-style: none;
  -webkit-mask-image: linear-gradient(90deg, #000 0, #000 95%, transparent);
          mask-image: linear-gradient(90deg, #000 0, #000 95%, transparent);
}
.dept-strip { gap: clamp(0.9rem, 0.4rem + 1.4vw, 1.6rem); }
.subcat-strip { gap: 1.35rem; padding-top: 0.15rem; }
.dept-strip::-webkit-scrollbar, .subcat-strip::-webkit-scrollbar { display: none; }

.dept-chip {
  --chip: var(--olive-600);
  flex: none; display: inline-flex; align-items: center; gap: 0.5rem;
  font-family: var(--font-body); font-weight: 600; font-size: 0.82rem;
  line-height: 1; white-space: nowrap; cursor: pointer;
  padding: 0.32rem 0 0.36rem;
  border: 0; border-bottom: 2px solid transparent; border-radius: 0;
  background: none; color: var(--text-faint);
  transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
              border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.dept-chip::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--chip); opacity: 0.42;
  transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.dept-chip[data-dept="all"]::before { display: none; }
.dept-chip:hover { color: var(--ink); }
.dept-chip:hover::before { opacity: 1; }
.dept-chip:focus-visible { outline: 2px solid var(--brass); outline-offset: 3px; }
.dept-chip[aria-pressed="true"] {
  background: none;
  color: var(--ink);
  border-bottom-color: var(--chip);
}
.dept-chip[aria-pressed="true"][data-dept="all"] { border-bottom-color: var(--ink); }
.dept-chip[aria-pressed="true"]::before { opacity: 1; }
.dept-chip .c {
  font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.04em;
  color: var(--text-faint); font-weight: 400;
}
.dept-chip[aria-pressed="true"] .c { color: var(--brass); }

/* the second level reads a step quieter than the first */
.subcat-chip {
  flex: none; font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.1em;
  text-transform: uppercase; white-space: nowrap; cursor: pointer; line-height: 1;
  padding: 0.28rem 0; border: 0; background: none;
  color: var(--text-faint); border-bottom: 1px solid transparent;
  transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
              border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.subcat-chip:hover { color: var(--ink); }
.subcat-chip:focus-visible { outline: 2px solid var(--brass); outline-offset: 3px; }
.subcat-chip[aria-pressed="true"] { color: var(--stamp); border-bottom-color: var(--stamp); }
.subcat-chip .c { opacity: 0.55; margin-left: 0.35em; }

/* ============================================================
   3. The grid
   ------------------------------------------------------------
   Three columns, 50px between them and 60px between rows, measured off
   the reference at 1280 (366px cards inside a 1200px column). Two
   columns below its `lg`, one on a phone.
   ============================================================ */
/* Denser than the reference's three-up. A 352px card whose photograph was
   440px tall put four products on a 1280 screen and made a 396-line price
   list feel like a lookbook — Sergio's note was that the cards are too big
   and the photos eat the space. Four columns, a square crop instead of 4/5,
   and tighter gutters: the card comes down from 352x606 to ~275x430 and
   twice as much of the catalog is on screen at once. */
.product-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  column-gap: clamp(0.9rem, 0.4rem + 1.6vw, 26px);
  row-gap: clamp(1.6rem, 0.9rem + 1.9vw, 34px);
}
@media (min-width: 520px)  { .product-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 880px)  { .product-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1180px) { .product-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* ---------- THE CARD IS A CRATE LABEL (ronda 7) ----------
   Sergio: "mejora el diseño de las tarjetas del catálogo, se ven muy feitas."

   He is right, and this repo's own brief says why: on this site "everything
   is a piece of paper from an import house … nothing is a plain rectangle
   with a flat border." The product card was the single exception. The block
   this replaces was titled "the card has no box" and took that literally —
   no ground, no frame, no shadow, a square photograph bled to the edge and
   six sizes of small type floating on the page background. Copied off a
   reference whose catalog is a nine-item lookbook. On a 396-line wholesale
   price list the same treatment reads as an unstyled table.

   So the card becomes the artifact it is describing: the printed label pasted
   on the end of a crate.

     ┌───────────────────────────┐  3px department colour, full bleed
     │ AST 00300         ASTURI  │  printed header: lot code · brand
     ├───────────────────────────┤
     │      [ photograph ]       │  pasted on the label, inset, hairlined
     ├───────────────────────────┤
     │ Asturi Classic VOO      ⓘ │  the NAME, in the display face
     │ CASE 12 × 4.23 OZ         │
     │ $36.30            [− 0 +] │  the price is the reason you stopped
     │ $3.03 PER UNIT · 4.23 OZ  │
     └───────────────────────────┘

   Three decisions do the work, and none of them is decoration:

   · **The header is real information, not a band.** The lot code and the brand
     were previously two of the six small lines competing under the photograph.
     Up here they are what a crate label actually prints at the top, and moving
     them leaves a hierarchy of three below the photo instead of six.
   · **The name is set in the display face.** It was 0.94rem Lenia at weight
     500 — the same voice as the pack spec and the item code. Nothing on the
     card was allowed to be the headline. GC Commune at 1.02rem makes the
     product the loudest thing on its own card.
   · **The photograph is inset.** A photo bled to the card's edge IS the card;
     inset by the label's padding, with paper visible around it, it becomes a
     photograph pasted onto a label. That gap is the whole difference between
     "image with text under it" and "artifact".

   CHEAP ON PURPOSE — 396 of these can be on screen at once:
   · no backdrop-filter (catalog packshots already opted out of the liquid
     glass in shapes.css §7b for exactly this reason)
   · no mask and no clip-path for the frame: a mask would also clip the card's
     box-shadow, which is the trap documented in CLAUDE.md
   · the department colour is a border-top, not an extra element
   · hover moves transform/box-shadow only */
.product-grid .product-card {
  background: var(--bone-pure);
  border: 1px solid var(--line);
  border-top: 3px solid var(--cat, var(--olive-600));
  border-radius: 2px;
  padding: 0.5rem 0.55rem 0.6rem;
  /* A label lying on a desk: the lift is tight and offset down, not a soft
     halo. Two stops so the edge stays crisp at 100% zoom. */
  box-shadow: 0 1px 0 rgba(22, 20, 15, 0.05), 0 2px 5px rgba(22, 20, 15, 0.07);
  overflow: visible;
  transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
@media (hover: hover) {
  .product-grid .product-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 2px 0 rgba(22, 20, 15, 0.05), 0 10px 22px rgba(22, 20, 15, 0.13);
    border-color: var(--line-strong);
  }
}

/* the printed head — lot code left, brand right, on a ruled line */
.product-card__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 0.5rem;
  padding-bottom: 0.4rem; margin-bottom: 0.45rem;
  border-bottom: 1px solid var(--line);
  font-family: var(--font-mono); font-size: 0.55rem; letter-spacing: 0.14em;
  text-transform: uppercase;
  min-width: 0;
}
.pc-code {
  color: var(--text-faint); font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.pc-brand {
  color: var(--stamp); letter-spacing: 0.16em;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* THE WHOLE CARD OPENS THE SHEET.
   An overlay link rather than a wrapper: `.product-card__body` is a grid with
   placed children, and wrapping half of them in an <a> would break the
   placement. It is a real <a href> so the sheet is deep-linkable, keyboard
   reachable and middle-clickable — product-sheet.js intercepts the click.
   The stepper and the ask link sit ABOVE it on z-index, which is what keeps
   pressing "+" from opening the sheet. */
.card-open {
  position: absolute; inset: 0; z-index: 1;
  border-radius: inherit;
}
.card-open:focus-visible { outline: 2px solid var(--brass); outline-offset: 3px; }
.product-grid .buy-rows,
.product-grid .product-card__actions,
.product-grid .card-ask { position: relative; z-index: 2; }

/* The packshots are shot at 420x525 (4/5). The frame is 1/1 now and the
   image is `object-fit: contain`-adjacent in effect — the product sits
   centred in its own field, so a square crop takes the empty studio floor
   off the top and bottom rather than the bottle. That single change is
   most of the height Sergio wanted back. */
.product-grid .packshot {
  aspect-ratio: 1 / 1;
  border-radius: 1px;
  overflow: hidden;
  /* the pasted-photograph hairline. The department colour has moved to the
     card's own top edge, where it reads as the label's colour code rather
     than as a stripe on the photo. */
  border: 1px solid var(--line);
  background:
    radial-gradient(120% 90% at 50% 0%, color-mix(in srgb, var(--cat, var(--olive-600)) 16%, var(--bone-pure)), var(--bone-deep));
}
/* No diagonal hatching anywhere in the document — this placeholder was
   still carrying a 135deg repeating gradient. Two dot grids at coprime
   spacings give the same paper grain and never phase into a stripe. */
.product-grid .packshot::after {
  background-image:
    radial-gradient(rgba(22, 20, 15, 0.07) 0.6px, transparent 0.7px),
    radial-gradient(rgba(22, 20, 15, 0.05) 0.5px, transparent 0.6px);
  background-size: 13px 13px, 29px 29px;
  background-position: 0 0, 7px 11px;
  mix-blend-mode: normal;
}
.product-grid .packshot__img { transition: transform 0.65s var(--ease-out); }
/* The veil is there so hover has something to lift. It was tuned for a
   photograph bled to the card's edge; inside a hairlined frame the same
   0.24 read as dirt collecting in the bottom of the frame, so it comes down
   to 0.14 and the hover lift below still registers. */
.product-grid .packshot--photo::before {
  background: linear-gradient(180deg, rgba(8, 10, 6, 0) 52%, rgba(8, 10, 6, 0.14));
  transition: opacity var(--dur-mid) ease;
}
.product-grid .packshot__flag { top: 12px; }
/* The stamp is placed off the CARD, and the card grew a printed header, so at
   0.85rem it was landing across the lot code. It belongs on the photograph. */
.product-grid .product-card__stamp { top: 2.5rem; left: 1rem; }

/* The reference cross-fades a second photograph in on hover, 300ms. We
   have one photograph per SKU, so the equivalent is the veil lifting off
   the one we have while it eases in. Gated on a real pointer so it
   cannot latch on touch. */
@media (hover: hover) {
  .product-grid .product-card:hover .packshot--photo::before { opacity: 0.3; }
  .product-grid .product-card:hover .packshot__img { transform: scale(1.045); }
}

/* ---------- the type block ----------
   Two rows now, not four. The brand and the item code moved to the printed
   header above the photograph, so what is left under it is the NAME (with the
   ask link beside it) and the order lines. `1fr` on the second row is what
   keeps every tear line in a row of cards on the same baseline when the names
   run to different depths — that has to stay whatever else changes here. */
.product-grid .product-card__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto 1fr;
  align-items: baseline;
  column-gap: 0.5rem;
  padding: 0.5rem 0 0;
  flex: 1;
}
/* THE NAME IS THE HEADLINE. It used to be 0.94rem Lenia at weight 500 — the
   same voice as the pack spec, the code and the price, which is most of why
   the card read as a table row. GC Commune is the display face everywhere else
   on this site (headings, ticket buttons, prices, the address block), and it
   is what gives a 269px card a hierarchy. */
.product-grid .product-card h3 {
  grid-column: 1; grid-row: 1;
  font-family: var(--font-display);
  font-size: 1.02rem; line-height: 1.16; margin: 0;
  font-weight: 400; letter-spacing: 0.002em;
  color: var(--ink);
}
.product-grid .product-card .tag-vegan { vertical-align: 0.14em; }

/* the tear line above the order lines */
.product-grid .product-card__actions,
.product-grid .buy-rows {
  grid-column: 1 / -1; grid-row: 2;
  align-self: end;
}
/* A sold-out card has one thing to say, so the struck price sits on the
   actions row with the disabled button instead of fighting it for the cell. */
.pc-soldprice {
  font-family: var(--font-display); font-size: 0.95rem;
  color: var(--text-faint); text-decoration: line-through;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.pc-soldprice .per {
  font-family: var(--font-mono); font-size: 0.55rem; letter-spacing: 0.1em;
  text-transform: uppercase; text-decoration: none; display: inline-block;
  margin-left: 0.15em;
}
.product-grid .product-card__actions {
  margin-top: 0.7rem; padding-top: 0.6rem;
  border-top: 1px solid var(--line);
  gap: 0.4rem;
  align-items: center; justify-content: space-between;
}
.product-grid .product-card__actions .btn { padding-block: 0.46rem; font-size: 0.78rem; }
.product-grid .btn-icon-sq { width: 36px; }

/* ============================================================
   4. Foot of the page
   ============================================================ */
.catalog-more { display: flex; justify-content: center; margin-top: clamp(2.5rem, 1.6rem + 2.6vw, 4rem); }
.catalog-empty__actions { display: flex; gap: 0.6rem; justify-content: center; flex-wrap: wrap; margin-top: 1.25rem; }
.catalog-foot {
  max-width: 62ch; margin: clamp(3rem, 2rem + 3vw, 5rem) auto 0; text-align: center;
  color: var(--text-faint); padding-top: 1.75rem; border-top: 1px solid var(--line);
}
.catalog-foot a { color: var(--brass); text-decoration: underline; text-underline-offset: 3px; }

/* ============================================================
   5. Narrow
   ------------------------------------------------------------
   Below the reference's `lg` the masthead stacks: the rack goes under
   the type and turns into a short scrolling shelf, which is what the
   reference does with its own carousel.
   ============================================================ */
@media (max-width: 1023px) {
  .cat-masthead__inner { grid-template-columns: minmax(0, 1fr); align-items: start; }
  .cat-masthead h1 { max-width: none; }
  .cat-masthead__lede { max-width: 60ch; }
  .dept-rack {
    height: clamp(180px, 34vw, 260px); min-height: 0;
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -ms-overflow-style: none;
  }
  .dept-rack::-webkit-scrollbar { display: none; }
  /* A scrolling shelf can't share width with its neighbours, so the
     accordion is off here and every spine keeps a legible width.

     A media query adds no specificity, so each selector here has to
     out-rank the accordion rule it is cancelling. The two :has() ones
     are not optional: without them `.dept-rack:has(.dept-slat:focus)
     .dept-slat` at (0,4,0) beats anything shorter and collapses the
     whole shelf to 9.5% the moment a spine is tapped. */
  .dept-slat,
  .dept-rack:hover .dept-slat,
  .dept-rack .dept-slat:hover,
  .dept-rack:has(.dept-slat:focus) .dept-slat,
  .dept-rack:has(.dept-slat:focus) .dept-slat:focus { width: 84px; }
  /* 84px is the FLOOR, not the width. Nine fixed spines only add up to
     756px, so between roughly 800 and 1023 they left a bare strip of
     --bone-deep at the end of the rack. grow:1 spends the slack on the
     spines; shrink:0 keeps them legible and hands the overflow to the
     scroller below ~800. flex-basis outranks the width above. */
  .dept-slat { flex: 1 0 84px; }
}

@media (max-width: 700px) {
  .catalog-bar { gap: 0.75rem; }
  .search-field { flex: 1 1 100%; max-width: none; }
  .catalog-meta { margin-left: 0; width: 100%; }
  .product-grid .price-block__case { font-size: 0.95rem; }
  .product-grid .product-card__actions { flex-wrap: wrap; }
}

@media (prefers-reduced-motion: reduce) {
  .dept-slat, .dept-slat__img, .dept-slat::after,
  .product-grid .packshot__img, .product-grid .packshot--photo::before { transition: none; }
  .product-grid .product-card:hover .packshot__img { transform: none; }
}

/* ============================================================
   THE ORDER LINES — buy by the case, by the unit, or both at once
   ------------------------------------------------------------
   Replaces the Case|Unit switch and the Add-to-cart button. Sergio:
   "ese case y unit quedó super mal proporcionado … debería ser case y la
   opción de subir o bajar la cantidad y lo mismo para unit … quita eso de
   add to cart, simplemente subiendo bajando la cantidad ya debería
   aparecer en el carro."

   WHY THE OLD SWITCH LOOKED WRONG, for the record: it carried
   `align-self: start` inside a `display: grid` parent, and in grid that is
   the BLOCK axis — the inline axis was left at `stretch`. So a control
   holding two 9px words was pulled the full width of the card. The
   property that would have held it in was `justify-self`.

   The card is a docket now: one line per way of buying, each with its own
   pack, price and counter. Two lines inside each row rather than one,
   because at the 4-column breakpoint the card is only ~255px wide.
   ============================================================ */
.buy-rows {
  display: grid; gap: 0.55rem;
  margin-top: 0.7rem; padding-top: 0.6rem;
  border-top: 1px solid var(--line);
}
.buy-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 0.6rem; row-gap: 0.18rem; align-items: center;
  padding: 0.4rem 0.5rem; margin-inline: -0.5rem;
  border-radius: var(--r-sm, 4px);
  transition: background var(--dur-fast) ease;
}
.buy-row__meta {
  grid-column: 1 / -1; min-width: 0;
  font-family: var(--font-mono); font-size: 0.58rem; letter-spacing: 0.11em;
  text-transform: uppercase; color: var(--text-faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.buy-row__meta b { font-weight: 500; color: var(--stamp); letter-spacing: 0.16em; }

/* EL AVISO DE MINIMO — la letra pequeña del albarán, no una alerta.
   Ocupa toda la fila de la rejilla de `.buy-row` (`grid-column: 1 / -1`), así
   que aparece sin desplazar el precio ni el contador. En reposo va en el gris
   del sistema; cuando el carrito acaba de subir la cantidad al mínimo se
   enciende en oxblood durante tres segundos — el mismo rojo de los sellos de
   aduana, que en esta imprenta es el color de "atención a esto". */
.buy-row__min {
  grid-column: 1 / -1;
  font-family: var(--font-mono); font-size: 0.6875rem;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-faint); margin-top: 2px;
  transition: color 180ms var(--ease-out);
}
.buy-row__min.on { color: var(--stamp); font-weight: 600; }
@media (prefers-reduced-motion: reduce) { .buy-row__min { transition: none; } }
/* THE PRICE IS THE ANCHOR. On a wholesale card the money is the reason the
   buyer stopped scrolling, and at 0.95rem in the body face it was the same
   size and voice as the item code. Display face, one step up, tabular so a
   column of four cards lines its decimals up. */
.buy-row__price {
  grid-column: 1;
  font-family: var(--font-display);
  font-size: 1.3rem; line-height: 1.1;
  font-variant-numeric: tabular-nums; white-space: nowrap;
  color: var(--ink);
}
/* ON ORDER. The row is the only place this state is shown on the card, so it
   cannot be carried by the number alone — a 0 and a 3 are one glyph apart at
   this size. The paper darkens and the price goes to ink. */
.buy-row[data-qty]:not([data-qty="0"]) {
  background: color-mix(in srgb, var(--basil) 9%, transparent);
}
.buy-row[data-qty]:not([data-qty="0"]) .buy-row__price { color: var(--green-900, var(--ink)); font-weight: 600; }

/* THE UNIT PRICE, WITHOUT A SECOND COUNTER (ronda 7)
   ------------------------------------------------------------
   Sergio: "showing unit price and cases price, we don't need 2 quantity's
   selector, just case."

   So the unit price stays and its stepper goes. It is a reference figure now —
   what one jar costs inside the case — and it has to read as data, not as a
   second thing to click. Three decisions carry that:

   · it is a `<p>`, not a `.buy-row`: no hover paper, no `data-qty` state, no
     touch target. Nothing about it invites a press.
   · the figure keeps `tabular-nums` and the price's own size band minus a
     step, so a column of cards still lines up its decimals.
   · `per unit` is set in the label voice (uppercase, tracked) which on this
     site always means "this is a field name". The money is the value.

   It sits on the case row's baseline grid rather than getting its own rule:
   one tear line per card is the pattern, and .buy-rows already draws it. */
.buy-note {
  grid-column: 1 / -1;
  display: flex; align-items: baseline; gap: 0.4rem; flex-wrap: wrap;
  margin: -0.1rem 0 0; padding: 0 0.5rem;
}
.buy-note__price {
  font-size: 0.78rem; line-height: 1.2; color: var(--text-muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.buy-note__per {
  font-family: var(--font-mono); font-size: 0.55rem; letter-spacing: 0.11em;
  text-transform: uppercase; color: var(--text-faint);
  white-space: nowrap;
}

/* the drawer's stepper, shrunk for a 255px card. It sits in a printed well —
   a field on the label rather than a pill floating next to it. */
.qty--card {
  grid-column: 2; height: 30px;
  background: color-mix(in srgb, var(--ink) 4%, transparent);
  border: 1px solid var(--line);
  border-radius: 2px;
}
.qty--card button { width: 28px; height: 28px; font-size: 0.92rem; }
.qty--card button:disabled { opacity: 0.35; cursor: default; }
.qty--card button:disabled:hover { background: none; }
.qty--card span,
.qty--card input { min-width: 24px; font-size: 0.72rem; }
/* En la tarjeta el pozo es rectangular, no pastilla, así que el foco se marca
   con el mismo trazo latón que usan los botones de al lado. */
.qty--card:focus-within { border-color: var(--brass); box-shadow: inset 0 0 0 1px var(--brass); }
.qty--card input { width: 3.6ch; }
.qty--card button:focus-visible { outline: 2px solid var(--brass); outline-offset: -2px; }
/* No pointer to be precise with: back to a real touch target. 44px is the
   floor Apple and Material both draw, and a 28px button is a miss waiting to
   happen on the control that now IS the add-to-cart. Verified under real
   mobile emulation at 375×812: pointer coarse, buttons 44×44. */
@media (hover: none), (pointer: coarse) {
  .qty--card { height: 44px; }
  .qty--card button { width: 44px; height: 44px; font-size: 1.05rem; }
  .qty--card span,
  .qty--card input { min-width: 28px; font-size: 0.8rem; }
  /* the same 44px floor this block exists for. The ask link was left at 26px,
     which is a miss waiting to happen on the one control that leaves the card.
     The BOX grows; the glyph does not, so the card's balance is unchanged. */
  .product-grid .card-ask { width: 44px; height: 44px; margin-top: -0.62rem; margin-right: -0.5rem; }
}

/* "ask about this item", moved up beside the name now that the actions row
   under the card is gone. It is the route to a custom import and the only
   thing on the card that is not the order.

   IT BELONGS TO THE NAME'S ROW, NOT THE ORDER'S. It used to be `grid-row: 2`
   with `align-self: start` — the SAME row as `.buy-rows`, whose first 0.7rem
   is the margin above the tear line. A 26px button starting at the top of that
   row therefore printed straight through the rule. It looked survivable only
   because a one-line name left row 1 shorter than the button; the moment a name
   wrapped, the button dropped onto the line. Measured before the fix at 1440:
   16 of 20 cards overlapped, single-line ones included — so this was never a
   two-line edge case, it was the wrong row.

   On row 1 the button cannot reach the tear line at any name depth, and
   because row 2 is `1fr` with `.buy-rows` pinned `end`, a slightly taller row 1
   spends the card's slack instead of pushing the tear lines out of alignment.
   The nudge pulls the 26px box onto the optical centre of the name's FIRST
   line (18.9px at 1.02rem/1.16), so every (i) in a row of cards sits on one
   line whether its neighbour's name wrapped or not. */
.product-grid .card-ask {
  grid-column: 2; grid-row: 1; align-self: start;
  margin-top: -0.22rem;
  width: 26px; height: 26px; display: grid; place-items: center;
  border-radius: 50%; color: var(--text-faint);
  transition: color var(--dur-fast) ease, background var(--dur-fast) ease;
}
.product-grid .card-ask svg { width: 16px; height: 16px; }
.product-grid .card-ask:hover { color: var(--green-900); background: color-mix(in srgb, var(--ink) 7%, transparent); }

/* the running total on the floating rail */
.rail-total {
  font-family: var(--font-display); font-weight: 600; font-size: 1.05rem;
  font-variant-numeric: tabular-nums; margin-left: auto; padding-right: 0.75rem;
}

/* ============================================================
   LA OFERTA SE ANUNCIA — 20 agosto 2026
   ------------------------------------------------------------
   Sergio: "se debería poder poner descuentos … y que eso se vea reflejado en
   ghl y en la página web y que lo anuncie en pagina web, ejemplo 15% de
   descuento."

   Un sello de rebaja en una casa importadora no es una pegatina de neón: es
   una marca de tampón sobre el papel, igual que el "Out of stock" que ya vive
   en la esquina opuesta. Los dos nunca coinciden — un producto agotado no está
   de oferta — así que comparten lenguaje y no compiten por el sitio.
   ============================================================ */
.product-card__off {
  position: absolute;
  top: 1rem;
  right: 1rem;
  z-index: 3;
  transform: rotate(4deg);
  background: var(--basil);
  color: #F4EFE2;
  border-radius: 2px;
  padding: 0.25rem 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  box-shadow: 0 2px 8px rgba(22, 20, 15, 0.22);
}
.product-grid .product-card__off { top: 2.5rem; right: 1rem; }

/* El precio de lista, tachado, delante del que se cobra. Se aprieta el
   tracking para que la fila siga cabiendo en una tarjeta de 269px. */
.buy-row__price s {
  color: var(--text-faint);
  font-weight: 400;
  margin-right: 0.3em;
  text-decoration-thickness: 1px;
}

/* AGOTADO YA NO ES UN BOTÓN APAGADO, ES UN ENLACE.
   Mantiene el peso visual de lo que era —una acción secundaria, en gris— pero
   se puede pulsar, porque en una casa que importa bajo pedido "agotado"
   significa "pregúntanos cuándo llega". */
.pc-soldask { white-space: nowrap; }

/* ============================================================
   §59  LA ESCALA DE TARIFAS  —  precio y ofertas   (27 ago 2026)
   ------------------------------------------------------------
   Sergio: "agrega a los filtros del catálogo del sitio web una opción para
   mostrar sólo productos con descuento activo, también por rangos de precio,
   por favor que quede estático y bien distribuido".

   "Estático" manda sobre casi todo lo demás, y descarta de entrada la respuesta
   obvia — un deslizador de mínimo y máximo. Un deslizador cambia de valor
   MIENTRAS se arrastra: la rejilla se repinta a cada píxel, la altura de la
   página cambia debajo del dedo y el contador nunca se está quieto. Además no
   dice qué hay: quien lo arrastra no sabe si acaba de vaciar la pantalla hasta
   que la vacía. Cinco casillas fijas dicen de antemano cuántos productos hay
   en cada tramo, no se mueven nunca, y funcionan igual con el dedo que con el
   tabulador.

   "Bien distribuido" es literal: las cinco casillas son EXACTAMENTE del mismo
   ancho (`grid-template-columns: repeat(5, 1fr)`) y llevan cifras tabulares,
   así que la escala no cambia de forma cuando el recuento pasa de 9 a 186.
   Ése es el fallo que convierte una barra de filtros en algo que "baila".

   El registro es distinto a propósito. Departamento y categoría son fichas
   sueltas; esto es UNA pieza: casillas contiguas separadas por filetes de
   1px, como la banda de tarifas de un impreso de aduana. Se lee como otra
   clase de pregunta porque lo es.

   Contraste medido sobre `--bone`: el rótulo y las casillas inactivas usan
   `--text-muted` (#5B5547, 6,4:1) y no `--text-faint`; a 0,66rem en versalitas
   espaciadas, `--text-faint` se queda en 4,4:1 — el mismo fallo que las
   tarjetas de reseñas (§57).
   ============================================================ */
.refine {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 0.5rem clamp(0.9rem, 0.5rem + 1.2vw, 1.7rem);
  padding-top: 0.3rem;
}
.refine[hidden] { display: none; }
.refine__g { display: flex; align-items: center; gap: 0.55rem; min-width: 0; }
.refine__g--right { margin-left: auto; }
.refine__lbl {
  font-family: var(--font-mono); font-size: 0.6rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--text-faint); white-space: nowrap;
}

/* la banda: cinco casillas iguales, unidas por filetes */
.band {
  display: grid; grid-template-columns: repeat(5, 1fr);
  border: 1px solid var(--line); background: color-mix(in srgb, var(--paper) 70%, transparent);
  width: clamp(260px, 30vw, 420px);
}
.band__cell {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1px; min-height: 34px; padding: 0.24rem 0.2rem;
  border: 0; border-left: 1px solid var(--line); border-radius: 0;
  background: none; cursor: pointer; color: var(--text-muted);
  font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.05em;
  font-variant-numeric: tabular-nums; line-height: 1.15;
  transition: background-color 0.22s var(--ease-out), color 0.22s var(--ease-out);
}
.band__cell:first-child { border-left: 0; }
.band__cell .c { font-size: 0.56rem; opacity: 0.62; }
.band__cell:hover:not(:disabled) { background: color-mix(in srgb, var(--brass) 12%, transparent); color: var(--ink); }
.band__cell:focus-visible { outline: 2px solid var(--brass); outline-offset: -2px; }
.band__cell[aria-pressed="true"] { background: var(--stamp); color: var(--paper); }
.band__cell[aria-pressed="true"] .c { opacity: 0.8; }
/* Una banda sin productos NO se esconde: se apaga y dice por qué. Esconderla
   movería las otras cuatro cada vez que cambia el departamento, y una escala
   que cambia de forma deja de leerse como una escala. */
.band__cell:disabled { opacity: 0.34; cursor: not-allowed; }

/* la oferta es la única acción de color de esta barra: el ticket de azafrán */
.offer-chip {
  flex: none; display: inline-flex; align-items: center; gap: 0.45rem;
  min-height: 34px; padding: 0.3rem 0.7rem;
  font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.11em;
  text-transform: uppercase; font-variant-numeric: tabular-nums;
  cursor: pointer; white-space: nowrap;
  border: 1px solid var(--line); border-radius: 0;
  background: color-mix(in srgb, var(--paper) 70%, transparent); color: var(--text-muted);
  transition: background-color 0.22s var(--ease-out), color 0.22s var(--ease-out),
              border-color 0.22s var(--ease-out);
}
.offer-chip .c { opacity: 0.62; }
.offer-chip:hover:not(:disabled) { color: var(--ink); border-color: var(--brass); }
.offer-chip:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
.offer-chip[aria-pressed="true"] {
  background: var(--saffron, #E9A93C); border-color: #C98F22; color: #3A2A08; font-weight: 700;
}
.offer-chip:disabled { opacity: 0.4; cursor: not-allowed; }

.refine__clear {
  flex: none; min-height: 34px; padding: 0.3rem 0.6rem;
  font-family: var(--font-mono); font-size: 0.6rem; letter-spacing: 0.12em;
  text-transform: uppercase; cursor: pointer;
  border: 0; background: none; color: var(--stamp);
  text-decoration: underline; text-underline-offset: 3px;
}
.refine__clear[hidden] { display: none; }
.refine__clear:hover { color: var(--brass); }
.refine__clear:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }

/* En pantallas estrechas la escala pasa a ocupar el ancho y las ofertas caen
   debajo, en su propia línea: apretar cinco casillas y un ticket en 375px es
   como se fabrica un toque fallido. Las casillas siguen midiendo 34px de alto,
   que con el ancho de un quinto de 343 da un objetivo cómodo. */
@media (max-width: 720px) {
  .refine { gap: 0.55rem 0.8rem; }
  .refine__g { width: 100%; }
  .refine__g--right { margin-left: 0; }
  .band { width: 100%; }
}

/* ============================================================
   §81  EL CATALOGO APILADO POR CATEGORIAS  (30 ago 2026)
   ------------------------------------------------------------
   Sergio: "que arriba diga pastas y este toda la categoria de pastas, pero
   no me gusta ese scroll horizontal … simplemente el titulo y abajo todas
   las pastas", y ademas "no debes cambiar el estilo visual de mi pagina".

   Por eso aqui NO hay ni una columna ni un gutter nuevos: la rejilla de cada
   grupo lleva la MISMA clase .product-grid que llevaba la lista plana, con
   sus cuatro columnas y sus saltos de 520/880/1180. Lo unico que cambia es
   que el contenedor deja de ser rejilla para poder apilar secciones.

   La cabecera reutiliza el filete de .eyebrow (base.css) en el color de la
   categoria, que es la misma variable --cat que ya llevan las tarjetas: asi
   el titulo y los productos de debajo se leen como una sola pieza.
   ============================================================ */
#productGrid.is-grouped { display: block; }

.cat-group + .cat-group { margin-top: clamp(2.4rem, 1.4rem + 3vw, 4.4rem); }

.cat-group__head {
  display: flex; align-items: baseline; gap: 0.9rem;
  margin-bottom: clamp(1rem, 0.6rem + 1.2vw, 1.6rem);
  padding-bottom: 0.55rem;
  border-bottom: 1px solid var(--line);
}

.cat-group__title {
  margin: 0; color: var(--ink);
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(1.15rem, 0.9rem + 1.05vw, 1.7rem);
  line-height: 1.12; min-width: 0;
}
/* El mismo filete corto de .eyebrow, en la tinta de la categoria. */
.cat-group__title::before {
  content: ""; display: inline-block;
  width: 26px; height: 2px; margin-right: 0.62rem;
  vertical-align: 0.26em;
  background: var(--cat, var(--olive));
}

/* font-variant-numeric: tabular-nums porque desde la ronda 13 la variable
   --font-mono ES Lenia, no una monoespaciada: sin pedirlo, las cifras de una
   columna de recuentos bailan de una fila a otra. */
.cat-group__count {
  margin-left: auto; white-space: nowrap;
  font-family: var(--font-mono); font-size: 0.62rem;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--text-faint); font-variant-numeric: tabular-nums;
}

@media (max-width: 560px) {
  /* En movil el recuento se va debajo: al lado del titulo obliga a partir el
     nombre de la categoria en dos lineas, y "Pesto, Pates & Antipasti" no
     cabe de otra forma. */
  .cat-group__head { flex-wrap: wrap; gap: 0.15rem 0.9rem; }
  .cat-group__count { margin-left: 0; width: 100%; }
}
