/* ============================================================
   products.css — Flex Europa · finaldesign
   Products list (§3.1): restrained left sidebar + results grid.

   REUSE: .showcase-card, .btn, .chip, .spec-row, .container,
   .section, .section-head, .mono-label and .visually-hidden all come
   from the approved home page unchanged. This file adds only the
   list-page shell — sidebar, toolbar, grid, pagination, filter sheet —
   and never redefines a component's internals.

   NEW COMPONENTS (justified in the §6 report): .breadcrumb and
   .pagination. Neither exists in finaldesign or any sibling design,
   and this engineering-skill installation ships no components-full.md
   to inherit them from. Both are drawn from existing tokens only.
   ============================================================ */

/* ---------------------------------------------------------------- Shell */
.catalogue {
  display: grid;
  /* minmax(0, 1fr), not 1fr: an `auto` minimum would let a wide grid child
     push the column past the container and reintroduce horizontal overflow. */
  grid-template-columns: var(--sidebar-inline-size) minmax(0, 1fr);
  gap: var(--space-2xl);
  align-items: start;
  padding-block: var(--space-2xl);
}

/* ------------------------------------------------------------- Aside stack
   The sidebar column holds two unrelated things — the category tree and
   the refine form — so it needs a wrapper of its own. Without one the
   grid would see three children and auto-place the form into the RESULTS
   column. */
.catalogue__aside {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xl);
  min-inline-size: 0;
}

/* ------------------------------------------------------------- Cat tree
   Links, not controls. Nothing here is scripted and nothing here holds
   state: the branch lives in the URL, and the backend marks the open one
   with aria-current="page".

   Built from what the page already uses — the sidebar's mono uppercase
   heading, the hairline, --step-size rows — so it reads as part of the
   same sidebar as the form below it rather than as a new component. */
.cat-tree__title {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--color-text);
  padding-block-end: var(--space-sm);
  border-block-end: var(--hairline-strong);
  margin-block-end: var(--space-sm);
}

.cat-tree__list,
.cat-tree__sublist {
  list-style: none;
}

.cat-tree__item+.cat-tree__item {
  padding-block-start: var(--space-sm);
  margin-block-start: var(--space-sm);
  border-block-start: var(--hairline);
}

.cat-tree__link,
.cat-tree__sublink {
  display: flex;
  align-items: center;
  padding-inline: var(--space-xs);
  margin-inline: calc(var(--space-xs) * -1);
  transition: background-color var(--transition-fast), color var(--transition-fast);
}

.cat-tree__link {
  min-block-size: var(--step-size);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--color-text);
}

.cat-tree__sublist {
  /* Indented by a hairline rather than by whitespace alone: the rule is
     what makes 35 children read as four branches instead of one list. */
  margin-block-start: var(--space-xs);
  margin-inline-start: var(--space-sm);
  padding-inline-start: var(--space-md);
  border-inline-start: var(--hairline);
}

.cat-tree__sublink {
  /* --step-size, like the parents. These were 32px on the reasoning that a
     dense index of links falls under the WCAG 2.5.8 inline exception — it
     does not. That exception is for a link INSIDE A SENTENCE, whose size is
     constrained by the surrounding text; a stacked list of standalone links
     is 35 standalone targets. §5 says 44, and the target gate said 44. */
  min-block-size: var(--step-size);
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
}

.cat-tree__link:hover,
.cat-tree__sublink:hover {
  background: var(--color-surface);
  color: var(--color-accent-deep);
}

.cat-tree__link:focus-visible,
.cat-tree__sublink:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* The open branch, marked by the server. */
.cat-tree__link[aria-current="page"],
.cat-tree__sublink[aria-current="page"] {
  color: var(--color-accent-deep);
  font-weight: var(--fw-medium);
  box-shadow: inset 2px 0 0 var(--color-accent-strong);
}

/* ---------------------------------------------------------------- Filters
   .filters-slot is the sidebar's docking point. products.js moves the ONE
   filter form between this slot and the sheet's body as the viewport
   crosses 992px — two copies of the markup would be two sources of truth
   for the same filter state. Below the collapse point the slot is empty,
   so it is taken out of the grid entirely rather than left as a gap. */
.filters {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.results {
  scroll-margin-block-start: var(--anchor-offset);
}

.filters__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  padding-block-end: var(--space-sm);
  border-block-end: var(--hairline-strong);
}

.filters__title {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--color-text);
}

/* "Clear all" is .text-action in components.css — see the note there.
   It was .filters__clear, which cart.html could not reach. */

.filter-group {
  border: none;
}

.filter-group__legend {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-block-end: var(--space-sm);
}

.filter-group__list {
  display: flex;
  flex-direction: column;
}

.filter-option {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  /* 44px row height from padding so the whole row is the target. */
  min-block-size: var(--step-size);
  padding-inline: var(--space-xs);
  margin-inline: calc(var(--space-xs) * -1);
  cursor: pointer;
  transition: background-color var(--transition-fast);
}

.filter-option:hover {
  background: var(--color-surface);
}

.filter-option__input {
  inline-size: 16px;
  block-size: 16px;
  accent-color: var(--color-accent-strong);
  flex: none;
  cursor: pointer;
}

.filter-option__label {
  font-size: var(--fs-sm);
  color: var(--color-text);
  cursor: pointer;
}

.filter-option__count {
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-size: var(--fs-tiny);
  color: var(--color-text-faint);
}

.filter-option__input:focus-visible {
  outline: 2px solid var(--color-accent-strong);
  outline-offset: 2px;
}

/* A facet that would return nothing is dimmed in place, never removed —
   the rows must not jump under the pointer as the user narrows down. */
.filter-option.is-disabled {
  cursor: not-allowed;
}

.filter-option.is-disabled:hover {
  background: none;
}

.filter-option.is-disabled .filter-option__label,
.filter-option.is-disabled .filter-option__count {
  color: var(--color-text-faint);
}

.filter-option.is-disabled .filter-option__input,
.filter-option.is-disabled .filter-option__label {
  cursor: not-allowed;
}

/* Price band — a min/max pair, not a slider (keyboard-operable by default). */
.filter-price {
  display: flex;
  align-items: flex-end;
  gap: var(--space-sm);
}

.filter-price__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  flex: 1 1 0;
  min-inline-size: 0;
}

.filter-price__label {
  font-family: var(--font-mono);
  font-size: var(--fs-tiny);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.filter-price__input {
  min-block-size: var(--step-size);
  padding-inline: var(--space-sm);
  border: var(--hairline-strong);
  border-radius: var(--radius-control);
  background: var(--color-bg);
  font-family: var(--font-mono);
  /* 16px minimum stops iOS zooming the page on focus. */
  font-size: var(--fs-md);
  color: var(--color-text);
  inline-size: 100%;
}

.filter-price__input:focus-visible {
  outline: none;
  border-color: var(--color-accent-strong);
  box-shadow: var(--focus-ring);
}

.filter-price__hint {
  margin-block-start: var(--space-sm);
  font-size: var(--fs-tiny);
  color: var(--color-text-muted);
}

/* ---------------------------------------------------------------- Toolbar */
.results__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding-block-end: var(--space-md);
  border-block-end: var(--hairline);
}

.results__count {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.results__count strong {
  color: var(--color-text);
  font-weight: var(--fw-semibold);
}

.results__mobile-category {
  display: none;
  margin-block-end: var(--space-md);
}

.results__mobile-category__label {
  display: block;
  margin-block-end: var(--space-xs);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.results__mobile-category__select {
  min-block-size: var(--step-size);
  padding-inline-start: var(--space-sm);
  border: var(--hairline-strong);
  border-radius: var(--radius-control);
  background: var(--color-bg);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--color-text);
  cursor: pointer;
  inline-size: 100%;
}

.results__mobile-category__select:focus-visible {
  outline: none;
  border-color: var(--color-accent-strong);
  box-shadow: var(--focus-ring);
}

.results__tools {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.sort {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.sort__label {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.sort__select {
  min-block-size: var(--step-size);
  /* Start only: the end padding is the shared select rule's, and it is
     what keeps a long option off the chevron. A shorthand here would
     overwrite it — components.css loads before this file. */
  padding-inline-start: var(--space-sm);
  border: var(--hairline-strong);
  border-radius: var(--radius-control);
  background: var(--color-bg);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--color-text);
  cursor: pointer;
}

.sort__select:focus-visible {
  outline: none;
  border-color: var(--color-accent-strong);
  box-shadow: var(--focus-ring);
}

/* Applied-filter chips — .chip reused, with a remove control added. */
.results__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  padding-block: var(--space-md);
}

.chip--removable {
  padding-inline-end: var(--space-xs);
}

.chip__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--step-size);
  block-size: var(--step-size);
  /* Negative margin keeps the 44px target without inflating the chip. */
  margin: calc((var(--step-size) - 18px) / -2);
  margin-inline-start: var(--space-xs);
  color: var(--color-text-muted);
}

.chip__remove:hover {
  color: var(--color-danger);
}

.chip__remove .icon {
  inline-size: 12px;
  block-size: 12px;
}

/* ---------------------------------------------------------------- Empty / error */
.results__state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
  text-align: center;
  padding-block: var(--space-3xl);
}

.results__state-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 64px;
  block-size: 64px;
  border: var(--hairline-strong);
  color: var(--color-text-faint);
}

.results__state-icon .icon {
  inline-size: 28px;
  block-size: 28px;
}

.results__state-title {
  font-family: var(--font-heading);
  font-size: var(--fs-xl);
  font-weight: var(--fw-semibold);
  color: var(--color-text);
}

.results__state-text {
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
  max-inline-size: 46ch;
}

/* ---------------------------------------------------------------- Skeleton cards
   Same aspect-ratio as .showcase-card, so the grid does not reflow
   when results land. */
.card-skeleton {
  aspect-ratio: 480 / 734;
  padding: var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.card-skeleton__eyebrow {
  block-size: 12px;
  inline-size: 45%;
}

.card-skeleton__media {
  flex: 1 1 0;
  min-block-size: 0;
}

.card-skeleton__title {
  block-size: 2.3em;
  font-size: var(--fs-lg);
}

.card-skeleton__desc {
  block-size: 3.1em;
  font-size: var(--fs-sm);
}

.card-skeleton__price {
  block-size: 1.4em;
  inline-size: 40%;
  font-size: var(--fs-md);
}

.card-skeleton__actions {
  block-size: 46px;
}

.card-skeleton__eyebrow,
.card-skeleton__media,
.card-skeleton__title,
.card-skeleton__desc,
.card-skeleton__price,
.card-skeleton__actions {
  background: linear-gradient(90deg,
      var(--drawer-skeleton-1) 0%, var(--drawer-skeleton-2) 50%, var(--drawer-skeleton-1) 100%);
  background-size: 200% 100%;
  animation: skeleton-sweep 1.2s var(--ease-inout) infinite;
}


/* ---------------------------------------------------------------- Mobile filter trigger
   Hidden on desktop, where the sidebar is always visible. */
.filters__trigger {
  display: none;
}

/* ---------------------------------------------------------------- No JavaScript
   The catalogue is rendered from the model, so without scripting there is
   nothing to show. Rather than leave the skeleton pulsing forever, say so
   and give a route that works. In the MVC build the grid is server-
   rendered and this block never applies.
   The html element carries `no-js` until the inline head script flips it,
   which is the project's existing convention. */
/* .results__noscript is gone: the catalogue is static markup now, so
   there is no state in which scripting being off leaves the page empty.
   The sort form's submit is the same idea kept — a control that works
   whether or not products.js ran. */
.sort__go {
  flex: none;
}

.js .sort__go {
  display: none;
}

.no-js .js-results-skeleton,
.no-js .results__bar,
.no-js .filters__trigger {
  display: none;
}

/* ---------------------------------------------------------------- Breakpoints */
@media (max-width: 991px) {

  /* The sidebar becomes a sheet; the results take the full width. The now
     empty docking slot leaves the grid so it contributes no gap. */
  .catalogue {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-lg);
  }

  /* The aside goes UNDER the results here. It is one grid column now, and
     43 category links above the first product would push the catalogue
     itself off the screen. Hiding it with the filter form was the other
     option and it is worse: the filters have a sheet to move into, the
     tree has nowhere, and the sub-categories would be unreachable on a
     phone entirely. Underneath the grid it reads as a category index. */
  .catalogue__aside {
    display: none;
    order: 2;
  }

  .results {
    order: 1;
  }

  .results__mobile-category {
    display: block;
    margin-top: var(--space-md);
  }

  /* Two columns of links, balanced, so the index is a block at the foot of
     the page rather than a 1,400px ribbon. break-inside keeps a parent and
     its children in the same column. */
  .cat-tree__list {
    columns: 2;
    column-gap: var(--space-xl);
  }

  .cat-tree__item {
    break-inside: avoid;
  }

  .cat-tree__item+.cat-tree__item {
    border-block-start: none;
  }

  /* Gated on `.js`, which the inline head script sets BEFORE first paint.
     That matters for CLS: earlier attempts hid the slot only once
     products.js had run, so the first paint showed the whole sidebar in
     the flow and the results then jumped ~600px up when it collapsed —
     a measured 0.514 layout shift. Gating on `.js` means the slot never
     paints at all when scripting is available, and without scripting the
     filters simply stay in the flow, degraded but fully usable. */
  .js .filters-slot {
    display: none;
  }

  .filters__trigger {
    display: inline-flex;
    inline-size: 100%;
    justify-content: center;
  }

  .results__bar {
    padding-block-end: 0;
    border-block-end: none;
  }

  .results__tools {
    inline-size: 100%;
  }

  .sort {
    inline-size: 100%;
    flex-direction: column;
    align-items: stretch;
  }

  .sort__label {
    display: inline-block;
    margin-block-end: var(--space-xs);
  }

  .sort .select-shell {
    flex: 1 1 0;
    min-inline-size: 0;
  }

  .sort__select {
    inline-size: 100%;
  }
}

@media (max-width: 575px) {
  .cat-tree__list {
    columns: 1;
  }
}

@media (max-width: 767px) {
  .results__bar {
    align-items: stretch;
  }

  .results__tools {
    inline-size: 100%;
  }

  .sort {
    inline-size: 100%;
  }

  /* The select is wrapped in .select-shell now (it carries the chevron),
     so the shell is the flex child that has to grow, not the select. */
  .sort .select-shell {
    flex: 1 1 0;
    min-inline-size: 0;
  }

  .sort__select {
    inline-size: 100%;
  }
}

/* ============================================================
   MOBILE FILTER SHEET
   Deliberately built on the SAME primitives as the cart drawer, and the
   focus trap / scroll lock / Esc / swipe logic is the drawer's own module
   (§3.1 "Reuse that logic — do not write a second implementation").

   The sheet's root element carries BOTH .cart-drawer and .filter-sheet.
   .cart-drawer supplies the fixed full-viewport shell, the z-index, the
   scrim fade and the .is-open rules; everything below overrides only the
   entry edge and the width. Reusing the __ element classes WITHOUT the
   root class inherited none of that: the panel had no positioned
   ancestor and stayed translated off-screen while the module still
   locked scroll and trapped focus — an invisible dialog.
   ============================================================ */
.filter-sheet .cart-drawer__panel {
  inline-size: min(var(--drawer-inline-size), var(--drawer-mobile-inline));
  /* Enters from the START edge, so it does not fight the bag drawer's
     mental model of "the bag lives on the end edge". */
  inset-inline-start: 0;
  inset-inline-end: auto;
  border-inline-start: none;
  border-inline-end: var(--hairline);
  transform: translateX(-100%);
}

[dir="rtl"] .filter-sheet .cart-drawer__panel {
  transform: translateX(100%);
}

/* Must stay AFTER the RTL rule: both are three classes' worth of
   specificity, so source order decides, and the open state has to win in
   both writing directions. */
.filter-sheet.is-open .cart-drawer__panel {
  transform: translateX(0);
}

.filter-sheet .cart-drawer__body {
  padding-block: var(--space-lg);
}

.filter-sheet .cart-drawer__footer {
  background: var(--color-bg);
}

.filter-sheet .cart-drawer__actions {
  grid-template-columns: 1fr 1fr;
}

@media (min-width: 992px) {

  /* Above the collapse point the sheet can never open. */
  .filter-sheet {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .filter-sheet .cart-drawer__panel {
    transition: none;
  }

  .card-skeleton__eyebrow,
  .card-skeleton__media,
  .card-skeleton__title,
  .card-skeleton__desc,
  .card-skeleton__price,
  .card-skeleton__actions {
    animation: none;
  }
}