/* The catalog (catalog.html, catalog-keyboards.html, catalog-mousepads.html; js/catalog.js). Tokens,
 * type and the sheet a card opens are style.css's and layouts.css's; only what the catalog adds is here.
 *
 * The page is laid out the way a shop's listing is: a column of filters down the left from the top
 * of the page, held by a hairline ring (the search at its head, then an accordion, EloShapes' way);
 * over the list, level with it, the title with the count at the left and the sort and the way back
 * to the finder at the right; and the products in a grid of grey cards on the white page, the
 * render on top, the name and four figures under it, the price and the button in a foot (Patryk,
 * 2026-10-20, after x-kom and the card lab). No shadows: a fill or a gap tells two things apart, and
 * a hairline only where neither can. The filter column and the price-at-the-foot come from the
 * shops in Storefolio (Ridge, Keychron, Master & Dynamic, Ellis Brigham) and from EloShapes.
 *
 * Motion is quick and on transform and opacity only, the way Storefolio's own system has it
 * (150ms for a colour, 160ms for a pop, 240–320ms for an entrance, all on one ease-out), and a
 * reader who asked for less of it gets none.
 *
 * Sections: the page, the fields, the head, the filters column, the accordion, the sliders, the
 * toolbar (narrow screens), the chips, the cards, then the narrow rules.
 */

/* ==== The page =============================================================================== */
/* The finder pages are one screen and never scroll (style.css, body); this one is a long list. */
body.catalog-page { overflow: auto }
/* The bar is backgroundless over the finder's scene; here cards scroll under it, so it takes the
   page ground. */
/* White page, grey blocks on it (Patryk, 2026-10-20, after x-kom): the filters' column and the cards
   are a light grey (--card), a step darker under the pointer (--card-hover), and the fields, the
   ticks and a card's render sit on white inside them. The finder pages keep their grey ground. */
body.catalog-page { --page: var(--surface); --card: #efeeec; --card-hover: #e7e5e3; background: var(--page) }
/* On a page of content (a catalog, a product page) the logo and the menu stand at the edges of the
   content, not of the window (Patryk, 2026-10-28): .catalog's 1240px less its 24px sides. A finder's
   scene fills the window, so its bar does; going from one to the other, the two slide out or in
   (category-nav.css, nav-brand and nav-side). */
.catalog-page .category-nav { background: var(--page); padding-inline: max(20px, calc((100% - 1240px) / 2 + 24px)) }
@media (max-width: 700px) { .catalog-page .category-nav { padding-inline: 10px } }
/* The articles, their index and the guides, keep the finder's light grey ground (Patryk, 2026-11-02),
   so their grey blocks turn white, as the finder's tiles are. */
body.fit-page { --page: var(--bg); --card: var(--surface); --card-hover: #fafaf9 }
.catalog {
  /* --field: what a field is filled with; --hover: what a row turns on hover. Everything stands on
     the white page now, the filters' column included (it has a ring, not a fill), so both are the
     cards' grey. */
  --field: var(--card);
  --hover: var(--card);
  --out: cubic-bezier(.22, 1, .36, 1);
  max-width: 1240px;
  margin: 0 auto;
  padding: 96px 24px 64px;
}
.catalog button:focus-visible, .catalog a:focus-visible,
.catalog input:focus-visible, .catalog select:focus-visible, .catalog summary:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* ==== The fields ============================================================================= */
/* Every field is a filled box with no edge, and the darker ring only when it has the focus. */
.c-search input, .c-sort select, .c-range select, .c-brand-find input {
  width: 100%;
  height: 40px;
  border: 0;
  border-radius: 10px;
  background: var(--field);
  color: var(--ink);
  font: inherit;
  transition: box-shadow .15s, background .15s;
}
.c-search input::placeholder, .c-brand-find input::placeholder { color: var(--muted) }
.c-search input:focus, .c-brand-find input:focus, .catalog select:focus { outline: none; box-shadow: var(--focus-field) }
.catalog select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b645f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 15px;
  cursor: pointer;
}
/* The search box, the magnifier parked at the left. */
.c-search { position: relative; display: flex; align-items: center; flex: 1 1 auto }
.c-search svg {
  position: absolute;
  left: 13px;
  width: 16px;
  height: 16px;
  color: var(--muted);
  pointer-events: none;
}
.c-search input { height: 44px; padding: 0 14px 0 40px; font-size: 14.5px }
/* The sort reads as one line of text, "Sort by: Popular", the value a bare dropdown with no box
   round it (Patryk, 2026-10-20): the chevron after it says it opens. */
.c-sort { display: flex; align-items: center; gap: 5px; flex: 0 0 auto; font-size: 14px; color: var(--muted) }
.catalog .c-sort select {
  /* As wide as the value picked, so the chevron sits right after "Popular" rather than after the
     longest option; a browser without field-sizing keeps the longest option's width. */
  field-sizing: content;
  width: auto;
  height: 32px;
  padding: 0 22px 0 0;
  border-radius: 6px;
  background-color: transparent;
  background-position: right 0 center;
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
}
/* The open list drawn as the page draws a panel, not the system's grey menu (Patryk, 2026-10-20):
   white, a hairline ring, a soft shadow, square-ish corners, a grey row under the pointer and the
   picked row in bold. Only where the browser lets a select be styled (customizable select, Chrome
   and Edge 135+); elsewhere the list stays the system's and the box above still matches. The
   chevron is the background image above, so the browser's own icon is hidden. */
@supports (appearance: base-select) {
  .catalog select, .catalog select::picker(select) { appearance: base-select }
  .catalog select { display: flex; align-items: center }
  .catalog select::picker-icon { display: none }
  .catalog select::picker(select) {
    min-width: anchor-size(width);
    /* Eight rows and a half, so the cut row says the list scrolls; the price rail has 16 steps. */
    max-height: 280px;
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--dim-soft) transparent;
    margin: 4px 0;
    padding: 4px 0;
    border: 0;
    border-radius: 10px;
    background: var(--surface);
    box-shadow: var(--shadow-pop);
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity .14s, transform .18s var(--out), overlay .18s allow-discrete, display .18s allow-discrete;
  }
  .catalog select:open::picker(select) { opacity: 1; transform: none }
  @starting-style { .catalog select:open::picker(select) { opacity: 0; transform: translateY(-4px) } }
  .catalog option {
    display: flex;
    align-items: center;
    min-height: 32px;
    padding: 0 12px;
    color: var(--ink);
    font-size: 13px;
    font-weight: 400;
    white-space: nowrap;
    cursor: pointer;
  }
  .catalog option::checkmark { display: none }
  .catalog option:hover, .catalog option:focus-visible { outline: none; background: var(--hover) }
  .catalog option:checked { font-weight: 600 }
  .catalog option:disabled { color: var(--dim); cursor: default }
  .catalog option:disabled:hover { background: transparent }
}
.catalog .c-sort select:focus { box-shadow: none }
.catalog .c-sort select:focus-visible { outline: 2px solid var(--muted); outline-offset: 2px }

/* ==== The head =============================================================================== */
/* Over the list and level with its cards (Patryk, 2026-10-20): the title and its line at the left,
   the sort at the right on the line's foot. */
.c-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px 32px;
  margin-bottom: 20px;
}
.c-head-text { min-width: 0 }
.c-head-tools { flex: 0 0 auto; display: flex; align-items: center; gap: 8px }
/* The finder's pill ("Mouse finder") stands in the bar left of the menu (category-nav.css,
   .nav-finder). On a phone the bar has no room for it, so there the same pill stands in the list's
   head beside the sort instead (.c-finder). */
.c-finder { display: none }
.c-title {
  margin: 0 0 8px;
  font-size: 30px;
  font-weight: 500;
  letter-spacing: -.8px;
  line-height: 1.2;
}
/* The count after the title in the title's own type (Patryk, 2026-10-20), as part of the heading
   rather than a grey aside. */
.c-count { margin-left: .15em; font-variant-numeric: tabular-nums }

/* ==== The filters column ===================================================================== */
/* A column down the left of the list from the top of the page, held by a hairline ring rather than
   a fill (Patryk, 2026-10-20). Inside, the
   same hairline divides it: the head "Filters" with Clear all, the search, then the accordion, one
   section to a band, each band filling grey under the pointer to its full width. It sticks under the bar and scrolls on its own when it is longer
   than the window, so the filters are at hand however far down the list the reader is; its foot
   fades out rather than being cut. */
.c-body {
  display: grid;
  grid-template-columns: 264px minmax(0, 1fr);
  gap: 28px;
  align-items: start;
}
.c-filters {
  position: sticky;
  top: 96px;
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 112px);
  border: 1px solid var(--line);
  border-radius: 16px;
  overflow: hidden;
}
.c-filters-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  height: 56px;
  padding: 0 18px 0 20px;
  border-bottom: 1px solid var(--line);
}
.c-filters-head h2 { margin: 0 auto 0 0; font-size: 17px; font-weight: 600; letter-spacing: -.3px }
.c-drawer-close {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--tile);
  color: var(--muted);
  transition: background .15s, color .15s;
}
.c-drawer-close:hover { background: var(--tile-hover); color: var(--ink) }
.c-drawer-close svg { width: 16px; height: 16px }
/* The search. */
.c-top { flex: 0 0 auto; display: flex; flex-direction: column; border-bottom: 1px solid var(--line) }
/* The search is a band of the column like the sections under it, not a box set into it (Patryk,
   2026-10-20: the grey field stood out of the ring): no fill and no corners, the magnifier where a
   section's name starts, and the band greys only while it has the focus. */
.c-top .c-search svg { left: 20px }
.c-top .c-search input { height: 50px; padding: 0 16px 0 46px; border-radius: 0; background: transparent; font-size: 13.5px }
.c-top .c-search input:hover { background: var(--card) }
.c-top .c-search input:focus { box-shadow: none; background: var(--card) }
.c-groups-wrap { position: relative; flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column }
/* The accordion scrolls on its own, with no scrollbar of its own: a native one took its width out of
   the column and cut the hairlines and the hover fills short (Patryk, 2026-10-20). The fade at the
   foot says there is more; the thumb laid over the edge (.c-rail) shows under the pointer. */
.c-groups {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
}
/* The column ends at its last band, with no empty strip under it (Patryk, 2026-10-20); the ring
   closes the last band, so its own hairline goes. The foot fades only while there is more below
   (js/catalog.js, rail), or the fade would grey out the last band of a column that fits. */
.c-groups > .c-group:last-child { border-bottom: 0 }
.c-groups.is-more {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent);
}
.c-groups::-webkit-scrollbar { display: none }
/* The overlay thumb (js/catalog.js, rail): a thin pill over the column's right edge, on top of the
   hairlines and fills rather than beside them, seen only while the pointer is over the column or
   the thumb is held. It widens a touch under the pointer, as a macOS scrollbar does. */
.c-rail {
  position: absolute;
  z-index: 2;
  top: 6px;
  bottom: 6px;
  right: 3px;
  width: 8px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s;
}
.c-rail i {
  display: block;
  width: 5px;
  margin-left: auto;
  border-radius: 999px;
  background: oklch(0 0 0 / .22);
  transition: width .15s, background .15s;
}
.c-filters:hover .c-rail.is-on, .c-rail.is-drag { opacity: 1; pointer-events: auto }
.c-rail:hover i, .c-rail.is-drag i { width: 8px; background: oklch(0 0 0 / .34) }

/* ==== The accordion ========================================================================== */
/* One band per section, closed on arrival, a hairline under each. A band's head is the name, what
   is picked inside it in grey while it is closed (.c-sum, js/catalog.js), and a chevron that turns;
   under the pointer it fills grey to the column's edges. Open, the filters stand under it as subsections, each under a small grey
   heading, and the body unfolds (the height on ::details-content where the browser can animate it,
   and a short fade-and-rise for the rest). */
.c-group { border-bottom: 1px solid var(--line) }
.c-group summary {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 48px;
  padding: 0 16px 0 20px;
  font-size: 13.5px;
  font-weight: 500;
  list-style: none;
  cursor: pointer;
  user-select: none;
  transition: background .15s;
}
.c-group summary::-webkit-details-marker { display: none }
.c-group summary:hover { background: var(--hover) }
.c-group-name { flex: 0 0 auto }
.c-sum {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 12.5px;
  color: var(--muted);
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: opacity .15s;
}
/* Open, the picks are in view; the head says only the name. */
.c-group[open] .c-sum { opacity: 0 }
.c-group summary svg { flex: 0 0 auto; width: 16px; height: 16px; color: var(--muted); transition: transform .24s var(--out) }
.c-group[open] summary svg { transform: rotate(180deg) }
/* The first row sits right under the head, its tick 7px below it (Patryk, 2026-10-20: 10px read as
   too far from the title). */
.c-group-body { padding: 0 20px 14px }
.c-group[open] .c-group-body { animation: c-rise .26s var(--out) both }
@supports (interpolate-size: allow-keywords) {
  .c-group { interpolate-size: allow-keywords }
  .c-group::details-content {
    height: 0;
    overflow: hidden;
    transition: height .26s var(--out), content-visibility .26s allow-discrete;
  }
  .c-group[open]::details-content { height: auto }
}
/* Inside the column a field is drawn as the column is, a hairline ring on white, and the ring darkens
   under the pointer and with the focus; the grey fill is the page's fields' (.c-head-tools, .c-bar). */
.c-filters { --field: var(--surface) }
.c-filters .c-brand-find input, .c-filters .c-range select { box-shadow: inset 0 0 0 1px var(--line) }
/* The column's fields are near square (Patryk, 2026-10-20: 10px read as too round beside the 5px ticks). */
.c-filters .c-brand-find input, .c-filters .c-range select, .c-filters .c-num { border-radius: 4px }
.c-filters .c-brand-find input:hover, .c-filters .c-range select:hover { box-shadow: inset 0 0 0 1px var(--tile-active) }
.c-filters .c-brand-find input:focus, .c-filters .c-range select:focus { box-shadow: inset 0 0 0 1px var(--muted) }

/* A list of rows, a tick box at the left and no count after the name. The tick is in every
   row's markup and painted by aria-pressed, so a pick never moves the rows under the pointer. A row
   runs to the column's ring on both sides, its hover grey full width like a section's head above it
   (Patryk, 2026-10-20: inset rows under a full-width head read as two different lists). */
.c-opts { display: flex; flex-direction: column }
/* Between one filter's options and the next filter's in a shared row (js/catalog.js, GROUPED): a
   hairline across the column, in place of a heading. */
.c-split { height: 1px; margin: 6px -20px; border: 0; background: var(--line) }
.c-opt {
  display: flex;
  align-items: center;
  gap: 10px;
  width: calc(100% + 40px);
  min-height: 32px;
  margin: 0 -20px;
  padding: 4px 20px;
  font-size: 13.5px;
  line-height: 1.3;
  text-align: left;
  color: var(--ink);
  transition: background .12s, color .12s;
}
.c-opt:hover { background: var(--hover) }
.c-opt > span { flex: 0 1 auto; min-width: 0 }
.c-tick {
  flex: 0 0 18px;
  width: 18px;
  height: 18px;
  padding: 2px;
  border-radius: 5px;
  background: var(--surface);
  /* One hairline, a shade darker than the column's (Patryk, 2026-10-20: the 1.5px ring read as a
     2px border). */
  box-shadow: inset 0 0 0 1px var(--dim-soft);
  color: transparent;
  transition: background .12s, box-shadow .12s, color .12s, transform .16s var(--out);
}
.c-opt:hover .c-tick { box-shadow: inset 0 0 0 1px var(--dim) }
.c-opt:active .c-tick { transform: scale(.9) }
.c-opt[aria-pressed="true"] .c-tick, .c-opt[aria-pressed="true"]:hover .c-tick { background: var(--ink); box-shadow: none; color: var(--raised) }
.c-opt[aria-pressed="true"] { font-weight: 500 }
.c-opt:disabled { color: var(--dim); cursor: default }
.c-opt:disabled:hover { background: transparent }
.c-opt:disabled .c-tick, .c-opt:disabled:hover .c-tick { background: transparent; box-shadow: inset 0 0 0 1px var(--line) }

/* Brands: a search over the rows, the rows as above. */
.c-brand-find { display: block; margin: 2px 0 8px }
.c-brand-find input { height: 36px; padding: 0 12px; font-size: 13px }

/* The price: two selects, From and To, side by side, each named over its box. */
.c-range { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 2px 0 10px }
.c-range label { display: flex; flex-direction: column; gap: 5px; min-width: 0 }
.c-range label > span:first-child { font-size: 12px; font-weight: 500; color: var(--muted) }
.c-range select { height: 38px; padding: 0 28px 0 12px; font-size: 13px }

/* ==== The sliders ============================================================================ */
/* A measurement: two fields to type the figures into, outlined as the price's selects are, the unit
   inside each; under them one hairline track with two ink handles (js/catalog.js, sliderHTML;
   Patryk, 2026-10-20: the ringed white handles on a 4px bar read as clumsy). Two native range inputs
   lie over the track, see-through but for their thumbs, so each thumb is dragged by its own input.
   The fill between the handles is drawn from --lo and --hi (0 to 1), inset by half a thumb on each
   side (--th) because a native thumb's centre stops half its width short of the input's ends. The
   later input is on top, so the lower handle, when both are at the right end, is raised by lo-top
   (paintSlider). */
.c-slider { --th: 16px; padding: 2px 0 4px }
.c-nums { display: flex; align-items: center; gap: 8px; margin-bottom: 12px }
.c-nums-dash { flex: 0 0 8px; height: 1px; background: var(--dim) }
.c-num {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  height: 38px;
  padding: 0 12px;
  border-radius: 10px;
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--line);
  cursor: text;
  transition: box-shadow .15s;
}
.c-num:hover { box-shadow: inset 0 0 0 1px var(--tile-active) }
.c-num:focus-within { box-shadow: inset 0 0 0 1px var(--muted) }
.c-num input {
  /* As wide as its figure, so the unit follows the figure ("54 g"); a click anywhere on the field
     still lands in it (the label). */
  flex: 0 1 auto;
  field-sizing: content;
  min-width: 1ch;
  max-width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  outline: none;
}
.c-num i { flex: 0 0 auto; margin-left: 4px; font-style: normal; font-size: 13px; color: var(--muted) }
.c-slider-track { position: relative; height: 20px }
.c-slider-track::before, .c-slider-track i {
  content: "";
  position: absolute;
  top: 9px;
  height: 2px;
  border-radius: 1px;
}
.c-slider-track::before { left: calc(var(--th) / 2); right: calc(var(--th) / 2); background: var(--line) }
.c-slider-track i {
  left: calc(var(--th) / 2 + var(--lo, 0) * (100% - var(--th)));
  right: calc(var(--th) / 2 + (1 - var(--hi, 1)) * (100% - var(--th)));
  background: var(--ink);
}
.c-slider-track input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 20px;
  margin: 0;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  pointer-events: none;
}
.c-slider.lo-top input[data-side="lo"] { z-index: 2 }
.c-slider-track input:focus { outline: none }
.c-slider-track input::-webkit-slider-runnable-track { height: 20px; background: transparent }
.c-slider-track input::-moz-range-track { height: 20px; background: transparent }
/* The handle: an ink dot with a white halo, so it stands clear of the fill it sits on. */
.c-slider-track input::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: var(--th);
  height: var(--th);
  margin-top: calc((20px - var(--th)) / 2);
  border: 0;
  border-radius: 50%;
  background: var(--ink);
  box-shadow: 0 0 0 3px var(--surface);
  cursor: grab;
  pointer-events: auto;
  transition: box-shadow .15s, transform .16s var(--out);
}
.c-slider-track input::-moz-range-thumb {
  width: var(--th);
  height: var(--th);
  border: 0;
  border-radius: 50%;
  background: var(--ink);
  box-shadow: 0 0 0 3px var(--surface);
  cursor: grab;
  pointer-events: auto;
  transition: box-shadow .15s, transform .16s var(--out);
}
.c-slider-track input:hover::-webkit-slider-thumb { box-shadow: 0 0 0 3px var(--surface), 0 0 0 7px rgb(41 37 36 / 8%) }
.c-slider-track input:hover::-moz-range-thumb { box-shadow: 0 0 0 3px var(--surface), 0 0 0 7px rgb(41 37 36 / 8%) }
.c-slider-track input:active::-webkit-slider-thumb { cursor: grabbing; transform: scale(1.12); box-shadow: 0 0 0 3px var(--surface), 0 0 0 7px rgb(41 37 36 / 12%) }
.c-slider-track input:active::-moz-range-thumb { cursor: grabbing; transform: scale(1.12); box-shadow: 0 0 0 3px var(--surface), 0 0 0 7px rgb(41 37 36 / 12%) }

.c-link {
  padding: 0;
  font-size: 13px;
  font-weight: 500;
  color: var(--muted-strong);
  text-decoration: underline;
  text-decoration-color: var(--tile-active);
  text-underline-offset: 3px;
  transition: color .15s;
}
.c-link:hover { color: var(--ink); text-decoration-color: currentColor }
.c-brands-all { margin-top: 8px }

/* ==== The toolbar (narrow screens) =========================================================== */
/* On the desktop the search heads the column (.c-top); the toolbar over the list is the narrow
   screens' (below), with the Filters button that opens the drawer. */
.c-bar { display: none }
/* How many groups hold a choice, on that button (js/catalog.js, updateFilters). */
.c-filter-btn b {
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--ink);
  color: var(--raised);
  font-size: 11px;
  font-weight: 600;
  line-height: 18px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}


/* ==== The cards ============================================================================== */
.c-list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.c-list:empty { display: none }
/* The plain list scripts/build-static-lists.cjs writes for crawlers. js/catalog.js replaces it on
   its first draw; until then it stays out of sight, and catalog.html's <noscript> style shows it to
   a browser that runs no script. */
.c-static { display: none }
/* One grey card per product in a grid of three, a shop's product grid (Patryk picked it from the
   card lab, 2026-10-20, over today's row and four others): the render large on top, the brand small
   over the name, the four key figures as rows with a muted label, and a foot behind a strip of the
   page's white with the price at the left and the black Amazon button at the right. No shadow and
   no ring: the card is told from the page by its grey alone. Under the pointer the card darkens a
   step and the render grows. The whole card opens the sheet (js/catalog.js), except that button;
   the name is its button for the keyboard. Each card comes in from a step below as it is drawn, the
   first dozen of a batch one after another (--i). */
.c-row, .c-empty {
  background: var(--card);
  border-radius: 14px;
}
.c-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto 1fr auto;
  grid-template-areas: "thumb" "main" "side";
  cursor: pointer;
  animation: c-in .32s var(--out) both;
  animation-delay: calc(var(--i, 0) * 28ms);
  transition: background .2s var(--out);
  position: relative;
}
/* The maker's logo in the top-left corner (js/catalog.js, logoHTML), in ink through a mask so every
   mark reads alike. Sized by area rather than by height: a 6:1 wordmark at a square symbol's height
   would shout, so the height falls with the square root of the mark's width over its height (28px
   for a square, 22.5px at 2:1, 16px at 4:1, 12.5px for Razer's 6.5:1), and the width follows from
   it, 128px at most (Patryk, 2026-10-20: a step up from 22px, the first marks read too small). The
   floor is 11px, low enough that the square-root rule holds for nearly every mark: a 15px floor
   left Razer's wide, letter-spaced wordmark half as large again as the others. */
.c-logo {
  position: absolute;
  top: 18px;
  left: 20px;
  z-index: 1;
  height: clamp(11px, calc(32px / sqrt(var(--r, 1))), 28px);
  width: calc(clamp(11px, calc(32px / sqrt(var(--r, 1))), 28px) * var(--r, 1));
  max-width: 128px;
  background: var(--ink);
  -webkit-mask: var(--logo) left center / contain no-repeat;
  mask: var(--logo) left center / contain no-repeat;
  pointer-events: none;
}
@media (hover: hover) {
  .c-row:hover { background: var(--card-hover) }
  .c-row:hover .c-thumb img { transform: scale(1.12) }
}
/* The renders are photographs on white; with no tile of their own (Patryk, 2026-10-20) they are
   multiplied straight onto the card, so the white drops out into its grey, under the pointer too. */
.c-thumb {
  grid-area: thumb;
  display: block;
  aspect-ratio: 4 / 3;
  margin: 18px 18px 0;
  border-radius: 10px;
  overflow: hidden;
}
.c-thumb img { display: block; width: 100%; height: 100%; object-fit: contain; mix-blend-mode: multiply; transform: scale(1.06); transition: transform .32s var(--out) }
/* Cropped to the product (js/catalog.js, fitThumb, the keyboards): the image is placed and sized by
   the script, held back until then so the small uncropped one does not flash first. */
.fit-shots .c-thumb { position: relative }
.fit-shots .c-thumb img { position: absolute; max-width: none; opacity: 0; transform: none; transition: transform .32s var(--out), opacity .15s }
.fit-shots .c-thumb.is-fit img { opacity: 1 }
@media (hover: hover) { .fit-shots .c-row:hover .c-thumb img { transform: scale(1.05) } }
.c-main { grid-area: main; min-width: 0; display: flex; flex-direction: column; padding: 20px 24px 22px }
.c-name { margin: 0; font-size: 18px; font-weight: 600; letter-spacing: -.4px; line-height: 1.25 }
.c-open { display: block; max-width: 100%; padding: 0; text-align: left }
a.c-open { color: inherit; text-decoration: none }
.c-open:hover .c-model { text-decoration: underline; text-decoration-color: var(--tile-active); text-underline-offset: 3px }
/* The brand, small, grey and in capitals, over the model's name. */
/* The maker as the catalog spells it, not in capitals (Patryk, 2026-10-20). */
.c-brand { display: block; margin-bottom: 3px; font-size: 13px; font-weight: 500; color: var(--muted) }
.c-model { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden }
/* "New": in its maker's store for under 30 days (js/catalog.js, newTag). The site's one blue, as a
   tint with a darker text, so a small word keeps 4.5:1. */
.c-new { display: inline-block; margin-left: 8px; padding: 1px 7px; border-radius: 999px; background: oklch(.95 .03 255);
  color: oklch(.45 .16 258); font-size: 11px; font-weight: 600; letter-spacing: .1px; vertical-align: 1px }
/* The key figures, four rows of them under the name (Patryk, 2026-10-20), the label at the left in
   grey and the figure after it. The label's width is fixed so the figures of a card line up in a
   column, as a retailer's spec list does. */
.c-specs {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 8px 0 0;
}
.c-spec { display: flex; align-items: baseline; gap: 10px; min-width: 0; font-size: 13px; line-height: 1.3 }
.c-spec dt { flex: 0 0 76px; color: var(--muted); white-space: nowrap }
.c-spec dd { flex: 1 1 auto; min-width: 0; margin: 0; font-weight: 500; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-variant-numeric: tabular-nums }
.c-spec.is-none dd { color: var(--dim-soft); font-weight: 400 }
/* The foot, behind a strip of the page's white across the card: the price, then the button. */
.c-side {
  grid-area: side;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 16px 18px 16px 24px;
  border-top: 3px solid var(--page);
}
.c-price {
  flex: 1 1 auto;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -.6px;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* "approx." after the figure (js/catalog.js, APPROX): small, plain and grey, so the figure leads. */
.c-approx { margin-left: 6px; font-size: 12.5px; font-weight: 400; letter-spacing: 0; color: var(--muted) }
.c-price[title] { cursor: help }
.c-price.is-unknown { font-size: 14px; font-weight: 400; color: var(--muted); padding: 3px 0 }
/* The Amazon search, the card's black button (style.css, .buy-btn) at a card's size; it gives a
   touch when pressed. */
.c-amz {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 38px;
  padding: 0 14px;
  border-radius: 10px;
  background: var(--ink);
  color: var(--raised);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  flex: 0 0 auto;
  transition: background .15s, transform .16s var(--out);
}
.c-amz:hover { background: var(--ink-deep) }
.c-amz:active { transform: scale(.97) }
.c-amz img { display: block; width: 15px; height: 15.5px; transform: translateY(1px) }
/* With a mouse the button waits for the card (Patryk, 2026-10-20): it fades and rises in under the
   pointer, and for a keyboard as soon as anything in the card has the focus. It keeps its place
   while hidden, so nothing moves when it comes. A touch screen has no hover to wait for, and shows
   it always. The rise is on `translate`, so the press's scale (transform) still works. */
@media (hover: hover) and (pointer: fine) {
  .c-amz { opacity: 0; translate: 0 4px; transition: background .15s, transform .16s var(--out), opacity .15s, translate .2s var(--out) }
  .c-row:hover .c-amz, .c-row:focus-within .c-amz { opacity: 1; translate: none }
}
/* Compare, in the top-right corner (js/catalog.js, cmpHTML; Patryk, 2026-11-01): the filters' tick
   box and the word, on no ground of its own; under the pointer a grey a shade darker than the card's,
   with a 6px corner (Patryk, 2026-11-01: not a round pill).
   With a mouse it waits for the card, as the Amazon button does,
   and stays once ticked; a touch screen shows it always. When ten are picked the boxes not ticked
   fade, and a tick on one makes the tray say why (js/compare-store.js). The box itself is hidden and
   the pill is its label, so the whole pill is the target. */
.c-cmp {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 10px 0 8px;
  border-radius: 6px;
  background: none;
  color: var(--ink);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  user-select: none;
  transition: background .15s, opacity .15s, translate .2s var(--out), transform .16s var(--out);
}
.c-cmp input { position: absolute; opacity: 0; pointer-events: none }
.c-cmp:hover { background: oklch(0 0 0 / .05) }
/* The box keeps the filters' white ground and hairline, which darkens under the pointer. */
.c-cmp .c-tick { flex-basis: 16px; width: 16px; height: 16px; border-radius: 4.5px }
.c-cmp:hover .c-tick { box-shadow: inset 0 0 0 1px var(--dim) }
.c-cmp:active { transform: scale(.96) }
.c-cmp input:checked + .c-tick { background: var(--ink); box-shadow: none; color: var(--raised) }
.c-cmp input:focus-visible + .c-tick { outline: 2px solid var(--muted); outline-offset: 2px }
.is-cmp-full .c-cmp:not(:has(input:checked)) { color: var(--dim); cursor: not-allowed; background: none }
@media (hover: hover) and (pointer: fine) {
  .c-cmp { opacity: 0; translate: 0 -4px }
  .c-row:hover .c-cmp, .c-row:focus-within .c-cmp, .c-cmp:has(input:checked) { opacity: 1; translate: none }
}

/* The compare tray is in compare-tray.css, which the finders load too. */

/* How many more shops the sheet lists: the grid's foot has no room for it, so it is the phone's
   alone and stays hidden there too; the sheet names them. */
.c-more-shops { display: none }

.c-empty {
  margin: 0;
  padding: 56px 24px;
  text-align: center;
  font-size: 15px;
  line-height: 1.5;
  color: var(--muted);
  animation: c-in .32s var(--out) both;
}
.c-empty .c-link { font-size: inherit }
.c-more { height: 1px }

/* Under 1100px two columns: three would leave a card too narrow for the dimensions. */
@media (max-width: 1100px) {
  .c-list { grid-template-columns: repeat(2, minmax(0, 1fr)) }
}

@keyframes c-in { from { opacity: 0; transform: translateY(8px) } }
@keyframes c-rise { from { opacity: 0; transform: translateY(-4px) } }

/* ==== The phone's pieces ===================================================================== */
/* The Filters button in the toolbar, the drawer's close and its "Show N mice" and the scrim are
   the narrow layout's alone (below); the desktop never shows them. */
.c-filter-btn, .c-drawer-close, .c-filters-foot, .c-scrim { display: none }

/* ==== Narrow ================================================================================= */
/* Up to 900px the column goes and the same aside is a drawer from the bottom (js/catalog.js,
   openDrawer): a tablet held upright has no width to spare for it. Closed, it is below the screen
   and hidden from the keyboard (visibility). The toolbar's fields over the white page are grey. */
@media (max-width: 900px) {
  .c-body { grid-template-columns: 1fr; gap: 0 }
  .c-top { display: none }
  .c-bar { --field: var(--card); display: flex; align-items: center; gap: 10px; height: 44px; margin-bottom: 12px }
  .c-filter-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    height: 44px;
    padding: 0 14px;
    border-radius: 10px;
    background: var(--field);
    font-weight: 600;
    transition: background .15s, transform .16s var(--out);
  }
  .c-filter-btn:hover { background: var(--card-hover) }
  .c-filter-btn:active { transform: scale(.97) }
  .c-filter-btn svg { width: 17px; height: 17px }
  .c-filter-btn b { margin-left: 2px }
  .c-filters {
    position: fixed;
    z-index: 50;
    inset: auto 0 0 0;
    top: auto;
    width: auto;
    max-height: 86vh;
    border: 0;
    border-radius: 18px 18px 0 0;
    background: var(--surface);
    box-shadow: 0 -8px 32px rgb(41 37 36 / 14%);
    visibility: hidden;
    transform: translateY(100%);
    transition: transform 300ms var(--out), visibility 0s linear 300ms;
  }
  @supports (height: 100dvh) { .c-filters { max-height: 86dvh } }
  body.filters-open { overflow: hidden }
  body.filters-open .c-filters { transform: none; visibility: visible; transition: transform 300ms var(--out) }
  .c-scrim {
    display: block;
    position: fixed;
    z-index: 45;
    inset: 0;
    background: var(--scrim);
    animation: c-fade .2s ease both;
  }
  .c-scrim[hidden] { display: none }
  .c-filters-head { height: auto; padding: 18px 20px 14px }
  .c-filters-head h2 { font-size: 19px }
  .c-drawer-close { display: grid }
  .c-groups { padding: 0 0 12px; -webkit-mask-image: none; mask-image: none }
  .c-group summary { height: 46px; font-size: 14.5px }
  /* A finger's handle: larger, on a taller track. */
  .c-slider { --th: 22px }
  .c-slider-track, .c-slider-track input { height: 28px }
  .c-slider-track::before, .c-slider-track i { top: 13px }
  .c-slider-track input::-webkit-slider-thumb { margin-top: 3px }
  .c-slider-track input::-webkit-slider-runnable-track, .c-slider-track input::-moz-range-track { height: 28px }
  .c-filters-foot {
    display: block;
    flex: 0 0 auto;
    padding: 12px 20px calc(12px + env(safe-area-inset-bottom));
    border-top: 1px solid var(--line);
    background: var(--surface);
  }
  .c-show {
    width: 100%;
    height: 46px;
    border-radius: 10px;
    background: var(--ink);
    color: var(--raised);
    font-size: 15px;
    font-weight: 600;
    transition: background .15s, transform .16s var(--out);
  }
  .c-show:hover { background: var(--ink-deep) }
  .c-show:active { transform: scale(.98) }
  /* A touch target a finger can hit: the rows grow a little in the drawer. */
  .c-opt { min-height: 40px; font-size: 14px }
}
@keyframes c-fade { from { opacity: 0 } }
@media (max-width: 700px) {
  .catalog { padding: 72px 16px 48px }
  .c-title { margin-bottom: 6px; font-size: 24px }
  /* The head comes apart (Patryk, 2026-11-02): the title with the finder's pill level with it at the
     right (the bar's own pill has no room here), then the search and Filters, then the sort across
     the whole width under them. The head and its tools give their children to the list's grid. */
  .c-results { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 12px }
  .c-results > * { grid-column: 1 / -1 }
  .c-head, .c-head-tools { display: contents }
  .c-head-text { grid-row: 1; grid-column: 1; align-self: center; margin-bottom: 14px }
  .c-head-text { min-width: 0 }
  .c-head-text .c-title { margin: 0; font-size: 22px; white-space: nowrap }
  .c-finder { grid-row: 1; grid-column: 2; align-self: center; margin-bottom: 14px; padding: 0 14px; font-size: 13.5px }
  .c-bar { grid-row: 2 }
  .c-sort {
    grid-row: 3; grid-column: 1 / -1; height: 44px; margin-bottom: 14px; padding: 0 0 0 14px;
    border-radius: 10px; background: none; box-shadow: inset 0 0 0 1px var(--line); font-size: 15px;
  }
  .catalog .c-sort select {
    field-sizing: fixed; flex: 1 1 auto; min-width: 0; height: 44px;
    padding: 0 38px 0 0; background-position: right 14px center; font-size: 16px;
  }
  .nav-finder { display: none }
  .c-finder {
    display: inline-flex;
    align-items: center;
    height: 36px;
    padding: 0 16px;
    border-radius: 999px;
    background: var(--ink);
    color: var(--raised);
    font-size: 14px;
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
    transition: background .15s, transform .16s var(--out);
  }
  .c-finder:hover { background: var(--ink-deep) }
  .c-finder:active { transform: scale(.97) }
  /* The toolbar rides under the menu bar, so the search and Filters are always a thumb away. */
  .c-bar {
    position: sticky;
    z-index: 20;
    top: 60px;
    gap: 8px;
    height: auto;
    margin: 0 -16px;
    padding: 4px 16px 8px;
    background: var(--page);
    transition: transform .28s cubic-bezier(.22, 1, .36, 1);
  }
  /* It goes up with the bar while the list is read down (js/nav.js), and comes back with it. */
  .nav-tucked .c-bar { transform: translateY(calc(-100% - 60px)) }
  .c-search { min-width: 0 }
  .c-search input { font-size: 16px }   /* under 16px iOS zooms the page into the field */

  /* One card a line (Patryk, 2026-11-02): Compare in the top-left corner and the maker's logo in the
     top-right, then a large render at the left with the name and every spec beside it, and the foot
     behind the white strip as on the grid: price at the left, button at the right. */
  .c-list { grid-template-columns: minmax(0, 1fr); gap: 12px }
  .c-row {
    grid-template-rows: auto auto auto;
    grid-template-columns: 128px minmax(0, 1fr);
    grid-template-areas: "cmp logo" "thumb main" "side side";
    gap: 0 14px;
    padding: 12px 16px 16px;
  }
  .c-cmp { position: static; grid-area: cmp; align-self: center; justify-self: start; margin-left: -8px }
  .c-logo { position: static; grid-area: logo; align-self: center; justify-self: end; -webkit-mask-position: right center; mask-position: right center }
  .c-thumb { width: 128px; height: 128px; aspect-ratio: auto; margin: 10px 0 14px; border-radius: 10px; align-self: center }
  .c-main { padding: 10px 0 14px; justify-content: center }
  .c-name { font-size: 16px }
  .c-brand { font-size: 12px }
  /* One spec a line in the column beside the render, the label at the left and the figure at the
     right, a retailer's phone list squeezed in. */
  .c-specs { gap: 4px; margin: 10px 0 0 }
  .c-spec { justify-content: space-between; gap: 8px; font-size: 12px }
  .c-spec dt { flex: 0 0 auto }
  .c-spec dd { flex: 0 1 auto; text-align: right }
  .c-side { gap: 12px; margin: 0 -16px -16px; padding: 14px 16px 16px }
  .c-price.is-unknown { padding: 0 }
  .c-amz { flex: 0 0 auto; padding: 0 16px }
}

/* A 360px Android: "Keyboards (1,532)" and its pill fill a 375px line exactly, so a step down. */
@media (max-width: 374px) {
  .c-head-text .c-title { font-size: 20px }
  .c-finder { padding: 0 12px; font-size: 13px }
}

/* A monitor photo is the screen across nearly its whole square, its top edge a seventh of the way
   down, so the maker's logo in the corner sat on the bezel, and a black logo on a black bezel (ASUS,
   MSI) could not be seen at all (Patryk, 2026-10-22). A mouse or a keyboard sits in the middle of its
   render and leaves that corner empty. On the monitor catalog the render starts under the logo: 18px
   to the logo, 28px at most of logo, 6px clear. On a phone the logo stands beside the render, over
   the name, so the render keeps the card's top. */
.monitors-catalog .c-thumb { margin-top: 52px }
@media (max-width: 700px) {
  .monitors-catalog .c-thumb { margin-top: 0 }
}

/* A reader who asked for less motion: everything lands at once, nothing rises, pops or scales. */
@media (prefers-reduced-motion: reduce) {
  .c-row, .c-empty, .c-group[open] .c-group-body, .c-scrim, .cmp-tray.is-in, .cmp-tray.is-full { animation: none }
  .c-row, .c-row .c-thumb img, .c-filters, .c-group summary svg, .c-group::details-content,
  .c-amz, .c-tick, .c-cmp, .cmp-tray-go, .c-filter-btn, .c-show, .nav-finder, .c-finder,
  .c-slider-track input::-webkit-slider-thumb, .c-slider-track input::-moz-range-thumb { transition: none }
  .c-amz, .c-cmp { translate: none }
  .c-row:hover .c-thumb img { transform: scale(1.06) }
}
