/* ==========================================================================
   CENTAURI 0.4.0 — the shelf layer (authored; copied through)
   GENERATED by scripts/generate.mjs from scripts/tokens.source.mjs. Do not edit.
   A local edit is not a disagreement to adjudicate; it is a build that no
   longer reproduces, and scripts/check.mjs will say so.
   ========================================================================== */
/* -----------------------------------------------------------------------------
   CENTAURI — the shelf layer. The blocks a product catalog needs and no
   operating surface does, for Centauri Catalog (ADR-014).

   It is called `shelf` and not `catalog` for the reason the page layer is not
   called `marketing`: a layer is named for what it HOLDS, and a profile and a
   layer that share a name emit one file instead of two (generate.mjs,
   nameCollisions). A shelf is also the premise in one word — things set side by
   side so they can be judged against one another.

   THE PREMISE. Console cites a value, Field glances at one, Marketing persuades.
   A catalog reader is COMPARING: deciding between several things by their
   specifications, where the question is never "what is this number" but "which
   of these is bigger, and by how much". That is a fourth reading act.

   It changes what the page is made of in one way that matters more than all the
   others: **the same field has to be in the same place in every item.** A spec
   sheet whose rows are ordered by what each manufacturer chose to publish is not
   a comparison, it is a pile of documents. So the spec list here is a grid with
   a fixed row order and an explicit absent state, not a definition list that
   flows.

   WHAT THIS LAYER DELIBERATELY DOES NOT ADD. The spec sheet is a table and the
   table layer already owns tables; `catalog+table` is an approved composition
   rather than a reimplementation here. The figure's frame, the tick of
   selection, the state layer and the micro label are all base-layer mechanisms
   and this composes them.

     the spec sheet itself    `centauri.table.css` under `catalog+table`
     the section rhythm       `--centauri-scaled-section`
     the selected item        the tick of selection, from the base layer
     every hover and focus    `.centauri-state`, from the base layer

   THE SIGNATURE DEVICE is the SHARED AXIS. Centauri's own signature object is
   the dimension line: a quantity drawn to a stated domain with end ticks. A
   catalog's version is that line drawn to a domain shared by every item on the
   page, so two bars in two different cards are the same scale and the longer one
   really is the larger number. A per-item axis — each bar normalized to its own
   maximum — makes every product look equally good and is the single most common
   way a comparison surface lies. `--centauri-axis-max` is set once on the shelf
   and read by every item; an item that sets its own fails the gate.
----------------------------------------------------------------------------- */

/* --- 1. THE SHELF ---------------------------------------------------------- */

/* The browse grid. `auto-fill` rather than a fixed column count, because the
   number of things that fit is a property of the viewport and not of the
   catalog; and a `minmax` floor rather than a media query, because the cell has
   a smallest size at which its photograph is still evidence. */
.centauri-shelf {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--centauri-shelf-min, 15rem), 1fr));
  gap: var(--centauri-scaled-6);
  /* The axis domain every item draws against. A product sets it once, from the
     largest value in the SET being compared, not from the largest on screen —
     scrolling must not rescale the bars. */
  --centauri-axis-max: 100;
}

/* --- 2. THE ITEM ----------------------------------------------------------- */

/* `--color-card` and not `--color-surface`: the item is a FRAME, and a frame
   the same color as the page it sits on is separated from it by the hairline
   alone. That reads as an object in the light theme, where the hairline is a
   3:1 stroke on near-white, and as a wireframe in the dark one, where the whole
   page is a near-black and a specimen has to be a surface lifted out of it.
   Elevation runs the same direction as everywhere else in the spine -- card
   above surface-alt above surface -- so this is the token the rest of the
   system already uses for a framed thing, not a new decision. */
.centauri-item {
  display: flex;
  flex-direction: column;
  gap: var(--centauri-scaled-3);
  min-width: 0;
  padding: var(--centauri-scaled-4);
  border: var(--centauri-static-stroke-edge) solid var(--color-edge);
  border-radius: var(--radius-card);
  background-color: var(--color-card);
}

/* An item is a container, so it is a box rather than a rule — invariant 5's own
   exception, and the one place in this layer a box is correct. */
.centauri-item__name {
  font-size: var(--text-datum);
  line-height: var(--text-datum--line-height);
  color: var(--color-strong);
  margin: 0;
}

/* The SKU is the thing a buyer types into a purchase order, so it is selectable
   text at the datum step in tabular figures, never a caption. */
.centauri-item__sku {
  font-size: var(--text-small);
  line-height: var(--text-small--line-height);
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
  user-select: all;
}

.centauri-item__price {
  font-size: var(--text-body);
  line-height: var(--text-body--line-height);
  font-variant-numeric: tabular-nums;
  color: var(--color-strong);
  margin-top: auto;
}

/* --- 3. THE FIGURE --------------------------------------------------------- */

/* A catalog photograph is EVIDENCE, not decoration: it is the specification for
   how the thing looks. So it is never cropped to fill a frame — `contain`, on a
   declared surface, at a fixed ratio so a grid of them does not jump. A crop
   removes the part of the product the buyer was looking for. */
.centauri-figure {
  aspect-ratio: 4 / 3;
  display: grid;
  place-items: center;
  overflow: hidden;
  border-radius: var(--radius-control);
  background-color: var(--color-surface-alt);
}

.centauri-figure > img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}

/* A product with no photograph says so. An empty frame reads as a slow network,
   and a buyer waits for something that is never coming. */
/* The words go in a `.centauri-label` inside the frame — the base layer already
   owns the tracked-caps micro label (invariant 8) and restating its tracking
   here would be a second copy of a number with one owner. */
.centauri-figure[data-absent] {
  color: var(--color-muted);
}

/* --- 4. THE SPEC LIST ------------------------------------------------------ */

/* A two-column grid rather than a flowing definition list, because the whole
   point is that row 4 is the same field in every item. `subgrid` when the items
   share a grid, so the labels line up ACROSS cards and not only within one. */
.centauri-spec {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--centauri-scaled-cell-y) var(--centauri-scaled-cell-x);
  margin: 0;
  font-size: var(--text-small);
  line-height: var(--text-small--line-height);
}

.centauri-spec__label {
  color: var(--color-muted);
  min-width: 0;
}

.centauri-spec__value {
  font-variant-numeric: tabular-nums;
  color: var(--color-prose);
  text-align: end;
}

/* Absence is a declaration, not a blank. `null` is "this product does not have
   this field"; `pending` is "we have not been told yet". They are different
   answers to a buyer and they get different words. */
.centauri-spec__value[data-absent="null"],
.centauri-spec__value[data-absent="pending"] {
  color: var(--color-muted);
  font-variant-numeric: normal;
}

/* --- 5. THE SHARED AXIS ---------------------------------------------------- */

/* The axis sits on its own row across both columns. A track inside a value cell
   is sized by that cell's content, so three cards produced three different track
   lengths (74 / 74 / 86px, measured 2026-09-14) and the bars stopped being
   comparable even though the domain was shared. A shared DOMAIN needs a shared
   TRACK; this is what makes both true at once, because every item is a column of
   one grid and every track is the width of its item. */
.centauri-spec__axis {
  grid-column: 1 / -1;
  margin-block-start: calc(-1 * var(--centauri-scaled-1));
}

/* The dimension line, drawn to a domain shared by the whole shelf. The bar's
   length is `--centauri-axis-value` over `--centauri-axis-max`, and the max is
   inherited from the shelf — an item that sets its own has normalized the bar to
   itself, which makes every product look equally good. */
.centauri-axis {
  display: block;
  position: relative;
  block-size: var(--centauri-static-stroke-edge);
  inline-size: 100%;
  background-color: var(--color-edge);
}

.centauri-axis__fill {
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: calc(100% * var(--centauri-axis-value, 0) / var(--centauri-axis-max, 1));
  background-color: var(--color-accent);
}

/* The end tick. It is what makes this a dimension line rather than a progress
   bar: a progress bar runs to a full state, a dimension line runs to a stated
   domain, and the tick is where that domain ends. */
.centauri-axis::after {
  content: "";
  position: absolute;
  inset-inline-end: 0;
  inset-block-start: calc(-1 * var(--centauri-static-tick-length));
  inline-size: var(--centauri-static-stroke-edge);
  block-size: calc(var(--centauri-static-tick-length) * 2);
  background-color: var(--color-edge-strong);
}

/* --- 6. THE COMPARE TRAY --------------------------------------------------- */

/* Comparison is the premise, so the set being compared is PRESENT rather than
   remembered. The tray is pinned, it names every item in it, and each has its
   own remove control — a count with no names is a number the buyer has to trust.
   It is a `<aside>` in the flow at narrow widths and pinned above it, never a
   floating layer over the grid. */
.centauri-compare {
  position: sticky;
  inset-block-end: 0;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--centauri-scaled-3);
  padding: var(--centauri-scaled-3) var(--centauri-scaled-4);
  border-block-start: var(--centauri-static-stroke-edge) solid var(--color-edge-strong);
  background-color: var(--color-surface);
}

.centauri-compare__item {
  display: inline-flex;
  align-items: center;
  gap: var(--centauri-scaled-2);
  min-block-size: var(--centauri-static-target-min);
  padding-inline: var(--centauri-scaled-2);
  border: var(--centauri-static-stroke-hairline) solid var(--color-edge);
  border-radius: var(--radius-chip);
  font-size: var(--text-small);
  line-height: var(--text-small--line-height);
  color: var(--color-prose);
}

/* Nothing selected is a state with words, not an absent tray. A tray that
   disappears takes the affordance with it and the buyer never learns it exists. */
.centauri-compare[data-absent="null"] {
  color: var(--color-muted);
  font-size: var(--text-small);
  line-height: var(--text-small--line-height);
}

/* --- 7. THE FACET RAIL ----------------------------------------------------- */

/* Filters are rules between groups, not boxes around them (invariant 5). The
   rail is one column at every width; under 600px it is the same markup in a
   disclosure, which the base layer already owns. */
.centauri-facets {
  display: flex;
  flex-direction: column;
  gap: var(--centauri-scaled-5);
  min-width: 0;
}

.centauri-facets__group + .centauri-facets__group {
  padding-block-start: var(--centauri-scaled-5);
  border-block-start: var(--centauri-static-stroke-hairline) solid var(--color-edge);
}

/* The count beside a facet is what makes a filter honest: it says how many
   things are behind the checkbox BEFORE it is ticked, so a buyer never picks a
   filter that empties the shelf. A facet with no matches stays visible and
   disabled rather than disappearing — a vanishing option cannot be reasoned
   about. */
/* One option per row. They were inline until measured on 2026-09-14, and a
   wrapped run of checkboxes reads as one control with several labels rather
   than several controls -- the count that belongs to each is the first thing
   lost. */
.centauri-facets__option {
  display: flex;
  align-items: center;
  gap: var(--centauri-scaled-2);
  min-block-size: var(--centauri-static-target-min);
  font-size: var(--text-small);
  line-height: var(--text-small--line-height);
  color: var(--color-prose);
}

.centauri-facets__count {
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
  font-size: var(--text-micro);
  line-height: var(--text-micro--line-height);
}

.centauri-facets__option[aria-disabled="true"] .centauri-facets__count {
  color: var(--color-muted);
}

/* The control itself, because a browser's default checkbox is 13x13 and SC
   2.5.8 wants 24. The label around it is already 44px tall and that is not the
   target — the input is, which is what the probe measured at 320px. The table
   layer carries the same rule for its select column, for the same reason, and
   on a coarse pointer the touch mechanism expands it again without moving this
   dimension. */
.centauri-facets__option input[type="checkbox"],
.centauri-compare__item input[type="checkbox"] {
  inline-size: var(--centauri-static-target-min);
  block-size: var(--centauri-static-target-min);
  margin: 0;
}

/* --- 8. NARROW ------------------------------------------------------------- */

/* The shelf's cell floor does the work above 600px. Below it the grid becomes
   one column and the figure loses its fixed ratio, because a 4:3 frame at 375px
   costs a third of the screen before a single specification is read. */
@media (max-width: 599px) {
  .centauri-shelf {
    grid-template-columns: 1fr;
  }

  /* A GRID rather than a flex row. The first draft said `flex-direction: row`,
     which put all six of an item's children on one line, and a flex item is
     sized by its own content: the five spec lists came out 107 to 131px wide,
     so the shared axis was drawn on five different tracks (measured 599px,
     2026-09-14) and at 375px the document was 161px wider than the viewport.
     The `grid-column` on the spec list below was already written for a grid and
     did nothing in a flex container, which is the tell that the block had never
     been rendered. */
  .centauri-item {
    display: grid;
    /* 6rem rather than a spacing token: the figure is evidence and the largest
       space in the ramp is 32px, which is an icon. This is the second literal
       length in the layer and it has the same justification as the first, the
       shelf's own cell floor -- both are the size at which a photograph is
       still worth looking at. */
    grid-template-columns: 6rem minmax(0, 1fr);
    column-gap: var(--centauri-scaled-4);
    align-items: start;
  }

  .centauri-figure {
    grid-column: 1;
    grid-row: 1 / span 2;
    aspect-ratio: 1;
  }

  /* The figure sits beside the two lines that NAME the thing. */
  .centauri-item__name,
  .centauri-item__sku {
    grid-column: 2;
  }

  /* Everything a buyer compares runs the full width of the card, so every axis
     track in the shelf is one length. The spec list keeps every row with it: a
     catalog that hides specifications on a phone has hidden the thing the buyer
     came for — the same rule the table layer holds, for the same reason. */
  .centauri-item > :not(.centauri-figure, .centauri-item__name, .centauri-item__sku) {
    grid-column: 1 / -1;
  }
}
