/* =========================================================================
   Page — the public storefront: catalogue, app detail, partner page
   -------------------------------------------------------------------------
   ONE FILE FOR THREE PAGES, and the reason is the split's own. `pages/` is
   split so that two page BRANCHES do not edit one file; these three ship in one
   branch as one surface, exactly as `apps.css` carries the three portal app
   pages (audit rule D-6). Blocks used on more than one of them are marked
   below, so a later split is a cut along a line that is already drawn.

   `components/rating.css` is a component and not part of this file, because the
   two rating chips are the one thing a fourth page — a portal review view, an
   admin editorial screen — will want next, and a block two surfaces share does
   not live in a file named after one of them.

   NO LITERAL COLOUR. Every value is a token from `tokens.css`.
   ========================================================================= */

/* ---- The filter form (catalogue) ------------------------------------------ */

/* `.storefront-filters` sets `display`, and the form is hidden with the
   `hidden` ATTRIBUTE until the module that honours it is running — so the two
   are paired here. Without the `[hidden]` rule the class would win on source
   order and the form would be visible with no module behind it, which is
   exactly the failure the attribute exists to prevent (L-FE-11). */
.storefront-filters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px 20px;
  align-items: end;
  margin-bottom: 32px;
  padding: 20px;
  border: 1px solid var(--doc-line);
  border-radius: var(--radius-lg);
  /* THE RECESS, and this is the depth model doing actual work
     (DESIGN_TOKENS.md §9.1). The filter bar is a TOOL and the cards below it
     are the CONTENT; putting both on --doc-panel made them one plane, so the
     eye had nothing to sort them by. On --sd-sunk the bar sits a step below
     the cards in both themes -- the order page < sunk < surface is measured
     and identical in light and dark, which is exactly what --sd-surface-alt
     could not have given us here. */
  background: var(--sd-sunk);
}

.storefront-filters[hidden] {
  display: none;
}

.storefront-filter-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

/* ---- The grid of cards (catalogue, partner page) -------------------------- */

.storefront-grid {
  display: grid;
  /* `auto-fit` rather than a column count: the number of listings is the API's
     answer and a `repeat(3, …)` would break on the first narrow viewport. At
     320 px this is one column, which is what the 320 px requirement means. */
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 20px;
}

.storefront-card {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.storefront-card-head {
  display: flex;
  align-items: flex-start;
  gap: 16px;
}

.storefront-card-title {
  min-width: 0;
}

/* The icon box. Sized here AND carrying `width`/`height` attributes from the
   API's stored dimensions, so the space is reserved before the bytes arrive —
   the attributes stop the layout shift, this makes every icon the same size
   whatever the partner uploaded.

   **The pixel values here, and on the two large image boxes further down, are
   deliberate literals — and they are the only ones left in this file.** Audit
   rule D-3 asks that spacing follow the 4 px scale and that radii and CONTROL
   heights come from tokens; the spacing does, and the radii and the pagination
   box use `--radius`, `--radius-lg` and `--control-h`. An image box is neither
   spacing nor a control: the token set has no value for "how big is a listing
   icon", inventing one would be a token nobody else can use, and reaching for
   the nearest existing metric would size the artwork by coincidence. 56 / 96 /
   64 are multiples of 8, so they sit on the scale without pretending to be it.

   **A fixed box is also what makes the icon immune to a missing dimension.**
   Measured directly against `buildPicture`: a picture whose `width`/`height` the
   API did not send, or sent as 0, gets no attributes at all — the function sets
   them only for positive integers. For an icon the CSS box means there is no
   shift either way. The one place that is not true is `.storefront-shot`, which
   has no fixed width, so a screenshot arriving without dimensions would shift.
   Both fields are `required` in the spec, so it is an edge rather than a case,
   and it is written down rather than guarded: a picture refused for missing
   metadata is worse than a shifted one. */
.storefront-icon {
  width: 56px;
  height: 56px;
  flex: 0 0 auto;
  border-radius: var(--radius);
  object-fit: contain;
  /* A well the icon sits in, so a transparent PNG has a floor. --sd-sunk and
     not --sd-surface-alt: the well was BELOW the card in light and ABOVE it
     in dark (L-FE-44), which made a transparent logo look mounted on a tile
     in one theme and cut into the card in the other. */
  background: var(--sd-sunk);
}

.storefront-icon-placeholder {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  font-weight: 700;
  color: var(--doc-ink-soft);
  border: 1px solid var(--doc-line);
}

.storefront-card-name {
  margin: 0;
  font-size: 18px;
  line-height: 1.35;
}

.storefront-card-link {
  color: var(--sd-text);
  text-decoration: none;
}

.storefront-card-link:hover,
.storefront-card-link:focus-visible {
  color: var(--sd-secondary);
  text-decoration: underline;
}

.storefront-publisher {
  margin: 4px 0 0;
  font-size: 13px;
  color: var(--doc-ink-soft);
}

.storefront-by {
  margin-right: 4px;
}

.storefront-publisher-link {
  color: var(--sd-secondary);
}

.storefront-card-summary {
  margin: 12px 0 0;
  color: var(--sd-text-soft);
}

.storefront-card-meta {
  margin: 12px 0 0;
  font-size: 12px;
  color: var(--doc-ink-soft);
}

/* Shared: catalogue cards, detail header, partner header. */
.storefront-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}

/* THE SAME CLASS IS A LINK ON ONE PAGE AND NOT ON THE OTHER, and only the
   link may look like one.

   On the DETAIL page the category chip is an `<a>` to the filtered catalogue
   (`app.js`); on the CATALOGUE it is a `<span>`, because filtering by the
   category you are already filtering by leads nowhere. Both carry
   `.storefront-category-chip`.

   The two chips on the detail header used to differ by tint — `badge-info`
   against `badge-muted` — and once both became facets they were
   pixel-identical at rest, with one of them navigating. `a:hover
   { text-decoration: underline }` and the focus ring still fire, so nothing
   was unreachable, but the affordance at rest was gone.

   SO THE SELECTOR CARRIES `a`. The first fix did not, and it painted the
   catalogue's plain `<span>` in the link colour — announcing a click that
   does not exist, which is the same defect with the sign flipped. Found by
   reading the computed colour on both pages rather than on the one the
   finding named.

   The ink is the site's own link colour, used by every other link on the
   page, so it is not the state tint §9.3 reserves. Measured on the chip's own
   ground: 5.33:1 in light and 7.26:1 in dark against `--sd-sunk`. */
.storefront-category-chip {
  text-decoration: none;
}

a.storefront-category-chip {
  color: var(--sd-secondary);
}

/* A string the partner has not written in any language the answer could reach.
   Shown as a gap rather than hidden: a card with a blank line looks broken,
   and "not translated yet" is a fact about the listing. */
.storefront-untranslated {
  color: var(--doc-ink-soft);
  font-style: italic;
}

.storefront-count {
  margin: 0 0 20px;
  color: var(--doc-ink-soft);
  font-size: 14px;
  /* "128 listings" is a figure a reader compares against the page they were
     on a moment ago (DESIGN_TOKENS.md §9.5). It is not in a column, so it is
     tabular and stays left-aligned: right alignment belongs to a numeric
     table column and would align this against nothing. */
  font-variant-numeric: tabular-nums;
}

/* The count is focused after a page turn so a keyboard user is not left at the
   bottom of a list that was replaced under them. It is not interactive, so it
   gets the ring and nothing else. */
.storefront-count:focus-visible {
  outline: 2px solid var(--sd-secondary);
  outline-offset: 4px;
}

.storefront-note {
  margin: 20px 0 0;
  font-size: 13px;
  color: var(--doc-ink-soft);
}

/* ---- The featured rail (catalogue) ---------------------------------------- */

/* Sets `display` and is hidden with the attribute — paired, as above. */
.storefront-rail {
  display: block;
  margin-bottom: 40px;
  padding-bottom: 32px;
  border-bottom: 1px solid var(--doc-line);
}

.storefront-rail[hidden] {
  display: none;
}

.storefront-rail-heading {
  margin: 0 0 4px;
  font-size: 20px;
}

.storefront-rail-lede {
  margin: 0 0 20px;
  color: var(--doc-ink-soft);
  font-size: 14px;
}

/* ---- Pagination (catalogue) ----------------------------------------------- */

.storefront-pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 40px;
}

.storefront-pagination[hidden] {
  display: none;
}

.storefront-page {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--control-h);
  /* `--control-h` — 42 px — and not a literal: this is a control and the token
     set has a height for controls.
     **The pointer target size, stated correctly.** WCAG 2.5.8 (AA) asks for
     24 × 24 CSS px, not 40; 44 × 44 is 2.5.5, which is AAA. A bare page number
     reaches neither on its own, which is why the box is set at all. 42 × 42
     clears AA with room and falls just short of AAA. *An earlier version of
     this comment said "40 px square: the pointer target size WCAG 2.5.8 asks
     for" — a stale number and the wrong criterion, written into the commit that
     was fixing exactly this class of overtaken comment.* */
  min-height: var(--control-h);
  padding: 0 12px;
  border: 1px solid var(--doc-line);
  border-radius: var(--radius);
  color: var(--sd-text);
  text-decoration: none;
  font-variant-numeric: tabular-nums;
}

.storefront-page:hover,
.storefront-page:focus-visible {
  border-color: var(--sd-secondary);
  color: var(--sd-secondary);
}

.storefront-page-step {
  padding: 0 16px;
}

/* The range between the two steps — "26–50 of 128". It is the whole of what the
   pager says about position since U-736 (ADR-0055): one base through the stack, so
   the label names ROWS rather than a page index a reader would have to know the base
   of. It is a `<span>` and not a control, so it takes none of `.storefront-page`.

   `.storefront-page-current` and `.storefront-page-gap` stood here and are gone with
   the numbered window they styled: there is no non-link current page any more, and
   no ellipsis between numbers that no longer exist. */
.storefront-page-range {
  padding: 0 12px;
  color: var(--doc-ink-soft);
  font-variant-numeric: tabular-nums;
  align-self: center;
}

/* ---- The fallback both dynamic regions ship (all three pages) -------------- */

/* Plain markup that the module replaces — NOT `<noscript>`, which covers only
   "scripting is off" and misses the far more common "the module never
   arrived" (L-FE-10). Styled as content rather than as a warning: it is the
   page a visitor without JavaScript actually reads. */
.storefront-fallback {
  padding: 28px;
  border: 1px solid var(--doc-line);
  border-radius: var(--radius-lg);
  background: var(--doc-panel);
}

.storefront-fallback p {
  margin: 0 0 16px;
  max-width: 70ch;
  color: var(--sd-text-soft);
}

/* ---- The breadcrumb (detail, partner page) -------------------------------- */

.storefront-breadcrumb {
  padding-top: 24px;
  font-size: 13px;
}

.storefront-breadcrumb ol {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.storefront-breadcrumb li + li::before {
  content: "/";
  margin-right: 8px;
  color: var(--doc-ink-soft);
}

.storefront-breadcrumb a {
  color: var(--sd-secondary);
}

/* ---- The detail page ------------------------------------------------------ */

/* The page heading, and it is deliberately NOT inside `.storefront-detail-head`.
   It exists in every state — no JavaScript, loading, 404, error — and only its
   text is data, which is why it is not part of the block a module replaces.
   The two detail pages share it, so the rule is written once. */
.storefront-page-heading {
  margin: 0 0 20px;
  font-size: clamp(28px, 5vw, 40px);
  line-height: 1.15;
}

.storefront-detail-head {
  display: flex;
  align-items: flex-start;
  gap: 20px;
  flex-wrap: wrap;
}

.storefront-detail-icon {
  width: 96px;
  height: 96px;
  flex: 0 0 auto;
  border-radius: var(--radius-lg);
  object-fit: contain;
  /* The recess, as on the catalogue card (DESIGN_TOKENS.md §9.1). */
  background: var(--sd-sunk);
}

.storefront-detail-title {
  flex: 1 1 320px;
  min-width: 0;
}

.storefront-detail-summary {
  margin-top: 12px;
}

.storefront-section {
  margin-top: 48px;
}

.storefront-section-heading {
  margin: 0 0 16px;
  font-size: 22px;
}

/* The long description is plain text, never markup — the API says so and this
   is what renders it. `pre-wrap` keeps the paragraph breaks the partner typed
   without anything parsing them, which is the whole point: a stored script tag
   is a text node here. */
.storefront-description {
  margin: 0;
  max-width: 75ch;
  white-space: pre-wrap;
  color: var(--sd-text-soft);
}

/* ---- The gallery (detail, partner page) ----------------------------------- */

/* A row of figures that scrolls itself. Not a carousel: a carousel needs
   JavaScript to be operable and hides most of its content from anybody reading
   the page linearly.

   `.scroll-x` is in `base.css` and provides the overflow; nothing here sets
   `overflow`, because two classes setting it on one element are decided by
   file order rather than by intent — which is the bug that cost U-702 a clipped
   comparison table (L-FE-18). */
.storefront-gallery {
  display: flex;
  gap: 16px;
  margin-top: 32px;
  padding-bottom: 12px;
}

.storefront-gallery:focus-visible {
  outline: 2px solid var(--sd-secondary);
  outline-offset: 4px;
}

.storefront-figure {
  margin: 0;
  flex: 0 0 auto;
}

.storefront-shot {
  height: auto;
  max-height: 340px;
  width: auto;
  max-width: 90vw;
  border: 1px solid var(--doc-line);
  border-radius: var(--radius);
  /* A screenshot with transparency sits in the recess, not on a tile that is
     lighter than the page in dark mode (L-FE-44). */
  background: var(--sd-sunk);
}

/* ---- Versions (detail) ---------------------------------------------------- */

/* A VERSION HISTORY IS ONE THING WITH ROWS, NOT A STACK OF THINGS
   (DESIGN_TOKENS.md §9.2). It used to be a column of separately framed boxes
   with a 20 px gap: eight versions drew sixteen horizontal hairlines where the
   list needs seven, and the outer edge of each box competed with the frame of
   the section around it.

   The technique is the reference's: the container carries the line colour and
   the rows sit on it with `gap: 1px`, so every separator is exactly one pixel,
   no edge is drawn twice, and the whole history reads as one panel. `overflow:
   hidden` is what lets the rows square off against a rounded container. */
.storefront-versions {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin-top: 20px;
  background: var(--doc-line);
  border: 1px solid var(--doc-line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.storefront-version {
  padding: 16px 20px;
  background: var(--doc-panel);
}

.storefront-version-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 12px;
}

/* `2.10.0` under `2.9.0` only lines up with tabular digits, and a version list
   exists to be compared down the column (DESIGN_TOKENS.md §9.5). It carries a
   figure and is not a numeric column, so it is tabular and stays left. */
.storefront-version-number {
  margin: 0;
  font-size: 16px;
  font-variant-numeric: tabular-nums;
}

/* The release date sits in the same head as the version and is the second
   thing compared down the list. */
.storefront-version-date {
  margin: 0;
  font-size: 13px;
  color: var(--doc-ink-soft);
  font-variant-numeric: tabular-nums;
}

.storefront-version-notes {
  margin: 12px 0 0;
  white-space: pre-wrap;
  color: var(--sd-text-soft);
}

/* ---- Links (detail, partner page) ----------------------------------------- */

.storefront-links {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 24px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.storefront-link {
  color: var(--sd-secondary);
}

/* An address `safeHttpUrl` refused: rendered as text a person can read, and
   never as a link this page will follow for them. Dotted underline so it is
   visibly not a link rather than a link that quietly does nothing. */
.storefront-link-unsafe {
  color: var(--doc-ink-soft);
  text-decoration: underline dotted;
  cursor: help;
}

/* ---- Reviews (detail) ----------------------------------------------------- */

/* Reviews are the same shape as the version history and get the same frame:
   one panel, hairline-separated rows (DESIGN_TOKENS.md §9.2). */
.storefront-reviews {
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: var(--doc-line);
  border: 1px solid var(--doc-line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.storefront-review {
  padding: 20px;
  background: var(--doc-panel);
}

.storefront-review-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 12px;
}

.storefront-review-date {
  font-size: 13px;
  color: var(--doc-ink-soft);
  font-variant-numeric: tabular-nums;
}

.storefront-review-title {
  margin: 12px 0 0;
  font-size: 16px;
}

.storefront-review-body {
  margin: 8px 0 0;
  white-space: pre-wrap;
  color: var(--sd-text-soft);
}

/* The partner's approved answer, set in from the review it answers so the two
   voices are visibly two. */
/* The partner's answer, set in from the review it answers so the two voices
   are visibly two.

   A HAIRLINE AND A RECESS, not a 3 px rule. Depth comes from the grounds
   (DESIGN_TOKENS.md §9.1/§9.2): the reply steps back onto --sd-sunk and is
   marked by the same 1 px line as everything else, where the thick rule was
   the heaviest stroke on the page for the quietest content on it. */
.storefront-review-reply {
  margin-top: 16px;
  padding: 12px 16px;
  border-left: 1px solid var(--doc-line);
  background: var(--sd-sunk);
  border-radius: 0 var(--radius) var(--radius) 0;
}

.storefront-review-reply-who {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--doc-ink-soft);
}

.storefront-review-reply-body {
  margin: 8px 0 0;
  white-space: pre-wrap;
  color: var(--sd-text-soft);
}

.storefront-back {
  margin-top: 48px;
}

/* ---- The partner page ----------------------------------------------------- */

.storefront-partner-banner {
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  margin-bottom: 28px;
  object-fit: cover;
}

.storefront-partner-head {
  display: flex;
  align-items: flex-start;
  gap: 20px;
  flex-wrap: wrap;
}

.storefront-partner-logo {
  width: 96px;
  height: 96px;
  flex: 0 0 auto;
  border-radius: var(--radius);
  object-fit: contain;
  /* The recess — the last of the four image wells (DESIGN_TOKENS.md §9.1). */
  background: var(--sd-sunk);
}

.storefront-partner-title {
  flex: 1 1 320px;
  min-width: 0;
}

.storefront-partner-headline {
  margin-top: 12px;
}

.storefront-partner-where {
  margin: 12px 0 0;
  color: var(--doc-ink-soft);
  font-size: 14px;
}

/* ---- Narrow viewports ----------------------------------------------------- */

/* At 320 px the icon beside a heading costs more width than it earns, and the
   card's own minimum already forces one column. */
@media (max-width: 480px) {
  .storefront-detail-icon,
  .storefront-partner-logo {
    width: 64px;
    height: 64px;
  }

  .storefront-filters {
    padding: 16px;
  }
}
