/* =======================================================================
 * jensik — PRODUCT ARCHIVE
 * =========================
 * /shop/ and every product category, tag and brand archive. Loaded on those
 * routes only, by nk_archive_enqueue() in inc/archive.php — like homepage.css
 * and unlike commerce.css, which is on every route in the store.
 *
 * It loads **after** woocommerce.css so it can win against Woo's compat sheet
 * without !important: `body.woocommerce-page` is on every page this file
 * styles, and a Woo rule carrying it out-specifies any single class of ours.
 * See .claude/rules/css.md.
 *
 * Designed at 360px. Two things drive nearly every decision here:
 *
 *   1. **Vertical space above the first product is the scarcest thing on the
 *      page.** The crumb line, the title, the category rail, the two controls
 *      and the chip rail together have to cost less than one fold, or a parent
 *      arrives at a shop and sees no shop. That is why the crumbs scroll on one
 *      line, the rail is a 56px disc rather than a 4:5 card, and the count and
 *      the view switch share a row.
 *   2. **Nothing may scroll the page sideways.** Four separate horizontal
 *      scrollers live here (crumbs, category rail, chip rail, and the grid's
 *      own bleed). Each bleeds to the gutter with a negative margin and takes
 *      the gutter back as padding; none of them is ever wider than the
 *      viewport.
 *
 * Sections
 *   1. Shell and head
 *   2. CategoryRail        .nk-cat-rail / .nk-cat-pill
 *   3. Toolbar             .nk-archive__toolbar / __controls / __meta
 *   4. QuickFilters        .nk-quick-filters
 *   5. Results, list view, skeletons
 *   6. Loader and pagination
 *   7. SortSheet           .nk-sort-list
 *   8. FilterSheet facets  .nk-filter-group
 *   9. Desktop             the filter sheet becomes a static sidebar
 * ===================================================================== */

/* =======================================================================
 * 1. SHELL AND HEAD
 * ===================================================================== */

.nk-archive {
  --nk-archive-rail-size: 56px;   /* the category pill's media disc */
  --nk-archive-list-thumb: 112px; /* the list view's fixed thumbnail */
  --nk-archive-sidebar: 264px;    /* desktop filter column */

  width: 100%;
  max-width: var(--nk-container-max);
  margin-inline: auto;
  padding-inline: max(var(--nk-gutter), var(--nk-safe-inline-start));
  padding-block: var(--nk-space-4) var(--nk-space-12);
}

.nk-archive__head {
  display: grid;
  gap: var(--nk-space-2);
  margin-block-end: var(--nk-space-4);
}

/*
 * The crumb line is desktop-only. On a phone the trail duplicated the page it
 * sits on — the last crumb *is* the <h1> directly beneath it — while the levels
 * above it are already reachable from the bottom nav, the drawer and the
 * category rail. It cost a line of the 360px fold for a path nobody walks back
 * up on a phone, so it is hidden below the theme's 1024px mobile↔desktop line
 * rather than rendered and scrolled. It is still in the markup: the whole
 * trail is one <ol> with `aria-current`, which is orientation a screen reader
 * user gets on any width, and hiding it in CSS keeps the archive cacheable —
 * the page is identical for every device.
 *
 * `.nk-breadcrumb` already scrolls rather than wraps; the bleed below lets the
 * last crumb run off the gutter instead of stopping dead a gutter short of it,
 * which reads as the end of the trail.
 */
.nk-archive__crumbs { display: none; }

@media (min-width: 1024px) {
  .nk-archive__crumbs {
    display: flex;
    margin-inline: calc(var(--nk-gutter) * -1);
    padding-inline: var(--nk-gutter);
    /* The component pads 12px above and below for a trail standing on its own.
       Here it is the first line of a head block that already has its own gap,
       and the links keep their full 44px target regardless — so the padding is
       the one thing that can go. */
    padding-block: 0;
  }
}

.nk-archive__title {
  font-family: var(--nk-font-display);
  font-size: var(--nk-text-2xl);
  font-weight: var(--nk-weight-bold);
  line-height: var(--nk-leading-tight);
  color: var(--nk-text-strong);
}

/*
 * The category description, clamped to two lines behind a native <details>.
 * No script and no measurement: the summary holds the text, `-webkit-line-clamp`
 * hides the rest, and opening removes the clamp. The marker is suppressed
 * because the affordance is the «بیشتر» word, which sits inside the clamp and
 * is therefore always on the last visible line.
 */
.nk-archive__desc {
  font-size: var(--nk-text-base);
  line-height: var(--nk-leading-normal);
  color: var(--nk-text-muted);
}
.nk-archive__desc > summary {
  list-style: none;
  cursor: pointer;
  position: relative;
}
.nk-archive__desc > summary::-webkit-details-marker { display: none; }

.nk-archive__desc-text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}
.nk-archive__desc[open] .nk-archive__desc-text {
  -webkit-line-clamp: unset;
  line-clamp: unset;
}
.nk-archive__desc-text p { margin: 0; }
.nk-archive__desc-text p + p { margin-block-start: var(--nk-space-2); }

.nk-archive__desc-more {
  color: var(--nk-text-accent);
  font-size: var(--nk-text-sm);
  font-weight: var(--nk-weight-medium);
}
.nk-archive__desc[open] .nk-archive__desc-more { display: none; }
.nk-archive__desc > summary:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: var(--nk-focus-ring-offset);
  border-radius: var(--nk-radius-sm);
}

/* =======================================================================
 * 2. CATEGORY RAIL — .nk-cat-rail / .nk-cat-pill
 *
 * The compact department strip. Deliberately not `.nk-catcard`, whose 4:5
 * photograph would take most of a 360px fold before a single product is
 * visible: a 56px disc over one line of text is the same navigation at a
 * fifth of the height, and it is the same artwork from nk_category_thumb().
 *
 * Native scroll-snap and no JavaScript, exactly like `.nk-brand-rail` —
 * `proximity`, not `mandatory`, because a department row is browsed rather
 * than paged, and mandatory yanks every release to the nearest pill.
 * ===================================================================== */

/*
 * **This is the one sticky element on the archive, and the only one.**
 *
 * It used to be the toolbar. The rail is the better choice on a phone for a
 * simple reason: what a parent wants within reach four rows into a category is
 * *the other categories*, not the sort they already chose. Sticking both stacks
 * ~150px of chrome under the header and leaves barely a card visible; sticking
 * the sort control and not the departments is the wrong one of the two.
 *
 * It bleeds to the screen edge so the stuck band spans the viewport, and takes
 * the gutter back as padding so nothing inside it moves — the track then bleeds
 * again from *this* box and lands in exactly the same place it did before.
 *
 * `--nk-z-sticky` is 100 against the header's 200, so the band can slide under
 * the header but never over it.
 */
.nk-cat-rail {
  position: sticky;
  /*
   * `--nk-header-stuck` is the header's *measured* height, published by
   * initHeader() in ui.js — never `--nk-header-height`, which is only the
   * height of `.nk-header__bar`. The announcement strip is inside the sticky
   * header, it is dismissible, and its sentence wraps to two lines at 360px,
   * so the real offset is anywhere from 56px to ~147px. Pinned at 56 the
   * header covered the top half of every category disc. The fallback keeps the
   * no-JS case sane rather than correct, which is the right trade for a rail
   * that is fully usable unpinned.
   */
  inset-block-start: var(--nk-header-stuck, var(--nk-header-height));
  z-index: var(--nk-z-sticky);

  /*
   * The inline pair is a bleed and its cancellation, and both halves are
   * load-bearing: the negative margin takes the band to the screen edge so the
   * stuck background spans it, and the padding gives that width straight back
   * so the track inside can bleed once more from a box that starts at the
   * gutter. Zero the padding and the track's own -16px lands *outside* the
   * viewport and the whole document goes 32px wide.
   */
  margin-inline: calc(var(--nk-gutter) * -1);
  padding-inline: var(--nk-gutter);
  /* Tight: the pills carry their own breathing room in the gap between the
     disc and the label, and a sticky band is height taken off every screenful
     below it. 4px is enough to keep the discs off the hairline. */
  margin-block-end: var(--nk-space-3);
  padding-block: var(--nk-space-1);
  background-color: var(--nk-surface-page);
  border-block-end: var(--nk-border-hairline);
}

/*
 * **Two classes, and that is the whole reason this rule works.**
 *
 * `base.css` ships `ul[class], ol[class] { margin: 0; padding: 0 }` — (0,1,1) —
 * and the track is a `<ul>` with a class, so every single-class rule for its
 * margin and padding lost silently. The declarations were written, served and
 * ignored: the track computed `margin-inline: 0` / `padding-inline: 0` and sat
 * one gutter inside each screen edge, so the row was cut by nothing the eye
 * could account for instead of by the edge of the phone. This is the exact trap
 * `.claude/rules/css.md` warns about under "our own base resets out-specify our
 * own components"; it has now caught three components.
 */
.nk-cat-rail .nk-cat-rail__track {
  display: flex;
  /*
   * The floor, not the spacing. Pills are sized by their own label (see
   * `.nk-cat-pill`), so two short names sit this far apart and two long ones
   * further — the rhythm follows the words rather than a number that has to
   * suit the longest of them.
   */
  gap: var(--nk-space-3);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  overscroll-behavior-x: contain;

  /*
   * Full-bleed: the track spans the viewport, so a half-scrolled pill is cut by
   * the edge of the screen and by nothing else, and the gutter comes back as
   * padding so the row still *starts* on the container's own margin.
   *
   * `scroll-padding-inline` MUST match `padding-inline`, or the first pill's
   * snap position sits one gutter inside the scrollport and the browser scrolls
   * the row on load to satisfy it — the rail opens already nudged off its start
   * edge, first pill cropped against the right edge in RTL. Same trap as the
   * brand rail.
   */
  margin-inline: calc(var(--nk-gutter) * -1);
  padding-inline: var(--nk-gutter);
  scroll-padding-inline: var(--nk-gutter);
  padding-block: var(--nk-space-half);
}
.nk-cat-rail .nk-cat-rail__track::-webkit-scrollbar { display: none; }
.nk-cat-rail .nk-cat-rail__track > li { flex: 0 0 auto; scroll-snap-align: start; }

/*
 * **The pill is as wide as its own name**, floored at the disc and capped
 * before one long department can take a third of the row. A fixed width made
 * the spacing a single compromise for every label at once: wide enough for
 * «کاپشن و پالتو» meant «همه» sat in a pool of empty space, and narrow enough
 * for «همه» meant «پیراهن و سارافون» became «پیراهن و …». Sized by content, the
 * gap between two discs is the track's floor plus whatever each label needs.
 */
.nk-cat-pill {
  display: grid;
  justify-items: center;
  gap: var(--nk-space-2);
  inline-size: auto;
  min-inline-size: var(--nk-archive-rail-size);
  max-inline-size: calc(var(--nk-archive-rail-size) + var(--nk-space-8));
  padding-block: 0;
  text-decoration: none;
  color: var(--nk-text-default);
  /* It is a link, but it reads and responds as a button: the platform's blue
     flash on tap is not a state this design system has. The press below is. */
  -webkit-tap-highlight-color: transparent;
}

.nk-cat-pill__media {
  display: grid;
  place-items: center;
  inline-size: var(--nk-archive-rail-size);
  block-size: var(--nk-archive-rail-size);
  border-radius: var(--nk-radius-circle);
  background-color: var(--nk-surface-sunken);
  /* A ring rather than a border: it does not participate in the box size, so
     the current state cannot shift the pill beside it by two pixels. */
  box-shadow: inset 0 0 0 1px var(--nk-border-subtle);
  overflow: hidden;
  color: var(--nk-text-muted);
  transition: box-shadow var(--nk-transition-base), transform var(--nk-transition-base);
}
.nk-cat-pill__media--all {
  background-color: var(--nk-surface-raised);
  color: var(--nk-text-default);
}
/* The mark sits *inside* the disc rather than filling it like a department
   photograph: it is a logo on a ground, and a logo cropped to a circle is a
   logo with its edges cut off. 32px of a 56px disc. */
.nk-cat-pill__mark {
  inline-size: 32px;
  block-size: 32px;
  object-fit: contain;
}
.nk-cat-pill__img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.nk-cat-pill__name {
  font-size: var(--nk-text-xs);
  line-height: var(--nk-leading-snug);
  text-align: center;
  color: var(--nk-text-muted);

  /*
   * One line, ellipsised. Two lines of a Persian category name would make the
   * rail's items different heights and the row would look broken.
   *
   * `max-inline-size`, never `inline-size`. The pill is an auto-width grid now,
   * so a label at `100%` is circular — it resolves against a column the label
   * itself is supposed to size — and the browser falls back to the only other
   * item in the track, the 56px disc. Every pill came out exactly 56px wide and
   * every name was truncated. A max lets the label's own width drive the
   * column and only clamps it at the pill's ceiling.
   */
  max-inline-size: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (hover: hover) {
  .nk-cat-pill:hover .nk-cat-pill__media {
    box-shadow: inset 0 0 0 1px var(--nk-border-strong);
    transform: translateY(-2px);
  }
  .nk-cat-pill:hover .nk-cat-pill__name { color: var(--nk-text-default); }
}
.nk-cat-pill:active .nk-cat-pill__media { transform: scale(0.96); }

/*
 * **"You are here" is the label, not a ring around the photograph.**
 *
 * The current pill used to take a 2px coral ring on its disc, and the shop
 * owner read it as a click artefact rather than as a state — which is fair:
 * tapping a department navigates to it, so the ring appeared *on the tap*, in
 * the action colour, exactly where a browser's own focus ring would be. Coral
 * on a label is the same cue the header's own current nav item uses, and it
 * cannot be mistaken for a control that has been left switched on.
 *
 * The disc keeps its neutral hairline in every state, so nothing about the
 * artwork's frame changes when the page does.
 */
.nk-cat-pill.is-current .nk-cat-pill__name {
  color: var(--nk-text-accent);
  font-weight: var(--nk-weight-semibold);
}

.nk-cat-pill:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: var(--nk-focus-ring-offset);
  border-radius: var(--nk-radius-md);
}

/* =======================================================================
 * 3. TOOLBAR — .nk-archive__toolbar
 *
 * Sticky under the header, at --nk-z-sticky, which the token file reserves for
 * exactly this ("sticky filter bars"). It is 100, and the header is 200, so
 * the bar can never ride over the header. Sticky because a parent who has
 * scrolled four rows into a category and wants a different sort should not
 * have to scroll back up to the top to say so.
 * ===================================================================== */

.nk-archive__toolbar {
  display: grid;
  /*
   * `minmax(0, 1fr)`, never the implicit `auto`. This grid contains two
   * horizontal scrollers, and an `auto` track sizes to its content's
   * max-content width — so wherever the container's own width is indefinite
   * (a shrink-to-fit parent, which is exactly what the design-system preview
   * page provides) the chip rail stretches the whole toolbar to 645px inside a
   * 360px screen and takes the document with it. A zero minimum is the
   * standard guard for a grid track holding an overflow scroller.
   */
  /*
   * On a phone the three controls — sort, filters, view switch — sit on one
   * line: the two sheet openers share the free column, the view discs take
   * what they need at the end. They used to be two 44px rows stacked, which
   * spent 112px of a 360px fold on chrome before a product was visible.
   */
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--nk-space-2);

  /* Bleeds so the sticky band spans the screen; the gutter comes back as
     padding, so nothing inside it moves. */
  margin-inline: calc(var(--nk-gutter) * -1);
  padding: var(--nk-space-2) var(--nk-gutter);
  background-color: var(--nk-surface-page);
  border-block-end: var(--nk-border-hairline);
}

/*
 * Sized by their own labels on mobile, not by the column. Two `1fr` tracks
 * stretched «مرتب‌سازی» and «فیلترها» across every pixel the view discs left
 * over, and two full-width outlined boxes read as the loudest thing on the
 * page — louder than the products under them. `auto` tracks give each button
 * its word plus a little air and hand the slack back to the row.
 */
.nk-archive__controls {
  display: grid;
  grid-template-columns: repeat(2, auto);
  justify-content: start;
  gap: var(--nk-space-2);
  grid-column: 1;
  grid-row: 1;
}

/*
 * One line, centred. These shipped as two-line controls — label above, current
 * value below — which meant the sort permanently displayed a value that is
 * always set and therefore never says anything, and the filter button
 * displayed «بدون فیلتر» most of the time. 52px of a 360px fold spent on two
 * captions that carry no state.
 *
 * What is left is the minimum a control needs: a glyph, a word, and — on the
 * filter only — a badge, and only when there is something to count. "No
 * filters" is the absence of the badge, not a sentence saying so.
 */
.nk-archive__control {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Tighter than the button default on every axis: the gap between glyph and
     word, the air either side of the pair, and the block padding.
     `.nk-btn`'s own 12px block padding pushed the box to 46px — *taller* than
     the 44px floor, so the touch minimum was never what was setting the
     height, 24px of padding around a 20px line was. At 8px the padding stops
     being the tallest thing in the box and `min-block-size` takes over, which
     is the whole point of having a floor. */
  gap: var(--nk-space-1);
  min-block-size: var(--nk-touch-target);
  padding-block: var(--nk-space-2);
  padding-inline: var(--nk-space-2);
}
/*
 * Two classes, and not because of a preference: `.nk-btn .nk-icon` in
 * elements.css pins every icon inside a button to `--nk-icon-md`, and it is
 * (0,2,0). The `'size' => 'sm'` these two controls pass to `nk_icon()` emits
 * `.nk-icon--sm`, which is (0,1,0) and loses — so both glyphs rendered at 20px
 * beside a 13px label and read as the loudest thing in the control. Matching
 * (0,2,0) from a later sheet is what actually sets the size. `xs`, level with
 * the label's own type, so the glyph supports the word instead of announcing
 * itself.
 */
.nk-archive__control > .nk-icon {
  inline-size: var(--nk-icon-xs);
  block-size: var(--nk-icon-xs);
  color: var(--nk-text-muted);
  flex: none;
}
.nk-archive__control-label {
  font-size: var(--nk-text-sm);
  font-weight: var(--nk-weight-medium);
  line-height: var(--nk-leading-snug);
}
/*
 * A one-digit count, not a label. `.nk-badge`'s snug line-height plus 3px of
 * block padding builds a 22px chip beside a 13px word, which is the badge
 * shouting a number that is usually 1. `leading-none` and the tightest
 * padding on the scale bring it to a 16px disc that sits on the label's own
 * line. (The badge component's own proportions are a separate, global job.)
 */
.nk-archive__control > .nk-badge {
  flex: none;
  padding-block: var(--nk-space-half);
  padding-inline: var(--nk-space-1);
  min-inline-size: var(--nk-icon-sm);
  justify-content: center;
  line-height: var(--nk-leading-none);
}

/*
 * Dissolved on mobile so its two children become items of the toolbar grid
 * directly: the view discs join the two controls on line one, and the count —
 * a 19px line of muted text, not a control — drops to a line of its own below
 * them. `display: contents` rather than moving the markup, because from 768px
 * the count and the discs pair up again as one right-aligned meta block, and
 * one DOM serving both is cheaper than two copies of it.
 */
.nk-archive__meta { display: contents; }

.nk-archive__count {
  grid-column: 1 / -1;
  grid-row: 2;
  display: inline-flex;
  align-items: center;
  gap: var(--nk-space-2);
  font-size: var(--nk-text-sm);
  color: var(--nk-text-muted);
  /* The count is a live region whose text is swapped on every filter change.
     Reserving its line stops the toolbar reflowing under a thumb mid-tap. */
  min-block-size: 1.35em;
}
.nk-archive__count .nk-icon { color: var(--nk-text-subtle); flex: none; }

.nk-archive__views {
  display: flex;
  gap: var(--nk-space-1);
  padding: var(--nk-space-half);
  border-radius: var(--nk-radius-pill);
  background-color: var(--nk-surface-sunken);
  flex: 0 0 auto;
  grid-column: 2;
  grid-row: 1;
  justify-self: end;
}
/*
 * A full 44px, not a smaller disc with an extended hit area. The card's heart
 * uses the extension trick because it sits *on a photograph* and a 44px chip
 * there is chrome over the product; here there is nothing behind the control
 * and no reason to lie about its size to an auditor or to a thumb. Two 44px
 * discs cost 96px of a 328px toolbar row and the count beside them needs ~70.
 */
.nk-archive__view {
  display: grid;
  place-items: center;
  inline-size: var(--nk-touch-target);
  block-size: var(--nk-touch-target);
  border-radius: var(--nk-radius-pill);
  color: var(--nk-text-muted);
  text-decoration: none;
  transition: background-color var(--nk-transition-base), color var(--nk-transition-base);
}

@media (hover: hover) {
  .nk-archive__view:hover { color: var(--nk-text-default); }
}
.nk-archive__view.is-current,
.nk-archive__view[aria-pressed="true"] {
  background-color: var(--nk-surface-raised);
  color: var(--nk-text-strong);
  box-shadow: var(--nk-shadow-xs);
}
.nk-archive__view:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: var(--nk-focus-ring-offset);
}

/* =======================================================================
 * 4. QUICK FILTERS — .nk-quick-filters
 *
 * Inside the sticky toolbar, so the shortcuts travel with it. `.nk-chip` and
 * `.nk-chip-row` are the design system's; only the density changes, and only
 * on the block axis — `.nk-chip` keeps its full 44px on a coarse pointer, so
 * this rail never drops below the touch minimum.
 * ===================================================================== */

.nk-quick-filters {
  margin-inline: calc(var(--nk-gutter) * -1);
}
.nk-quick-filters__track {
  padding-inline: var(--nk-gutter);
  scroll-padding-inline: var(--nk-gutter);
  scroll-snap-type: x proximity;
}
.nk-quick-filters__track > * { scroll-snap-align: start; }

/* Quick chips are a shortcut, not a facet list: smaller type and tighter
   padding than a chip in the sheet, so more of them are reachable in a thumb's
   travel. */
.nk-chip--quick {
  font-size: var(--nk-text-sm);
  padding-inline: var(--nk-space-3);
  gap: var(--nk-space-1);
}
.nk-chip--quick .nk-icon { color: currentColor; }

/* A removable chip's × is the control's own affordance, so it is the one thing
   that brightens on hover rather than the whole chip. */
.nk-chip--removable { padding-inline-end: var(--nk-space-2); }
.nk-chip--removable .nk-icon {
  color: var(--nk-text-muted);
  transition: color var(--nk-transition-base);
}
@media (hover: hover) {
  .nk-chip--removable:hover .nk-icon { color: var(--nk-text-accent); }
}

.nk-chip--clear {
  border-style: dashed;
  color: var(--nk-text-accent);
  border-color: var(--nk-border-accent);
  background-color: transparent;
}
@media (hover: hover) {
  .nk-chip--clear:hover { background-color: var(--nk-surface-accent); }
}

/* =======================================================================
 * 5. RESULTS
 * ===================================================================== */

.nk-archive__results { margin-block-start: var(--nk-space-4); }

/* The grid dims rather than empties while a new page is being fetched. An
   empty container collapses the page to nothing and throws the scroll position
   away, which on a phone reads as the site crashing. */
.nk-archive__results[aria-busy="true"] [data-nk-grid] {
  opacity: 0.45;
  transition: opacity var(--nk-duration-fast) var(--nk-ease-standard);
  pointer-events: none;
}

/*
 * **The empty state gets the whole grid, not one column of it.**
 *
 * This class goes on `[data-nk-grid]` itself — both `nk_archive_results()` and
 * `archive.js` put it there — and it used to say `grid-column: 1 / -1`, which
 * is a rule for a grid *item*. On the grid it did nothing, so the empty
 * state's panel was placed as an ordinary item in the first of two product
 * columns: «محصولی با این فیلترها پیدا نشد» broke over four lines and «حذف همه
 * فیلترها» wrapped inside its own button, on a screen with half its width
 * standing empty beside them.
 *
 * Spanning the **child** across every track is the fix, and it is deliberately
 * not a `grid-template-columns` override on the wrapper: the breakpoint rules
 * that set the product grid to 3 and 4 columns are the same (0,1,0) and are
 * declared later, so an override here would be silently beaten from 768px up —
 * which is the identical class of bug this comment exists to record.
 * `1 / -1` needs no knowledge of how many columns there are.
 */
.nk-archive__empty-wrap > * { grid-column: 1 / -1; }

/*
 * LIST VIEW — .nk-product-grid--list
 *
 * A modifier on the *grid*, never on the card: `nk_product_card()` renders one
 * contract and the container decides how deep the row is, exactly as
 * `.nk-product-rail` does. Nothing in PHP knows which view is on except the
 * `sizes` attribute, which has to, because the thumbnail is a fixed 112px here
 * and half a viewport in the grid.
 */
.nk-product-grid--list {
  grid-template-columns: minmax(0, 1fr);
  gap: var(--nk-space-3);
}

.nk-product-grid--list .nk-product-card {
  display: grid;
  grid-template-columns: var(--nk-archive-list-thumb) minmax(0, 1fr);
  align-items: stretch;
}
.nk-product-grid--list .nk-product-card__media {
  border-end-end-radius: 0;
  border-start-end-radius: 0;
}
.nk-product-grid--list .nk-product-card__body {
  padding: var(--nk-space-3) var(--nk-space-4);
  justify-content: center;
}
/* The title has the width of a row rather than of a column, so the two-line
   clamp that keeps a grid tidy is a waste of it here. */
.nk-product-grid--list .nk-product-card__title {
  -webkit-line-clamp: 2;
  line-clamp: 2;
  font-size: var(--nk-text-md);
}
/* The price is centred in a card because a card is a column. In a row it is
   one item in a line of text and belongs at the reading edge. */
.nk-product-grid--list .nk-product-card__footer,
.nk-product-grid--list .nk-price { justify-content: flex-start; text-align: start; }

/* A row does not lift: there is nothing beside it for the lift to separate it
   from, and a full-width card rising off the page reads as a glitch. */
@media (hover: hover) {
  .nk-product-grid--list .nk-product-card:hover { transform: none; }
}

@media (min-width: 768px) {
  .nk-product-grid--list { --nk-archive-list-thumb: 160px; }
  .nk-product-grid--list .nk-product-card__body { padding: var(--nk-space-4) var(--nk-space-5); }
}

/*
 * Skeletons. The same grid class as the real results, so a placeholder box is
 * the *same box* — `.nk-skeleton--media` already carries --nk-ratio-product.
 * Matching by construction rather than by two numbers that have to agree is
 * the only way a loading state stays honest through a redesign.
 */
.nk-archive__skeletons { margin-block-start: var(--nk-grid-gap); }
.nk-archive__skeleton {
  display: grid;
  gap: var(--nk-space-2);
}

/* =======================================================================
 * 6. LOADER AND PAGINATION
 * ===================================================================== */

.nk-archive__loader {
  display: grid;
  justify-items: center;
  gap: var(--nk-space-4);
  margin-block-start: var(--nk-space-8);
}

/*
 * A scroll position, not content: 1px tall, immediately after the grid.
 *
 * **The prefetch distance is the observer's `rootMargin`, never a negative
 * margin here.** It was `margin-block-start: -60vh` — pulling the sentinel up
 * into the tail of the grid — and that is subtly, badly wrong: after each
 * append the sentinel lands *above* the viewport, and with a footer this tall
 * the shopper is already past it at the true bottom of the document. The first
 * page auto-loaded (the scroll passed through the sentinel on the way down) and
 * every page after it silently did not. It has no height-in-hand at all now;
 * `rootMargin` in archive.js does the reaching.
 *
 * The negative bottom margin cancels the loader grid's own row gap, so a 1px
 * element does not cost 17px of space under the last row of cards.
 */
.nk-archive__sentinel {
  inline-size: 100%;
  block-size: 1px;
  margin-block-end: calc(var(--nk-space-4) * -1);
  pointer-events: none;
}

.nk-archive__more { min-inline-size: 220px; }
.nk-archive__more.is-loading { pointer-events: none; opacity: 0.7; }

.nk-archive__end {
  font-size: var(--nk-text-sm);
  color: var(--nk-text-muted);
}

/*
 * Which loading control is visible is the shop's setting, expressed here and
 * nowhere else. All three are always in the DOM as real links — see
 * nk_archive_loader() — so a crawler and a script-blocked browser always get
 * complete pagination, whichever mode is on.
 */
.nk-archive[data-mode="pages"] .nk-archive__sentinel,
.nk-archive[data-mode="pages"] .nk-archive__more { display: none; }

.nk-archive[data-mode="more"] .nk-archive__sentinel { display: none; }

/*
 * Infinite and "load more" hide the numbered pagination **only once the script
 * has taken over** (`.nk-archive` gains `is-enhanced`). Without that guard a
 * script-blocked browser in infinite mode would have a sentinel that does
 * nothing, a "load more" link that reloads the page, and no page numbers.
 */
.nk-archive.is-enhanced[data-mode="infinite"] .nk-archive__pagination,
.nk-archive.is-enhanced[data-mode="more"] .nk-archive__pagination {
  display: none;
}
/* Infinite scroll still shows the button after `maxAuto` automatic loads, so
   a keyboard user and anyone hunting for the footer can stop the treadmill.
   The script sets this attribute; CSS only reveals what it decided. */
.nk-archive.is-enhanced[data-mode="infinite"] .nk-archive__more { display: none; }
.nk-archive.is-enhanced[data-mode="infinite"][data-auto="paused"] .nk-archive__more { display: inline-flex; }

.nk-pagination__gap {
  display: grid;
  place-items: center;
  min-inline-size: var(--nk-space-6);
  color: var(--nk-text-muted);
}

/* =======================================================================
 * 7. SORT SHEET — .nk-sort-list
 * =====================================================================
 *
 * MOVED. `.nk-sort-list` / `.nk-sort-option` are now primitives, in
 * `elements.css`: the product page's review sort uses the same control, and
 * `archive.css` is only enqueued on archive routes. Only `.nk-sheet--sort`,
 * which is this file's own surface, stayed here.
 * ===================================================================== */

/* =======================================================================
 * 8. FACET GROUPS — .nk-filter-group
 *
 * **Not `.nk-facet`, which is already taken.** `search.css` owns that name for
 * the search overlay's facets, and they are a genuinely different shape: a
 * wrapping row of chips, six colours wide, built by `search.js`. These are a
 * scrolling column of checkbox rows with counts, because an archive's category
 * facet is twenty terms and a chip cloud of twenty terms is not scannable.
 *
 * Two names for two components is the rule here, not a workaround — but they
 * are close enough to be worth converging one day, as one component with a
 * `--chips` variant. That means changing `search.js`'s builder, so it is a
 * deliberate job and not a side effect of something else.
 * ===================================================================== */

/*
 * **Each group is a `<details>`, and the heading is its `<summary>`.**
 *
 * The sheet was a single column of every option in the catalogue: seventeen
 * categories, then the sizes, then the colours, then the brands, each an
 * untouchable 44px row. Reaching «برند» meant scrolling past a hundred
 * checkboxes, and the shop owner's complaint — that the list eats the screen —
 * was the honest reading of it. Collapsed, the sheet is a menu of five or six
 * headings that fits on one phone screen, and any one of them is a tap from
 * open.
 *
 * `<details>` and not a scripted accordion: the disclosure state, the keyboard
 * behaviour and the ARIA are the browser's, it costs no JavaScript, and with
 * the script blocked every group is still openable. `.nk-archive__desc`
 * already uses the same element for the same reason.
 *
 * The old `<legend>` trap is retired with the `<fieldset>` — a `<summary>` is
 * not painted inside a border box, so a separator on either edge is safe now.
 * It stays on the bottom edge because the border on an *open* group should sit
 * under its options rather than between the heading and them.
 */
.nk-filter-group {
  border: 0;
  padding: 0;
  margin: 0;
}
/*
 * **Every group is wrapped, so one rule draws every separator.** It was two
 * rules — `.nk-filter-group__wrap:not(:last-child)` for the facets and
 * `:not(:last-of-type)` for the two hand-written groups — and `:last-of-type`
 * counts among siblings *of the same element name*: the switches and the price
 * group were the only two bare `<details>` in the form, so the price group was
 * `:last-of-type` and lost its rule while eight groups still followed it. The
 * line under «محدوده قیمت» was missing for exactly that reason.
 *
 * `nk_archive_group_open()` now emits the wrapper itself, so a group cannot be
 * added without one and the selector has no special cases left.
 */
.nk-filter-group__wrap:not(:last-child) > .nk-filter-group {
  border-block-end: var(--nk-border-hairline);
}
/* The facet groups carry one more element between the form and the wrapper,
   for `data-nk-facet-group`; it must not break the "am I last" question. */
.nk-filter-group__group:not(:last-child) .nk-filter-group {
  border-block-end: var(--nk-border-hairline);
}
.nk-filter-group__group:last-child .nk-filter-group__wrap:last-child > .nk-filter-group {
  border-block-end: 0;
}

/*
 * The row is the control: the whole heading toggles, so it takes the full
 * touch target and the chevron is a cue rather than a second, smaller button.
 * The native marker goes — the chevron is the affordance, and two disclosure
 * glyphs on one row is one too many.
 */
.nk-filter-group__title {
  display: flex;
  align-items: center;
  gap: var(--nk-space-2);
  min-block-size: var(--nk-touch-target);
  padding: 0;
  margin: 0;
  list-style: none;
  cursor: pointer;
  font-family: var(--nk-font-display);
  /*
   * `--nk-text-md` (16px) over the options' `--nk-text-base` (15px). It was
   * the other way round — the heading at 15px above 16px option labels — so
   * every group read as a caption over its own contents. A heading may be the
   * same size as what it labels, or larger; it may not be smaller.
   */
  font-size: var(--nk-text-md);
  font-weight: var(--nk-weight-semibold);
  color: var(--nk-text-strong);
  -webkit-tap-highlight-color: transparent;
}
/* Muted, so the row still reads as one heading rather than as a glyph and a
   word competing. It sits before the text in both directions — logical order,
   no mirroring needed. */
.nk-filter-group__icon { color: var(--nk-text-subtle); flex: none; }
.nk-filter-group__title::-webkit-details-marker { display: none; }
.nk-filter-group__title:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: calc(var(--nk-focus-ring-offset) * -1);
  border-radius: var(--nk-radius-sm);
}

/* The heading takes the slack so the chevron parks at the far edge and the
   selected-count badge stays beside the words it counts. */
.nk-filter-group__title-text { margin-inline-end: auto; }

/*
 * `chevron-down` is not in `icons.json#directional`, and that is correct: a
 * vertical chevron has no handedness to mirror. The rotation is the open
 * state, and it is a transform so it costs no layout.
 */
.nk-filter-group__chevron {
  color: var(--nk-text-subtle);
  transition: transform var(--nk-transition-base);
}
.nk-filter-group[open] > .nk-filter-group__title .nk-filter-group__chevron {
  transform: rotate(180deg);
}

/* Breathing room under an open heading, and under the last option before the
   next group's rule. Closed, a group costs its 44px heading and nothing. */
.nk-filter-group__options {
  display: grid;
  padding-block-end: var(--nk-space-3);
}

/*
 * The disclosure gets a movement, because without one a group appearing whole
 * between one frame and the next reads as the page jumping rather than as the
 * group opening — there is nothing to tell the eye where the new rows came
 * from.
 *
 * **Opening only, and an `animation` rather than a `transition`.** A
 * `<details>` hides its content outright when closed, so there is no closing
 * frame to transition *from* — a close is instant in every browser and
 * pretending otherwise needs script holding the element open, which this sheet
 * is not worth. `::details-content` would do both natively and is not in
 * Safari yet; when it is, this becomes four lines and the behaviour improves
 * on its own.
 *
 * The slide is 6px, not a height animation: animating `block-size` on a list
 * whose height nobody knows means measuring in JS, and the eye reads the fade
 * and the small settle as "this opened" without either.
 */
@keyframes nk-group-open {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
.nk-filter-group[open] > .nk-filter-group__options,
.nk-filter-group[open] > .nk-range,
.nk-filter-group[open] > .nk-filter-group__price-row {
  animation: nk-group-open var(--nk-duration-fast) var(--nk-ease-out) both;
}

/* The motion layer's standing rule: reduced motion returns to no movement at
   all, not to a faster one. */
@media (prefers-reduced-motion: reduce) {
  .nk-filter-group[open] > .nk-filter-group__options,
  .nk-filter-group[open] > .nk-range,
  .nk-filter-group[open] > .nk-filter-group__price-row {
    animation: none;
  }
  .nk-filter-group__chevron { transition: none; }
}

/*
 * `flex-start`, and the name does **not** grow.
 *
 * The count used to be `flex: 1 1 auto` on the name with the number pushed
 * against the far edge, which put a column of small grey figures a full row
 * away from the words they belong to — unreadable in exactly the way the shop
 * owner described. As a badge directly after the term it is read in the same
 * eye movement, and it is the same `.nk-badge--count` the toolbar's filter
 * button and each group heading use.
 */
/* One step under the heading — see the note on `.nk-filter-group__title`. The
   switches carry no `__name`, so the size is stated on the row. */
.nk-filter-group .nk-check { font-size: var(--nk-text-base); }
.nk-filter-group__option { gap: var(--nk-space-3); }
.nk-filter-group__name { flex: 0 1 auto; }

/*
 * **The category facet is drawn as the tree it already is.**
 *
 * «کفش مجلسی» and «کفش» were two adjacent rows of one flat column, with «کوله
 * و کیف» sitting between «کفش» and «اکسسوری» as though the three were peers —
 * so nothing on screen said which term lived inside which. The shop maintains
 * this hierarchy in wp-admin; the sheet was throwing it away.
 *
 * Indent plus a guide line, and both are needed. The indent alone is ambiguous
 * at a glance on a 360px screen — Persian labels vary enough in length that a
 * 16px step reads as ragged rather than as structure — and the line is what
 * turns a run of children into one visible group. `padding-inline-start` and
 * `border-inline-start`, so the branch grows from the right edge in RTL and
 * the left in LTR with no override.
 *
 * Two levels only; `nk_archive_facet_tree()` promotes anything deeper. A third
 * indent would spend on whitespace what a phone needs for the words.
 */
.nk-filter-group__children {
  display: grid;
  padding-inline-start: var(--nk-space-4);
  margin-inline-start: var(--nk-space-3);
  border-inline-start: var(--nk-border-hairline);
}

/* A parent is the head of its branch, not a louder option: one weight step,
   no colour change. Colour here would compete with the coral that means
   "selected", which is the only signal in this list that must stay unique. */
.nk-filter-group__option.is-parent .nk-filter-group__name {
  font-weight: var(--nk-weight-medium);
}
.nk-filter-group__option > .nk-badge {
  flex: none;
  /* Neutral, because this is how many products *exist*, not a promotion and
     not something the shopper switched on. The coral count belongs to the
     heading, where it means "you have selected this many". */
  background-color: var(--nk-surface-sunken);
  color: var(--nk-text-muted);
}
/*
 * A facet with nothing behind it is dimmed, never removed. Removing it makes
 * the list jump under the finger between two taps; dimming says "this
 * combination is empty" and leaves the shopper's place intact.
 */
.nk-filter-group__option.is-empty {
  opacity: 0.45;
  pointer-events: none;
}

.nk-filter-group--price .nk-filter-group__price-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--nk-space-3);
  padding-block-end: var(--nk-space-3);
}
/* The slider is the primary control and the typed fields are the precise one,
   so they sit under it rather than beside it — and close enough to read as the
   same control, which is why the gap is 2 and not the group's own 3. */
.nk-filter-group--price .nk-range { margin-block-end: var(--nk-space-2); }
/* `.nk-field + .nk-field` is a 20px top margin, written for fields stacked in a
   column. Side by side in a grid it offsets the second box below the first. */
.nk-filter-group--price .nk-field + .nk-field { margin-block-start: 0; }
/* The amount is typed left-to-right even in an RTL document, like every other
   number field in the theme. */
.nk-filter-group--price .nk-input { text-align: start; }

/* =======================================================================
 * 9. DESKTOP
 *
 * The two stacked controls are a phone pattern. From 1024px the archive gets
 * a real second dimension, and the filter sheet — the *same element*, not a
 * duplicate of its markup — stops being a fixed overlay and becomes a sticky
 * sidebar column. One DOM, two presentations; there is no second copy of a
 * facet list anywhere in this project.
 *
 * archive.js drops `role="dialog"` and `aria-modal` from it at this width, so
 * a sidebar is not announced as a modal. With the script blocked the sidebar
 * still renders and still submits — it is only mislabelled, which is the
 * cheaper failure of the two available.
 * ===================================================================== */

@media (min-width: 768px) {
  .nk-archive { padding-block-start: var(--nk-space-6); }
  .nk-archive__title { font-size: var(--nk-text-3xl); }

  /* Room for a label beside each control instead of under it — and for the
     button's own type size and padding, which are trimmed on a phone only. */
  .nk-archive__controls { grid-template-columns: repeat(2, minmax(0, 200px)); }
  .nk-archive__control {
    gap: var(--nk-space-2);
    padding-inline: var(--nk-space-3);
  }
  .nk-archive__control-label { font-size: var(--nk-text-base); }
  /* The label grows here, so the glyph grows with it — still level with the
     type rather than the button default's 20px. */
  .nk-archive__control > .nk-icon {
    inline-size: var(--nk-icon-sm);
    block-size: var(--nk-icon-sm);
  }

  .nk-archive__toolbar {
    grid-template-columns: minmax(0, auto) minmax(0, 1fr);
    align-items: center;
    gap: var(--nk-space-4);
  }
  /* There is room for the count beside the discs again, so the meta block
     reassembles: `display: contents` is undone and its children go back to
     being flex items rather than toolbar grid items. */
  .nk-archive__meta {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--nk-space-3);
    min-block-size: var(--nk-touch-target);
  }
  /* The chip rail spans both columns rather than squeezing between them. */
  .nk-quick-filters { grid-column: 1 / -1; }
}

@media (min-width: 1024px) {
  /*
   * Explicit rows and columns, not `grid-template-areas`. Two of these five
   * children are conditional — the category rail hides itself when the branch
   * has no siblings, and the whole toolbar can be switched off in wp-admin —
   * and a named area with nothing in it is a row of blank space, while an
   * explicit row with nothing in it collapses. There is no `row-gap` for the
   * same reason: each block already carries its own bottom margin.
   *
   * The sort sheet is `position: fixed` at every width, so it is out of flow
   * and never becomes a grid item. Only the filter sheet is placed.
   */
  .nk-archive {
    display: grid;
    grid-template-columns: var(--nk-archive-sidebar) minmax(0, 1fr);
    column-gap: var(--nk-space-8);
    align-items: start;
  }
  .nk-archive__head { grid-column: 1 / -1; grid-row: 1; }

  /*
   * The rail stops following the page here. Sticking it was a phone answer to
   * a phone problem — a thumb four rows down cannot reach the departments — and
   * on a desktop the filter sidebar is already the thing that follows you, one
   * sticky element per layout. It also has to go back to `static` explicitly or
   * a sticky item in a grid row would fight the sidebar spanning beside it.
   */
  .nk-cat-rail {
    grid-column: 1 / -1;
    grid-row: 2;
    position: static;
    margin-inline: 0;
    padding-inline: 0;
    padding-block: 0;
    margin-block-end: var(--nk-space-6);
    background: none;
    border-block-end: 0;
  }
  .nk-sheet--filters { grid-column: 1; grid-row: 3 / span 2; align-self: start; }
  .nk-archive__toolbar { grid-column: 2; grid-row: 3; align-self: start; }
  .nk-archive__results { grid-column: 2; grid-row: 4; }

  /* On desktop the toolbar has no reason to bleed — it sits inside a column,
     not across the screen — and a sticky band inside a grid column would
     overlap the sidebar's own scroll. */
  .nk-archive__toolbar {
    position: static;
    margin-inline: 0;
    padding-inline: 0;
    background: none;
    border-block-end: 0;
  }

  /* Filters live in the sidebar; only the sort control stays. */
  .nk-archive__control[data-nk-sheet-open="nk-archive-filters"] { display: none; }
  .nk-archive__controls { grid-template-columns: minmax(0, 220px); }

  /* --- the sheet, as a column ------------------------------------- */
  .nk-sheet--filters {
    position: sticky;
    inset: auto;
    /* Same rule as the category rail: the *measured* header, not the bar
       height. At 1280 the header is 174px (strip + bar + category row) and
       this was offsetting by 96, which slid the whole sidebar under it. */
    inset-block-start: calc(var(--nk-header-stuck, var(--nk-header-height)) + var(--nk-space-4));
    z-index: var(--nk-z-base);
    display: block;
    visibility: visible;
    pointer-events: auto;
    transition: none;
  }
  /*
   * `.nk-sheet` is a centred dialog from 768px (feedback.css), and that block
   * selects `.nk-sheet:not(.nk-sheet--sticky-bottom) > .nk-sheet__panel` —
   * (0,3,0), which outranks a plain `.nk-sheet--filters .nk-sheet__panel`.
   * The sidebar therefore has to match it, `opacity` included: inheriting the
   * dialog's closed `opacity: 0` would leave the column laid out, taking its
   * grid space, and completely invisible.
   */
  .nk-sheet--filters > .nk-sheet__panel,
  .nk-sheet--filters[data-open] > .nk-sheet__panel {
    max-inline-size: none;
    max-block-size: calc(100vh - var(--nk-header-stuck, var(--nk-header-height)) - var(--nk-space-8));
    margin-inline: 0;
    border-radius: var(--nk-radius-lg);
    border: var(--nk-border-hairline);
    box-shadow: none;
    transform: none;
    opacity: 1;
    transition: none;
  }
  .nk-sheet--filters > .nk-sheet__panel > .nk-sheet__handle { display: none; }
  /* The × closes a sheet. There is nothing to close here. */
  .nk-sheet--filters .nk-sheet__head [data-nk-sheet-close] { display: none; }
  /* Every facet applies on change, so "show N products" would be a button that
     does nothing. «حذف همه» stays, because it is the one thing the facet list
     itself cannot say. */
  .nk-sheet--filters [data-nk-filters-apply] { display: none; }
  .nk-sheet--filters > .nk-sheet__panel > .nk-sheet__foot { padding-block-end: var(--nk-space-4); }
  .nk-sheet--filters .nk-sheet__foot > .nk-btn { flex: 1 1 auto; }
}

@media (min-width: 1280px) {
  .nk-archive { --nk-archive-sidebar: 280px; }
}

/* A disabled-looking link is still a link, so it is `aria-disabled` plus this,
   never a `disabled` attribute an `<a>` does not have. */
.nk-btn.is-disabled {
  opacity: 0.45;
  pointer-events: none;
}
