/* File: app/static/css/core.css
 * File Version: 44
 * History:
 *   V1 20260804  Initial - minimal core stylesheet for the fallback template layer
 *   V2 20260810  The unthemed coming-soon page (17c)
 *   V3 20260812  The page fills the viewport, so the footer sits at the bottom
 *   V4 20260814  The gallery block's structural grid
 *   V5 20260821  The page builder canvas
 *   V6 20260825  The cell-edge resize grip
 *   V7 20260825  The live preview panel
 *   V8 20260910  The offline-payment receipt block (F-153)
 *   V9 20260914  The overlays core JS builds are styled here, not in one theme (F-176)
 *   V10 20260914  The guest's conversation page (F-186)
 *   V11 20260915  The first-run page, which no theme dresses (ruling 56)
 *   V12 20260917  Links follow the theme's brand token; the green was a pre-token literal (ruling 61)
 *   V13 20260917  craftyfarm renamed craftysales - the theme takes the platform's name (ruling 62)
 *   V14 20260917  The order page's parcels and the events page's calendar and rail (ruling 63);
 *                 the receipt block's border read a variable nothing defines
 *   V15 20260918  The journal's chips, photographs and product rails; the legal contact line (ruling 65)
 *   V16 20260927  The search results' list of pages and posts (F-220)
 *   V17 20260927  --space-3, which admin.css has used since V0.5.37 and nothing
 *                 defined, and --space-1 beside it (F-237)
 *   V18 20260927  The product type's panel on an unthemed page: a two-column list (R4)
 *   V19 20260927  Records and documents on an unthemed page, and the lookup page (R5)
 *   V20 20260927  The spec list's rules name its element: the admin's spec section shares
 *                 the class and was being laid out as a grid (F-240)
 *   V21 20260927  Merchandising on an unthemed page: the struck price, the unit price (R6)
 *   V22 20260927  Booking a visit and a shopper's own visit, on an unthemed page (R7)
 *   V23 20260927  The hero, and R8's promises, what's on and journal blocks, on an
 *                 unthemed page; a theme that draws its own leaves these unused (R8)
 *   V24 20260928  A share's monthly offer and the page that takes it, unthemed (R10)
 *   V25 20260929  A plugin's data in core's card, in any theme that draws it no way
 *                 of its own (F-283)
 *   V26 20260930  The shell's menus, search, links and policies, laid out (F-310); a
 *                 picture never wider than its column (F-311)
 *   V27 20260930  Sold by weight: the estimate's note on a page, a card, a basket line, the
 *                 checkout and an order line (docs/22 Later 2)
 *   V28 20260930  A collection time's select keeps to the phone's width; the page's main
 *                 column takes its width from the page, not its content (docs/22 Later 3)
 *   V29 20260930  The Find us page's places on an unthemed page (docs/22 Later 3)
 *   V30 20261001  The shop's transfer details keep their lines on the order page (F-340)
 *   V31 20261001  "Write a review": its box keeps to a phone's width (F-371)
 *   V32 20261003  Products side by side: the picker and the table (docs/22 Later 8)
 *   V33 20261003  The lookup page's registration form (docs/22 Later 9)
 *   V34 20261004  A link wears the brand as words, where a theme names one; a field's
 *                 hint is the muted ink and not the browser's gray (F-445)
 *   V35 20261004  The page builder's resize grip stays hidden until scripting marks the canvas: its
 *                 rule lost to the admin's rule for every button (F-458)
 *   V36 20261005  What is marked hidden is not drawn, whatever gives it a display (F-475)
 *   V37 20261006  The launch page's picture, and the shop's name on it: the page wears the theme now,
 *                 and this is what it has with none (F-479)
 *   V38 20261006  The first-run page's boxes have an edge and its button is drawn (F-480)
 *   V39 20261006  The error pages name the shop, and the page of a shop that is down is drawn (F-481)
 *   V40 20261006  The page builder in a narrow window: its bar wraps, its columns stack (F-494)
 *   V41 20261007  The page builder's grip and its empty cell's words are in an ink that reads (F-500)
 *   V42 20261009  The row of languages at the foot of the page (docs/22 Later 12)
 *   V43 20261014  The currencies row at the foot of the page, styled as the languages' (docs/22 Later 11)
 *   V44 20261030  V0.6.230: American English, the owner's word of 2026-10-30
 *
 * The CORE layer's stylesheet. It exists so an unthemed install and an error
 * page are legible, and for no other reason.
 *
 * Deliberately tiny and deliberately opinion-free. Design tokens
 * (color.surface, space.4, radius.md, font.body) compiled to CSS custom
 * properties arrive with the theming layer in Phase 2; anything decorative
 * written here now is something CraftySales would have to override later.
 *
 * The custom properties below are placeholders with the names the token
 * compiler will emit, so core markup already reads from the right variables
 * when Phase 2 starts setting them.
 */

:root {
  --color-surface: #ffffff;
  --color-text: #1a1a18;
  --color-muted: #5c5c56;
  /* The pre-token shell color, kept as the fallback for a page with no
     tokens sheet. On a themed page the theme's brand wins: the first child
     theme (Orchard, terracotta) showed every link in this green, because
     `a { color }` below read a literal nothing in a theme could reach. */
  --color-accent: var(--cs-color-brand, #2f6b3f);
  /* The same color as words: a link, a pass mark. A theme whose brand fills
     a button well but is too light to read as small text names a deeper one
     in `color.brand-text`, and what the brand fills keeps the brand (F-445). */
  --color-accent-text: var(--cs-color-brand-text, var(--color-accent));
  /* F-237: admin.css has written --space-3 since V0.5.37 -- the media grid's
     gap, a dozen paddings -- and nothing defined it, so every one computed
     to zero. The scale is n x 0.25rem; --space-1 and --space-3 are the steps
     between the ones that were declared. */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --measure: 38rem;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* What is marked `hidden` is not drawn (F-475).
 *
 * The browser's own rule for the attribute is `display: none`, and it is the
 * weakest rule there is: anything a stylesheet says about an element's
 * `display` outranks it. `.admin button { display: inline-flex }` did. So the
 * menu builder's "Save the new order", hidden until a row is dragged, was on
 * the page from the start -- pressed there, it answered that the arrangement
 * could not be confirmed -- and the layouts screen's "Show" stayed beside a
 * select that had already taken over from it.
 *
 * `!important`, because this has to win against every rule that will be
 * written after it, in this sheet, the admin's and a theme's. Nothing else
 * says `display` that way but the editor's own separator image, which is
 * never hidden. */
[hidden] {
  display: none !important;
}

/* A column the height of the viewport, so the footer is at the bottom of the
 * window rather than wherever the content happened to stop. Any page shorter
 * than the screen -- a policy of two sentences, an article about a goose -- had
 * its footer floating in the middle of the window. */
body {
  margin: 0;
  padding: var(--space-6);
  background: var(--color-surface);
  color: var(--color-text);
  font-family: var(--font-body);
  line-height: 1.5;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.skip-link {
  position: absolute;
  left: -9999px;
}

.skip-link:focus {
  position: static;
  display: inline-block;
  margin-bottom: var(--space-4);
}

.site-header,
.site-main,
.site-footer {
  max-width: var(--measure);
  margin-inline: auto;
}

/* Takes the slack, which is what pushes the footer down. `0 auto` rather than
 * `1` so a long page is not compressed to fit.
 *
 * **As wide as the page allows, not as wide as its content** (docs/22 Later 3).
 * A flex item with auto side margins shrinks to fit what is in it, so a select
 * as wide as its longest option -- a collection time and when its orders close
 * -- pushed a phone's page to 556 px: a percentage limit on the select counts
 * for nothing while its own minimum is being worked out. */
.site-main {
  flex: 1 0 auto;
  width: 100%;
}

.site-header {
  padding-block-end: var(--space-4);
}

.site-name {
  margin: 0;
  font-weight: 600;
}

/* F-310. The shell core draws for a theme that draws none: each list a
 * wrapping row, so a long menu breaks onto the next line rather than past the
 * edge of a phone. */
.site-nav ul,
.site-footer-group ul,
.site-languages ul,
.site-currencies ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-nav,
.site-search,
.site-footer-group,
.site-languages,
.site-currencies {
  margin-block: var(--space-2);
}

/* The languages a shop speaks, at the foot of the page: the one the page is
 * in is words, set apart from the links to the others. */
.site-language--current,
.site-currency--current {
  font-weight: 600;
}

/* docs/22 Later 11. What a shopper who picked another currency is told once:
 * the figures beside the prices are estimates. */
.site-currency-note {
  margin: var(--space-1) 0 0;
}

.site-search input {
  max-width: 100%;
}

.site-actions,
.site-legal {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin: 0;
}

/* The basket's count, beside its word. */
.site-count {
  font-weight: 600;
}

.site-footer {
  margin-block-start: var(--space-8);
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--color-muted);
  color: var(--color-muted);
  font-size: 0.875rem;
}

a {
  color: var(--color-accent-text);
}

/* What a field says while it is empty (F-449). Left alone it is the browser's
 * own: a gray in Chrome that reads on white and on nothing darker -- 4.1:1 on
 * the shipped theme's cream, where the gallery's search box sits -- and in
 * Firefox the field's ink faded to about half, so one hint was two colors.
 * The muted ink, whole, wherever a field is drawn: the theme's on a storefront,
 * core's own in the admin and on a page no theme dresses. */
::placeholder {
  color: var(--cs-color-ink-muted, var(--color-muted));
  opacity: 1;
}

/* F-311. A picture is never wider than the column it sits in. Without it a
 * product photograph pushed core's product page 900 px wide on a 375 px phone,
 * and the shop grid and the events page with it -- found the first time a
 * core page the shipped theme overrides was photographed (F-309).
 * `:where()` gives it no specificity at all, so every theme's own rule for
 * its own pictures wins; this is only the default core's pages lacked. */
:where(img, video) {
  max-width: 100%;
  height: auto;
}

.vacation-notice {
  margin-block: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--color-accent);
}

/* --- Error pages ------------------------------------------------------- */

.error-main {
  max-width: var(--measure);
  margin-inline: auto;
  padding-block-start: var(--space-8);
  /* Off the edges of a phone, where a headline in a theme's face ran from
   * one to the other (F-481). */
  padding-inline: var(--space-4);
}

.error-status {
  margin: 0;
  color: var(--color-muted);
  font-size: 0.875rem;
  letter-spacing: 0.08em;
}

.error-headline {
  margin-block: var(--space-2) var(--space-4);
  font-size: 1.5rem;
}

.error-detail,
.error-actions {
  color: var(--color-muted);
}

/* Whose shop this is (F-481). */
.error-shop {
  margin: 0 0 var(--space-6);
  font-weight: 600;
}

/* The page of a shop that is down: its banner, its own words, its clock and
 * where else the shop is. None had a rule here, and this was the only sheet
 * the page linked (F-481). */
.error-banner {
  margin-block: 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-muted);
  border-radius: 4px;
}

.error-message {
  margin-block-end: var(--space-4);
}

.error-countdown {
  font-variant-numeric: tabular-nums;
}

.error-channels {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
}

/* --- First-run setup (unthemed) -----------------------------------------
 *
 * Rendered before any tenant exists, so no theme sheet is loaded: this is
 * the whole of the page's styling. The honeypot rule is here for the same
 * reason -- a honeypot that is visible on the one page a scanner is most
 * interested in is a text box a person fills in.
 *
 * `[data-plain]` is the launch page drawn with no shop to read: the same
 * case, and until V0.6.134 its box and button were the browser's own there
 * (F-481). The mark is on the stand-alone shell's `html`, so none of this
 * reaches a page a theme draws. */

.setup-main {
  max-width: 40rem;
}

.setup-form fieldset {
  border: 1px solid var(--color-line, #ddd);
  margin-block: var(--space-4);
  padding: var(--space-4);
}

.setup-form .field {
  display: grid;
  gap: var(--space-2);
  margin-block: 0 var(--space-4);
}

/* The page's lettering was all these had: a box with the page's face and
 * the browser's own inset edge (F-480). An edge and a ground of the page's. */
.setup-form .field input,
.setup-form .field select,
[data-plain] .coming-soon__field input {
  font: inherit;
  padding: var(--space-2);
  max-width: 100%;
  color: var(--color-text);
  background: var(--color-surface);
  border: 1px solid var(--color-muted);
  border-radius: 4px;
}

/* The page's one action, and the first button a buyer of the software ever
 * presses. It was the browser's own: 13 pixel Arial, 111 by 21, under boxes
 * 602 wide (F-480). */
.setup-form button,
[data-plain] .coming-soon__submit {
  font: inherit;
  font-weight: 600;
  padding: var(--space-3) var(--space-6);
  color: var(--color-surface);
  background: var(--color-accent);
  border: 1px solid var(--color-accent);
  border-radius: 4px;
  cursor: pointer;
}

.setup-page .field-hint {
  color: var(--color-muted);
  font-size: 0.875rem;
}

.setup-page .field-error,
[data-plain] .coming-soon__error {
  color: var(--color-accent-text);
  font-size: 0.9375rem;
}

.setup-page .hp-field {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* --- The launch page, before a theme draws it -----------------------------
 *
 * What a shop that has not opened shows the public. The page wears the theme:
 * it extends the shell, so the theme's sheet is linked after this one (F-479;
 * until V0.6.130 it linked this file and no other, and these rules were all it
 * had under every theme). What is here is the page with no theme at all:
 * legible, opinion-free, and nothing a theme would have to undo.
 */

/* The picture the shop chose, above the words, and never so tall that the
 * words leave the first screen. A theme may lay it behind them. */
.coming-soon__picture img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 50vh;
  object-fit: cover;
}

.coming-soon__shop {
  margin: var(--space-4) 0 var(--space-2);
  font-weight: 600;
}

.coming-soon__logo,
.coming-soon__mark {
  max-height: 2.75rem;
  width: auto;
  vertical-align: middle;
}

.coming-soon__main {
  max-width: var(--measure);
  margin-inline: auto;
  padding-block-start: var(--space-8);
}

.coming-soon__eyebrow {
  margin: 0;
  color: var(--color-muted);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.coming-soon__title {
  margin-block: var(--space-2) var(--space-4);
  font-size: 1.75rem;
}

.coming-soon__countdown {
  display: flex;
  gap: var(--space-4);
  margin-block-end: var(--space-4);
}

.coming-soon__unit {
  display: flex;
  flex-direction: column;
}

/* Tabular figures so the seconds column does not shift the row each tick. */
.coming-soon__unit > [data-countdown-part] {
  font-size: 1.5rem;
  font-variant-numeric: tabular-nums;
}

.coming-soon__label {
  color: var(--color-muted);
  font-size: 0.75rem;
  text-transform: uppercase;
}

.coming-soon__form {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  align-items: center;
}

.coming-soon__note,
.coming-soon__done {
  color: var(--color-muted);
}

/* The honeypot: clipped, never `display: none`. A bot that reads the computed
 * style skips anything display-hidden, which is the one thing this field must
 * not advertise. */
.coming-soon__hp {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- Conversations (design 19b) -----------------------------------------
 *
 * The customer's side of a support thread. Core layer, so an unthemed install
 * still reads as a conversation rather than as one column of paragraphs.
 */

.thread { list-style: none; margin: 0 0 var(--space-6); padding: 0; display: grid; gap: var(--space-4); }
.thread__item { padding: var(--space-4); border: 1px solid var(--color-muted); border-radius: 6px; max-width: 44rem; }
.thread__item.is-staff { margin-inline-start: auto; border-inline-start: 3px solid var(--color-accent); }
.thread__who { margin: 0 0 var(--space-2); font-size: 0.875rem; font-weight: 600; }
.thread__who .muted { font-weight: 400; color: var(--color-muted); }
/* Whitespace preserved: somebody describing what arrived broken writes in
 * paragraphs, and flattening them loses the sense. */
.thread__body { margin: 0; white-space: pre-wrap; }

/* The guest's conversation page (`storefront/message_thread.html`, F-186).
 * The customer's own messages sit on the near side; the shop's on the far
 * side with the accent rule -- the same two rules the account page uses. */
.message-thread { max-width: 44rem; }
.thread__item.is-customer { margin-inline-end: auto; }
.reply-form { max-width: 44rem; margin-block-end: var(--space-6); }
.reply-form textarea { width: 100%; max-width: 100%; }

/* "Write a review" (`account/reviews.html`). Its box is sixty columns -- about
 * 460px -- and a theme sizes the controls it wraps in `.field`, which this
 * form does not use: on a phone the box ran off the screen (F-371). */
.review-form textarea { width: 100%; max-width: 100%; box-sizing: border-box; }

/* --- Pictures (docs/05_block_system.md) ----------------------------------
 *
 * The core layer's gallery. Structure, not design: an even grid that wraps,
 * with each photograph's own description under it. A theme with a stylesheet
 * overrides both this and the template -- BeeGreen draws three skins and six
 * arrangements over the same block.
 */

.gallery-grid {
  list-style: none;
  margin: 0 0 var(--space-6);
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
  gap: var(--space-4);
}

.gallery-item figure { margin: 0; }

.gallery-item img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 4px;
}

.gallery-caption {
  margin-block-start: var(--space-2);
  font-size: 0.875rem;
  color: var(--color-muted);
}

.block-blurb { color: var(--color-muted); }

/* --- The overlays core JS builds ------------------------------------------
 *
 * `static/js/gallery.js` and `static/js/quick-view.js` *create* this markup --
 * the lightbox and the quick-view dialog are not in any template a theme can
 * override. docs/01_architecture.md section 5.6 says core JS must not ship
 * one theme's DOM, and until V0.6.0 it did in the other direction: the
 * classes it emitted were styled only in BeeGreen's stylesheet, so on the
 * theme that actually ships a tap on a gallery photograph produced an
 * unstyled full-screen div with a raw image and three unstyled buttons
 * (F-176). The rule that follows: markup core JS emits, core CSS styles.
 *
 * Structure and legibility only, on the guaranteed `--cs-*` tokens -- the
 * compiled defaults every install serves from /assets/tokens.css. A theme
 * overrides freely; BeeGreen already does.
 */

/* `static/js/dropzone.js` marks a zone while a file is over it. The buyer's
 * photograph upload uses the same script as the admin, and only the admin
 * stylesheet knew the class -- so on the storefront a drag produced no
 * feedback at all. */
.dropzone.is-over {
  border-style: solid;
  border-color: var(--cs-color-brand);
}

/* A figure the script has made operable: a pointer and a visible focus ring,
 * so a keyboard user can tell which photograph Enter will open. */
.is-openable {
  cursor: pointer;
}
.is-openable:focus-visible {
  outline: 2px solid var(--cs-color-brand);
  outline-offset: 2px;
}

.cs-gallery__controls {
  display: flex;
  justify-content: flex-end;
  gap: var(--cs-space-2);
  margin-block-start: var(--cs-space-2);
}

.cs-gallery__arrow {
  border: 1px solid var(--cs-color-border);
  border-radius: var(--cs-radius-sm);
  background: var(--cs-color-surface);
  color: var(--cs-color-ink);
  font: inherit;
  line-height: 1;
  padding: var(--cs-space-2) var(--cs-space-3);
  cursor: pointer;
}
.cs-gallery__arrow:focus-visible {
  outline: 2px solid var(--cs-color-brand);
  outline-offset: 2px;
}

/* The product type's panel on a page no theme draws (R4): its lines as a
 * list of label and answer, two columns where there is room. The shipped
 * theme draws its own table, bars and year strip from the same view. */
.product-details { margin-top: var(--space-6); max-width: var(--measure); }
.product-details-blurb { color: var(--color-muted); }
/* F-240. `dl.`, never the bare class: the admin's product editor has a
 * <section class="product-specs"> too, and V0.6.23 laid it out as this
 * two-column grid -- its heading in one column, its whole form in the other. */
dl.product-specs {
  display: grid;
  grid-template-columns: minmax(8rem, 1fr) 2fr;
  gap: var(--space-1) var(--space-4);
  margin: 0;
}
dl.product-specs dt { font-weight: 600; }
dl.product-specs dd { margin: 0; }
.product-spec-pass { color: var(--color-accent-text); font-weight: 600; }

/* R5. The record a product page shows, the documents it offers, a line's
 * record on the order page, and the lookup page -- legible, no more. */
.product-record-code { font-family: ui-monospace, monospace; }
.product-record-note { color: var(--color-muted); }
.product-documents { padding-left: var(--space-4); }
.product-document-kind { margin-left: var(--space-2); color: var(--color-muted); }
.order-line-record { flex-basis: 100%; margin-top: var(--space-2); }
/* F-340. The shop's transfer details, kept as typed: an account number on its own line. */
.order-transfer-details { white-space: pre-line; overflow-wrap: anywhere; }
.lookup-form { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
/* R10: a share paid monthly -- the line under the button, and the page it opens. */
.product-monthly { margin-top: var(--space-3); color: var(--color-muted); }
.share-monthly-form { display: grid; gap: var(--space-2); max-width: 32rem; }
.lookup-result { margin-top: var(--space-6); }
.lookup-not-found { color: var(--color-muted); }
/* docs/22 Later 9: a unit's warranty registered on the page that found it. */
.lookup-register { display: grid; gap: var(--space-2); max-width: 32rem; margin-top: var(--space-6); }

/* `static/js/product-gallery.js` swaps a poster for the player itself. */
.cs-shots__player {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

.cs-lightbox-open {
  overflow: hidden;
}

.cs-lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--cs-space-3);
  padding: var(--cs-space-5);
  background: var(--cs-color-surface-inverse);
}
.cs-lightbox[hidden] {
  display: none;
}

.cs-lightbox__frame {
  order: -1;
  flex-basis: 100%;
  margin: 0;
  max-width: min(90vw, 60rem);
  max-height: 86vh;
  display: flex;
  flex-direction: column;
  gap: var(--cs-space-2);
  margin-inline: auto;
}

.cs-lightbox__image {
  display: block;
  max-width: 100%;
  max-height: 76vh;
  object-fit: contain;
  margin-inline: auto;
}

.cs-lightbox__caption {
  color: var(--cs-color-ink-inverse-muted);
  font-size: var(--cs-size-text-sm);
  text-align: center;
}

.cs-lightbox__close,
.cs-lightbox__step {
  border: 1px solid var(--cs-color-border-inverse);
  border-radius: var(--cs-radius-sm);
  background: transparent;
  color: var(--cs-color-ink-inverse);
  font: inherit;
  font-size: var(--cs-size-text-md);
  line-height: 1;
  padding: var(--cs-space-2) var(--cs-space-3);
  cursor: pointer;
}
.cs-lightbox__close:focus-visible,
.cs-lightbox__step:focus-visible {
  outline: 2px solid var(--cs-color-ink-inverse);
  outline-offset: 2px;
}

.cs-lightbox__close {
  position: absolute;
  top: var(--cs-space-4);
  right: var(--cs-space-4);
}

.cs-quick-dialog {
  width: min(56rem, calc(100vw - var(--cs-space-6)));
  max-height: min(88vh, 46rem);
  padding: 0;
  border: 1px solid var(--cs-color-border);
  border-radius: var(--cs-radius-md);
  background: var(--cs-color-surface);
  color: var(--cs-color-ink);
  box-shadow: var(--cs-shadow-lg);
  overflow: hidden;
}

.cs-quick-dialog::backdrop {
  background: var(--cs-color-surface-inverse);
  opacity: 0.6;
}

.cs-quick-dialog__close {
  position: absolute;
  top: var(--cs-space-2);
  right: var(--cs-space-2);
  z-index: 2;
  width: var(--cs-space-6);
  height: var(--cs-space-6);
  padding: 0;
  border: 1px solid var(--cs-color-border);
  border-radius: var(--cs-radius-circle);
  background: var(--cs-color-surface);
  color: var(--cs-color-ink-muted);
  font: inherit;
  line-height: 1;
  cursor: pointer;
}

.cs-quick-dialog__body {
  max-height: min(88vh, 46rem);
  overflow-y: auto;
}

/* ── the page builder canvas ──────────────────────────────────────────────
 *
 * Rows of cells, each cell a drop target. The admin's own stylesheet rather
 * than a theme's: the builder is the application, not the shop, and it has to
 * look the same whichever theme is being composed.
 */

.canvas {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin: 1rem 0;
}

.canvas-row {
  border: 1px solid #d5d9de;
  border-radius: 4px;
  background: #fff;
}

.canvas-row--muted { background: #f6f7f8; }
.canvas-row--inverse { background: #eceff2; }

.canvas-row__bar {
  display: flex;
  /* Its grip, its name, two arrows, three selects and Remove row are wider
   * than a phone on one line, and 11px wider than a tablet (F-494). */
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid #e4e7ea;
  font-size: 0.85rem;
}

/* The quiet ink, not a fainter one: a grip is what a row is taken hold of by, and
 * at 3.15:1 it was hard to find (F-500). */
.canvas-row__grip { cursor: grab; color: var(--color-muted); }
.canvas-row__name { font-weight: 700; }
.canvas-row__control { display: inline-flex; align-items: center; gap: 0.25rem; }
.canvas-row__drop { margin-left: auto; }

.canvas-row__cells {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 0.75rem;
  padding: 0.75rem;
  align-items: start;
}

.canvas-cell {
  grid-column: span 12;
  min-width: 0;
  border: 1px dashed #c8ced5;
  border-radius: 4px;
  padding: 0.5rem;
  background: #fcfdfd;
  transition: border-color 120ms ease, background 120ms ease;
}

.canvas-cell--1 { grid-column: span 1; }
.canvas-cell--2 { grid-column: span 2; }
.canvas-cell--3 { grid-column: span 3; }
.canvas-cell--4 { grid-column: span 4; }
.canvas-cell--5 { grid-column: span 5; }
.canvas-cell--6 { grid-column: span 6; }
.canvas-cell--7 { grid-column: span 7; }
.canvas-cell--8 { grid-column: span 8; }
.canvas-cell--9 { grid-column: span 9; }
.canvas-cell--10 { grid-column: span 10; }
.canvas-cell--11 { grid-column: span 11; }
.canvas-cell--12 { grid-column: span 12; }

/* The cue the drop is aimed at this one. Solid and tinted rather than a
 * one-pixel color change: the pointer is carrying a block and the answer to
 * "where will this land" has to be readable at a glance. */
.canvas-cell--over {
  border-style: solid;
  border-color: #2f6f4f;
  background: #eef6f1;
}

.canvas-cell__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
  font-size: 0.75rem;
  color: #6b737c;
}

.canvas-cell__empty {
  margin: 0;
  padding: 1rem 0.5rem;
  text-align: center;
  font-size: 0.8rem;
  /* Words, so 4.5:1 at least: these were 3.1:1 (F-500). */
  color: var(--color-muted);
}

.canvas-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

[data-cs-dragging="true"] {
  opacity: 0.45;
}

.canvas-row__moves {
  display: inline-flex;
  gap: 0.15rem;
}

.canvas-row__moves button {
  padding: 0.1rem 0.4rem;
  line-height: 1;
}

/* ── the cell-edge resize grip ────────────────────────────────────────────
 *
 * Sits in the grid gap on a cell's right edge, so it costs no column. Making
 * it a grid item would have meant the row's twelve columns became eleven plus
 * a handle, and every span in the document would then mean something slightly
 * different from what it says.
 *
 * **Hidden until the canvas is enhanced.** `layout-builder.js` stamps
 * `data-cs-enhanced` on the canvas when it initializes, so with scripting off,
 * blocked, or still loading, the grip is not there at all. A visible control
 * that does nothing is worse than an absent one -- and the split select above
 * still changes the columns, and posts on its own.
 */

.canvas-cell {
  position: relative;
}

/* `.canvas-cell` as well, so this outweighs the admin's rule for every button
 * (`.admin button`, which sets `display`). Named alone it lost, and with
 * scripting off each cell showed an empty 30px button that did nothing --
 * the very thing the paragraph above says must not be there (F-458). */
.canvas-cell .canvas-cell__resize {
  display: none;
}

[data-cs-canvas][data-cs-enhanced] .canvas-cell__resize {
  display: block;
  position: absolute;
  top: 0;
  bottom: 0;
  /* Half the row gap, so the grip straddles the boundary rather than sitting
   * inside one of the two cells it moves. */
  right: -0.375rem;
  width: 0.75rem;
  padding: 0;
  border: 0;
  background: none;
  cursor: col-resize;
  z-index: 2;
}

/* The visible line, thinner than the target. The grip is 12px wide because
 * that is roughly a pointer's worth of tolerance; it *looks* like 2px because
 * a 12px bar between every pair of cells would read as a third column. */
.canvas-cell__resize span {
  display: block;
  width: 2px;
  height: 100%;
  margin: 0 auto;
  border-radius: 1px;
  background: #c8ced5;
  transition: background 120ms ease;
}

.canvas-cell__resize:hover span,
.canvas-cell__resize:focus-visible span {
  background: #2f6f4f;
}

.canvas-cell__resize:focus-visible {
  outline: 2px solid #2f6f4f;
  outline-offset: 1px;
}

/* While a drag is in progress the pointer is captured by the grip, so the
 * cursor would otherwise revert to a text caret the moment it crossed a cell.
 * Set on the body because the pointer can be anywhere on the page by then. */
[data-cs-resizing] {
  cursor: col-resize;
  user-select: none;
}

[data-cs-resizing] .canvas-cell {
  /* The width transition is right for a click on the split select and wrong
   * during a drag, where it makes the cell lag a pointer it should be pinned
   * to. */
  transition: none;
}

/* ── the builder in a narrow window ───────────────────────────────────────
 *
 * F-494. Measured at 375: the page was 348px wider than its window. Two
 * columns stayed side by side, 98 to 153px each, around an editor that wants
 * three times that, and a field kept the 402px a wide screen gives it.
 */

/* A field is never wider than its column. The article editor's form is a
 * stacked form, which says so already; the builder's is not one. */
.canvas-cell input,
.canvas-cell select,
.canvas-cell textarea {
  max-width: 100%;
  box-sizing: border-box;
}

@media (max-width: 40rem) {
  /* Columns stack. What each is of twelve is still written on it, and is
   * still what the shop draws. */
  .canvas-row__cells > .canvas-cell { grid-column: span 12; }

  /* Nothing to drag between two columns one above the other; the Columns
   * select still changes them. */
  [data-cs-canvas][data-cs-enhanced] .canvas-cell__resize { display: none; }
}

/* ── the builder's live preview ───────────────────────────────────────────
 *
 * An iframe, not a fragment. A theme's stylesheet is written against a
 * storefront document -- it sets rules on `body` and expects its own cascade --
 * so splicing the preview into the admin's DOM would restyle the admin and
 * misrepresent the theme in one move.
 *
 * The frame carries `sandbox` with no value, which denies scripts, forms,
 * popups and same-origin access. Blocks may hold merchant-authored HTML, and a
 * shop with staff accounts is a shop where the person previewing is not
 * necessarily the person who wrote it.
 */

.preview {
  margin-top: 1.5rem;
}

.preview__heading {
  margin: 0 0 0.25rem;
  font-size: 1rem;
}

.preview__frame {
  display: block;
  width: 100%;
  /* Tall enough that a two-row page is visible without scrolling the frame,
   * and capped so the editor above it does not scroll off the screen. The
   * frame scrolls its own content, which is what an iframe does. */
  height: min(70vh, 40rem);
  margin-top: 0.75rem;
  border: 1px solid #c8ced5;
  border-radius: 4px;
  background: #ffffff;
}

/* Pushed to the right end of the action bar: Save is the primary act on this
 * screen and the preview is a check on it, so they should not sit adjacent and
 * look like a pair. */
.canvas-actions__preview {
  margin-left: auto;
}

/* **F-153. The receipt a buyer sends for an offline payment.**
 *
 * Here rather than in a theme because the block is drawn on a shop that has
 * installed nothing else: `tests/test_theme_covers_its_markup.py` refused this
 * release until it was, and it was right to -- an unstyled file input on the
 * page where somebody proves they paid reads as a broken shop.
 *
 * Deliberately quiet. This sits directly under the payment instructions and
 * competes with nothing; a bordered box is enough to say the two belong
 * together. */
.proof-upload {
  margin: 1rem 0;
  padding: 1rem;
  border: 1px solid var(--cs-color-border, #ddd);
  border-radius: 4px;
}

.proof-upload .field {
  margin: 0 0 0.5rem;
}

/* Receipts already sent, listed under the form so a buyer can see the shop has
 * them -- which is the whole reassurance the upload is for. */
.proof-list {
  margin: 0.75rem 0 0;
  padding-left: 1.25rem;
}

.proof-list li {
  margin-bottom: 0.25rem;
}

/* --- The customer's parcels (F-196, ruling 63) ----------------------------
 * Structural only: a list with the carrier on its own line and the tracking
 * number as text a phone can copy. The link is the merchant's own. */
.order-parcels {
  margin-block: var(--space-4);
}

.parcels {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-4);
}

.parcel {
  padding: var(--space-4);
  border: 1px solid var(--cs-color-border, #ddd);
  border-radius: 4px;
}

.parcel > p {
  margin: 0 0 var(--space-2);
}

.parcel > p:last-child {
  margin-bottom: 0;
}

.parcel-carrier {
  font-weight: 600;
}

.parcel-state,
.parcel-untracked {
  color: var(--color-muted);
  font-size: 0.875rem;
}

.parcel-tracking {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
}

.parcel-tracking code {
  letter-spacing: 0.06em;
}

/* --- What's on: the month and the rail (design 7a, ruling 63) --------------
 * The grid is a table so it reads as one with scripting off and in a screen
 * reader; seven equal columns, and the chips wrap inside a day rather than
 * widening it. Phone-safe: the table never exceeds the page, and a day cell
 * at 375px is a number and a chip or two. */
.calendar {
  margin-block: var(--space-4) var(--space-8);
}

.calendar-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  margin: 0 0 var(--space-2);
}

.calendar-month {
  font-size: 1.25rem;
}

.calendar-arrow {
  display: inline-block;
  min-width: 2rem;
  text-align: center;
  text-decoration: none;
  font-size: 1.25rem;
  line-height: 1;
  border: 1px solid var(--cs-color-border, #ddd);
  border-radius: 4px;
  padding: var(--space-2);
}

.calendar-today {
  font-size: 0.875rem;
}

.calendar-grid {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
  font-size: 0.875rem;
}

.calendar-grid th {
  padding: var(--space-2) 0;
  font-weight: 600;
  text-align: center;
  color: var(--color-muted);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.calendar-day {
  vertical-align: top;
  min-height: 4rem;
  height: 4rem;
  padding: var(--space-2);
  border: 1px solid var(--cs-color-border, #ddd);
  overflow-wrap: anywhere;
}

.calendar-day.is-outside {
  color: var(--color-muted);
}

.calendar-number {
  display: inline-block;
  min-width: 1.5rem;
  line-height: 1.5rem;
  text-align: center;
  border-radius: 999px;
}

.calendar-day.is-today .calendar-number {
  background: var(--color-accent);
  color: var(--color-surface);
}

.calendar-chip {
  display: block;
  margin-top: var(--space-2);
  font-size: 0.75rem;
  line-height: 1.3;
}

/* The time never breaks in the middle: at 375px a day cell is narrow, and
 * "22:5 / 5" is not a time. The title may wrap; the time goes to its own line. */
.calendar-chip-time {
  display: block;
  white-space: nowrap;
}

@media (min-width: 48rem) {
  .calendar-chip-time {
    display: inline;
  }
}

.calendar-chip.is-full,
.calendar-chip.is-canceled {
  color: var(--color-muted);
}

.calendar-chip.is-canceled {
  text-decoration: line-through;
}

.upcoming {
  margin-block-end: var(--space-8);
}

.upcoming-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--cs-color-border, #ddd);
}

.upcoming-row {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: var(--space-4);
  row-gap: var(--space-2);
  padding-block: var(--space-4);
  border-bottom: 1px solid var(--cs-color-border, #ddd);
}

.upcoming-date {
  grid-row: 1 / span 4;
  font-weight: 600;
  white-space: nowrap;
}

.upcoming-title {
  font-weight: 600;
}

.upcoming-when,
.upcoming-extras,
.upcoming-add,
.upcoming-none {
  color: var(--color-muted);
  font-size: 0.875rem;
}

/* --- The journal: filter, photographs, the rail (ruling 65) ---------------
 * Structural only. The chips are links in a row that wraps; a card's picture
 * sits above its title; the rail is a small list of product links with a
 * thumbnail beside each name. */
.journal-filter {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block: var(--space-4);
}

.journal-filter-link {
  display: inline-block;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--cs-color-border, #ddd);
  border-radius: 999px;
  text-decoration: none;
  font-size: 0.875rem;
}

.journal-filter-link.is-selected {
  border-color: var(--color-accent);
  background: var(--color-accent);
  color: var(--color-surface);
}

.post-card-picture {
  display: block;
  margin-block-end: var(--space-2);
}

.post-card-image,
.post-hero-image {
  display: block;
  width: 100%;
  height: auto;
}

.post-card-category,
.post-category {
  margin: 0;
  color: var(--color-muted);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.post-hero {
  margin: var(--space-4) 0;
}

.post-products {
  margin-block: var(--space-4);
}

.post-products-after {
  margin-block-start: var(--space-8);
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--cs-color-border, #ddd);
}

.post-products-label {
  margin: 0 0 var(--space-2);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-muted);
}

.post-products-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}

.post-product {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  text-decoration: none;
}

.post-product-thumb {
  flex: none;
  width: 3rem;
  height: 3rem;
  object-fit: cover;
  border-radius: 4px;
}

.post-product-name {
  flex: 1 1 auto;
  min-width: 0;
}

.post-product-price {
  flex: none;
  font-variant-numeric: tabular-nums;
}

/* Under the contents: only drawn when the shop has a contact page. */
.legal-contact {
  margin: var(--space-4) 0 0;
  font-size: 0.875rem;
  color: var(--color-muted);
}

/* ---- search results (F-220) ----
 * The pages and journal posts a search finds, as a plain list with what each
 * one is beside it. A theme dresses the rest. */
.search-links {
  list-style: none;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}

/* R6. The ordinary price struck out beside a sale, and a price per unit of
   weight -- quieter than the price they explain. */
.price-was { margin-left: var(--space-1); color: var(--color-muted); font-weight: 400; }
.price-unit { display: block; font-size: 0.875rem; color: var(--color-muted); }
.product-preorder { font-weight: 600; }
.product-breaks { font-size: 0.875rem; }
.product-size-guide { margin: var(--space-2) 0; }

/* docs/22 Later 2. Sold by weight: the note that the price is an estimate,
   quieter than the price it explains, wherever the price is. */
.price-weighed,
.cart-line-weighed,
.order-line-weighed { display: block; font-size: 0.875rem; color: var(--color-muted); }
.checkout-weighed { margin: var(--space-2) 0 0; font-size: 0.875rem; color: var(--color-muted); }

/* docs/22 Later 3. A collection time says when its orders close, and a select
   is as wide as its longest option: within the page's width -- see .site-main --
   the delivery fieldset gives up its own minimum and the select keeps to it. */
.checkout-delivery { min-inline-size: 0; }
.delivery-window select { max-width: 100%; }

/* R6. A pack offered on an unthemed product page, and the rails under it. */
.product-bundle { margin-top: var(--space-6); padding: var(--space-4); border: 1px solid var(--color-muted); }
.product-bundle-items { margin: 0 0 var(--space-3); padding-left: 1.1rem; }
.product-bundle-saving { display: block; font-size: 0.875rem; }
.product-rail { margin-top: var(--space-6); }
.product-buybar {
  position: sticky;
  bottom: 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-6);
  padding: var(--space-2) var(--space-4);
  border-top: 1px solid var(--color-muted);
  background: var(--color-surface);
}

/* R6. A featured product block, on an unthemed page. */
.block-featured { margin: var(--space-6) 0; }
.block-featured-link { display: grid; gap: var(--space-2); max-width: var(--measure); }
.block-featured-title { font-weight: 600; }
.block-featured-blurb { max-width: var(--measure); }
.block-featured-price { font-weight: 600; }

/* R6. A listing's sort and filters, on an unthemed page. */
.browse-form { display: grid; gap: var(--space-3); margin: 0 0 var(--space-6); }
.browse-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); margin: 0; }
.browse-facet { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: 0; min-inline-size: 0; }
.browse-check { display: inline-flex; align-items: center; gap: var(--space-1); }
.browse-count { color: var(--color-muted); }

/* R6. How far off free delivery is, on an unthemed basket. */
.cart-free-delivery { margin: var(--space-3) 0; }
.cart-free-delivery progress { display: block; width: 100%; }

/* R7. Booking a visit, on an unthemed page: the choices as lists of links, the
   month as a table, the day's times in a row. A taken time is struck through. */
.booking-eyebrow { margin: 0; font-size: 0.875rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-muted); }
.booking-services ul, .booking-people, .booking-slots { list-style: none; margin: 0 0 var(--space-4); padding: 0; }
.booking-services li { margin: 0 0 var(--space-2); }
.booking-services a[aria-current], .booking-people a[aria-current], .booking-slots a[aria-current], .booking-calendar a[aria-current] { font-weight: 700; }
.booking-label { margin: var(--space-4) 0 var(--space-2); font-weight: 600; }
.booking-people, .booking-slots { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); }
.booking-month, .booking-day, .booking-where, .booking-summary { margin: var(--space-6) 0; }
.booking-month-nav { display: flex; gap: var(--space-4); }
.booking-calendar { border-collapse: collapse; }
.booking-calendar th, .booking-calendar td { width: 2.5rem; height: 2.25rem; text-align: center; }
.booking-calendar td { color: var(--color-muted); }
.booking-legend, .booking-none { color: var(--color-muted); }
.booking-zone { margin: var(--space-3) 0; }
.booking-inside { font-weight: 600; }
.booking-outside { font-weight: 600; color: var(--color-text); }
.booking-summary { padding: var(--space-4); border: 1px solid var(--color-muted); }
.booking-form { display: grid; gap: var(--space-2); max-width: var(--measure); }

/* R7. A shopper's own visit. */
.visit-status { font-weight: 600; }
.visit-details { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-2) var(--space-4); }
.visit-details dt { font-weight: 600; }
.visit-details dd { margin: 0; }
.visit-cancel { margin: var(--space-4) 0; }

/* R8. The hero on an unthemed page: the picture, then the words under it. The
   shipped theme draws its own over the photograph; these are core's markup. */
section.block-hero { display: grid; gap: var(--space-4); margin: var(--space-6) 0; }
.hero-image { display: block; width: 100%; height: auto; }
.hero-words { max-width: var(--measure); }
.hero-heading { margin: 0 0 var(--space-3); }
.hero-subheading { margin: 0 0 var(--space-4); color: var(--color-muted); }
.hero-cta { display: inline-block; font-weight: 600; }

/* R8. Promises, what's on and the journal on an unthemed page: plain lists. */
section.block-promises, section.block-events-band, section.block-journal { margin: var(--space-6) 0; }
.promise-list, .band-events { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
.promise { display: grid; gap: var(--space-1); }
.promise-title { font-weight: 600; }
.promise-text, .band-event-when, .band-event-note { color: var(--color-muted); }
.band-event { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3); }
.band-visit { margin-top: var(--space-4); padding: var(--space-4); border: 1px solid var(--color-muted); }
.band-more { margin: var(--space-3) 0 0; }

/* F-283. A plugin's data in core's card (app/templates/slots/_default.html), drawn
   in any theme -- the shipped one included -- that has no partial of its own for it. */
.slot-card { display: grid; gap: var(--space-2); margin: var(--space-4) 0; }
.slot-card__title { margin: 0; font-weight: 600; }
.slot-card__text { margin: 0; color: var(--color-muted); }
.slot-card__items { margin: 0; padding-left: var(--space-4); }
.slot-card__link { justify-self: start; font-weight: 600; }

/* docs/22 Later 3. The Find us page's places on an unthemed page: a block each,
   its facts a two-column list, as a visit's are. */
.find-us-places { margin: var(--space-6) 0; }
.find-us-place { margin: var(--space-4) 0; }
.find-us-place h3 { margin: 0 0 var(--space-2); }
.find-us-place address { font-style: normal; }
.find-us-place-instructions { white-space: pre-line; overflow-wrap: anywhere; color: var(--color-muted); }
.find-us-place-facts { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--space-2) var(--space-4); }
.find-us-place-facts dt { font-weight: 600; }
.find-us-place-facts dd { margin: 0; }
.find-us-hours, .find-us-shut { list-style: none; margin: 0; padding: 0; }
.find-us-season { display: block; color: var(--color-muted); }

/* docs/22 Later 8: products side by side. The picker opens under a
 * category's grid; the table scrolls inside its own box on a phone, its
 * first column -- what each row is -- held still beside the scroll. */
.compare { max-width: 72rem; }
.compare-picker { margin-block: 1.5rem; }
.compare-picker summary { cursor: pointer; font-weight: 600; }
.compare-picker__hint { color: var(--color-muted); margin-block: 0.5rem; }
.compare-picker__list { list-style: none; margin: 0 0 1rem; padding: 0;
  display: grid; gap: 0.35rem; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); }
.compare__scroll { overflow-x: auto; }
.compare-table { border-collapse: collapse; min-width: 100%; }
.compare-table th, .compare-table td { padding: 0.6rem 0.75rem; text-align: left;
  vertical-align: top; border-bottom: 1px solid var(--color-border, #e2e2dc); }
.compare-table thead th { min-width: 11rem; }
.compare-table tbody th { position: sticky; left: 0; background: var(--color-bg, #fff);
  font-weight: 600; }
.compare-table img { display: block; width: 100%; max-width: 12rem; height: auto;
  margin-bottom: 0.4rem; }
.compare-table__name { display: block; font-weight: 600; }
.compare__note { color: var(--color-muted); }
