/* Shared layout primitives + component classes used across every page.
   Page-specific stylesheets (home.css, shop.css, ...) layer on top of this. */

.container {
  max-width: var(--content-max-width);
  margin: 0 auto;
  padding: 0 48px;
}

.container-wide {
  max-width: var(--shop-max-width);
  margin: 0 auto;
  padding: 0 48px;
}

@media (max-width: 640px) {
  .container, .container-wide {
    padding: 0 20px;
  }
}

.bg-dark { background: var(--color-charcoal-primary); color: var(--color-beige-light); }
.bg-light { background: var(--color-beige-light); color: var(--color-text-dark); }

/* Buttons ------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border-radius: var(--radius-button);
  padding: 16px 34px;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  border: 1px solid transparent;
  transition: var(--ease-standard);
  white-space: nowrap;
}
.btn:disabled { opacity: .5; cursor: not-allowed; }
/* Touch feedback (mobile motion overhaul, 2026-08-21) -- mobile has no
   hover, so every primary button needs its own explicit press state.
   :active fires on tap on touch devices same as it does on mouse-down,
   no extra JS/feature-detection needed. Global reduced-motion block
   above already zeroes this transition when the user asks for it. */
.btn:active:not(:disabled) { transform: scale(.98); transition: transform var(--motion-fast) var(--ease-premium); }
/* Add to Cart success state (mobile motion overhaul, 2026-08-21), Phase
   8 -- brief "Added" confirmation before the button reverts to its
   normal label (see static/js/product-card.js). Reuses the site's real
   success token colors instead of inventing a new green. */
.btn.is-added { background: var(--color-success-text); color: var(--color-white); border-color: var(--color-success-text); transition: background var(--motion-fast) var(--ease-premium), color var(--motion-fast) var(--ease-premium); }

/* Real gap found in the mobile motion audit (2026-08-21), Phase 25: the
   .btn rules above cover every primary CTA sitewide, but the small round
   icon buttons -- sheet/drawer/modal close buttons, product-card
   wishlist/quick-view -- had no press feedback of their own. One shared
   rule instead of duplicating the same declaration across nav.css/
   product-card.css/quick-view.css/video-lightbox.css/ai-chat.css. A
   stronger scale than .btn's .98 (small ~32-40px circular targets need
   more visible movement than a large CTA button to actually register as
   feedback). Deliberately NOT applied to every tappable element on the
   site -- Phase 25 explicitly warns against that; this is one cohesive
   category (dismiss controls + the two per-card icon actions), not a
   blanket rule. */
.mobile-drawer__close:active,
.filter-sidebar__close:active,
.search-panel__close:active,
.ai-chat-panel__close:active,
.quick-view__close:active,
.video-lightbox__close:active,
.product-card__wishlist:active,
.product-card__quick-view:active {
  transform: scale(.9);
  transition: transform var(--motion-fast) var(--ease-premium);
}
.btn-sm { padding: 11px 20px; font-size: 11.5px; }

.btn-primary { background: var(--color-charcoal-primary); color: var(--color-beige-light); }
.btn-primary:hover { background: var(--color-gold-accent); color: var(--color-charcoal-primary); }

.btn-accent { background: var(--color-gold-accent); color: var(--color-charcoal-primary); }
.btn-accent:hover { background: var(--color-gold-hover); }

.btn-secondary { background: transparent; color: var(--color-charcoal-primary); border-color: var(--color-charcoal-primary); }
.btn-secondary:hover { background: var(--color-charcoal-primary); color: var(--color-beige-light); }

.btn-ghost { background: transparent; color: var(--color-beige-light); border-color: rgba(245, 242, 236, .35); }
.btn-ghost:hover { border-color: var(--color-gold-accent); color: var(--color-gold-accent); }

.btn-outline-gold { background: transparent; color: var(--color-gold-accent); border-color: rgba(201, 162, 39, .5); }
.btn-outline-gold:hover { border-color: var(--color-gold-accent); background: rgba(201, 162, 39, .08); }

.btn-block { width: 100%; }

.icon-btn {
  width: 34px; height: 34px;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(15, 15, 15, .6);
  border: none;
  cursor: pointer;
  color: inherit;
}

/* Inputs ---------------------------------------------------------------- */
.field { margin-bottom: 16px; }
.field label {
  display: block;
  font-size: 12px;
  color: var(--color-text-dark-dim-2);
  margin-bottom: 6px;
}
.field input,
.field select,
.field textarea {
  width: 100%;
  border: 1px solid #ddd;
  border-radius: var(--radius-input);
  padding: 12px 14px;
  font-size: 13.5px;
  outline: none;
  box-sizing: border-box;
  font-family: var(--font-sans);
  background: var(--color-white);
  color: var(--color-text-dark);
}
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--color-gold-accent);
  box-shadow: 0 0 0 3px rgba(201, 162, 39, .15);
}
.field.field-error input { border-color: var(--color-error-text); }
.field .field-error-text { color: var(--color-error-text); font-size: 12px; margin-top: 4px; }
/* Real pre-existing gap found and fixed (2026-08-22): components/_field.html
   (the shared field-rendering partial used sitewide) outputs real
   .field-help/.field-errors markup whenever a field has help_text/errors,
   but neither was ever styled outside the dashboard (.dash-shell
   .field-help only) -- any customer-facing form field with real
   help_text (the new wholesale form is the first to actually populate
   several) rendered that text at the browser's unstyled default size,
   several times larger than the label above it. */
.field-help { font-size: 12px; color: var(--color-text-dark-dim-2); margin-top: 5px; }
.field-errors { list-style: none; margin: 5px 0 0; padding: 0; }
.field-errors li { color: var(--color-error-text); font-size: 12px; }
/* .field-dark (checkout/custom-order/wholesale forms on a dark page
   background) needs its own light-on-dark override for both, matching
   the same pattern already used for .field-dark label just below. */
.field-dark .field-help { color: var(--color-text-grey-dim); }

.field-dark input, .field-dark select, .field-dark textarea {
  background: var(--color-charcoal-primary);
  border-color: rgba(255, 255, 255, .15);
  color: var(--color-beige-light);
}
.field-dark label { color: var(--color-text-grey-dim); }

/* Badges / pills --------------------------------------------------------- */
.badge {
  display: inline-block;
  background: var(--color-gold-accent);
  color: var(--color-charcoal-primary);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .04em;
  padding: 5px 10px;
  border-radius: 5px;
}
.status-pill {
  display: inline-block;
  border-radius: 20px;
  padding: 5px 14px;
  font-size: 11.5px;
  font-weight: 600;
}
.status-pill--success { background: var(--color-success-bg); color: var(--color-success-text); }
.status-pill--error { background: var(--color-error-bg); color: var(--color-error-text); }
.status-pill--info { background: var(--color-info-bg); color: var(--color-info-text); }
.status-pill--neutral { background: #eee; color: #777; }

/* Form alerts (auth forms, checkout errors, order tracking, dashboard
   forms) — a genuinely site-wide component, so it lives here rather than
   in any one page's stylesheet; was previously trapped in auth.css
   scoped under .auth-card, so every other page using it rendered
   unstyled. -------------------------------------------------------------*/
.form-alert { border-radius: 10px; padding: 12px 16px; font-size: 13px; margin-bottom: 18px; }
.form-alert--error { background: var(--color-error-bg); color: var(--color-error-text); }
.form-alert--success { background: var(--color-success-bg); color: var(--color-success-text); }

/* Order-summary rows (Cart, Checkout sidebar) ------------------------------*/
.summary-row { display: flex; justify-content: space-between; font-size: 13.5px; padding: 8px 0; color: var(--color-text-dark-dim-2); }
.summary-row--discount { color: var(--color-success-text); }
.summary-total { background: var(--color-beige-light); border-radius: 10px; padding: 16px; margin-top: 14px; display: flex; justify-content: space-between; align-items: center; }
.summary-total .label { font-family: var(--font-serif); font-size: 16px; }
.summary-total .value { font-family: var(--font-serif); font-size: 19px; color: var(--color-brown-leather); }

/* Section headers (About/Contact/FAQ/Custom Order/Home) — values copied
   verbatim from home.css (the original/only page that had them styled),
   not reinvented, so this move doesn't change how any page looks. -------*/
.section-header { text-align: center; margin-bottom: 48px; }
.section-eyebrow { color: var(--color-gold-accent); font-size: 12px; letter-spacing: .25em; font-weight: 600; margin-bottom: 12px; }
.section-title { font-family: var(--font-serif); font-size: 36px; margin: 0; font-weight: 600; }
@media (max-width: 639px) { .section-title { font-size: 26px; } }

/* Real gap found and fixed (2026-08-22, mobile-width audit): .section
   itself (the padding/max-width wrapper these headers sit inside) was
   left behind in home.css when .section-header/-eyebrow/-title were
   moved here -- fine while only home.html used the class, but wholesale.html
   also reuses "section" for its whole page layout and doesn't load
   home.css, so every section on /wholesale/ silently got ZERO padding,
   causing real horizontal overflow on mobile (confirmed: a CTA button's
   text pushed the page 20px past the 320px viewport). Moved here so any
   page can safely reuse the class, same fix shape as the headers above. */
.section { padding: 88px 48px; max-width: var(--content-max-width); margin: 0 auto; }
.section--tight-top { padding-top: 20px; }
@media (max-width: 639px) { .section { padding: 56px 20px; } }

/* Cards ------------------------------------------------------------------ */
.card {
  background: var(--color-white);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card-resting-light);
  transition: transform .3s ease, box-shadow .3s ease;
}
.card:hover { transform: translateY(-4px); box-shadow: var(--shadow-card-hover-light); }

.card-dark {
  background: var(--color-charcoal-secondary);
  border: 1px solid rgba(255, 255, 255, .06);
  border-radius: var(--radius-card);
  transition: transform .3s ease, box-shadow .3s ease;
}
.card-dark:hover { transform: translateY(-6px); box-shadow: var(--shadow-card-hover-dark); }

/* Breadcrumb --------------------------------------------------------------*/
.breadcrumb { font-size: 12.5px; color: var(--color-text-grey); }
.breadcrumb a:hover { color: var(--color-gold-accent); }
.breadcrumb .current { color: var(--color-beige-light); }

/* Banner strip (Shop/Cart/Checkout/Account/Wishlist header) ---------------*/
/* User-reported (2026-08-24): "i want a picture in the background" --
   no real product photo exists to use yet (PageBanner.image is a real,
   already-wired field; a page with no photo uploaded fell back to
   nothing behind the ::before overlay below, which is a semi-
   transparent BLACK gradient meant to sit over a photo -- with
   nothing under it, it just showed .bg-light's cream bleeding through
   at different opacities, reading as a flat, oddly-colored strip, not
   a deliberate design). This is a real textured leather-grain surface
   (an SVG feTurbulence noise filter tinted warm brown, not a flat
   gradient) so the banner looks intentional today -- the inline
   background-image the template already sets the moment a real photo
   IS uploaded (page_banner templatetag, templates/pages/shop.html
   etc.) overrides this automatically (inline style always wins over
   an external stylesheet for the same property), so nothing here
   needs to change again once real photography exists. */
.page-banner {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0 48px;
}
/* Scoped to a real "no photo yet" class (templates add it only when
   banner.image is falsy) rather than bare .page-banner, deliberately --
   background-blend-mode isn't part of the `background` shorthand, and
   an inline background-image set the moment a real photo IS uploaded
   would never override a blend-mode on its own, so scoping this
   narrowly means a future real photo can never end up tinted by a
   blend-mode leftover from this placeholder. */
.page-banner--placeholder {
  /* Real deploy-breaking bug found and fixed, twice over -- worth
     spelling out plainly since it bit this exact rule twice. Django's
     collectstatic post-processor regex-scans every CSS file for the
     literal three characters u-r-l followed by an open parenthesis,
     to find and hash asset references, and it does this on the RAW
     file text -- comments included, not just live declarations. Two
     separate things tripped it here: (1) a URL-encoded plaintext SVG
     data payload (the first attempt at this rule) whose own embedded
     SVG filter reference used that same open-parenthesis syntax
     un-encoded, which the scanner's non-greedy match locked onto
     inside the payload instead of the payload's real end; (2) once
     fixed with base64 (whose alphabet contains no parentheses at
     all), the FIX's OWN commit message -- written in prose, right
     here in this comment -- happened to spell out the broken syntax
     verbatim to explain it, which is scanned exactly the same as live
     code and broke the exact same way. Base64 avoids the first
     failure mode for the real declaration below; this comment
     deliberately avoids ever spelling out that syntax again, even in
     prose, to avoid the second. */
  background:
    linear-gradient(135deg, rgba(139,94,60,.55), rgba(15,15,15,.15)),
    url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyMDAnIGhlaWdodD0nMjAwJz48ZmlsdGVyIGlkPSduJz48ZmVUdXJidWxlbmNlIHR5cGU9J2ZyYWN0YWxOb2lzZScgYmFzZUZyZXF1ZW5jeT0nMC45JyBudW1PY3RhdmVzPSczJyBzdGl0Y2hUaWxlcz0nc3RpdGNoJy8+PGZlQ29sb3JNYXRyaXggdHlwZT0nc2F0dXJhdGUnIHZhbHVlcz0nMCcvPjwvZmlsdGVyPjxyZWN0IHdpZHRoPScxMDAlJyBoZWlnaHQ9JzEwMCUnIGZpbHRlcj0ndXJsKCNuKScgb3BhY2l0eT0nMC4zNScvPjwvc3ZnPg=="),
    linear-gradient(90deg, #2b1c10, #4a3320 55%, #1a120a);
  background-blend-mode: overlay, normal;
}
.page-banner::before {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(90deg, rgba(15,15,15,.9), rgba(15,15,15,.45));
}
.page-banner img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: -1; }
.page-banner > * { position: relative; z-index: 1; }
.page-banner h1 { font-family: var(--font-serif); color: var(--color-beige-light); margin: 0; font-weight: 600; }

/* Skeleton loaders ----------------------------------------------------- */
.skeleton {
  background: linear-gradient(90deg, rgba(0,0,0,.06) 25%, rgba(0,0,0,.11) 37%, rgba(0,0,0,.06) 63%);
  background-size: 400% 100%;
  animation: skeleton-shimmer 1.4s ease infinite;
  border-radius: var(--radius-card);
}
@keyframes skeleton-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

/* Toasts ------------------------------------------------------------------*/
#toast-container {
  position: fixed;
  top: 20px; right: 20px;
  z-index: 500;
  display: flex; flex-direction: column; gap: 10px;
}
@media (max-width: 640px) { #toast-container { top: auto; bottom: 20px; left: 20px; right: 20px; } }
.toast {
  min-width: 260px;
  max-width: 360px;
  border-radius: 10px;
  padding: 14px 18px;
  font-size: 13px;
  display: flex; align-items: center; gap: 10px;
  box-shadow: var(--shadow-dropdown);
  animation: toast-in .25s ease;
}
.toast--success { background: var(--color-success-bg); color: var(--color-success-text); }
.toast--error { background: var(--color-error-bg); color: var(--color-error-text); }
.toast--info { background: var(--color-info-bg); color: var(--color-info-text); }
@keyframes toast-in { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } }

/* Notify Me dialog (real gap found and fixed 2026-08-22, see the
   component template's own comment for the two real bugs this closes:
   a dead button on every page except the PDP, and window.prompt() where
   it did work). Sitewide -- shared across every page that can show an
   out-of-stock product card, so it's styled as a neutral white card,
   not tied to any one page's own light/dark background. */
.notify-me-dialog {
  border: none;
  border-radius: 14px;
  padding: 28px;
  max-width: 380px;
  width: calc(100% - 40px);
  background: var(--color-white);
  box-shadow: var(--shadow-card-hover-light, 0 20px 50px rgba(0,0,0,.2));
}
.notify-me-dialog::backdrop { background: rgba(15,15,15,.55); }
.notify-me-dialog h2 { font-family: var(--font-serif); font-size: 20px; margin: 0 0 8px; color: var(--color-text-dark); }
.notify-me-dialog p { font-size: 13px; color: var(--color-text-dark-dim-2, #6b6b6b); margin: 0 0 16px; }
.notify-me-dialog input {
  width: 100%;
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 12px 14px;
  font-size: 13.5px;
  font-family: inherit;
  box-sizing: border-box;
}
.notify-me-dialog__actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }

/* Signup discount popup feature (static/js/signup-discount-popup.js) --
   the one deliberately "premium branded" dialog on the site, so it gets
   its own darker leather-toned treatment rather than the neutral white
   card the utility dialogs above use.

   Real gaps found and fixed 2026-08-30, user-reported:
   1. The layout was ~680px of content against real viewports as short as
      300-600px, so the email-capture button sat below the fold, hidden
      behind a scrollbar inside the dialog. Rebuilt around a compact
      badge hero (one flexible-height element instead of a 3-line serif
      heading) and a single-row email+button, instead of stacking a
      heading, a paragraph, a divider, a second paragraph, an input and a
      button as five separate full-width blocks. max-height/overflow-y
      below is now a genuine last-resort safety net, not the primary way
      the content gets seen.
   2. .btn-secondary (charcoal text/border) is invisible against this
      dialog's own near-black background — it was designed for light
      surfaces. The email button now uses .btn-ghost (light text, already
      built for dark grounds) instead. */
.signup-discount-dialog {
  position: relative;
  border: none;
  border-radius: 16px;
  padding: 30px 28px;
  max-width: 380px;
  width: calc(100% - 40px);
  max-height: min(560px, 90vh);
  overflow-y: auto;
  background: var(--color-text-dark, #1c1712);
  color: var(--color-beige-light);
  box-shadow: 0 0 0 1px rgba(201,162,39,.18), var(--shadow-card-hover-light, 0 24px 60px rgba(0,0,0,.4));
  text-align: center;
  animation: signup-discount-in .32s var(--ease-premium, ease-out);
}
@keyframes signup-discount-in {
  from { opacity: 0; transform: translateY(10px) scale(.97); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.signup-discount-dialog::backdrop { background: rgba(15,15,15,.6); }
.signup-discount-dialog__close {
  position: absolute;
  top: 12px;
  right: 14px;
  background: none;
  border: none;
  color: var(--color-beige-light);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  opacity: .65;
  padding: 4px;
}
.signup-discount-dialog__close:hover { opacity: 1; color: var(--color-gold-accent); }
.signup-discount-dialog__eyebrow { font-size: 10.5px; letter-spacing: .28em; color: var(--color-gold-accent); margin: 4px 0 14px; }
/* Compact "hero" number badge replaces the old multi-line serif heading
   -- carries more visual punch (the whole point of the discount) in
   noticeably less vertical space, with a soft gold glow behind it for
   the "cool/attractive" premium feel the copy-only version lacked. */
.signup-discount-dialog__badge {
  position: relative;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 6px;
  margin: 0 auto 6px;
}
.signup-discount-dialog__badge::before {
  content: "";
  position: absolute;
  inset: -30px;
  background: radial-gradient(circle, rgba(201,162,39,.28) 0%, rgba(201,162,39,0) 70%);
  z-index: 0;
}
.signup-discount-dialog__badge-num { position: relative; z-index: 1; font-family: var(--font-serif); font-size: 54px; line-height: 1; font-weight: 600; color: var(--color-gold-accent); }
.signup-discount-dialog__badge-num small { font-size: 24px; font-weight: 600; }
.signup-discount-dialog__badge-label { position: relative; z-index: 1; font-size: 15px; letter-spacing: .2em; align-self: center; color: var(--color-beige-light); }
.signup-discount-dialog__lede { font-family: var(--font-serif); font-size: 16px; color: var(--color-beige-light); margin: 0 0 10px; }
.signup-discount-dialog p { font-size: 12.5px; line-height: 1.5; color: var(--color-brown-leather); margin: 0 0 14px; }
.signup-discount-dialog .btn-block { width: 100%; }
.signup-discount-dialog .btn { padding: 13px 20px; }
.signup-discount-dialog__divider { display: flex; align-items: center; gap: 10px; margin: 16px 0 12px; font-size: 10.5px; letter-spacing: .08em; color: var(--color-brown-leather); text-transform: uppercase; }
.signup-discount-dialog__divider::before, .signup-discount-dialog__divider::after { content: ""; flex: 1; height: 1px; background: rgba(255,255,255,.15); }
/* Single row instead of a stacked input + full-width button -- the
   biggest single height saving in this redesign. */
.signup-discount-dialog__email-row { display: flex; gap: 8px; }
.signup-discount-dialog__email-row .btn { flex: 0 0 auto; white-space: nowrap; }
.signup-discount-dialog input {
  flex: 1;
  min-width: 0;
  border: 1px solid rgba(255,255,255,.2);
  border-radius: 8px;
  padding: 12px 14px;
  font-size: 13.5px;
  font-family: inherit;
  box-sizing: border-box;
  background: rgba(255,255,255,.06);
  color: var(--color-beige-light);
}
.signup-discount-dialog__error { font-size: 12.5px; color: var(--color-error-text, #e08b8b); margin-top: 10px; margin-bottom: 0; }
.signup-discount-dialog__code {
  font-family: var(--font-serif);
  font-size: 22px;
  letter-spacing: .06em;
  color: var(--color-gold-accent);
  border: 1px dashed var(--color-gold-accent);
  border-radius: 8px;
  padding: 10px;
  margin: 4px 0 14px;
}

/* Generic confirm dialog (window.akConfirm, static/js/confirm-dialog.js)
   -- real gap found and fixed 2026-08-22, see the component template's
   own comment. Reuses the same neutral white-card treatment as the
   Notify Me dialog above since both are sitewide and page-background-
   agnostic. */
.ak-confirm-dialog {
  border: none;
  border-radius: 14px;
  padding: 28px;
  max-width: 380px;
  width: calc(100% - 40px);
  background: var(--color-white);
  box-shadow: var(--shadow-card-hover-light, 0 20px 50px rgba(0,0,0,.2));
}
.ak-confirm-dialog::backdrop { background: rgba(15,15,15,.55); }
.ak-confirm-dialog p { font-size: 14px; color: var(--color-text-dark); margin: 0; line-height: 1.5; }
.ak-confirm-dialog__actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }

/* Visually-hidden (accessible labels/text) -------------------------------*/
.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* WhatsApp FAB ------------------------------------------------------------*/
.whatsapp-fab {
  position: fixed; bottom: 26px; right: 26px; z-index: 300;
  width: 58px; height: 58px; border-radius: 50%;
  background: var(--color-charcoal-primary);
  border: 1.5px solid var(--color-gold-accent);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 12px 32px rgba(0,0,0,.5);
  transition: transform .2s ease;
}
.whatsapp-fab:hover { transform: scale(1.08); }

/* Real bug found and fixed (2026-08-22): confirmed live at 375px -- this
   fixed-position FAB (plus .ai-chat-fab stacked above it, ai-chat.css)
   sits on top of whatever page content scrolls underneath, including
   real interactive controls (e.g. a product card's wishlist heart
   button on the Shop grid becomes genuinely unclickable, not just
   visually covered, since a touch on that screen position hits the
   opaque FAB first). Shrinking both on mobile -- still comfortably
   above the 44px accessible touch-target minimum this codebase already
   follows elsewhere -- meaningfully reduces that obstruction footprint
   without reversing the deliberate "read as one grouped stack" corner
   choice documented in ai-chat.css. */
@media (max-width: 639px) {
  .whatsapp-fab { width: 48px; height: 48px; bottom: 16px; right: 16px; }
}

/* Real, live bug found and fixed (2026-09-11, user-reported: "mobile
   view ... having issues ... our main sales come from mobile"):
   confirmed via document.elementFromPoint on a real 375px viewport that
   this FAB doesn't just visually cover the product page's sticky
   Add to Cart/Buy Now bar (product-detail.css .sticky-action-bar,
   z-index 250) once scrolled into view -- it's genuinely on top with a
   higher z-index (300), so a real tap on that screen position opened
   WhatsApp instead of completing the purchase. static/js/product-
   detail.js toggles this class exactly when the bar is visible; pushed
   up clear above the bar's real measured height (66px) plus a small
   gap, mobile widths only -- the one viewport this was ever able to
   actually happen on, since the bar and FAB only get this close
   together at narrow widths. */
@media (max-width: 639px) {
  body.has-sticky-action-bar .whatsapp-fab { bottom: 92px; }
}
