/* Store homepage styles — public/index.html only.

   EXTRACTED FROM AN INLINE <style>, byte for byte. Every rule below is the
   one that was in the page; the four-space indentation is inherited from
   living inside <style> and was left alone so the move could be verified as
   a move.

   A SECOND FILE RATHER THAN AN APPEND TO main.css, and the block's own
   opening comment says why: main.css is shared with the admin and the card
   pages, so six hundred lines of shop chrome added to it would be six hundred
   lines downloaded and parsed on every /admin load that can never match
   anything.

   LINKED AFTER main.css, in the exact position the <style> occupied, so the
   cascade is unchanged. Nothing here collides with main.css — the two share
   no selector at all — and there is no @media, no !important and no
   z-index that depends on source order against it. */
    /* Store-only styles. main.css is shared with the admin and the card pages,
       so nothing here goes in it — every value below is one of its variables,
       and no new colour is introduced. */

    /* --- Header ------------------------------------------------------------
       Brand row, search and category pills are one crimson block rather than
       three stacked bars, so the shop reads as a header and a catalogue rather
       than as four separate strips.

       THE COLOUR IS var(--crimson), which brand.js writes from the vendor's own
       brand_color_primary at load. Nothing here names a colour of its own — the
       only literals are #fff and the rgba whites layered on top of it, which is
       the one pair that stays legible whatever primary the vendor picks. */
    /* THREE SIBLINGS NOW, NOT ONE BLOCK, and the reason is position:sticky.
       Brand, search and pills still read as one crimson block — each carries the
       same background and between them the same padding, so the rendered rhythm
       is unchanged: 10 above the brand, 10 either side of the search, 12 below
       the pills.

       They had to separate because A STICKY ELEMENT IS CONFINED TO ITS PARENT'S
       BOX. Inside the single wrapper the search pinned for 39px — measured, in
       headless Chrome against this stylesheet — and then left with the rest of
       the header, because the pills below it are the last thing in that box.
       Out here its parent is #app, which spans the grid, and it holds for the
       length of the catalogue.

       The admin inventory search hit this exact wall at 35px and was fixed the
       same way. See the sticky-search section in CLAUDE.md. */
    .store-header-wrap {
      background: var(--crimson);
      padding: 10px 12px 0;
    }

    .store-header-top {
      display: flex;
      align-items: center;
      gap: 10px;
    }

    .store-brand-name {
      flex: 1;
      min-width: 0;
      font-size: 16px;
      font-weight: 600;
      color: #fff;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* Stands in for a logo the vendor has not uploaded. The first letter of
       their own name, never a baked-in mark. */
    .store-brand-initial {
      width: 32px;
      height: 32px;
      flex-shrink: 0;
      border-radius: var(--radius-sm);
      background: rgba(255, 255, 255, 0.2);
      color: #fff;
      font-size: 15px;
      font-weight: 600;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .store-icon-btn {
      position: relative;
      min-width: 44px;
      min-height: 44px;
      display: flex;
      align-items: center;
      justify-content: center;
      background: none;
      border: none;
      color: rgba(255, 255, 255, 0.85);
      cursor: pointer;
      text-decoration: none;
    }

    /* Sits on the cart glyph rather than beside it, so the header does not
       reflow when the first item goes in.
       White on crimson rather than the other way round: the badge used to be
       crimson, which is now the colour behind it. */
    .cart-badge {
      position: absolute;
      top: 4px;
      right: 2px;
      min-width: 17px;
      height: 17px;
      padding: 0 4px;
      border-radius: 9px;
      background: #fff;
      color: var(--crimson);
      font-size: 10px;
      font-weight: 600;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    /* Only rendered while the vendor is actually at a show, and deliberately
       quiet — it is a signpost, not a call to action. */
    .show-link {
      display: flex;
      align-items: center;
      gap: 3px;
      min-height: 44px;
      padding: 0 4px;
      font-size: 11px;
      color: rgba(255, 255, 255, 0.75);
      text-decoration: none;
      white-space: nowrap;
      flex-shrink: 0;
    }

    /* THE SEARCH STICKS, AND ONLY THE SEARCH. The brand row and the pills
       scroll away as they always did: the three together hold about a third of
       a phone screen, and a shopper who has scrolled into the catalogue wants
       the catalogue.

       top:0 — the header above is in normal flow, so there is nothing fixed for
       an offset to clear.

       z-index:10, the same number the other two sticky searches carry. Above
       the product grid, which is unpositioned and would otherwise be free to
       paint over this the day a card gains a position of its own; far below
       #product-detail at 200 and the three bottom sheets at 250/251, every one
       of which has to cover it.

       THE BACKGROUND IS LOAD-BEARING rather than decoration — without it the
       product cards show through the search box as they pass beneath. It is the
       block's own var(--crimson), which brand.js writes from the vendor's
       brand_color_primary, so this follows a white-labelled theme like the rest
       of the header.

       The 10px that was margin-top is padding-top now, and the 10px that was
       .store-cat-row's margin-top is this padding-bottom. A sticky element's
       margin sits OUTSIDE the box that gets the background, so as margins they
       would have left two crimson-free stripes across the header at rest and a
       gap under the bar once pinned. */
    .store-search-row {
      position: sticky;
      top: 0;
      z-index: 10;
      background: var(--crimson);
      padding: 10px 12px;
    }

    /* What is left of the single wrapper's bottom padding, now that the pills
       are their own sibling. */
    .store-cat-wrap {
      background: var(--crimson);
      padding: 0 12px 12px;
    }

    .store-search-box {
      display: flex;
      align-items: center;
      gap: 8px;
      min-height: 40px;
      padding: 0 10px;
      border-radius: var(--radius-md);
      background: rgba(255, 255, 255, 0.15);
      border: 0.5px solid rgba(255, 255, 255, 0.4);
      color: rgba(255, 255, 255, 0.85);
    }

    /* main.css gives every input a surface background, a border and 12px of
       padding. The box around it is the control here, so all three are undone —
       the font-size is NOT: 16px is what stops iOS zooming the page on focus,
       and a shopper typing mid-scroll is exactly when that hurts. */
    .store-search-box input {
      flex: 1;
      min-width: 0;
      min-height: 40px;
      padding: 0;
      background: transparent;
      border: none;
      border-radius: 0;
      color: #fff;
      font-size: 16px;
    }

    .store-search-box input:focus {
      border: none;
    }

    .store-search-box input::placeholder {
      color: rgba(255, 255, 255, 0.7);
    }

    /* --- Category pills ------------------------------------------------------
       Fixed row, no scroll: five slots at most, and a shopper can see every one
       of them without discovering that the row moves. The names come from the
       vendor's own categories, so they are whatever length they are — the pills
       wrap their text and the row stretches them all to match the tallest,
       rather than truncating a name the vendor chose. */
    .store-cat-row {
      display: flex;
      align-items: stretch;
      gap: 6px;
      /* The 10px above the pills belongs to .store-search-row's padding-bottom
         now. As a margin here it would collapse out through .store-cat-wrap's
         zero top padding and open a strip of page background between two
         crimson blocks. */
      margin-top: 0;
    }

    .store-cat-pill {
      flex: 1;
      min-width: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      text-align: center;
      min-height: 28px;
      padding: 5px 6px;
      font-size: 11px;
      line-height: 1.2;
      white-space: normal;
      word-break: break-word;
      border-radius: var(--radius-xl);
      border: 0.5px solid rgba(255, 255, 255, 0.5);
      background: transparent;
      color: #fff;
      cursor: pointer;
    }

    .store-cat-pill.active {
      background: #fff;
      border-color: #fff;
      color: var(--crimson);
      font-weight: 600;
    }

    /* The one pill that does not share the row's width. It is a fixed door into
       the full list, so it stays the same size whether there are two categories
       or twenty. */
    .store-cat-more {
      flex: 0 0 auto;
      width: 62px;
    }

    /* --- Filter / sort bar ---------------------------------------------------
       Below the header rather than inside it: these are catalogue controls, and
       putting them on the crimson would have made the header a third of the
       screen on a phone. */
    .store-controls {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 10px 1rem;
      border-bottom: 0.5px solid var(--border);
    }

    .store-control-btn {
      display: flex;
      align-items: center;
      gap: 5px;
      min-height: 38px;
      padding: 8px 12px;
      font-size: 12px;
      background: var(--surface);
      border: 0.5px solid var(--border-mid);
      border-radius: var(--radius-sm);
      color: var(--text-secondary);
      cursor: pointer;
    }

    /* A dot rather than a count. How many filters are on does not help; that
       any are on is the thing a shopper needs, because it explains why the
       grid is short. */
    .store-filter-badge {
      color: var(--crimson);
      font-size: 14px;
      line-height: 1;
    }

    /* --- Bottom sheets -------------------------------------------------------
       Three of them — categories, filters, sort — sharing one set of rules and
       one open/close pair in the script. Written into the markup once and wired
       once, like the product detail above; only their contents change. */
    .store-sheet-overlay {
      display: none;
      position: fixed;
      inset: 0;
      z-index: 250;
      background: rgba(0, 0, 0, 0.6);
    }

    .store-sheet {
      display: none;
      position: fixed;
      left: 0;
      right: 0;
      bottom: 0;
      z-index: 251;
      max-height: 80vh;
      overflow-y: auto;
      padding: 8px 16px 24px;
      background: var(--surface2);
      border-top-left-radius: var(--radius-lg);
      border-top-right-radius: var(--radius-lg);
    }

    .sheet-handle {
      width: 36px;
      height: 4px;
      margin: 0 auto 12px;
      border-radius: 2px;
      background: var(--border-mid);
    }

    .sheet-title {
      font-size: 14px;
      font-weight: 600;
      color: var(--text-primary);
      margin-bottom: 6px;
    }

    .sheet-section-title {
      font-size: 11px;
      letter-spacing: 0.06em;
      color: var(--text-secondary);
      margin: 16px 0 8px;
    }

    .sheet-cat-item {
      display: flex;
      align-items: center;
      justify-content: space-between;
      width: 100%;
      min-height: 44px;
      padding: 0 2px;
      background: none;
      border: none;
      border-bottom: 0.5px solid var(--border);
      color: var(--text-primary);
      font-size: 14px;
      text-align: left;
    }

    .sheet-cat-item.active {
      color: var(--crimson);
      font-weight: 600;
    }

    /* Always in the markup, shown only on the active row — so the row's height
       does not change as the selection moves down the list. */
    .sheet-check { visibility: hidden; }
    .sheet-cat-item.active .sheet-check { visibility: visible; }

    .sheet-chip-row {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
    }

    .sheet-chip {
      min-height: 38px;
      padding: 8px 14px;
      font-size: 12px;
      border-radius: var(--radius-xl);
      border: 0.5px solid var(--border-mid);
      background: var(--surface);
      color: var(--text-secondary);
      cursor: pointer;
    }

    .sheet-chip.active {
      background: var(--crimson);
      border-color: var(--crimson);
      color: #fff;
    }

    .sheet-actions {
      display: flex;
      gap: 10px;
      margin-top: 20px;
    }

    .product-grid {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 12px;
      padding: 1rem;
    }

    .product-card {
      background: var(--surface);
      border: 0.5px solid var(--border);
      border-radius: var(--radius-md);
      overflow: hidden;
      display: flex;
      flex-direction: column;
      cursor: pointer;
    }

    .product-image {
      height: 160px;
      background: var(--surface2);
      display: flex;
      align-items: center;
      justify-content: center;
      overflow: hidden;
    }

    .product-image img {
      max-width: 100%;
      max-height: 100%;
      object-fit: contain;
    }

    /* Stands in for a missing photo. Says what the thing is rather than showing
       a broken-image glyph. */
    .product-image-placeholder {
      padding: 8px;
      font-size: 11px;
      line-height: 1.3;
      color: var(--text-muted);
      text-align: center;
    }

    .product-body {
      padding: 10px;
      display: flex;
      flex-direction: column;
      gap: 6px;
      flex: 1;
    }

    /* Two lines then ellipsis, so a long supply name cannot make one card
       taller than the one beside it and break the grid rhythm. */
    .product-name {
      font-size: 13px;
      line-height: 1.35;
      color: var(--text-primary);
      display: -webkit-box;
      -webkit-line-clamp: 2;
      -webkit-box-orient: vertical;
      overflow: hidden;
      min-height: 35px;
    }

    .product-price {
      font-size: 15px;
      font-weight: 500;
      color: var(--silver);
    }

    .product-add-btn {
      margin-top: auto;
      width: 100%;
      min-height: 36px;
      padding: 8px;
      font-size: 12px;
      font-weight: 500;
      background: var(--crimson);
      color: #fff;
      border: none;
      border-radius: var(--radius-sm);
      cursor: pointer;
    }

    /* --- The card's quantity stepper ----------------------------------------
       What the Add to Cart button becomes once the product is in the cart.

       IT IS THE FOOTER, in the button's place and never wrapped in anything —
       margin-top:auto against .product-body's flex column is what holds every
       card's control to the bottom of its box however the name above it wraps.
       A container around it would put the auto margin on the wrapper and the
       stepper would float back up. */
    .product-stepper {
      margin-top: auto;
      display: flex;
      align-items: center;
      width: 100%;
      gap: 0;
      border-radius: var(--radius-sm);
      overflow: hidden;
      border: 0.5px solid var(--crimson);
    }

    /* 36px matches .product-add-btn, so swapping one for the other does not
       change the card's height. */
    .product-qty-btn {
      width: 44px;
      min-height: 36px;
      background: var(--crimson);
      color: #fff;
      border: none;
      font-size: 18px;
      font-weight: 500;
      cursor: pointer;
      flex-shrink: 0;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .product-qty-display {
      flex: 1;
      text-align: center;
      font-size: 13px;
      font-weight: 500;
      color: var(--crimson);
      background: var(--surface);
      min-height: 36px;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    /* Skeletons match the real card's box exactly, so the grid does not jump
       when the products land. */
    .skeleton-card {
      background: var(--surface);
      border: 0.5px solid var(--border);
      border-radius: var(--radius-md);
      overflow: hidden;
    }

    .skeleton-image { height: 160px; background: var(--surface2); }

    .skeleton-line {
      height: 10px;
      margin: 10px;
      border-radius: var(--radius-sm);
      background: var(--surface2);
    }

    #product-detail {
      display: none;
      position: fixed;
      inset: 0;
      z-index: 200;
      background: var(--bg);
      overflow-y: auto;
    }

    .detail-header {
      display: flex;
      align-items: center;
      gap: 10px;
      padding: 10px 14px;
      border-bottom: 0.5px solid var(--border);
      background: var(--surface);
      position: sticky;
      top: 0;
      z-index: 1;
    }

    .qty-btn {
      width: 44px;
      height: 44px;
      background: var(--surface2);
      border: 0.5px solid var(--border);
      color: var(--text-primary);
      border-radius: var(--radius-md);
      font-size: 20px;
      line-height: 1;
      cursor: pointer;
    }

    .qty-btn:disabled { opacity: 0.4; cursor: default; }

    /* --- Detail thumbnail strip ---------------------------------------------
       Only drawn when a product has more than one image. Horizontal and
       scrollable rather than wrapped: eight thumbnails wrapped to two rows push
       the price and the Add to Cart button below the fold on a phone, and the
       whole point of the strip is that it costs the shopper nothing to ignore.

       Scrollbars are hidden globally by main.css, so the row scrolls without a
       gutter. */
    .detail-thumb-strip {
      display: flex;
      gap: 8px;
      overflow-x: auto;
      padding-bottom: 2px;
    }

    .detail-thumb {
      flex: 0 0 auto;
      width: 56px;
      height: 56px;
      padding: 0;
      background: var(--surface);
      border: 0.5px solid var(--border);
      border-radius: var(--radius-sm);
      overflow: hidden;
      cursor: pointer;
    }

    /* The selected one is marked with the vendor's own primary colour, so which
       image is showing is readable without comparing it to the one above. */
    .detail-thumb.active {
      border-color: var(--crimson);
      border-width: 1.5px;
    }

    .detail-thumb img {
      width: 100%;
      height: 100%;
      object-fit: contain;
      display: block;
    }

    .qty-display {
      width: 48px;
      text-align: center;
      color: var(--text-primary);
      font-size: 16px;
    }

    /* Prose styles for the sanitised description HTML. main.css resets every
       margin and padding to zero globally, so without these a bullet list
       renders with no bullet indent and paragraphs run together. Store-only,
       which is why they live here rather than in main.css. */
    .product-description p { margin-bottom: 0.5rem; }
    .product-description p:last-child { margin-bottom: 0; }

    .product-description ul,
    .product-description ol {
      padding-left: 1.25rem;
      margin-bottom: 0.5rem;
    }

    .product-description ul { list-style: disc; }
    .product-description ol { list-style: decimal; }
    .product-description li { margin-bottom: 0.25rem; }

    /* Bold lifts to the primary text colour so it reads as emphasis against
       the secondary grey the description is set in. */
    .product-description strong { color: var(--text-primary); }

    .vendor-pill {
      display: inline-block;
      background: var(--surface2);
      color: var(--text-secondary);
      border-radius: var(--radius-sm);
      padding: 3px 9px;
      font-size: 11px;
      text-transform: uppercase;
      letter-spacing: 0.05em;
    }
