/* ============================================================
   Baltic-Lasers shop bundle
   ------------------------------------------------------------
   Page-specific styles for the customer-facing shop flow.
   Loaded by /shop.php, /product.php, /cart.php, /checkout.php
   (and the shop-card partial that those pages share).

   Why not in portal.css?
   - These rules are only used inside the shop flow. Loading them
     on every dashboard / admin page would bloat the cache for no
     benefit.
   Why not inline <style> blocks in PHP anymore?
   - Co-locating with markup made the PHP files harder to scan,
     duplicated rules between shop-card uses, and required a
     defined()-guard to avoid double-emission. One cached CSS file
     is simpler and CSP-safe.

   All rules pull from portal palette tokens (--accent, --warning,
   --danger, --bg-secondary, etc.) — see docs/css-tokens.md for the
   canonical list.
   ============================================================ */


/* ── Shared shop-card partial ─────────────────────────────────
   Used by both the catalogue grid on /shop.php and the
   Suggested Products grid at the bottom of /product.php. */
.bl-card-image-wrap   { position:relative; display:block; }
.bl-card-hashtags     { position:absolute; top:8px; left:8px; display:flex; flex-direction:column;
                        gap:4px; pointer-events:none; }
/* Brand accent for chips — pulls from the portal palette
   (--accent = #9F2942) so the hashtag chip matches the
   Shop nav, CART button, primary buttons. Fallback hex matches
   the var so a missing :root never goes off-brand. */
.bl-card-hashtag-chip { background:var(--accent,#9F2942); color:#fff; font-size:11px;
                        line-height:1; padding:4px 8px; border-radius:99px; font-weight:600;
                        box-shadow:var(--shadow-sm, 0 1px 2px rgba(17,24,39,0.04)); }
.bl-card-discount     { position:absolute; top:8px; right:8px; background:var(--danger,#DC2626);
                        color:#fff; font-size:12px; line-height:1; padding:5px 9px;
                        border-radius:99px; font-weight:700;
                        box-shadow:var(--shadow-sm, 0 1px 2px rgba(17,24,39,0.04));
                        pointer-events:none; }
.bl-card-strike       { color:var(--text-muted,#6B7280); font-weight:500; font-size:.85em; margin-right:6px; }
.bl-card-price-sale   { color:var(--danger,#DC2626); }
.bl-card-price-from   { font-size:.7em; color:var(--text-muted,#6B7280); font-weight:500; margin-right:3px; }


/* ── /shop.php — listing + always-open sidebar on desktop ──── */
.bl-shop-alert         { display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; }
.bl-shop-alert-info    { margin-bottom:18px; }
.bl-shop-alert-actions { display:flex; gap:10px; }
.bl-shop-alert-hint    { font-size:12px; color:var(--text-muted); margin-top:8px; }

.shop-layout { display:block; }
@media (min-width: 900px) {
  .shop-layout {
    display:grid;
    grid-template-columns:280px minmax(0, 1fr);
    gap:24px;
    align-items:start;
  }
  /* Sidebar's "magic sticky" — scrolls along with the page until
     one of its edges hits the viewport edge, then pins there.
     `top:` is updated by the inline JS at the foot of shop.php;
     overflow stays visible so the whole filter list participates
     in the page scroll rather than a nested scrollbox. */
  .shop-layout .shop-filter-panel {
    display:block !important;
    position:sticky;
    top:80px;
  }
  /* Filters button is mobile-only on this layout — target the
     Filters trigger by ID so the Sort trigger (which shares the
     .shop-filter-trigger class) survives. */
  #shop-filter-trigger { display:none !important; }
  /* "Hide filters" button inside the panel only matters when the
     panel can hide — on desktop it's always open, so drop it. */
  #shop-filter-hide { display:none !important; }
  /* Sidebar sections need real breathing room — they're stacked
     vertically with no neighbour-context squeeze like the original
     horizontal toolbar dropdown had. */
  .shop-layout .shop-filter-panel .shop-filter-section { margin-bottom:22px; }
  .shop-layout .shop-filter-panel .shop-filter-section:last-of-type { margin-bottom:0; }
  /* Let the device list grow to its natural height — the sidebar
     itself scrolls when content exceeds the viewport, no need for
     a tighter inner scrollbox. */
  .shop-layout .shop-filter-panel .shop-filter-options {
    max-height:none;
    overflow:visible;
  }
}


/* ── /cart.php ─────────────────────────────────────────────── */
.bl-empty-actions { margin-top:14px; display:flex; gap:10px; justify-content:center; flex-wrap:wrap; }
.bl-empty-hint    { font-size:12px; color:var(--text-muted); margin-top:14px; }
.bl-text-danger   { color:var(--danger,#DC2626); }
.bl-text-muted    { color:var(--text-muted); }
.bl-cart-unavail-name { color:var(--danger,#DC2626); }
.bl-cart-unavail-warn { color:var(--danger,#DC2626); font-weight:600; margin-top:4px; }
/* Unavailable row uses the portal's danger-bg token for a consistent
   pink tint — matches how alert-danger renders elsewhere. */
.cart-line.is-unavailable { background:var(--danger-bg,#FEE2E2); border-left:3px solid var(--danger,#DC2626); }
.bl-card-header-tight { margin:-24px -24px 16px; padding:14px 24px; border-bottom:1px solid var(--border); }
.bl-vat-reverse       { color:var(--text-muted); font-size:11.5px; font-family:var(--mono); letter-spacing:.05em; }
.bl-checkout-cta          { width:100%; margin-top:14px; }
.bl-checkout-cta-disabled { opacity:.55; cursor:not-allowed; }
.bl-checkout-block-msg    { font-size:12px; color:var(--danger,#DC2626); margin-top:8px; line-height:1.5; font-weight:600; }
.bl-checkout-helper       { font-size:11px; color:var(--text-muted); margin-top:10px; line-height:1.55; }

/* Live "another customer is checking out this" warning. Renders
   per row when shop_checkout_active_competition reports a competing
   session for the SKU. Pulls --warning-bg / --warning from the
   portal palette so the pill matches the rest of the warning
   surfaces (skipped-products banner, low-stock chips). */
.bl-concurrent-warn       { display:inline-flex; align-items:center; gap:6px;
                            margin-top:6px; padding:4px 10px;
                            background:var(--warning-bg,#FEF3C7);
                            border:1px solid var(--warning,#D97706); border-radius:999px;
                            color:#7C2D12; font-size:12px; font-weight:600; line-height:1.3; }
.bl-concurrent-warn::before { content:"⏱"; font-size:13px; }


/* ── /checkout.php — auto-split + skipped-products notices ─── */
.bl-checkout-skip-notice,
.bl-checkout-concurrent-notice { background:var(--warning-bg,#FEF3C7);
                                 border:1px solid var(--warning,#D97706); border-radius:8px;
                                 padding:12px 16px; margin-bottom:18px;
                                 color:#7C2D12; font-size:14px; line-height:1.5; }
.bl-checkout-skip-notice a    { color:inherit; text-decoration:underline; }


/* ── /product.php — PDP gallery, variations, lightbox ─────── */
.bl-rel                      { position:relative; }
.bl-pdp-hashtag-col          { position:absolute; top:12px; left:12px; display:flex;
                               flex-direction:column; gap:6px; pointer-events:none; z-index:5; }
/* Brand accent — same token as the listing card hashtag chip
   so the visual identity carries through PDP → cart. */
.bl-pdp-hashtag-chip         { background:var(--accent,#9F2942); color:#fff; font-size:13px;
                               line-height:1; padding:6px 11px; border-radius:99px;
                               font-weight:600; box-shadow:var(--shadow-sm, 0 1px 2px rgba(17,24,39,0.04)); }
.bl-pdp-discount-chip        { position:absolute; top:12px; right:12px; background:var(--danger,#DC2626);
                               color:#fff; font-size:13px; line-height:1; padding:6px 11px;
                               border-radius:99px; font-weight:700;
                               box-shadow:var(--shadow-sm, 0 1px 2px rgba(17,24,39,0.04));
                               pointer-events:none; z-index:5; }
.bl-pdp-price-strike         { color:var(--text-muted,#6B7280); font-weight:500; font-size:22px;
                               vertical-align:middle; margin-right:12px; display:inline-block; }
.bl-pdp-price-strike[hidden] { display:none; }
.bl-pdp-price-from           { font-size:15px; color:var(--text-muted,#6B7280); font-weight:500;
                               margin-right:6px; vertical-align:baseline; }
/* Tag-qualified + ancestor-qualified so it wins over
   portal.css:4387 (.product-detail-price strong{color:var(--text)})
   which has specificity (0,1,1) and was painting the sale price in
   default body colour. The new selector is (0,2,2), wins cleanly. */
.product-detail-price strong.bl-pdp-price-sale { color:var(--danger,#DC2626); }
.bl-pdp-variations           { margin-top:14px; display:flex; flex-direction:column; gap:14px; }
/* Fieldset reset — the physical `padding:0` shorthand DOES zero
   physical padding, but some engines treat `padding-inline-start`
   (which the UA stylesheet uses for fieldset) as a logical property
   that survives a physical shorthand reset. Explicit logical
   overrides + min-inline-size:0 (fieldset's UA default is
   min-content, which can widen unexpectedly) close that gap so the
   chip row sits flush with the column edge. */
.bl-pdp-axis                 { border:0; margin:0; padding:0;
                               padding-inline:0; padding-block:0;
                               min-inline-size:0; }
.bl-pdp-axis-legend          { font-weight:600; font-size:14px; margin-bottom:6px; padding:0;
                               padding-inline:0; }
.bl-pdp-chips                { display:flex; flex-wrap:wrap; gap:8px; padding:0; margin:0; }
.bl-pdp-chip                 { display:inline-flex; align-items:center; justify-content:center;
                               padding:6px 14px; border:1.5px solid var(--border-light,#E5E7EB);
                               border-radius:999px; cursor:pointer; font-size:14px;
                               background:#fff; color:inherit; transition:all .12s ease;
                               overflow:hidden; position:relative; }
.bl-pdp-chip.is-swatch       { padding:0; border-radius:8px; width:48px; height:48px; }
/* Visually-hidden radio — kept in the DOM for keyboard / a11y but
   stripped of all rendering box so it doesn't push the chip text
   inside the inline-flex label.
   Selector intentionally tag-qualified (input.bl-pdp-chip-input)
   so it ties the (0,1,1) specificity of portal.css's
   `input[type="radio"] { width:18px; height:18px; … }` and wins
   on source order (shop.css loads AFTER portal.css). The plain
   `.bl-pdp-chip-input` (0,1,0) was losing the cascade, leaving the
   radio at its full 18×18 box and pushing the chip label right —
   the indent the customer reported. */
input.bl-pdp-chip-input      { position:absolute; width:1px; height:1px;
                               padding:0; margin:-1px; border:0;
                               background:transparent;
                               overflow:hidden; clip:rect(0,0,0,0);
                               clip-path:inset(50%); white-space:nowrap;
                               pointer-events:none; opacity:0; }
.bl-pdp-chip-swatch          { width:100%; height:100%; object-fit:cover; display:block; }
/* Left-aligned to match the rest of the info-rail (title, price,
   axis labels, chips) — the customer reads top-to-bottom flush left. */
.bl-pdp-variation-name       { margin-top:10px; font-weight:600; font-size:15px; }

/* Maintenance device picker — required step on is_maintenance
   products. Chip layout mirrors the variation picker so the
   customer recognises the interaction model. Selected chip flips
   to brand-accent via .is-checked (set by JS on radio change). */
/* Picker takes a full row inside .product-detail-action so the
   Quantity input + Add CTA wrap to a new line below the device
   chips, instead of squeezing in next to them on wide viewports. */
.bl-pdp-maintenance          { margin-top:18px; display:flex; flex-direction:column; gap:8px;
                               flex-basis:100%; width:100%; }
.bl-pdp-maintenance-label    { font-weight:600; font-size:14px; }
/* Chips stacked vertically (one per row), all sized to the widest
   one's natural content. A single-column grid with
   grid-template-columns:max-content gives both: the track width is
   max-content of the WIDEST child, so every chip in the column
   stretches to that width. No magic px caps needed. */
.bl-pdp-maintenance-chips    { display:grid; grid-template-columns:max-content; gap:10px; }
@media (max-width: 480px) {
  /* Narrow phones — fall back to full-width so the longest device
     label doesn't blow out the column. Chips still stack vertically. */
  .bl-pdp-maintenance-chips  { grid-template-columns:1fr; }
}

/* Chip is a vertical card: brand_model headline, then meta rows
   (serial / location) stacked under it. Width is driven by the
   grid track above so we don't pin min/max here — the chip just
   fills its grid cell. */
.bl-pdp-maintenance-chip     { display:flex; flex-direction:column; gap:2px;
                               padding:10px 14px;
                               border:1.5px solid var(--border-light,#E5E7EB);
                               border-radius:10px; cursor:pointer; font-size:14px;
                               background:#fff; color:inherit;
                               transition:border-color .14s ease, background .14s ease,
                                          color .14s ease, box-shadow .14s ease;
                               position:relative; }
.bl-pdp-maintenance-chip:hover {
                               border-color:var(--accent,#9F2942);
                               box-shadow:var(--shadow-sm, 0 1px 2px rgba(17,24,39,0.04)); }
/* CSS-only selected state via :has() — survives even when the JS
   change-handler doesn't fire (browser-specific timing edge with
   form-attribute-associated radios surfaced once already). The JS
   still adds .is-checked as a belt-and-braces back-up; either path
   styles the chip identically. */
.bl-pdp-maintenance-chip:has(input:checked),
.bl-pdp-maintenance-chip.is-checked {
                               border-color:var(--accent,#9F2942);
                               background:var(--accent,#9F2942); color:#fff; }
.bl-pdp-maintenance-chip:has(input:checked) .bl-pdp-maintenance-chip-meta,
.bl-pdp-maintenance-chip.is-checked .bl-pdp-maintenance-chip-meta {
                               color:rgba(255,255,255,.85); }
.bl-pdp-maintenance-chip-name { font-weight:600; font-size:15px; line-height:1.3; }
.bl-pdp-maintenance-chip-meta { display:flex; gap:6px; font-size:12px;
                                line-height:1.45; color:var(--text-muted,#6B7280); }
.bl-pdp-maintenance-chip-meta-label  { font-weight:600; text-transform:uppercase;
                                       letter-spacing:.04em; font-size:11px;
                                       align-self:center; }
.bl-pdp-maintenance-chip-meta-value  { flex:1; min-width:0; word-break:break-word; }
.bl-pdp-maintenance-empty    { margin:0; }
.bl-pdp-maintenance-error    { margin-bottom:6px; }

/* Required-state — fires when the customer clicks Add without
   picking a device. Mirrors the portal's .form-group.has-error
   pattern (danger-bg, danger left-rule, consent-pulse) used on
   checkout fields, so the visual language is consistent. Chips
   inside the picker also flip to a danger border so the eye lands
   on the actual required control, not just the outer box. */
.bl-pdp-maintenance.is-required             { background:var(--danger-bg,#FEE2E2);
                                              border-left:3px solid var(--danger,#DC2626);
                                              padding:10px 12px; border-radius:4px;
                                              animation:consent-pulse 1.6s ease-out,
                                                        bl-shake .42s var(--ease-spring, ease-out); }
.bl-pdp-maintenance.is-required .bl-pdp-maintenance-label    { color:var(--danger,#DC2626); }
.bl-pdp-maintenance.is-required .bl-pdp-maintenance-chip:not(.is-checked) {
  border-color:var(--danger,#DC2626);
}
@keyframes bl-shake {
  0%, 100% { transform:translateX(0); }
  25%      { transform:translateX(-6px); }
  75%      { transform:translateX(6px); }
}

/* Cart row — "For your [Brand Model]" headline followed by serial
   and location stacked below so the customer can tell two
   same-product rows apart at a glance. */
.bl-cart-device              { margin-top:6px; font-size:13px;
                               color:var(--text-muted,#6B7280); }
.bl-cart-device-name         { font-weight:600; color:var(--text,#111827); }
.bl-cart-device-meta         { display:flex; flex-direction:column; gap:1px;
                               margin-top:2px; }
.bl-cart-device-meta-row     { display:flex; gap:6px; font-size:12px; line-height:1.45; }
.bl-cart-device-meta-label   { font-weight:600; text-transform:uppercase;
                               letter-spacing:.04em; font-size:10.5px;
                               align-self:center; opacity:.85; }

.bl-pdp-stock-spaced         { margin-top:18px; }
.bl-pdp-qty-input            { max-width:120px; }
.bl-pdp-card-title           { font-size:15px; }
.bl-pdp-stock-table          { font-size:13px; width:100%; }
.bl-pdp-stock-table th       { padding:6px 8px; }
.bl-pdp-stock-table th.bl-th-left  { text-align:left; }
.bl-pdp-stock-table th.bl-th-right { text-align:right; }
.bl-pdp-stock-row            { cursor:pointer; }
.bl-pdp-stock-cell           { padding:6px 8px; vertical-align:middle; }
.bl-pdp-stock-cell-right     { padding:6px 8px; text-align:right; vertical-align:middle; }
.bl-pdp-stock-thumb-row      { display:flex; align-items:center; gap:10px; }
.bl-pdp-stock-thumb          { width:36px; height:36px; object-fit:cover; border-radius:4px;
                               border:1px solid var(--border-light,#E5E7EB); flex:none; }
.bl-pdp-stock-thumb-empty    { width:36px; height:36px; border-radius:4px;
                               background:var(--bg-secondary,#F1F4F8);
                               border:1px solid var(--border-light,#E5E7EB);
                               flex:none; }
.bl-pdp-stock-row-link       { color:inherit; text-decoration:underline;
                               text-decoration-color:var(--border-light,#E5E7EB);
                               text-underline-offset:3px; }

/* Stock-per-variation Price column. compare-at strike + discount
   chip + small excl-VAT sub-line mirror the listing-card price
   block so a sale on one variation reads identically across the
   storefront. */
.bl-pdp-stock-price          { white-space:nowrap; }
.bl-pdp-stock-strike         { color:var(--text-muted,#6B7280); font-weight:500;
                               font-size:.85em; margin-right:6px; }
.bl-pdp-stock-discount-chip  { display:inline-block; margin-left:6px; padding:1px 7px;
                               background:var(--danger,#DC2626); color:#fff;
                               font-size:11px; font-weight:700; line-height:1.4;
                               border-radius:99px; }
.bl-pdp-stock-excl           { font-size:11px; color:var(--text-muted,#6B7280);
                               font-family:var(--mono); letter-spacing:.02em;
                               margin-top:2px; font-weight:400; }

/* Hover-zoom cursor on the gallery image — pure CSS so it survives
   the variation gallery swap (which destroys + recreates the frame
   nodes, dropping any JS-set inline cursor that would otherwise
   stick from the initial pass). Inactive frames keep the default
   cursor — they have pointer-events:none from portal.css anyway. */
.product-gallery-frame.is-active,
.product-gallery-frame.is-active img { cursor:zoom-in; }


/* Image lightbox — moved to document.body via JS on init so the
   max-int z-index isn't scoped to a parent stacking context. */
.bl-lightbox-overlay         { position:fixed; inset:0; background:rgba(0,0,0,0.92);
                               z-index:2147483647; display:none; align-items:center;
                               justify-content:center; padding:20px; }
.bl-lightbox-btn             { position:absolute; background:#fff; border:0; color:#111;
                               font-size:28px; line-height:1; cursor:pointer;
                               width:52px; height:52px; border-radius:50%;
                               display:flex; align-items:center; justify-content:center;
                               box-shadow:0 4px 16px rgba(0,0,0,0.4); }
.bl-lightbox-close           { top:18px; right:18px; font-weight:300; }
.bl-lightbox-prev            { left:20px; top:50%; transform:translateY(-50%); }
.bl-lightbox-next            { right:20px; top:50%; transform:translateY(-50%); }
.bl-lightbox-img             { max-width:95vw; max-height:90vh; object-fit:contain; display:block; }
.bl-lightbox-counter         { position:absolute; bottom:18px; left:50%; transform:translateX(-50%);
                               color:#fff; font-size:13px; background:rgba(0,0,0,0.5);
                               padding:5px 12px; border-radius:99px; }

/* ── Cart item selection ─────────────────────────────────────────── */
/* Full-width sub-header between the active list and the deselected rows. */
.cart-deselected-head {
  font-size: 11px; font-weight: 600; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: .08em;
  padding: 14px 16px 4px; border-top: 1px solid var(--border-light);
}
/* Deselected rows keep their full layout (qty selector, price) but read
   as inactive: dimmed, with the qty controls disabled in the markup. */
.cart-line.is-deselected { opacity: .5; }
.cart-line.is-deselected .cart-prod-name { color: var(--text-muted); }
/* Checkbox sits left of the product image (first child of the flex
   .cart-line-product cell). */
.cart-line-select { display: flex; align-items: center; flex: 0 0 auto; }
.cart-line-select input[type="checkbox"] { width: 18px; height: 18px; margin: 0; cursor: pointer; }
