/* Category / catalog pages. Figma node 25:414: full-width intro, then a 285px
   filter column beside a three-column grid of 285x524 cards. */

.category-page {
  padding: var(--space-2xl) 0 var(--space-5xl);
}

.category-page__header {
  margin-bottom: var(--space-2xl);
}

/* Scoped to the category page specifically — an unscoped `.page-title h1`
   would win on every other page too (it was, before the generic
   `.page > .page-title h1` rule in components.css exposed the collision:
   same specificity, but this file loads after, so it always overrode). The
   category listing wants a smaller title than the generic 3xl since a grid
   of cards follows immediately below it. */
.category-page .page-title h1 {
  margin: 0 0 var(--space-xs);
  /* The search page puts the shopper's own query in this heading; a long
     unbroken term must wrap instead of overflowing a 390px viewport. */
  overflow-wrap: anywhere;
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-tight);
}

.category-description {
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  max-width: 62rem;
}

.category-description p {
  margin: 0;
}

.catalog-layout {
  display: grid;
  grid-template-columns: var(--filter-column-width) minmax(0, 1fr);
  align-items: start;
  gap: var(--space-md);
}

.catalog-layout__results {
  min-width: 0;
}

/* ---- Refresh state ----
   nopCommerce's public.catalogproducts.js shows `.ajax-products-busy` while it
   fetches and hides it when the server response lands, so this one rule covers
   every refresh path: filters, sorting and paging. It toggles with jQuery
   show/hide rather than the `hidden` attribute this theme normally uses, which
   is why the resting state is `display: none` here — `hidden` is authoritative
   in base.css and would outrank the inline display jQuery writes to show it.
   For the same reason the shown element is a block, not a flex container:
   jQuery restores a div's default display, so the spinner centres on a margin.

   The veil sits over the grid being replaced instead of emptying it, so the
   page keeps its height and nothing jumps under the pointer mid-request. */
.products-container {
  position: relative;
}

.ajax-products-busy {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 1;
  background-color: var(--color-busy-veil);
}

/* Sticky, not just centred at the top: a filter change made from the bottom of
   a long grid would otherwise spin somewhere far above the viewport, leaving
   only a dimmed page with no sign anything is happening. Offset clears the
   sticky header. */
.ajax-products-busy .loading-icon {
  display: block;
  position: sticky;
  top: calc(var(--header-height) + var(--space-md));
  margin: var(--space-2xl) auto 0;
  width: 3rem;
  height: 3rem;
}

.category-grid .item-grid,
.product-grid .item-grid {
  --grid-gap: var(--space-md);

  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--grid-row-gap, var(--grid-gap)) var(--grid-gap);
  list-style: none;
  margin: 0;
  padding: 0;
}

/* The catalog card is the shared compact card (book-card.css, "Compact
   variant"), which keys off .catalog-layout. */

/* Card hover motion uses the file's own prototype timing
   (DISSOLVE / EASE_IN / 100ms): the cover lifts clear of the tinted panel. */

@media (hover: hover) and (pointer: fine) {
  .book-card__cover-frame {
    transition: transform var(--motion-card) var(--ease-in);
  }

  .book-card:hover .book-card__cover-frame {
    transform: translateY(-0.5rem);
  }

  .book-card__add-to-cart {
    transition: filter var(--motion-card) var(--ease-in);
  }
}

@media (prefers-reduced-motion: reduce) {
  .book-card:hover .book-card__cover-frame {
    transform: none;
  }
}

/* ---- Filter panel ---- */

/* The filter button and the phone sort button are one control shape: the
   same outlined box, type and hover (style guide rule 2). */
.filter-drawer-toggle,
.catalog-sort__toggle {
  display: none;
}

.filter-drawer {
  --filter-actions-height: 7.5rem;
}

.filter-drawer__header {
  display: block;
  margin-bottom: var(--space-md);
}

.filter-drawer__heading {
  margin: 0;
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
}

.filter-drawer__close.drawer-close {
  display: none;
}

.filter-drawer__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Disclosure and selection controls are shared primitives; this panel owns placement. */
.product-filter {
  min-width: 0;
  padding-bottom: var(--space-xs);
  border-bottom: 1px solid var(--color-border);
}

.product-filter__title {
  margin-bottom: var(--space-xs);
}

.product-filter:not([open]) > .product-filter__title {
  margin-bottom: 0;
}

.product-filter[open] > .product-filter__title {
  position: sticky;
  top: var(--site-header-offset, var(--header-height));
  z-index: 2;
  margin-inline: calc(-1 * var(--space-2xs));
  padding-inline: var(--space-2xs);
  background-color: var(--color-canvas);
}

.filter-drawer .product-filter__title,
.filter-drawer .filter-more__toggle {
  min-height: var(--tap-target);
}

.filter-drawer .product-filter__title {
  font-size: var(--font-size-sm);
}

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

.filter-drawer .filter-option {
  gap: var(--space-2xs);
  min-height: var(--tap-target);
  padding-block: 0;
  font-size: var(--font-size-xs);
}

.filter-option__count {
  color: var(--color-muted);
  white-space: nowrap;
}

/* Live counts change under the shopper's eyes; fixed-width digits keep the
   labels and the apply button from shifting as they do. */
.filter-option__count,
.filter-drawer__result-count {
  font-variant-numeric: tabular-nums;
}

.filter-drawer__result-count {
  white-space: nowrap;
}

.filter-option:has(> input:disabled) .filter-option__count {
  color: var(--color-subtle);
}

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

.filter-more__toggle {
  order: 1;
}

.filter-category-links {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ---- Dual-handle price slider ----
   A custom control (two role="slider" handles over one track) because a native
   range input carries a single thumb; see the Book Catalog plugin's
   Views/CatalogFilters/Default.cshtml. */
.price-slider {
  position: relative;
  height: var(--space-md);
  /* Inset by half a handle so a handle parked on either bound stays inside the
     panel instead of overflowing it (and the page) at narrow widths. */
  margin: 0 calc(var(--space-md) / 2) var(--space-xs);
  touch-action: none;
}

.price-slider__track,
.price-slider__fill {
  position: absolute;
  top: 50%;
  height: var(--space-3xs);
  border-radius: var(--radius-pill);
  transform: translateY(-50%);
}

.price-slider__track {
  right: 0;
  left: 0;
  background-color: var(--color-border);
}

.price-slider__fill {
  background-color: var(--color-action);
}

.price-slider__handle {
  position: absolute;
  top: 50%;
  width: var(--space-md);
  height: var(--space-md);
  margin-left: calc(var(--space-md) / -2);
  border-radius: var(--radius-pill);
  background-color: var(--color-action);
  box-shadow: 0 0 0 0.125rem var(--color-canvas);
  transform: translateY(-50%);
  cursor: grab;
  touch-action: none;
}

.price-slider.is-dragging .price-slider__handle {
  cursor: grabbing;
}

.price-slider__handle:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}

/* The two number fields of a range facet (price, publication year). */
.range-fields {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
}

.filter-drawer__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  margin-top: var(--space-md);
}

@media (min-width: 62rem) {
  .filter-drawer {
    display: grid;
    grid-template-rows: auto 1fr;
  }

  .filter-drawer__body {
    grid-column: 1;
    grid-row: 2;
    padding-bottom: calc(var(--filter-actions-height) + var(--space-md));
  }

  .filter-drawer__actions {
    grid-column: 1;
    grid-row: 2;
    position: sticky;
    top: calc(100dvh - var(--filter-actions-height) - var(--space-md));
    z-index: 1;
    align-self: start;
    min-height: var(--filter-actions-height);
    margin: 0;
    padding: var(--space-sm);
    border-radius: var(--radius-md);
    background-color: var(--color-canvas);
    box-shadow: var(--shadow-card);
  }
}

/* The pill primitive (controls.css), a size down and tracked: the drawer's
   pair sits in a narrow column or bar, not on the page. Clear is the quiet
   primitive on the desktop card and the outline pill below it. */
.filter-drawer__apply {
  font-size: var(--font-size-xs);
  letter-spacing: var(--letter-spacing-caps);
}

/* ---- Sorting ---- */

.catalog-toolbar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2xs);
  margin-bottom: var(--space-md);
}

.catalog-selectors {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2xs);
  font-size: var(--font-size-sm);
  color: var(--color-muted);
}

/* Wider screens show the sort panel inline as one quiet row - label, field,
   direction glyph - exactly as before; the phone button never renders. */
.catalog-sort {
  position: relative;
}

.catalog-sort__panel,
.catalog-sort__row {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3xs);
}

.catalog-sort__label--direction {
  display: none;
}

.catalog-selectors__direction-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

/* An unboxed select, so the row reads as text with a glyph as in Figma. */
.catalog-selectors__select {
  border: 0;
  background: transparent;
  font: inherit;
  color: var(--color-ink);
  cursor: pointer;
}

/* A real control now, not decoration: it has a hover, a pressed state and a
   disabled state for fields that carry no opposite (position, newest). */
.catalog-selectors__direction {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap-target);
  min-height: var(--tap-target);
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-ink);
  cursor: pointer;
}

.catalog-selectors__direction[aria-pressed="true"] {
  background-color: var(--color-brand-soft);
}

.catalog-selectors__direction:disabled {
  color: var(--color-subtle);
  cursor: default;
}

/* The shared sort glyph (Views/Shared/_SortIcon.cshtml). Its bars flip to read
   as a direction, not just a toggled colour; the arrow stays put, so ascending
   and descending are distinct shapes. The phone toggle mirrors the direction
   button's pressed state, so both surfaces always show the applied order. */
.sort-icon {
  flex: none;
  width: var(--sort-icon-size);
  height: var(--sort-icon-size);
}

.sort-icon__bars {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform var(--motion-switch) var(--ease-out);
}

.catalog-selectors__direction[aria-pressed="true"] .sort-icon__bars,
.catalog-sort:has([data-sort-direction][aria-pressed="true"])
  .catalog-sort__toggle
  .sort-icon__bars {
  transform: scaleY(-1);
}

/* ---- Pager ---- */

.pager {
  margin-top: var(--space-2xl);
}

/* The search page has no form of its own (the header search box is the entry
   point) and renders the category listing above; `.search-button` stays a
   shared primitive in shared/controls.css for the native forum search. */

.no-result {
  color: var(--color-muted);
  padding: var(--space-xl) 0;
}

/* ---- Responsive ---- */

@media (max-width: 61.9375rem) {
  .catalog-layout {
    display: block;
  }

  /* Filter button and sorting share one row that stays under the sticky
     header while the grid scrolls. The row is a full-bleed canvas band so
     cards pass beneath it cleanly. The header's height varies with its
     search row, so shell/header-offset.js publishes it; the token is the
     fallback. */
  .catalog-toolbar {
    position: sticky;
    top: var(--site-header-offset, var(--header-height));
    z-index: var(--catalog-toolbar-z);
    justify-content: space-between;
    margin-inline: calc(-1 * var(--gutter));
    padding: var(--space-2xs) var(--gutter);
    background-color: var(--color-canvas);
  }

  .filter-drawer-toggle {
    display: inline-flex;
  }

  .catalog-selectors {
    margin-inline-start: auto;
  }

  /* The drawer is a fixed frame: only the body scrolls, so the header and
     the Apply/Clear bar stay in view for the whole filtering session instead
     of waiting at the end of a long form. */
  .filter-drawer {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: flex;
    flex-direction: column;
    background-color: var(--color-canvas);
    overflow: hidden;
    transform: translateX(-100%);
    visibility: hidden;
  }

  .filter-drawer--open {
    transform: translateX(0);
    visibility: visible;
  }

  @media (prefers-reduced-motion: no-preference) {
    .filter-drawer {
      transition: transform var(--motion-drawer) var(--ease-out);
    }
  }

  .filter-drawer__header {
    display: flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: space-between;
    margin: 0;
    padding: var(--gutter) var(--gutter) var(--space-md);
  }

  /* The gutter lives on the scroller, not the drawer, so focus rings and the
     price handles parked on a bound are not clipped at its edge. */
  .filter-drawer__body {
    flex: 1 1 auto;
    min-height: 0;
    padding: 0 var(--gutter) var(--space-md);
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .product-filter[open] > .product-filter__title {
    top: 0;
  }

  /* The bar's surface is the shared `.ui-action-bar` (components.css). Its
     pair is one row of two pills a third and two thirds wide: Clear, the
     outline pill (controls.css), on the left and Apply under the thumb on the
     right, so the bar stays one row high and the list above keeps the screen.
     The markup keeps Apply first, which is the desktop card's visual order and
     the order a keyboard reaches them in; the grid areas only swap them here,
     where the drawer is used by touch. */
  .filter-drawer__actions {
    display: grid;
    flex-shrink: 0;
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
    grid-template-areas: "clear apply";
    gap: var(--space-2xs);
    margin: 0;
  }

  .filter-drawer__apply {
    grid-area: apply;
  }

  /* Tracked caps, as the pill beside it. */
  .filter-drawer__clear {
    grid-area: clear;
    letter-spacing: var(--letter-spacing-caps);
  }

  .filter-drawer__close.drawer-close {
    display: inline-flex;
  }

  .category-grid .item-grid,
  .product-grid .item-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Phones keep two books per row so a listing can be scanned, not scrolled one
   cover at a time. The catalog card tightens to fit the half-width column:
   smaller gaps, body padding, badge and type. The home page cards are not
   affected - everything here is scoped to the catalog listing. */
@media (max-width: 36.25rem) {
  /* Phones: two equal buttons. Sorting collapses behind its own button, which
     opens a panel with the field and direction controls below the row. The
     50% basis sizes each item's border box, so the filter button's own
     padding does not make it wider than the sort button's wrapper. */
  .filter-drawer-toggle,
  .catalog-selectors {
    flex: 1 1 50%;
    min-width: 0;
  }

  .catalog-sort {
    position: static;
    width: 100%;
  }

  .catalog-sort__toggle {
    display: flex;
    width: 100%;
  }

  .catalog-sort__panel {
    position: absolute;
    top: 100%;
    right: var(--gutter);
    left: var(--gutter);
    display: none;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-sm);
    padding: var(--space-sm);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background-color: var(--color-canvas);
    box-shadow: var(--shadow-card);
  }

  .catalog-sort--open .catalog-sort__panel {
    display: flex;
  }

  /* While the panel is open the whole page dims and stops scrolling (the
     body gets .no-scroll); only the toolbar's buttons and the panel stay
     lit. The row lifts above the sticky header so its backdrop covers the
     header too. */
  .catalog-toolbar:has(.catalog-sort--open) {
    z-index: var(--catalog-toolbar-open-z);
  }

  /* The shared .page-backdrop, placed under the toolbar's lit controls. */
  .catalog-sort__backdrop {
    z-index: 0;
  }

  .filter-drawer-toggle,
  .catalog-sort__toggle {
    position: relative;
    z-index: 1;
  }

  .catalog-sort__panel {
    z-index: 1;
  }

  .catalog-sort__row {
    justify-content: space-between;
    gap: var(--space-sm);
    min-height: var(--tap-target);
  }

  .catalog-sort__label--direction {
    display: inline;
  }

  /* The direction control spells out its state here instead of relying on
     the glyph alone. */
  .catalog-selectors__direction {
    gap: var(--space-2xs);
    padding-inline: var(--space-sm);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
  }

  .catalog-selectors__direction-label {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip: auto;
  }

  .category-grid .item-grid,
  .product-grid .item-grid {
    --grid-gap: var(--space-xs);

    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .catalog-layout .book-card__cover-frame {
    padding-top: var(--space-lg);
  }

  .catalog-layout .book-card__body {
    padding: var(--space-xs);
    gap: var(--space-3xs);
  }

  .catalog-layout .book-card__badges {
    top: var(--space-xs);
  }

  .catalog-layout .book-card__badge {
    min-width: 0;
    min-height: var(--space-lg);
    padding-inline: var(--space-2xs);
    font-size: var(--font-size-3xs);
  }

  .catalog-layout .book-card__title {
    font-size: var(--font-size-sm);
  }

  .catalog-layout .book-card__title--long,
  .catalog-layout .book-card__author {
    font-size: var(--font-size-xs);
  }

  .catalog-layout .price__current {
    font-size: var(--font-size-sm);
  }

  /* The price and icon-only action share one row on most phones; on the
     narrowest ones the action drops below instead of overflowing the card. */
  .catalog-layout .book-card__footer {
    flex-wrap: wrap;
    padding-top: var(--space-xs);
  }
}

/* ---- Dropdown placement overrides ---- */

/* The sort control is unboxed in the design, so its trigger stays borderless. */
.catalog-selectors .styled-select__trigger {
  min-height: 0;
  padding: 0;
  border: 0;
  background: transparent;
  font-size: var(--font-size-sm);
  width: auto;
}

.catalog-selectors .styled-select__listbox {
  left: auto;
  right: 0;
  min-width: 12rem;
}
