/* @import MUST COME FIRST. The CSS spec ignores any @import that follows a rule other than
   @charset or @layer, silently. Placing this after the @font-face blocks below made it a no-op:
   the tokens never loaded, --bn-charcoal resolved to nothing, and the footer rendered
   transparent. Caught by probing the live page rather than by the file looking right.

   Bottleneck's own tokens, and the neutral pre-client defaults for the client palette. portal.css
   imports this separately: portal-v2.html does not load index.css. */
@import url("./brand-tokens.css");

/* THE GOOGLE FONTS @import IS DELETED (2026-08-09, brief Task 25), having been moved to the top of
   this file earlier the same day and measured.

   Moving it made it work. It was never the route the fonts arrive by: applyTheme() (theme.js:59)
   injects <link id="theme-fonts"> from the client theme's googleFontsUrl, and a client with no theme
   file of its own falls back to one that has it. Oceanside Alloy and morphd render in Cormorant
   either way, at one stylesheet and two woff2 each - the URLs are byte-identical so the browser
   dedupes them.

   Demo is the client that measured the cost. It paints everything in Die Grotesk, renders nothing in
   Jost or Cormorant, and downloaded 64KB of webfont it never used. That waste grows with every
   client that brings its own font.

   WHAT IS LOST is a typeface if JavaScript never runs, on a configurator that cannot function
   without it. --font-body and --font-display still name 'Jost' and 'Cormorant Garamond' below as the
   platform default; applyTheme sets them, and the link, together.

   verify-css-imports-come-first.cjs keeps the general rule - every @import precedes the first rule -
   which caught a real fault in brand-tokens.css and is the part worth having. */

@font-face {
  font-family: 'Die Grotesk';
  src: url('/website/fonts/die-grotesk-a-regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Die Grotesk';
  src: url('/website/fonts/die-grotesk-a-black.woff2') format('woff2');
  font-weight: 900;
  font-style: normal;
  font-display: swap;
}

/* ═══════════════════════════════════════════════════════════════════════════
   SHARED BASE - loaded by every page
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Design tokens ───────────────────────────────────────────────────────── */
/* THE CLIENT PALETTE. applyClientColourOverrides() (utils.js) sets every one of these
   from the active client's colour columns as soon as the clients row has loaded.

   These defaults USED TO BE OCEANSIDE ALLOY'S, and the token names still say "teal"
   because that is what Oceanside Alloy's primary is. Two things followed from that:

   1. Every other client's page rendered in Oceanside Alloy's palette until the row
      loaded, then snapped to its own.
   2. Worse, the tokens applyClientColourOverrides did NOT set - --teal-dk, --teal-light
      and --teal-pale - never snapped. They stayed Oceanside Alloy's on every client,
      permanently. --teal-dk alone drives sixteen client-facing surfaces including the
      cart panel, the Add to Quote Cart button, the hero and every price. MORPHD's
      customers were looking at #2c4347.

   The defaults are now Bottleneck's neutrals (brand-tokens.css) and all five tokens are
   client-driven. Before we know whose page this is, it is our chrome, and an honest grey
   beats another company's brand. Renaming the tokens off "teal" is a separate sweep. */

:root {
  --teal:       var(--bn-neutral);
  --teal-dk:    var(--bn-neutral-dk);
  --teal-mid:   var(--bn-neutral-mid);
  --teal-light: var(--bn-neutral-light);
  --teal-pale:  var(--bn-neutral-pale);
  --sand:       var(--bn-cream);
  --white:      #ffffff;
  --dark:       #1c2426;
  --mid:        #4a5c5e;
  --muted:      #8a9e9f;
  --border:     #d8e4e5;
  /* ── A CONTROL'S BORDER IS NEUTRAL AND IS NOT THE CLIENT'S (2026-09-08, TIM'S AUDIT item 6) ──
     Tim: "an input border does NOT take the client's custom colour. It is one neutral border
     everywhere", and "READ IT OFF THE OCEANSIDE CONFIGURATOR and use that exact value."

     MEASURED, not chosen: --border is CLIENT-CUSTOMISED and the two live values differ -
     oceanside-alloy #d8e4e5, the demo #e8d8c8 - so the same form reads differently per client for
     no reason anybody asked for. This is Oceanside's own value, promoted to the one every control
     uses, and it is a SEPARATE token rather than a re-pick of --border because --border still has
     legitimate per-client work to do on panels and rules.

     A SECOND GREY THAT MEANT THE SAME THING would be the drift this item exists to remove; this is
     a second grey that means something DIFFERENT, and the difference is "a control, on any
     client". */
  --control-border: #d8e4e5;
  --border-lt:  #eaf0f1;
  --red:        #c0392b;
  --green:      #2d6a4f;
  --amber:      #c87e00;
  /* ── A RADIUS THAT CAN BE SEEN (2026-09-09, THE QUEUE) ──────────────────────────────────────
     Tim: "There is no radius of the boxes and there should."

     THE EARLIER ITEM DID ITS JOB AND IS NOT REOPENED. It found three radii for one idea - the tick
     box row at 8px, every typed control at none, and this token used by neither - and unified them
     on this token. MEASURED OFF THE PAGE afterwards, which is what the new item asks for: every
     control resolved to 2px. So they ARE one value, and 2px reads as a square corner, which is the
     case the item warns about in as many words: "a shared radius of zero satisfies the same radius
     and is not what was asked for."

     SO THE FIX IS THE VALUE, NOT THE PLUMBING, and it is one line because the plumbing was done.

     6px IS TAKEN FROM THE PORTAL RATHER THAN INVENTED, which is the item's third establish
     question - whether the panes agree with the form. The portal's own control radii cluster at
     6, 7 and 8px, and 6 is the modest end of that cluster. A DECISION, and REVERSIBLE by this one
     line. */
  --radius:     6px;

  /* Font tokens - set by applyTheme(); fallbacks match Oceanside defaults. */
  --font-body:    'Jost', sans-serif;
  --font-display: 'Cormorant Garamond', serif;
}

/* ── Reset ───────────────────────────────────────────────────────────────── */

* { margin: 0; padding: 0; box-sizing: border-box; }

/* ── Base ────────────────────────────────────────────────────────────────── */

body {
  font-family: var(--font-body);
  background: var(--sand);
  color: var(--dark);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* ── Header ──────────────────────────────────────────────────────────────── */

header {
  background: var(--white);
  border-bottom: 1px solid var(--border);
  padding: 0 48px;
  display: flex;
  align-items: center;
  height: 72px;
  flex-shrink: 0;
}

.oa-logo img:not(.client-logo) { height: 26px; width: auto; }

/* Standard class for client logos in headers, panels, and carts */
.client-logo { max-height: 48px; max-width: 200px; width: auto; height: auto; object-fit: contain; }

/* The BRAND-NAME FALLBACK, used when a client has no logo for this surface. It sits inside the
   header's catalogue link, so without this it inherited the link underline and link colour and
   read as clickable text rather than as a brand mark. The link still works; it just stops
   advertising itself as one. Reported 2026-08-07: MORPHD rendered underlined and link-styled. */
/* display:inline-block is LOAD-BEARING, not cosmetic. text-decoration PROPAGATES from an ancestor
   and is painted over descendants; `text-decoration: none` on this span does not remove the
   underline the wrapping <a> draws. An inline-block establishes its own decoration context, which
   does. A first fix set only text-decoration/color here, the computed style on the span read
   "none", and the mark still rendered blue and underlined on screen. */
.client-logo--text {
  display: inline-block;
  font-family: 'Archivo', sans-serif; font-weight: 700; font-size: 20px;
  letter-spacing: -0.015em; text-decoration: none; color: var(--dark, #23343a);
  max-height: none; max-width: none; white-space: nowrap;
}
.header-dark .client-logo--text { color: var(--teal-light, #cfe3e3); }
/* mix-blend-mode: screen is ONLY for dark headers (e.g. the demo header, headerDark:
   true), where the white logo sits on the brand-dark background. On a light header
   (white bg) screen makes a dark logo's pixels transparent, so OA's dark logo rendered
   invisible. Scope it to the dark header so it never leaks onto normal client logos. */
.header-dark .client-logo { mix-blend-mode: screen; }

.header-divider {
  width: 1px;
  height: 32px;
  background: var(--border);
  margin: 0 24px;
}

.header-sub {
  font-size: 0.72rem;
  color: var(--muted);
  font-weight: 300;
  letter-spacing: 1.5px;
  text-transform: uppercase;
}

.header-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 24px;
}

.header-trust-item {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.68rem;
  color: var(--muted);
  font-weight: 400;
}

/* Wrapper that spaces the back link away from the subtitle */
.header-back { margin-left: 16px; }

.btn-home {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.65rem;
  font-weight: 500;
  color: var(--muted);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  text-decoration: none;
  padding: 6px 12px;
  border: 1px solid var(--border);
  background: var(--white);
  transition: border-color 0.15s, color 0.15s;
}

.btn-home:hover {
  border-color: var(--teal);
  color: var(--teal);
}

/* ── Footer ──────────────────────────────────────────────────────────────── */

/* THE FOOTER'S RULES MOVED TO brand-tokens.css (2026-08-12, brief Task 21). The portal needs the
   IDENTICAL footer and portal-v2.html does not load this stylesheet, so leaving them here would have
   meant a second copy in portal.css. brand-tokens.css is imported by both and already names the
   POWERED BY footer as our chrome, so it is the one place they can live. */

/* ── Shared responsive ───────────────────────────────────────────────────── */

@media (max-width: 640px) {
  header { padding: 0 20px; }
  .header-right { display: none; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   INDEX PAGE - product catalogue
   ═══════════════════════════════════════════════════════════════════════════ */

.page {
  max-width: 1360px;
  margin: 40px auto;
  padding: 0 24px;
  flex: 1;
}

/* ── Hero section (full-bleed dark teal) ─────────────────────────────────── */

.page-hero {
  background: var(--teal-dk);
  padding: 52px 0 44px;
  width: 100%;
  flex-shrink: 0;
}

.page-hero-inner {
  max-width: 1360px;
  margin: 0 auto;
  padding: 0 24px;
  text-align: center;
}

/* THE HERO SITS ON --teal-dk, so its text tints with --client-ink-dk rather than always with white
   (2026-08-09, brief Task 4).

   BOTH ALPHAS WENT UP, 0.5 -> 0.72 here and 0.68 -> 0.80 on the description, and the reason is the
   same one the portal nav already carries: an alpha tuned on one client's ground is quietly
   illegible on another's. These were set against Oceanside Alloy's #2c4347, which is very dark. A
   client who clears brand_color now gets it DERIVED - primary darkened 30% - and on MORPHD's real
   tan that lands at #6d5d45, where the old values composite to 3.95:1 and 4.22:1. 0.82 clears 4.5:1
   there and is more legible, not less, on the dark grounds they were tuned for. Both are 0.82 now
   because two hero lines on the same ground differing by an eighth of an alpha was a distinction
   nothing was reading. */
.page-hero-eyebrow {
  font-size: 0.62rem;
  font-weight: 500;
  color: rgba(var(--client-ink-dk-rgb, 255, 255, 255), 0.82);
  letter-spacing: 3px;
  text-transform: uppercase;
  margin-bottom: 14px;
}

.page-hero-title {
  font-family: var(--font-display);
  font-size: 3.4rem;
  font-weight: 300;
  color: var(--client-ink-dk, #fff);
  letter-spacing: 0.5px;
  margin-bottom: 18px;
  line-height: 1;
}

.page-hero-desc {
  font-size: 0.85rem;
  color: rgba(var(--client-ink-dk-rgb, 255, 255, 255), 0.82);
  font-weight: 300;
  line-height: 1.75;
  max-width: 560px;
  margin: 0 auto 32px;
}

.page-hero-steps {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.hero-step-box {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 10px 18px;
  border: 1px solid rgba(255, 255, 255, 0.25);
}

.hero-step-num {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.18);
  color: var(--white);
  font-size: 0.65rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.hero-step-text {
  font-size: 0.62rem;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.82);
  letter-spacing: 1.5px;
  text-transform: uppercase;
}

/* ── Request a Quote CTA bar ─────────────────────────────────────────────── */

.quote-cta-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 20px;
  background: var(--teal-dk);
  margin-bottom: 16px;
  color: var(--client-ink-dk, #fff);
}

.quote-cta-msg {
  font-size: 0.78rem;
  font-weight: 300;
  color: rgba(255, 255, 255, 0.85);
}

.quote-cta-btn {
  padding: 10px 22px;
  background: var(--white);
  color: var(--teal-dk);
  border: none;
  font-family: var(--font-body);
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s;
}
.quote-cta-btn:hover { background: var(--teal-light); }

/* ── Product grid ─────────────────────────────────────────────────────────── */

/* ONE RULE, TWO SURFACES (2026-08-09, brief Task 19). The portal's product grid uses the same
   declaration, so the client's grid is a preview of this one rather than a differently-shaped list
   of the same products. Fixed at three columns until now, which is not responsive on either side:
   three cramped cards on a laptop and three enormous ones on a wide monitor. */
/* WIDER, CENTRED, AND HONEST AT ONE CARD (2026-08-18, queue Task 21).
   The column count was already responsive; the faults were the other three. 260px is a narrow card
   for six lines of prose, so it goes to 320. And auto-fill lays tracks across the WHOLE container
   whether or not there are cards to fill them, so MORPHD's single product sat at 260px hard against
   the left edge of a 1360px band, which reads as broken rather than as sparse.
   justify-content: center packs the tracks that actually have cards and centres them, so one card is
   a centred card and nine still fill the row. auto-fit is the wrong fix here: it would stretch that
   single card to 1360px, which is the same fault with the opposite shape. */
.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  justify-content: center;
  gap: 24px;
}

.loading-msg {
  font-size: 0.8rem;
  color: var(--muted);
  font-weight: 300;
}

/* ── Product card ─────────────────────────────────────────────────────────── */

.product-card {
  background: var(--white);
  border: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  transition: border-color 0.15s, box-shadow 0.15s;
}

.product-card:hover {
  border-color: var(--teal-mid);
  box-shadow: 0 2px 12px rgba(var(--portal-accent-dk-rgb), 0.08);
}

.product-card-head {
  padding: 18px 28px;
  border-bottom: 1px solid var(--border-lt);
  display: flex;
  align-items: center;
  gap: 10px;
}

.product-card-head h2 {
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--teal);
  letter-spacing: 2.5px;
  text-transform: uppercase;
}

.product-card-badge {
  background: var(--teal-light);
  color: var(--teal);
  font-size: 0.68rem;
  font-weight: 600;
  padding: 3px 8px;
  margin-left: auto;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  white-space: nowrap;
}

.product-card-body {
  padding: 24px 28px;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.product-card-desc {
  font-size: 0.9rem;
  color: var(--mid);
  line-height: 1.8;
  font-weight: 300;
  flex: 1;
}

.product-card-meta {
  font-size: 0.65rem;
  color: var(--muted);
  font-weight: 400;
  letter-spacing: 0.5px;
}

.product-card-from {
  font-size: 0.75rem;
  color: var(--teal);
  font-weight: 500;
  letter-spacing: 0.5px;
}

.product-card-img {
  overflow: hidden;
  background: transparent;
  border-bottom: 1px solid var(--border-lt);
}

/* ── THE GRID CARD IS FRAMED ON THE SAME RATIO AS EVERY OTHER IMAGE (2026-09-10, THE QUEUE) ──────
   Tim: "EVERY IMAGE ON EVERY CUSTOMER SURFACE, the index grid card, the product header hero, a
   section's image, a field's image: ALL SQUARE, ALL CONTAINED, NOTHING CROPPED."

   THE ITEM SAYS THESE IMAGES ARE ALREADY BEING CROPPED. THEY ARE NOT, and the correction is worth
   keeping because it changes what this rule has to do. The declaration replaced here was
   `object-fit: contain` at a fixed `height: 225px`, so a render already fit inside its box whole.
   What "the image FILLS its frame" describes is that Oceanside's renders happen to be about 360x225
   themselves, so they nearly fill a 360x225 box and their flat orange ground runs edge to edge. The
   fault was never a crop. It is that the box's shape was the box's own and not a stated one.

   SO THE ONLY CHANGE IS THE SHAPE, and the consequence Tim has already accepted in advance is
   exactly the one that follows: at ~360 wide the frame goes from 225 tall to ~360 tall, every card
   grows by roughly 140px, and the catalogue shows about four across a screen instead of six. THAT
   IS EXPECTED AND IS NOT COMPENSATED FOR BY SHRINKING THE IMAGE, which the item forbids in those
   words.

   THE BAND ABOVE AND BELOW IS PAINTED IN THE IMAGE'S OWN SAMPLED EDGE COLOUR, written inline per
   card from products.hero_frame_color, so an orange render sits on orange rather than on ~70px of
   cream. This declaration is the fallback for an image whose edges did not agree. */
.product-card-img img {
  width: 100%;
  aspect-ratio: var(--photo-frame-ratio, 1);
  height: auto;
  object-fit: contain;
  background-color: var(--photo-frame-fallback-bg, #faf9f7);
  display: block;
  transition: transform 0.4s ease;
}

.product-card:hover .product-card-img img { transform: scale(1.03); }

.btn-configure {
  display: block;
  width: 100%;
  padding: 16px;
  background: var(--teal-dk);
  color: var(--client-ink-dk, #fff);
  border: none;
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 3px;
  text-transform: uppercase;
  text-decoration: none;
  text-align: center;
  cursor: pointer;
  margin-top: auto;
  transition: background 0.15s;
}

.btn-configure:hover { background: var(--teal-mid); color: var(--client-ink-mid, #fff); }

/* ── Error state ──────────────────────────────────────────────────────────── */

.error-msg {
  font-size: 0.8rem;
  color: var(--red);
  font-weight: 300;
  line-height: 1.7;
}

/* ── Index responsive ────────────────────────────────────────────────────── */

@media (max-width: 640px) {
  .page { margin: 24px auto; }
  .product-grid { grid-template-columns: 1fr; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   QUOTE CART - shared across index + product pages
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Cart FAB ─────────────────────────────────────────────────────────────── */

.cart-fab {
  position: fixed;
  bottom: 32px;
  right: 32px;
  z-index: 1000;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 18px;
  background: var(--teal-dk);
  color: var(--client-ink-dk, #fff);
  border: none;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 2px;
  text-transform: uppercase;
  box-shadow: 0 4px 20px rgba(var(--portal-accent-dk-rgb), 0.3);
  transition: background 0.15s, box-shadow 0.15s;
}

.cart-fab:hover {
  background: var(--teal-mid);
  box-shadow: 0 6px 28px rgba(var(--portal-accent-dk-rgb), 0.4);
  color: var(--client-ink-mid, #fff);
}

@keyframes wc-fab-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(var(--portal-accent-rgb), 0.55); }
  70%  { box-shadow: 0 0 0 14px rgba(var(--portal-accent-rgb), 0); }
  100% { box-shadow: 0 0 0 0 rgba(var(--portal-accent-rgb), 0); }
}
.wc-fab--pulse {
  animation: wc-fab-pulse 0.8s ease-out;
}

.cart-fab--hidden { display: none; }

.cart-fab-badge {
  background: var(--white);
  color: var(--teal-dk);
  font-size: 0.65rem;
  font-weight: 700;
  min-width: 18px;
  height: 18px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 4px;
  line-height: 1;
}

/* ── Cart panel ───────────────────────────────────────────────────────────── */

.cart-panel {
  position: fixed;
  bottom: 0;
  right: 32px;
  z-index: 999;
  width: 380px;
  max-height: 80vh;
  background: var(--white);
  border: 1px solid var(--border);
  border-bottom: none;
  box-shadow: 0 -4px 32px rgba(var(--portal-accent-dk-rgb), 0.15);
  display: flex;
  flex-direction: column;
  transform: translateY(100%);
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.cart-panel--open { transform: translateY(0); }

.cart-panel-head {
  background: var(--teal-dk);
  padding: 16px 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
  color: var(--client-ink-dk, #fff);
}

.cart-panel-title {
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--white);
  letter-spacing: 3px;
  text-transform: uppercase;
}

.cart-panel-close {
  background: none;
  border: none;
  color: rgba(255, 255, 255, 0.7);
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
  padding: 2px 6px;
  transition: color 0.15s;
}
.cart-panel-close:hover { color: var(--white); }

.cart-panel-body {
  overflow-y: auto;
  flex: 1;
}

.cart-items-list { padding: 4px 0; }

.cart-empty {
  padding: 24px 20px;
  font-size: 0.78rem;
  color: var(--muted);
  text-align: center;
  font-weight: 300;
}

/* ── Cart items ───────────────────────────────────────────────────────────── */

.cart-item {
  padding: 14px 20px;
  border-bottom: 1px solid var(--border-lt);
}
.cart-item:last-child { border-bottom: none; }

.cart-item-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}

.cart-item-name {
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--dark);
  text-transform: uppercase;
  letter-spacing: 1px;
}

.cart-item-price {
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--teal-dk);
  white-space: nowrap;
}

.cart-item-summary {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 8px;
}

.cart-item-summary span {
  font-size: 0.66rem;
  color: var(--muted);
  font-weight: 300;
}

.cart-item-notes-box {
  margin-top: 6px;
  padding: 8px 10px;
  background: rgba(0, 0, 0, 0.03);
  border-left: 2px solid var(--teal);
  border-radius: 2px;
  font-size: 0.72rem;
  color: var(--text);
  line-height: 1.45;
  word-break: break-word;
  overflow-wrap: break-word;
  max-width: 100%;
  box-sizing: border-box;
}

.cart-item-notes-label {
  font-weight: 600;
  color: var(--muted);
  margin-right: 4px;
}

.cart-item-notes-text {
  color: inherit;
}

.cart-item-notes-box.is-collapsible .cart-item-notes-text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.cart-item-notes-box.is-collapsible.is-expanded .cart-item-notes-text {
  display: inline;
  -webkit-line-clamp: unset;
  line-clamp: unset;
  overflow: visible;
}

.cart-item-notes-toggle {
  background: none;
  border: none;
  padding: 4px 0 0;
  margin-top: 2px;
  color: var(--teal);
  font-size: 0.7rem;
  font-family: inherit;
  cursor: pointer;
  text-decoration: underline;
}

.cart-item-notes-toggle:hover {
  opacity: 0.8;
}

.cart-item-desc-box {
  margin-top: 6px;
  padding: 10px 12px;
  background: rgba(0, 0, 0, 0.04);
  border-left: 3px solid var(--teal);
  border-radius: 2px;
  font-size: 0.78rem;
  color: var(--text);
  line-height: 1.55;
  word-break: break-word;
  overflow-wrap: break-word;
  max-width: 100%;
  box-sizing: border-box;
}

.cart-item-desc-label {
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 4px;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.cart-item-desc-text {
  color: inherit;
}

.cart-item-desc-box.is-collapsible .cart-item-desc-text {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  white-space: normal;
}

.cart-item-desc-box.is-collapsible.is-expanded .cart-item-desc-text {
  display: block;
  -webkit-line-clamp: unset;
  line-clamp: unset;
  overflow: visible;
  white-space: pre-wrap;
}

.cart-item-desc-toggle {
  background: none;
  border: none;
  padding: 4px 0 0;
  margin-top: 4px;
  color: var(--teal);
  font-size: 0.7rem;
  font-family: inherit;
  cursor: pointer;
  text-decoration: underline;
}

.cart-item-desc-toggle:hover {
  opacity: 0.8;
}

.cart-item-remove {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 0.6rem;
  color: var(--muted);
  font-family: var(--font-body);
  padding: 0;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  transition: color 0.15s;
}
.cart-item-remove:hover { color: var(--red); }

.cart-item-name-block {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.cart-item-reconfigure {
  font-size: 0.6rem;
  color: var(--teal);
  text-decoration: none;
  letter-spacing: 0.5px;
  transition: color 0.15s;
}
.cart-item-reconfigure:hover { color: var(--teal-mid); }

/* A price that moved during an edit says so on the line (2026-08-17, queue Task B).
   Tokens only, no literal colour: this file is shared by every client. Deliberately not
   the error colour - a reprice is information, not a fault. */
.cart-item-price-moved {
  font-size: 0.72rem;
  line-height: 1.35;
  color: var(--text-muted, #6b6b6b);
  margin-top: 3px;
}

/* ── Cart handling fee + grand total ──────────────────────────────────────── */

.cart-handling-fee {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 10px 20px 6px;
  border-top: 1px dashed var(--border);
  gap: 8px;
}

.cart-handling-label {
  font-size: 0.68rem;
  color: var(--muted);
  letter-spacing: 0.3px;
}

.cart-handling-price {
  font-size: 0.72rem;
  color: var(--mid);
  font-weight: 500;
  white-space: nowrap;
}

.cart-grand-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 6px 20px 14px;
  gap: 8px;
}

.cart-grand-total-label {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--dark);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.cart-grand-total-price {
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--teal-dk);
  white-space: nowrap;
}

/* ── Cart panel footer ────────────────────────────────────────────────────── */

.cart-panel-footer {
  border-top: 1px solid var(--border);
  padding: 16px 20px;
  flex-shrink: 0;
  background: var(--white);
}

.cart-customer-form {
  padding: 16px 20px;
  border-bottom: 1px solid var(--border-lt);
  margin-bottom: 0;
}

/* ── Cart indicative note ─────────────────────────────────────────────────── */

.cart-indicative-note {
  font-size: 0.62rem;
  color: var(--muted);
  font-weight: 300;
  line-height: 1.5;
  text-align: center;
  margin-bottom: 10px;
}

.cart-customer-title {
  font-size: 0.6rem;
  font-weight: 500;
  color: var(--muted);
  letter-spacing: 2px;
  text-transform: uppercase;
  margin-bottom: 10px;
}

.cart-customer-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-bottom: 8px;
}

.cart-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 8px;
}

.cart-customer-grid .cart-field { margin-bottom: 0; }

/* WHERE IT IS GOING (2026-09-13, THE QUEUE "A quote carries no delivery address and cannot say
   pickup"). It sits under Your Details in the same panel and wears the same vocabulary: the title is
   the customer-title above, the boxes are cart-fields, and the only new shapes are the two radios and
   the sentence about freight. A client that does not ask for it renders none of this. */
.cart-delivery {
  border-top: 1px solid var(--border-lt, rgba(0,0,0,0.08));
  padding-top: 12px;
  margin-bottom: 12px;
}
.cart-delivery-choice {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 10px;
}
.cart-delivery-opt {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.72rem;
  font-weight: 300;
  color: var(--dark);
  cursor: pointer;
}
.cart-delivery-opt input { accent-color: var(--client-primary, var(--teal)); }
.cart-delivery-note {
  font-size: 0.62rem;
  color: var(--muted);
  font-weight: 300;
  line-height: 1.5;
  margin: 8px 0 0;
}

.cart-field label {
  font-size: 0.58rem;
  font-weight: 500;
  color: var(--muted);
  letter-spacing: 1.5px;
  text-transform: uppercase;
}

.cart-field input {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--border);
  background: var(--white);
  color: var(--dark);
  font-family: var(--font-body);
  font-size: 0.85rem;
  font-weight: 400;
  transition: border-color 0.15s;
}
.cart-field input:focus {
  outline: none;
  border-color: var(--teal-mid);
}

.cart-submit-btn {
  width: 100%;
  padding: 13px;
  background: var(--teal-dk);
  color: var(--client-ink-dk, #fff);
  border: none;
  font-family: var(--font-body);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 3px;
  text-transform: uppercase;
  cursor: pointer;
  margin-bottom: 8px;
  transition: background 0.15s;
}
.cart-submit-btn:not(:disabled):hover { background: var(--teal-mid); color: var(--client-ink-mid, #fff); }
.cart-submit-btn:disabled {
  background: var(--border);
  color: var(--muted);
  cursor: not-allowed;
}
/* Visible "not ready" state (2026-07-04): the button stays CLICKABLE so a click is never silently
   swallowed - it validates and shows which field is missing - but reads as disabled until ready. */
.cart-submit-btn--disabled {
  background: var(--border);
  color: var(--muted);
}

.cart-submit-status {
  font-size: 0.7rem;
  line-height: 1.5;
  display: none;
  padding: 8px 12px;
}
.cart-submit-status:not(:empty) { display: block; }
.cart-submit-status.status-ok {
  background: #eef5f0;
  border-left: 2px solid var(--green);
  color: var(--green);
}
.cart-submit-status.status-err {
  background: #fdf0ef;
  border-left: 2px solid var(--red);
  color: var(--red);
}

/* ── Cart status bar (index page only) ────────────────────────────────────── */

.cart-status-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  background: var(--teal-pale);
  border: 1px solid var(--border-lt);
  border-left: 3px solid var(--teal-mid);
  margin-bottom: 28px;
  font-size: 0.72rem;
  color: var(--mid);
  font-weight: 300;
  min-height: 40px;
}

.cart-status-msg { flex: 1; }

.cart-status-view {
  padding: 6px 14px;
  background: var(--teal-dk);
  color: var(--client-ink-dk, #fff);
  border: none;
  font-family: var(--font-body);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 2px;
  text-transform: uppercase;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s;
}
.cart-status-view:hover { background: var(--teal-mid); color: var(--client-ink-mid, #fff); }

/* ── Standard products section (index page) ───────────────────────────────── */

.standard-products-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-top: 40px;
  padding-top: 32px;
  border-top: 1px solid var(--border-lt);
  margin-bottom: 16px;
}

.standard-products-subhead {
  font-size: 0.68rem;
  color: var(--muted);
  letter-spacing: 0.5px;
  white-space: nowrap;
}

.products-section-heading {
  font-size: 0.62rem;
  font-weight: 500;
  color: var(--muted);
  letter-spacing: 2.5px;
  text-transform: uppercase;
  margin: 0;
  padding: 0;
  border: none;
}

/* ── Standard product image gallery ────────────────────────────────────────── */

/* AN IMAGE WELL IS ALWAYS WHITE (2026-08-09, brief Task 12). `transparent` showed the page ground
   through, which is --sand - the client's surface_color. Oceanside Alloy's is cream, so every
   product photo on that site sat on a tint, on top of the four different baked-in backgrounds the
   hero images already carry. */
.sp-gallery {
  position: relative;
  background: var(--photo-well-bg);
  overflow: hidden;
  line-height: 0;
}

.sp-slides {
  display: flex;
  transition: none;
}

/* Scoped to .sp-gallery to beat .product-card-img img { display:block } (0,1,1)
   and .product-hero-img { display:block } (0,1,0) via higher specificity (0,2,0). */
.sp-gallery .sp-slide {
  display: none;
  width: 100%;
  /* FRAMED ON THE SAME RATIO AS THE STATIC CARD IMAGE (2026-09-10). A card with several photos
     renders this carousel instead of the plain <img> above, and a card that changed shape when its
     product gained a second photo would be the "two surfaces disagreeing" fault at its most
     visible: two cards side by side in one grid, different heights, same content. The fixed 225px
     is what made that possible and it is replaced rather than kept alongside, because a height and
     an aspect-ratio in one box means the height wins and the ratio is decoration. */
  aspect-ratio: var(--photo-frame-ratio, 1);
  height: auto;
  object-fit: contain;
  background-color: var(--photo-frame-fallback-bg, #faf9f7);
  flex-shrink: 0;
}

.sp-gallery .sp-slide--active {
  display: block;
}

.sp-dots {
  position: absolute;
  bottom: 8px;
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  gap: 6px;
}

.sp-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: rgba(255,255,255,0.5);
  cursor: pointer;
  transition: background 0.15s;
}

.sp-dot--active {
  background: #fff;
}

.sp-arrow-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: rgba(255,255,255,0.85);
  color: #1a1a1a;
  border: 1px solid rgba(0,0,0,0.5);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s;
  z-index: 2;
  padding: 0;
}

.sp-arrow-btn:hover {
  background: rgba(255,255,255,1);
}

.sp-arrow-btn--prev { left: 8px; }
.sp-arrow-btn--next { right: 8px; }

/* ── Powder coat checkbox row ───────────────────────────────────────────────── */

.sp-powder-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
  cursor: pointer;
}

.sp-powder-check {
  accent-color: var(--teal);
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  cursor: pointer;
}

.sp-powder-label {
  font-size: 0.75rem;
  color: var(--body);
  line-height: 1.4;
}

.sp-powder-price {
  color: var(--teal);
  font-weight: 500;
}

/* ── Standard product powder coat colour input ───────────────────────────── */

.sp-powder-colour-row {
  margin-bottom: 8px;
  margin-top: 6px;
}

.sp-powder-colour-input {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--border);
  background: var(--white);
  color: var(--dark);
  font-family: var(--font-body);
  font-size: 0.82rem;
  font-weight: 400;
  transition: border-color 0.15s;
}

.sp-powder-colour-input:focus {
  outline: none;
  border-color: var(--teal-mid);
}

.sp-powder-colour-error {
  font-size: 0.65rem;
  color: var(--red);
  font-weight: 400;
  margin-top: 4px;
}

.product-card-price {
  font-family: var(--font-display);
  font-size: 2rem;
  font-weight: 400;
  color: var(--teal-dk);
  letter-spacing: 0.5px;
  margin-bottom: 12px;
}

.btn-add-to-quote {
  display: block;
  width: 100%;
  padding: 13px;
  background: transparent;
  color: var(--teal);
  border: 1px solid var(--teal);
  font-family: var(--font-body);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 3px;
  text-transform: uppercase;
  text-align: center;
  cursor: pointer;
  margin-top: auto;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.btn-add-to-quote:hover:not(:disabled) {
  background: var(--teal);
  color: var(--client-ink, #fff);
}
.btn-add-to-quote:disabled {
  background: var(--teal-light);
  color: var(--teal-mid);
  border-color: var(--teal-light);
  cursor: default;
}

/* ── Cart success message (shown in panel body after submission) ─────────── */

.cart-success-msg {
  padding: 32px 20px;
  text-align: center;
}

.cart-success-msg p {
  font-size: 0.82rem;
  color: var(--teal-dk);
  font-weight: 400;
  line-height: 1.7;
}

.cart-success-msg p:first-child {
  font-weight: 500;
  font-size: 0.9rem;
  margin-bottom: 6px;
}

/* ── Cart responsive ─────────────────────────────────────────────────────── */

@media (max-width: 780px) {
  .product-grid    { grid-template-columns: 1fr; }
  .header-right    { display: none; }
  .cart-panel      { width: 100vw; right: 0; left: 0; border-radius: 0; }
  .page-hero-title { font-size: 2.2rem; }
  .page-hero-steps { gap: 8px; flex-direction: column; align-items: center; }
  .hero-step-box   { padding: 8px 12px; width: 100%; max-width: 280px; justify-content: center; }
  .quote-cta-bar   { flex-direction: column; align-items: flex-start; gap: 10px; }
}

@media (max-width: 480px) {
  .cart-fab { right: 16px; bottom: 16px; }
  .page     { margin: 24px auto; }
}

/* ── Custom enquiry banner (index page) ───────────────────────────────────── */

.custom-enquiry-banner {
  margin-top: 32px;
  padding-top: 32px;
  border-top: 1px solid var(--border-lt);
}

.custom-enquiry-banner__inner {
  background: var(--teal-pale);
  border: 1px solid var(--border);
  border-left: 3px solid var(--teal-mid);
  padding: 28px 32px;
  display: flex;
  align-items: center;
  gap: 24px;
  flex-wrap: wrap;
}

.custom-enquiry-banner__inner h2 {
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--teal-dk);
  letter-spacing: 2px;
  text-transform: uppercase;
  white-space: nowrap;
}

.custom-enquiry-banner__inner p {
  font-size: 0.82rem;
  color: var(--mid);
  font-weight: 300;
  line-height: 1.65;
  flex: 1;
  min-width: 200px;
}

.custom-enquiry-banner__cta {
  display: inline-block;
  padding: 12px 22px;
  background: var(--teal-dk);
  color: var(--client-ink-dk, #fff);
  border: none;
  font-family: var(--font-body);
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.15s;
}
.custom-enquiry-banner__cta:hover { background: var(--teal-mid); color: var(--client-ink-mid, #fff); }

@media (max-width: 780px) {
  .custom-enquiry-banner__inner { flex-direction: column; align-items: flex-start; gap: 14px; }
  .custom-enquiry-banner__cta   { align-self: flex-start; }
}

/* ── Cart: POA note (mixed cart with custom + priced items) ───────────────── */

.cart-poa-note {
  display: block;
  font-size: 0.6rem;
  font-weight: 400;
  color: var(--muted);
  margin-top: 2px;
}
