/* GENERATED by tools/assemble.py — do not edit.
   Source: shared/css/style.css and the partials it imports.
   Edit those; this file is rebuilt on every run. */

/* ===== partials/_variables.css ===== */
/* =========================================================================
   GulfRabit · Design Tokens
   Single source of truth for colour, type, spacing, radius, elevation, motion.
   Every other partial and module reads from these custom properties — never
   hard-code a hex, a shadow, or a radius elsewhere.
   ========================================================================= */

:root {
  /* ---- Brand · Primary ------------------------------------------------- */
  --gr-cyan:        #1BB4D4;   /* primary — CTAs, links, active states       */
  --gr-cyan-dark:   #0E7C99;   /* hover / pressed, deep accents, gradient    */
  --gr-lime:        #9ACD3C;   /* secondary — highlights, success/sale badge */
  --gr-lime-light:  #C3E86B;   /* light accents, gradient partner            */

  /* ---- Brand · Neutrals (the premium base — 80% of the canvas) --------- */
  --gr-black:       #0A0A0A;   /* primary background (warm black)            */
  --gr-charcoal:    #151515;   /* card / section backgrounds on black        */
  --gr-graphite:    #1F1F1F;   /* elevated surfaces, input fields            */
  --gr-border:      #2A2A2A;   /* hairline borders on dark surfaces          */
  --gr-white:       #FFFFFF;   /* text on dark / background on light         */
  --gr-off-white:   #F7F8F7;   /* light-mode section backgrounds             */
  --gr-ink:         #101314;   /* primary text on light backgrounds          */
  --gr-gray-500:    #8A8F8C;   /* secondary / muted text                     */
  --gr-gray-300:    #D8DBD9;   /* dividers on light backgrounds              */

  /* ---- Surface roles · LIGHT THEME ------------------------------------- */
  /* The palette above lists brand colours; THESE are what the UI actually
     paints with. Every partial and module reads the role, never the raw hex,
     so the whole canvas can be re-themed from this block alone.             */
  --surface-page:    #FFFFFF;  /* the page canvas                           */
  --surface-raised:  #FFFFFF;  /* cards, menus, modals, drawers (+ border)  */
  --surface-sunken:  #F4F6F5;  /* inputs, image wells, hovered rows         */
  --surface-band:    #F7F8F7;  /* alternating section bands, footer         */
  --surface-inverse: #0A0A0A;  /* deliberate dark blocks (rare)             */

  --border-hairline: #E5E9E7;  /* dividers, card outlines (decorative)      */
  --border-input:    #808A85;  /* form controls — meets 3:1 for UI          */

  /* Text ramp — every value below is AA-contrast on --surface-page.        */
  --text-primary:    #101314;  /* headings + body            18.5:1         */
  --text-secondary:  #414B47;  /* supporting copy, labels     9.0:1         */
  --text-muted:      #667069;  /* captions, meta              5.1:1         */
  --text-on-accent:  #04222b;  /* text sitting ON cyan/lime fills           */
  --text-inverse:    #FFFFFF;  /* text on --surface-inverse                 */

  /* Brand colours re-cut for legibility on white. Raw --gr-cyan (2.5:1) and
     --gr-lime fail contrast as text on light — use these for anything that
     carries meaning; keep the raw brand hues for fills, borders and glows.  */
  --link:            #0E7C99;  /* links, overlines            4.8:1         */
  --link-hover:      #0A5F76;  /*                             6.4:1         */
  --accent-ink:      #0E7C99;  /* meaningful icons on light   4.8:1         */
  --gold-ink:        #8A6D22;  /* premium/VIP text            4.8:1         */
  --lime-ink:        #547C10;  /* stars, success text         4.9:1         */

  /* Washes & scrims — dark-on-light now, the inverse of the dark theme.    */
  --wash-hover:      rgba(16,19,20,.055);
  --wash-shimmer:    rgba(16,19,20,.055);
  --scrim:           rgba(16,19,20,.45);
  --glass-header:    rgba(255,255,255,.82);
  --glass-overlay:   rgba(255,255,255,.94);
  --scrollbar-thumb: #C3CAC6;

  /* ---- Semantic -------------------------------------------------------- */
  --gr-success:     #9ACD3C;   /* reuse lime — no generic green              */
  --gr-error:       #E5484D;
  --gr-warning:     #E8B342;
  --gr-gold-accent: #C9A24B;   /* ONLY Premium/Imported/VIP luxury details   */

  /* ---- Brand gradient (accents only — never a full page background) ---- */
  --gr-gradient:      linear-gradient(135deg, #1BB4D4 0%, #9ACD3C 100%);
  --gr-gradient-soft: linear-gradient(135deg, rgba(27,180,212,.14) 0%, rgba(154,205,60,.14) 100%);
  /* Gradient for TEXT on light: the raw brand gradient reads at 1.9:1 on white
     and is unreadable, so display type uses this ink-weight cut instead. */
  --gr-gradient-ink:  linear-gradient(135deg, #0E7C99 0%, #4E7A0F 100%);

  /* ---- Typography ------------------------------------------------------ */
  --font-display: 'Clash Display', 'General Sans', 'Inter', system-ui, sans-serif;
  /* Bengali sits in the stack so Bangla renders correctly ANYWHERE, not only
     inside a [lang="bn"] wrapper — unicode-range means it only downloads when
     Bengali codepoints are actually present. */
  --font-body:    'Inter', 'Noto Sans Bengali', 'Noto Kufi Arabic', system-ui, -apple-system, sans-serif;

  /* Modular type scale · 1.25 ratio — pull sizes from here, never ad-hoc.  */
  --fs-12: 0.75rem;   /* 12 — micro tags, legal        */
  --fs-14: 0.875rem;  /* 14 — small text, captions     */
  --fs-16: 1rem;      /* 16 — body base                */
  --fs-20: 1.25rem;   /* 20 — lead, small headings     */
  --fs-25: 1.5625rem; /* 25 — H4                       */
  --fs-31: 1.9375rem; /* 31 — H3                       */
  --fs-39: 2.4375rem; /* 39 — H2                       */
  --fs-49: 3.0625rem; /* 49 — H1                       */

  --fw-regular:  400;
  --fw-medium:   500;
  --fw-semibold: 600;
  --fw-bold:     700;

  --lh-tight:  1.15;
  --lh-snug:   1.3;
  --lh-normal: 1.6;

  /* ---- Spacing · 8px baseline grid (precision alignment, no eyeballing) */
  --space-1: 0.25rem;  /*  4 */
  --space-2: 0.5rem;   /*  8 */
  --space-3: 0.75rem;  /* 12 */
  --space-4: 1rem;     /* 16 */
  --space-5: 1.5rem;   /* 24 */
  --space-6: 2rem;     /* 32 */
  --space-7: 3rem;     /* 48 */
  --space-8: 4rem;     /* 64 */
  --space-9: 6rem;     /* 96 */
  --space-10: 8rem;    /* 128 — section rhythm on large screens */

  /* ---- Radius · exactly two, never deviate ----------------------------- */
  --radius-sm: 8px;    /* inputs, buttons, tags, small controls */
  --radius-lg: 16px;   /* cards, modals, media, large surfaces  */
  --radius-pill: 999px;

  /* ---- Elevation · soft, low-opacity, large-blur (never harsh drops) ---
     Tuned for a white canvas: on light backgrounds a shadow reads far louder
     than the same shadow on black, so these are ~a third of the dark-theme
     opacity. Depth comes from the hairline border first, shadow second.    */
  --shadow-sm: 0 1px 2px rgba(16,19,20,.05), 0 1px 3px rgba(16,19,20,.07);
  --shadow-md: 0 4px 16px rgba(16,19,20,.08), 0 2px 6px rgba(16,19,20,.06);
  --shadow-lg: 0 18px 48px rgba(16,19,20,.12), 0 6px 16px rgba(16,19,20,.08);
  /* Brand glow for hero CTAs / focus rings */
  --shadow-glow: 0 8px 24px rgba(27,180,212,.32);

  /* ---- Motion · subtle, crafted (no bounce, no gimmick easing) --------- */
  --ease-out:   cubic-bezier(0.16, 1, 0.3, 1);
  --ease-inout: cubic-bezier(0.65, 0, 0.35, 1);
  --dur-fast:   150ms;
  --dur-base:   200ms;
  --dur-slow:   300ms;

  /* ---- Layout ---------------------------------------------------------- */
  --container-max: 1400px;
  --header-h: 76px;
  --z-header: 1000;
  --z-drawer: 1050;
  --z-toast:  1100;
  --z-modal:  1090;
}

/* ===== partials/_fonts.css ===== */
/* =========================================================================
   GulfRabit · Fonts
   One style concern, one partial. Latin comes from Google Fonts (Inter);
   Bengali is SELF-HOSTED here.
   ========================================================================= */

/* ---- Bengali ------------------------------------------------------------
   Not one of Shajgoj, Ghorer Bazar or Daraz loads a Bengali webfont. Daraz
   self-hosts Roboto with cyrillic, greek and vietnamese subsets and NO Bengali
   at all, while Bangla is their mobile default language — so Bangla text on the
   biggest storefront in the country renders in whatever the device happens to
   have. Fixing that is the cheapest visible advantage available to this project.

   Noto Sans Bengali, SIL Open Font License 1.1 — free to self-host and
   redistribute. Self-hosted rather than linked so it survives a strict CSP,
   costs no third-party connection, and cannot be broken by a CDN outage.

   ONE file covers every weight: Google serves byte-identical woff2 for
   wght@400, wght@600 and wght@100..900, so this is the variable font and the
   `font-weight: 100 900` range is the correct descriptor. Declaring discrete
   weights instead would have shipped the same 105 KB twice.

   unicode-range is the important part: the browser downloads this file ONLY
   when Bengali codepoints are actually on the page. Latin pages pay nothing.
   Range copied verbatim from Google's own bengali subset. */
@font-face {
  font-family: 'Noto Sans Bengali';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../../assets/fonts/noto-sans-bengali-variable.woff2') format('woff2');
  unicode-range: U+0951-0952, U+0964-0965, U+0980-09FE, U+1CD0, U+1CD2,
                 U+1CD5-1CD6, U+1CD8, U+1CE1, U+1CEA, U+1CED, U+1CF2,
                 U+1CF5-1CF7, U+200C-200D, U+20B9, U+25CC, U+A8F1;
}

/* Bangla runs slightly larger than Latin at the same px size and needs more
   leading for its ascenders and matras. Applied by language, so mixed-script
   pages get it automatically without a wrapper class. */
[lang="bn"],
.bn {
  font-family: 'Noto Sans Bengali', var(--font-body);
  line-height: 1.75;
  /* Bangla has no case distinction — uppercasing produces nonsense. */
  text-transform: none;
  letter-spacing: 0;
}

/* ===== partials/_typography.css ===== */
/* =========================================================================
   GulfRabit · Typography
   One typographic voice, applied consistently. Sizes come only from the
   scale in _variables.css. Prices use tabular-nums so they align in columns.
   ========================================================================= */

html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

body {
  font-family: var(--font-body);
  font-size: var(--fs-16);
  line-height: var(--lh-normal);
  font-weight: var(--fw-regular);
  color: var(--text-primary);
  background-color: var(--surface-page);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* ---- Headings — display face, tight leading, balanced wrapping -------- */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  letter-spacing: -0.01em;
  color: inherit;
  margin: 0;
  text-wrap: balance;
}

/* Tracking tightens as the type grows.
 *
 * Letter-spacing is optical, not a constant. The gaps a face needs at 16px are
 * visibly loose at 49px — the word stops reading as one shape and starts
 * reading as separate letters, which is the difference between a headline that
 * looks set and one that looks typed. Every display face is drawn on this
 * assumption; Clash Display especially.
 *
 * The values step with the size rather than being one -0.01em for everything,
 * which is what the file did before.
 */
h1, .h1 { font-size: clamp(2.25rem, 5vw, var(--fs-49)); font-weight: var(--fw-bold); letter-spacing: -0.028em; }
h2, .h2 { font-size: clamp(1.9rem, 4vw, var(--fs-39)); letter-spacing: -0.022em; }
h3, .h3 { font-size: clamp(1.5rem, 3vw, var(--fs-31)); letter-spacing: -0.016em; }
h4, .h4 { font-size: var(--fs-25); }
h5, .h5 { font-size: var(--fs-20); font-weight: var(--fw-semibold); }
h6, .h6 { font-size: var(--fs-16); font-weight: var(--fw-semibold); letter-spacing: 0; }

p { margin: 0; text-wrap: pretty; }
p + p { margin-top: var(--space-4); }

/* ---- Lead / secondary / captions ------------------------------------- */
.lead      { font-size: var(--fs-20); line-height: var(--lh-snug); color: var(--text-secondary); }
.text-muted-gr { color: var(--text-muted); }
.caption   { font-size: var(--fs-14); color: var(--text-muted); }
.overline  {
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--link);
}

/* ---- Links ----------------------------------------------------------- */
a { color: var(--link); text-decoration: none; transition: color var(--dur-fast) var(--ease-out); }
a:hover { color: var(--link-hover); }

/* ---- Prices / numerals — align in lists and carts -------------------- */
/* nowrap: formatBDT renders "৳ 1,450" with a real space, so a narrow column
   (2-up cards at 375px) would otherwise break the symbol onto its own line. */
.price, .tabular { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }
.price { white-space: nowrap; }
.price { font-weight: var(--fw-semibold); }
.price--strike { color: var(--text-muted); text-decoration: line-through; font-weight: var(--fw-regular); }

/* ---- Gradient text (used sparingly for a single hero accent word) ---- */
.text-gradient {
  background: var(--gr-gradient-ink);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

/* ---- Arabic content inherits the Kufi fallback automatically --------- */
[lang="ar"], .arabic { font-family: 'Noto Kufi Arabic', var(--font-body); direction: rtl; }

/* ===== partials/_buttons.css ===== */
/* =========================================================================
   GulfRabit · Buttons
   Micro-interactions: slight scale/opacity shift on hover (not colour-snap).
   One radius (--radius-sm). Reuse — never restyle buttons per page.
   ========================================================================= */

.btn-gr {
  --_bg: var(--surface-sunken);
  --_fg: var(--text-primary);
  --_bd: var(--border-hairline);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 0.75rem 1.5rem;
  min-height: 48px;
  font-family: var(--font-body);
  font-size: var(--fs-16);
  font-weight: var(--fw-semibold);
  line-height: 1;
  color: var(--_fg);
  background: var(--_bg);
  border: 1px solid var(--_bd);
  border-radius: var(--radius-sm);
  cursor: pointer;
  user-select: none;
  text-align: center;
  transition: transform var(--dur-base) var(--ease-out),
              opacity var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
/* The lift is a POINTER affordance. On touch, :hover applies on tap and stays,
   so every button on the site sat 2px high with a shadow after being pressed —
   permanently, until something else was tapped. Read as "this button is
   waiting for you" on a form somebody had already submitted. */
@media (hover: hover) {
  .btn-gr:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
}
/* Touch gets the press instead, and it is the more honest feedback anyway:
   the finger pushes down, so the button goes down. */
.btn-gr:active { transform: translateY(0); opacity: 0.9; }
.btn-gr:focus-visible {
  outline: 2px solid var(--gr-cyan);
  outline-offset: 2px;
}
.btn-gr:disabled { opacity: 0.45; cursor: not-allowed; transform: none; box-shadow: none; }

/* ---- Variants -------------------------------------------------------- */
.btn-primary-gr {
  --_bg: var(--gr-cyan);
  --_fg: #04222b;
  --_bd: transparent;
}
.btn-primary-gr:hover { --_bg: var(--gr-cyan-dark); --_fg: var(--text-inverse); box-shadow: var(--shadow-glow); }

.btn-gradient-gr {
  background: var(--gr-gradient);
  color: #04222b;
  border-color: transparent;
}
.btn-gradient-gr:hover { box-shadow: var(--shadow-glow); }

.btn-outline-gr {
  --_bg: transparent;
  --_fg: var(--text-primary);
  --_bd: var(--border-hairline);
}
.btn-outline-gr:hover { --_bd: var(--gr-cyan-dark); --_fg: var(--link); }

.btn-ghost-gr {
  --_bg: transparent;
  --_fg: var(--text-secondary);
  --_bd: transparent;
  min-height: 40px;
  padding: 0.5rem 0.9rem;
}
@media (hover: hover) {
  .btn-ghost-gr:hover { --_fg: var(--text-primary); background: var(--wash-hover); box-shadow: none; transform: none; }
}

/* On light sections */
.btn-outline-gr--light { --_fg: var(--text-primary); --_bd: var(--border-hairline); }
.btn-outline-gr--light:hover { --_bd: var(--gr-cyan-dark); --_fg: var(--gr-cyan-dark); }

/* ---- Sizes ----------------------------------------------------------- */
.btn-sm-gr { min-height: 38px; padding: 0.45rem 0.9rem; font-size: var(--fs-14); }
.btn-lg-gr { min-height: 56px; padding: 1rem 2rem; font-size: var(--fs-20); }
.btn-block-gr { width: 100%; }

/* ---- Icon-only round button (header actions, steppers) --------------- */
.btn-icon-gr {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-primary);
  cursor: pointer;
  position: relative;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.btn-icon-gr:hover { background: var(--wash-hover); color: var(--link); }
.btn-icon-gr:focus-visible { outline: 2px solid var(--gr-cyan); outline-offset: 2px; }

/* ===== partials/_cards.css ===== */
/* =========================================================================
   GulfRabit · Cards (product card, category card, generic surface)
   Gentle lift on hover (translateY -4px + shadow, 200ms). Radius --radius-lg.
   ========================================================================= */

/* ---- Generic elevated surface ---------------------------------------- */
.surface-gr {
  background: var(--surface-raised);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
}

/* ---- Product card ---------------------------------------------------- */
.product-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface-raised);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: transform var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
/* Hover effects are gated on a device that HAS hover.
 *
 * Without the query, a phone applies :hover on tap and then keeps it until you
 * tap something else — so a card you touched stays lifted and outlined, which
 * reads as "selected" and is not. Every hover rule below is inside the guard;
 * touch gets the press state instead, further down. */
@media (hover: hover) {
  .product-card:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-lg);
    border-color: var(--gr-cyan-dark);
  }
}

/* The press state, which is what a phone actually has. Settling INTO the page
 * rather than lifting out of it — the gesture is a push, and matching it is
 * the difference between a card that feels physical and one that feels like a
 * div that changed colour. */
.product-card:active { transform: scale(.985); }

@media (prefers-reduced-motion: reduce) {
  .product-card, .product-card__img, .product-card__actions { transition: none; }
  .product-card:active { transform: none; }
}

.product-card__media {
  position: relative;
  aspect-ratio: 4 / 5;
  background: var(--surface-sunken);
  overflow: hidden;
}
.product-card__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* subtle unifying vignette so mixed stock photography reads as one system */
  transition: transform var(--dur-slow) var(--ease-out);
}
@media (hover: hover) {
  .product-card:hover .product-card__img { transform: scale(1.04); }
}
.product-card__media::after {
  content: "";
  position: absolute; inset: 0;
  box-shadow: inset 0 0 0 1px rgba(16,19,20,.05);
  pointer-events: none;
}

/* Wishlist / quick view / compare.
 *
 * THESE USED TO BE opacity:0 UNTIL HOVER, WHICH MEANT THEY DID NOT EXIST ON A
 * PHONE. Three actions, unreachable, on the device most of this shop is
 * browsed from. They are visible by default now and the reveal is the
 * exception, applied only where a pointer can hover.
 *
 * On touch they sit at 40px and slightly translucent so they read as secondary
 * to the product image without becoming a guessing game. */
.product-card__actions {
  position: absolute;
  top: var(--space-3); right: var(--space-3);
  display: flex; flex-direction: column; gap: var(--space-2);
  z-index: 2;
  transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}

.product-card__actions .btn-icon-gr {
  /* 40px, not the 44px minimum: three of them stack down a card that is 160px
     wide on a small phone, and each sits on top of the product photo rather
     than beside anything mis-tappable. */
  width: 40px; height: 40px;
  opacity: .92;
  backdrop-filter: blur(6px);
  box-shadow: 0 1px 3px rgba(16, 19, 20, .12);
  transition: transform var(--dur-fast) var(--ease-out),
              opacity var(--dur-fast) var(--ease-out);
}
.product-card__actions .btn-icon-gr:active { transform: scale(.9); opacity: 1; }

@media (hover: hover) {
  /* Where there IS a pointer, keep the original reveal — it is cleaner, and a
     mouse can find them the moment the card is approached. */
  .product-card__actions { opacity: 0; transform: translateX(8px); }

  .product-card:hover .product-card__actions,
  .product-card:focus-within .product-card__actions { opacity: 1; transform: translateX(0); }

  .product-card__actions .btn-icon-gr:hover { transform: scale(1.08); opacity: 1; }
}

.product-card__body { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-4); }
.product-card__brand { font-size: var(--fs-12); color: var(--text-muted); text-transform: uppercase; letter-spacing: .08em; }
.product-card__title {
  font-family: var(--font-body);
  font-size: var(--fs-16);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  color: var(--text-primary);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.product-card__price-row { display: flex; align-items: baseline; gap: var(--space-2); margin-top: auto; }
.product-card__price { font-size: var(--fs-20); color: var(--text-primary); }
/* The CTA's offset from the price used to be an inline style on every card —
   duplicated in productCardHTML and eight times in index.html, so the phone
   layout could not tune it without !important. It is one rule now. */
.product-card__body .btn-gr { margin-top: var(--space-2); }
.product-card__stars { display: inline-flex; align-items: center; gap: 1px; }
.product-card__stars svg { display: block; }

/* ---- Premium products carry a gold hairline ---------------------------
 *
 * The shop's whole claim is that everything is imported and premium, and until
 * now the only thing saying so on a card was a small gold badge competing with
 * a lime SALE badge for the same corner.
 *
 * :has() keys the treatment off the badge that is already rendered, so no
 * markup changes and nothing new to keep in step — a product tagged premium
 * gets the border because it got the badge.
 *
 * Restraint is the point. A hairline and a barely-there warm wash, not a gold
 * card: on a grid of twelve, anything louder stops reading as "this one is
 * special" and starts reading as decoration.
 *
 * :has() is Chrome 105+ / Safari 15.4+ / Firefox 121+. On anything older the
 * rule is simply skipped and the card renders normally — the badge still says
 * premium. That is why the treatment is additive decoration and carries no
 * information of its own: a browser without :has() must not lose a fact, only
 * an emphasis.
 */
.product-card:has(.badge-premium) {
  border-color: color-mix(in srgb, var(--gold-ink) 32%, var(--border-hairline));
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--gold-ink) 3.5%, transparent), transparent 38%),
    var(--surface-raised);
}

@media (hover: hover) {
  .product-card:has(.badge-premium):hover {
    border-color: color-mix(in srgb, var(--gold-ink) 60%, transparent);
    box-shadow: 0 18px 48px rgba(138, 109, 34, .16), 0 6px 16px rgba(16, 19, 20, .07);
  }
}

/* ---- The card on a phone ---------------------------------------------
 *
 * Two cards fit across a 360px screen, which makes each about 160px wide.
 * Everything below is about what still works at that size — the desktop
 * proportions were designed for a 280px card and quietly fall apart.
 *
 * Measured against a 360×800 viewport, which is the common Android size in
 * Bangladesh, not a 390px iPhone.
 */
@media (max-width: 575.98px) {
  /* 4:5 is 200px of image on a 160px card, and with the body underneath one
     card nearly fills the fold. Square gives back 40px per card — the
     difference between seeing one row and seeing two. */
  .product-card__media { aspect-ratio: 1 / 1; }

  /* ---- The body is where the height went -----------------------------
   *
   * THE IMAGE IS NOT TOUCHED BELOW THIS LINE. The square media well above is
   * the product, and shrinking it to save space would be saving space by
   * selling less. Every reduction here is type and padding.
   *
   * Measured on a 393px viewport before this block: media 162px, body 156px.
   * The photo and the paragraph about the photo were the same size, and the
   * card cleared 310px — two of them and a phone screen is full.
   *
   * The body now runs ~116px, a 26% cut. Nothing was removed: brand, title,
   * price, the struck-through original and the CTA are all still there, set at
   * the size a 164px column actually wants rather than the size a 280px
   * desktop card was designed at.
   *
   * A third of the saving is leading, not type size. At 12px the body's
   * default 1.6 line-height reserves 19px per 12px line — nine wasted pixels
   * on every row, four rows deep. Each element below gets the leading its own
   * size needs.
   */
  .product-card__body { padding: 8px 10px 9px; gap: 2px; }

  .product-card__title {
    /* 14px → 12px. Two lines still clears ~34 characters at this width —
       "Medjool Dates — Premium Jumbo (1kg)" lands without truncating. */
    font-size: .75rem;
    line-height: 1.25;
    letter-spacing: -0.002em;
    -webkit-line-clamp: 2;
  }

  /* The price stays the loudest thing in the body — it drops by the same
     proportion as everything else, not more, so the hierarchy is unchanged. */
  .product-card__price { font-size: .875rem; }
  .product-card__price-row { gap: 5px; line-height: 1.15; }
  .price--strike { font-size: .75rem; }

  /* "Save ৳ 350" is the discount stated a THIRD time — the badge on the photo
     already says -19% and the struck-through ৳ 1,800 already says what it was.
     On a 280px desktop card that repetition is emphasis; in a 240px rail card
     on a phone it is a whole extra line of body for no new fact, and it is the
     line that pushed the premium card past the grid card's height. The PDP
     keeps it (.pdp-price .price-saving), where there is room to labour it. */
  .product-card .price-saving { display: none; }

  .product-card__brand {
    font-size: 9px;
    line-height: 1.25;
    letter-spacing: .07em;
    /* The origin is the whole proposition on an import shop — "Saudi Arabia"
       earns its line even at this size. One line, ellipsis if it runs. */
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  /* "Add to Cart" wraps to two lines in a 136px button and the card grows a
     ragged extra row. Smaller type, tighter tracking, one line.
     32px rather than 38: this is a secondary path — the whole card is a link
     to the PDP and the cart is reachable from the header — so it can sit under
     the 44px floor that primary controls hold to. It is still a 144×32 target,
     comfortably past WCAG 2.5.8's 24×24 minimum. */
  .product-card__body .btn-gr {
    font-size: 11px;
    min-height: 32px;
    margin-top: 6px;
    padding-inline: var(--space-2);
    letter-spacing: .01em;
    white-space: nowrap;
  }

  /* Stars are decoration at this size and the review count is unreadable.
     The rating still reaches assistive tech through the aria-label. */
  .product-card__stars svg { width: 11px; height: 11px; }
  /* .caption is 14px — larger than the product title once the title is 12px,
     which put the loudest type in the body on "(44)". The leading matters as
     much as the size: at 1.6 a 10px caption still reserves a 16px row. */
  .product-card__body .caption { font-size: 10px; line-height: 1.2; }

  .product-card__badges { top: 6px; left: 6px; gap: 3px; }
  .badge-gr { padding: 2px 6px; font-size: 9px; letter-spacing: .03em; }

  .product-card__actions { top: 6px; right: 6px; gap: 6px; }
  .product-card__actions .btn-icon-gr { width: 34px; height: 34px; }
}

/* ---- Category card --------------------------------------------------- */
.category-card {
  position: relative;
  display: flex; flex-direction: column; justify-content: flex-end;
  min-height: 180px;
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-hairline);
  background: var(--surface-raised);
  overflow: hidden;
  transition: transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
}
@media (hover: hover) {
  .category-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); border-color: var(--gr-cyan-dark); }
}
.category-card:active { transform: scale(.985); }
.category-card__icon { width: 40px; height: 40px; color: var(--link); margin-bottom: var(--space-3); }
.category-card__title { font-family: var(--font-display); font-size: var(--fs-20); font-weight: var(--fw-semibold); }
.category-card__count { font-size: var(--fs-14); color: var(--text-muted); }

/* ---- Badges / tags --------------------------------------------------- */
.badge-gr {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 9px;
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  letter-spacing: .04em;
  text-transform: uppercase;
  border-radius: var(--radius-sm);
  line-height: 1.4;
}
.badge-sale     { background: var(--gr-lime); color: var(--gr-ink); }
.badge-new      { background: rgba(27,180,212,.12); color: var(--link-hover); border: 1px solid rgba(27,180,212,.32); }
.badge-premium  { background: rgba(201,162,75,.16); color: var(--gold-ink); border: 1px solid rgba(201,162,75,.45); }
.badge-out      { background: var(--surface-sunken); color: var(--text-muted); border: 1px solid var(--border-hairline); }
.badge-origin   { background: var(--wash-hover); color: var(--text-secondary); border: 1px solid var(--border-hairline); text-transform: none; letter-spacing: 0; }

.product-card__badges { position: absolute; top: var(--space-3); left: var(--space-3); display: flex; flex-direction: column; gap: var(--space-1); z-index: 2; }

/* ---- Elevation on a white canvas -------------------------------------
   Card and page are both white now, so the hairline border does the
   separating and a very soft shadow supplies the depth. */
.surface-gr, .product-card, .category-card { box-shadow: var(--shadow-sm); }

/* ---- Absolute saving ---------------------------------------------------
   Sits with the price as a second signal to the percentage badge. Uses the
   accessible green (--lime-ink, 4.9:1) rather than raw --gr-lime, which is
   1.9:1 on white and unreadable as text. */
.price-saving {
  display: inline-block;
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  color: var(--lime-ink);
  font-variant-numeric: tabular-nums;
}
.pdp-price .price-saving { margin-left: var(--space-2); }

/* ===== partials/_navigation.css ===== */
/* =========================================================================
   GulfRabit · Navigation (header, mega-menu, mobile drawer, announcement bar)
   Sticky glass on scroll — backdrop blur + semi-transparent black once past
   the hero. This is a key "expensive" detail.
   ========================================================================= */

/* ---- Announcement bar ------------------------------------------------ */
.announce-bar {
  background: var(--gr-gradient);
  color: #04222b;
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  text-align: center;
}
/* Block-centered (not flex) so long text wraps within the bar instead of
   forcing the document wider than the viewport on mobile. */
.announce-bar__inner { position: relative; text-align: center; padding: 8px 2.75rem; line-height: 1.35; max-width: var(--container-max); margin-inline: auto; }
.announce-bar__close { position: absolute; right: var(--space-3); top: 50%; transform: translateY(-50%); background: none; border: 0; color: #04222b; cursor: pointer; opacity: .7; line-height: 1; }
@media (hover: hover) {
  .announce-bar__close:hover { opacity: 1; }
}
.announce-bar[hidden] { display: none; }

/* ---- Header shell ---------------------------------------------------- */
.site-header {
  position: sticky; top: 0; z-index: var(--z-header);
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color var(--dur-slow) var(--ease-out),
              backdrop-filter var(--dur-slow) var(--ease-out),
              border-color var(--dur-slow) var(--ease-out),
              box-shadow var(--dur-slow) var(--ease-out);
}
/* toggled by header-nav.js once scrolled past a threshold */
.site-header.is-scrolled {
  background: var(--glass-header);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  backdrop-filter: blur(16px) saturate(140%);
  border-bottom-color: var(--border-hairline);
  box-shadow: var(--shadow-md);
}
.site-header__inner {
  display: flex; align-items: center; gap: var(--space-5);
  height: var(--header-h);
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--space-5);
}
/* Horizontal lockup: the square rabbit mark + a styled wordmark (the source
   logo is a square stack, so it reads tiny as a wide header image). */
.site-header__logo { display: inline-flex; align-items: center; gap: 9px; flex-shrink: 0; }
.site-header__logo img { height: 38px; width: 38px; display: block; }
.site-header__wordmark { font-family: var(--font-display); font-weight: var(--fw-bold); font-size: var(--fs-20); color: var(--link); letter-spacing: -0.01em; line-height: 1; }

/* Mobile header: trim to essentials so nothing overflows on small screens. */
@media (max-width: 991px) {
  .header-action-optional { display: none !important; }
  .site-header__inner { gap: var(--space-3); padding-inline: var(--space-4); }
  .site-header__wordmark { font-size: 1.1rem; }
}
@media (max-width: 360px) { .site-header__wordmark { display: none; } }
.site-header__actions { display: flex; align-items: center; gap: var(--space-1); margin-left: auto; }

/* ---- Primary nav + mega-menu ----------------------------------------- */
/* Desktop nav appears at Bootstrap's lg (992px) so it lines up with the
   hamburger's .d-lg-none — no dead zone where neither shows. */
.primary-nav { display: none; }
@media (min-width: 992px) { .primary-nav { display: flex; align-items: center; gap: var(--space-2); } }

.nav-item { position: relative; }
.nav-link {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 0.6rem 0.9rem;
  font-size: var(--fs-16); font-weight: var(--fw-medium);
  color: var(--text-secondary);
  border-radius: var(--radius-sm);
  cursor: pointer; background: none; border: 0;
  transition: color var(--dur-fast) var(--ease-out);
}
.nav-link:hover, .nav-item.is-open .nav-link { color: var(--text-primary); }
/* animated gradient underline for the active section */
.nav-link[aria-current="page"]::after {
  content: ""; position: absolute; left: 0.9rem; right: 0.9rem; bottom: 6px; height: 2px;
  background: var(--gr-gradient); border-radius: var(--radius-pill);
}

/* Anchored to the left edge of the Shop item, not centred on it.
   Centred, an 880px panel hung ~180px off the left of a 1280px window and the
   entire first column was outside the viewport — measured: column one sat at
   x = -179, so "Imported Food" and its three links were simply not on screen.
   The width formula reserves the nav item's own offset so the panel cannot
   run off the right either at the 992px breakpoint where it first appears. */
.mega-menu {
  position: absolute; top: calc(100% + 10px); left: 0;
  transform: translateY(8px);
  width: min(816px, calc(100vw - 15rem));
  background: var(--surface-raised);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-6);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out), visibility var(--dur-base);
}
.nav-item.is-open .mega-menu { opacity: 1; visibility: visible; pointer-events: auto; transform: translateY(0); }
.mega-menu__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-6); }
.mega-menu__col-title { font-size: var(--fs-12); text-transform: uppercase; letter-spacing: .1em; color: var(--link); margin-bottom: var(--space-3); }
.mega-menu__link { display: block; padding: 6px 0; color: var(--text-secondary); font-size: var(--fs-14); }
.mega-menu__link:hover { color: var(--text-primary); padding-left: 6px; transition: padding var(--dur-fast) var(--ease-out); }

/* ---- Search field in header ------------------------------------------ */
.header-search { display: none; flex: 1; max-width: 520px; }
@media (min-width: 992px) { .header-search { display: block; } }
.header-search__field {
  display: flex; align-items: center; gap: var(--space-2);
  height: 46px; padding: 0 var(--space-4);
  background: var(--surface-sunken);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-pill);
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.header-search__field:focus-within { border-color: var(--gr-cyan-dark); box-shadow: 0 0 0 3px rgba(27,180,212,.15); }
.header-search__input { flex: 1; background: none; border: 0; color: var(--text-primary); font-size: var(--fs-14); outline: none; }
.header-search__input::placeholder { color: var(--text-muted); }

/* ---- Count badge on cart / wishlist icons ---------------------------- */
.action-badge {
  position: absolute; top: 4px; right: 4px;
  min-width: 18px; height: 18px; padding: 0 5px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: var(--fw-bold); line-height: 1;
  color: #04222b; background: var(--gr-lime);
  border-radius: var(--radius-pill);
  transform: scale(0); transition: transform var(--dur-base) var(--ease-out);
}
.action-badge.is-active { transform: scale(1); }

/* ---- Mobile offcanvas drawer ------------------------------------------
   A narrow category rail, not a full-width menu. It holds one flat list —
   the live categories, then the two account/company links — because the
   drawer it replaced spent its first row on a "Shop" accordion that hid the
   categories behind a tap, and the categories are the whole point of it.

   Sizing lives here rather than inline on the <aside> so it is one edit for
   all 26 pages the header is inlined into. Everything the JS toggles
   (transform via #mobileNav.is-open) still works off the class. */
.gr-offcanvas {
  position: fixed; top: 0; left: 0;
  height: 100%;
  width: clamp(208px, 56vw, 304px);
  z-index: 1050;
  display: flex; flex-direction: column;
  background: var(--surface-raised);
  color: var(--text-primary);
  border-right: 1px solid var(--border-hairline);
  /* Long and soft: at this width the drawer reads as a panel lifted off the
     page, and a tight shadow would read as a border with a smudge. */
  box-shadow: 24px 0 60px -20px rgba(10, 19, 22, .28);
  transform: translateX(-100%);
  transition: transform .34s var(--ease-out);
  overscroll-behavior: contain;
}
/* A single wash of brand colour behind the logo. The panel is white on white
   otherwise, and white on white at this width looks unfinished rather than
   minimal. */
.gr-offcanvas::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 240px;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(27,180,212,.075), rgba(154,205,60,.035) 42%, transparent);
}

.mobile-nav__head {
  position: relative; z-index: 1;
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--space-2);
  padding: 14px 14px 16px 16px;
}
.mobile-nav__logo { width: 80px; height: auto; }
.mobile-nav__head .btn-icon-gr { margin-top: -2px; flex: none; }

.mobile-nav__body {
  position: relative; z-index: 1;
  flex: 1; min-height: 0;
  display: flex; flex-direction: column;
  overflow-y: auto;
  padding-bottom: var(--space-5);
}
.mobile-nav__list { list-style: none; margin: 0; padding: 0; }

.mobile-nav__link {
  position: relative;
  display: flex; align-items: center; gap: var(--space-2);
  min-height: 44px;                 /* the AAA target size, and the row rhythm */
  padding: 9px 16px;
  font-size: .9375rem;              /* 15 — one step under the body size */
  font-weight: var(--fw-semibold);
  letter-spacing: -.006em;
  line-height: 1.25;
  color: var(--text-primary);
  transition: background var(--dur-fast) var(--ease-out),
              padding-left var(--dur-base) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
/* Hairlines between rows, inset to the text column. Ruled edge to edge they
   look like a table; inset they look like a considered list. */
.mobile-nav__list > li + li > .mobile-nav__link::before {
  content: ""; position: absolute; top: 0; left: 16px; right: 16px;
  height: 1px; background: var(--border-hairline);
}
/* Brand spine, scaled from nothing on press. Cheaper than a colour change and
   it points at the row the finger is on. */
.mobile-nav__link::after {
  content: ""; position: absolute; left: 0; top: 7px; bottom: 7px;
  width: 3px; border-radius: 0 3px 3px 0;
  background: var(--gr-gradient);
  transform: scaleY(0);
  transition: transform var(--dur-base) var(--ease-out);
}
@media (hover: hover) {
  .mobile-nav__link:hover { background: var(--wash-hover); padding-left: 21px; color: var(--link-hover); }
  .mobile-nav__link:hover::after { transform: scaleY(1); }
}
.mobile-nav__link:active { background: var(--wash-hover); }
.mobile-nav__link:active::after { transform: scaleY(1); }
.mobile-nav__link[aria-current="page"] { color: var(--link); }
.mobile-nav__link[aria-current="page"]::after { transform: scaleY(1); }

/* Account / company. Pushed to the foot of the panel and set quieter, so the
   categories own the list and these stay reachable without competing. */
.mobile-nav__list--minor { margin-top: auto; padding-top: var(--space-3); }
.mobile-nav__list--minor::before {
  content: ""; display: block; height: 1px; margin: 0 16px var(--space-2);
  background: var(--border-hairline);
}
.mobile-nav__list--minor .mobile-nav__link {
  min-height: 40px;
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  color: var(--text-secondary);
}
.mobile-nav__list--minor > li + li > .mobile-nav__link::before { display: none; }

/* Rows arrive just behind the panel, ~26ms apart, so the drawer resolves into
   a list instead of appearing whole. --i is set per row in the markup and by
   category-menu.js when it re-renders from the API. */
#mobileNav.is-open .mobile-nav__link {
  animation: navRowIn .32s var(--ease-out) backwards;
  animation-delay: calc(var(--i, 0) * 26ms + 70ms);
}
@keyframes navRowIn {
  from { opacity: 0; transform: translateX(-10px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .gr-offcanvas { transition: none; }
  #mobileNav.is-open .mobile-nav__link { animation: none; }
  .mobile-nav__link { transition: none; }
}

/* Kept for the API-rendered child rows in category-menu.js. */
.mobile-nav__sublink { display: block; padding: var(--space-2) var(--space-4); color: var(--text-secondary); font-size: var(--fs-16); }

/* ---- Search autocomplete panel --------------------------------------- */
.header-search { position: relative; }
.search-suggest {
  position: absolute; top: calc(100% + 8px); left: 0; right: 0; z-index: 5;
  background: var(--surface-raised);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-2);
  overflow: hidden;
}
.search-suggest__item { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); color: var(--text-secondary); }
.search-suggest__item:hover { background: var(--surface-sunken); color: var(--text-primary); }
.search-suggest__item img { border-radius: var(--radius-sm); object-fit: cover; background: var(--surface-sunken); }
.search-suggest__title { display: block; font-size: var(--fs-14); font-weight: var(--fw-medium); color: var(--text-primary); }

/* Overlay search variant sits inside .search-overlay__box (full-screen) */
.search-overlay .search-suggest { position: static; margin-top: var(--space-3); box-shadow: none; }

/* ---- Breadcrumb ------------------------------------------------------ */
.breadcrumb-gr { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); font-size: var(--fs-14); color: var(--text-muted); }
.breadcrumb-gr a { color: var(--text-muted); }
.breadcrumb-gr a:hover { color: var(--link); }
.breadcrumb-gr__sep { opacity: .5; }

/* ---- Search: popular queries + merchandised placeholder ----------------
   Rotation timing is driven from search-autocomplete.js; the transition and
   the resting look live here (context.md §2). */
.search-suggest__heading {
  padding: var(--space-2) var(--space-3) var(--space-1);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.search-suggest__item--query { color: var(--text-secondary); }
.search-suggest__item--query svg { color: var(--text-muted); flex-shrink: 0; }

.header-search__input::placeholder,
.search-overlay__box input::placeholder {
  transition: opacity var(--dur-base) var(--ease-out);
}
.header-search__input.is-swapping::placeholder,
.search-overlay__box input.is-swapping::placeholder { opacity: 0; }

/* ===== partials/_forms.css ===== */
/* =========================================================================
   GulfRabit · Forms & inputs
   Consistent radius (--radius-sm), visible focus, clear error state.
   ========================================================================= */

.field-gr { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-4); }
.label-gr { font-size: var(--fs-14); font-weight: var(--fw-medium); color: var(--text-secondary); }
.label-gr .req { color: var(--link); }

.input-gr,
.select-gr,
.textarea-gr {
  width: 100%;
  min-height: 48px;
  padding: 0.75rem 1rem;
  font-family: var(--font-body);
  font-size: var(--fs-16);
  color: var(--text-primary);
  background: var(--surface-sunken);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-sm);
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.textarea-gr { min-height: 120px; resize: vertical; line-height: var(--lh-normal); }
.input-gr::placeholder, .textarea-gr::placeholder { color: var(--text-muted); }
.input-gr:focus, .select-gr:focus, .textarea-gr:focus {
  outline: none;
  border-color: var(--gr-cyan-dark);
  box-shadow: 0 0 0 3px rgba(27,180,212,.15);
}

/* Select caret */
.select-gr {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23667069' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1rem center;
  padding-right: 2.5rem;
}

/* Validation states (toggled by utils/validate-form.js) */
.field-gr.is-invalid .input-gr,
.field-gr.is-invalid .select-gr,
.field-gr.is-invalid .textarea-gr { border-color: var(--gr-error); box-shadow: 0 0 0 3px rgba(229,72,77,.14); }
.field-error { font-size: var(--fs-12); color: var(--gr-error); min-height: 1em; }
.field-gr.is-valid .input-gr { border-color: var(--gr-success); }

/* Checkbox / radio */
.check-gr { display: inline-flex; align-items: flex-start; gap: var(--space-2); cursor: pointer; font-size: var(--fs-14); color: var(--text-secondary); }
.check-gr input { width: 20px; height: 20px; accent-color: var(--gr-cyan); margin-top: 1px; }

/* Inputs on light sections (checkout, contact on off-white) */
.field-gr--light .label-gr { color: var(--text-primary); }
.field-gr--light .input-gr,
.field-gr--light .select-gr,
.field-gr--light .textarea-gr {
  color: var(--text-primary); background: var(--surface-raised); border-color: var(--border-input);
}
.field-gr--light .input-gr::placeholder { color: var(--text-muted); }

/* Quantity stepper */
.qty-stepper { display: inline-flex; align-items: center; border: 1px solid var(--border-input); border-radius: var(--radius-sm); overflow: hidden; background: var(--surface-sunken); }
.qty-stepper__btn { width: 42px; height: 46px; display: grid; place-items: center; background: none; border: 0; color: var(--text-primary); cursor: pointer; font-size: var(--fs-20); transition: background var(--dur-fast) var(--ease-out); }
.qty-stepper__btn:hover:not(:disabled) { background: var(--wash-hover); color: var(--link); }
.qty-stepper__btn:disabled { opacity: .4; cursor: not-allowed; }
.qty-stepper__input { width: 48px; height: 46px; text-align: center; background: none; border: 0; color: var(--text-primary); font-variant-numeric: tabular-nums; font-weight: var(--fw-semibold); outline: none; }

/* ===== partials/_modals-offcanvas.css ===== */
/* =========================================================================
   GulfRabit · Cart drawer & modals (quick view)
   Re-skins Bootstrap offcanvas/modal so nothing looks "Bootstrap default".
   ========================================================================= */

/* ---- Cart drawer (offcanvas from the right) -------------------------- */
.cart-drawer { width: min(420px, 92vw); background: var(--surface-raised); color: var(--text-primary); border-left: 1px solid var(--border-hairline); }
.cart-drawer__header { display: flex; align-items: center; justify-content: space-between; padding: var(--space-5); border-bottom: 1px solid var(--border-hairline); }
.cart-drawer__title { font-family: var(--font-display); font-size: var(--fs-20); font-weight: var(--fw-semibold); }
.cart-drawer__body { flex: 1; overflow-y: auto; padding: var(--space-4) var(--space-5); }
.cart-drawer__footer { padding: var(--space-5); border-top: 1px solid var(--border-hairline); background: var(--surface-page); }

.cart-line { display: grid; grid-template-columns: 64px 1fr auto; gap: var(--space-3); padding: var(--space-3) 0; border-bottom: 1px solid var(--border-hairline); }
.cart-line__thumb { width: 64px; height: 64px; border-radius: var(--radius-sm); object-fit: cover; background: var(--surface-sunken); }
.cart-line__title { font-size: var(--fs-14); font-weight: var(--fw-medium); line-height: var(--lh-snug); }
.cart-line__meta { font-size: var(--fs-12); color: var(--text-muted); }
.cart-line__price { font-variant-numeric: tabular-nums; font-weight: var(--fw-semibold); white-space: nowrap; }
.cart-line__remove { background: none; border: 0; color: var(--text-muted); cursor: pointer; font-size: var(--fs-12); }
.cart-line__remove:hover { color: var(--gr-error); }

.cart-summary-row { display: flex; justify-content: space-between; font-size: var(--fs-14); color: var(--text-secondary); margin-bottom: var(--space-2); }
.cart-summary-row--total { font-size: var(--fs-20); color: var(--text-primary); font-weight: var(--fw-semibold); padding-top: var(--space-3); border-top: 1px solid var(--border-hairline); margin-top: var(--space-3); }

/* ---- Empty cart state ------------------------------------------------ */
.cart-empty { text-align: center; padding: var(--space-8) var(--space-4); color: var(--text-muted); }
.cart-empty__icon { width: 56px; height: 56px; margin: 0 auto var(--space-4); color: var(--border-input); }

/* ---- Quick-view / generic modal -------------------------------------- */
.gr-modal .modal-content { background: var(--surface-raised); border: 1px solid var(--border-hairline); border-radius: var(--radius-lg); color: var(--text-primary); box-shadow: var(--shadow-lg); }
.gr-modal .modal-header, .gr-modal .modal-footer { border-color: var(--border-hairline); }

/* ---- Backdrop (shared) ----------------------------------------------- */
.offcanvas-backdrop.show, .modal-backdrop.show { background: var(--scrim); opacity: 1; -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); }

/* ---- Full-screen search overlay -------------------------------------- */
.search-overlay {
  position: fixed; inset: 0; z-index: var(--z-modal);
  background: var(--glass-overlay);
  -webkit-backdrop-filter: blur(20px); backdrop-filter: blur(20px);
  display: flex; flex-direction: column; align-items: center;
  padding-top: 12vh;
  opacity: 0; visibility: hidden;
  transition: opacity var(--dur-slow) var(--ease-out), visibility var(--dur-slow);
}
.search-overlay.is-open { opacity: 1; visibility: visible; }
.search-overlay__box { width: min(720px, 92vw); }

/* ===== partials/_animations.css ===== */
/* =========================================================================
   GulfRabit · Animations, skeletons, scrollbar, toasts
   Subtle only — 150–300ms, no bounce. Respects prefers-reduced-motion.
   ========================================================================= */

/* ---- Scroll reveal (IntersectionObserver adds .is-visible) -----------
   Scoped to html:not(.no-js). An inline script in <head> drops .no-js before
   first paint, so with JavaScript enabled nothing changes — but with it off,
   these rules never apply and the content is simply visible. Unscoped, this
   was opacity:0 with nothing left to remove it, which hid most of the Sourcing
   page, the About values and much of the home page from anyone without JS. */
html:not(.no-js) [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
  will-change: opacity, transform;
  transition-delay: var(--reveal-delay, 0ms);   /* optional stagger, set inline */
}
html:not(.no-js) [data-reveal].is-visible { opacity: 1; transform: translateY(0); }

/* ---- Skeleton shimmer ------------------------------------------------ */
.skeleton {
  position: relative;
  overflow: hidden;
  background: var(--surface-sunken);
  border-radius: var(--radius-sm);
}
.skeleton::after {
  content: "";
  position: absolute; inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.9), transparent);
  animation: gr-shimmer 1.4s infinite;
}
@keyframes gr-shimmer { 100% { transform: translateX(100%); } }
.skeleton--text { height: 0.8em; margin-bottom: 0.5em; }
.skeleton--media { aspect-ratio: 4/5; border-radius: var(--radius-lg); }

/* ---- Toast ----------------------------------------------------------- */
.toast-stack {
  position: fixed; z-index: var(--z-toast);
  right: var(--space-5); bottom: var(--space-5);
  display: flex; flex-direction: column; gap: var(--space-3);
  width: min(360px, calc(100vw - 2rem));
}
@media (max-width: 767px) { .toast-stack { left: 50%; right: auto; transform: translateX(-50%); bottom: var(--space-4); align-items: center; } }
.toast-gr {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-sunken);
  border: 1px solid var(--border-hairline);
  border-left: 3px solid var(--gr-cyan);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  color: var(--text-primary);
  font-size: var(--fs-14);
  transform: translateX(120%);
  animation: gr-toast-in var(--dur-slow) var(--ease-out) forwards;
}
.toast-gr.is-leaving { animation: gr-toast-out var(--dur-base) var(--ease-out) forwards; }
.toast-gr--success { border-left-color: var(--gr-lime); }
.toast-gr--error   { border-left-color: var(--gr-error); }
.toast-gr__icon { flex-shrink: 0; width: 20px; height: 20px; }
@keyframes gr-toast-in  { to { transform: translateX(0); } }
@keyframes gr-toast-out { to { transform: translateX(120%); opacity: 0; } }

/* ---- Fade/scale in for modals & overlays ----------------------------- */
@keyframes gr-fade-up { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
.animate-fade-up { animation: gr-fade-up var(--dur-slow) var(--ease-out) both; }

/* ---- Custom scrollbar (default browser bars look cheap) -------------- */
* { scrollbar-width: thin; scrollbar-color: var(--gr-cyan-dark) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--surface-page); }
::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: var(--radius-pill); border: 2px solid var(--surface-page); }
::-webkit-scrollbar-thumb:hover { background: var(--gr-cyan-dark); }

/* ---- Reduced motion -------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; scroll-behavior: auto !important; }
  [data-reveal] { opacity: 1; transform: none; }
}

/* ---- Images settle in, rather than popping -----------------------------
 *
 * Driven by shared/js/components/image-settle.js. The selector deliberately
 * requires BOTH classes to hide anything: `.settle` is added by the script, so
 * an image is only ever hidden by code that is running and will un-hide it. If
 * the script fails or never loads, nothing here applies and every photo shows
 * normally — the same no-js contract as [data-reveal].
 */
img.settle {
  opacity: 0;
  transform: scale(1.015);
  /* Slower fade than movement: the eye reads the opacity change as "arriving"
     and the scale as "settling", and matching their durations makes it look
     like one mechanical slide instead. */
  transition: opacity 420ms var(--ease-out), transform 620ms var(--ease-out);
}

img.settle.is-settled {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  /* The script bails before adding the class, but a stylesheet that only works
     because a script agreed with it is a stylesheet waiting to be wrong. */
  img.settle { opacity: 1; transform: none; transition: none; }
}

/* ===== partials/_utilities.css ===== */
/* =========================================================================
   GulfRabit · Utilities & layout primitives
   Small, reusable helpers that Tailwind/Bootstrap don't express cleanly.
   ========================================================================= */

/* ---- Container & section rhythm -------------------------------------- */
.gr-container { max-width: var(--container-max); margin-inline: auto; padding-inline: var(--space-5); }
@media (min-width: 1024px) { .gr-container { padding-inline: var(--space-6); } }

.section-gr { padding-block: var(--space-8); }
@media (min-width: 1024px) { .section-gr { padding-block: var(--space-10); } }

/* Light / dark section canvases (80/20 neutral system) */
.canvas-dark  { background: var(--surface-page);     color: var(--text-primary); }
.canvas-panel { background: var(--surface-band);   color: var(--text-primary); }
.canvas-light { background: var(--surface-band);   color: var(--text-primary); }
.canvas-light .text-muted-gr { color: var(--text-muted); }

/* ---- Section heading block ------------------------------------------- */
.section-head { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: var(--space-4); margin-bottom: var(--space-6); }
.section-head__titles { display: flex; flex-direction: column; gap: var(--space-2); }

/* ---- The section header on a phone ------------------------------------
 *
 * Every shelf opened with a header sized for a 1400px canvas: a 12px eyebrow
 * over a 1.9rem title, 24px of air beneath it. At 393px that block was taller
 * than the product photo it was introducing — the header announced the shelf
 * more loudly than the shelf sold anything.
 *
 * The whole thing comes down to 75%. Not the title alone: shrinking the h2 and
 * leaving the eyebrow, the trailing link and the gaps at full size just moves
 * the imbalance around. Type, tracking, gaps and the chrome beside it all step
 * down together, which is the only way a smaller header still reads as one
 * deliberate object rather than a title someone typed in the wrong size.
 *
 * .home-cat overrides this further for the category directory (higher
 * specificity, modules/home/home.css) — that block still wins where it applies.
 */
@media (max-width: 575.98px) {
  /* align-items stays `end` from the base rule. Baseline is the tempting
     choice for "put the link on the title's line" and it is wrong here:
     .section-head__titles is a flex column, so its baseline is its FIRST
     line — the eyebrow. Everything beside it then hangs off "CURATED FOR
     YOU" and floats a full line above the heading it belongs to. */
  .section-head {
    gap: var(--space-3);
    margin-bottom: var(--space-4);
  }
  .section-head__titles { gap: 1px; min-width: 0; }

  /* 12px → 10px. Uppercase at 10px needs its tracking eased back or the
     letters stop cohering into words; .12em is the measured floor here. */
  .section-head .overline {
    font-size: .625rem;
    letter-spacing: .12em;
  }

  /* 1.9rem → 1.425rem, exactly 75%, matching the category directory. */
  .section-head h2 {
    font-size: 1.425rem;
    line-height: 1.18;
    letter-spacing: -0.018em;
  }

  /* "Shop all →" at 16px in a bordered pill now outweighs the title it sits
     beside. A text link with a padded tap area carries the same job — the
     same trade .home-cat already made, applied to every shelf. */
  .section-head > .btn-gr {
    flex: none;
    min-height: 0;
    padding: .4rem 0;
    border: 0;
    background: none;
    box-shadow: none;
    font-size: var(--fs-14);
    font-weight: var(--fw-semibold);
    color: var(--link);
    white-space: nowrap;
  }
  .section-head > .btn-gr:hover,
  .section-head > .btn-gr:active { background: none; box-shadow: none; transform: none; }

  /* Rail arrows follow the title down — 44px circles either side of a
     1.425rem heading read as the primary control on the row. */
  .section-head .btn-icon-gr { width: 34px; height: 34px; }
  .section-head .btn-icon-gr svg { width: 17px; height: 17px; }
}

/* ---- Gradient divider ------------------------------------------------ */
.divider-gradient { height: 2px; border: 0; background: var(--gr-gradient); border-radius: var(--radius-pill); opacity: .8; }
.divider-hairline { height: 1px; border: 0; background: var(--border-hairline); }

/* ---- Horizontal scroll-snap rail (product carousels) ----------------- */
.snap-rail { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(240px, 1fr); gap: var(--space-4); overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: var(--space-3); scrollbar-width: none; }
.snap-rail::-webkit-scrollbar { display: none; }
.snap-rail > * { scroll-snap-align: start; }
@media (min-width: 1024px) { .snap-rail { grid-auto-columns: minmax(280px, 1fr); } }

/* ---- Responsive product grid ----------------------------------------- */
.product-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(2, 1fr); }
@media (min-width: 768px)  { .product-grid { grid-template-columns: repeat(3, 1fr); gap: var(--space-5); } }
@media (min-width: 1024px) { .product-grid { grid-template-columns: repeat(4, 1fr); } }

/* ---- Trust / feature strip ------------------------------------------- */
.trust-strip { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-4); }
@media (min-width: 768px) { .trust-strip { grid-template-columns: repeat(4, 1fr); } }
.trust-item { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.trust-item > div { min-width: 0; }               /* let text wrap instead of overflowing the grid track */
.trust-item__icon { width: 32px; height: 32px; color: var(--gold-ink); flex-shrink: 0; }
.trust-item__title { font-size: var(--fs-14); font-weight: var(--fw-semibold); }
.trust-item__sub { font-size: var(--fs-12); color: var(--text-muted); }

/* ---- Accessibility helpers ------------------------------------------- */
.visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.skip-link { position: absolute; left: var(--space-4); top: -60px; z-index: calc(var(--z-header) + 1); background: var(--gr-cyan); color: #04222b; padding: 0.6rem 1rem; border-radius: var(--radius-sm); font-weight: var(--fw-semibold); transition: top var(--dur-base) var(--ease-out); }
.skip-link:focus { top: var(--space-4); }

/* ---- Empty-state block ----------------------------------------------- */
.empty-state { text-align: center; padding: var(--space-9) var(--space-4); }
.empty-state__icon { width: 64px; height: 64px; margin: 0 auto var(--space-4); color: var(--border-input); }
.empty-state__title { font-family: var(--font-display); font-size: var(--fs-25); margin-bottom: var(--space-2); }
.empty-state__text { color: var(--text-muted); max-width: 40ch; margin: 0 auto var(--space-5); }

/* ---- Compare tray (floating, global) --------------------------------- */
.compare-tray {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: var(--space-4); z-index: 40;
  width: min(880px, calc(100vw - 2rem));
  background: var(--glass-overlay);
  -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  animation: gr-fade-up var(--dur-slow) var(--ease-out) both;
}
.compare-tray__inner { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); padding: var(--space-3) var(--space-4); flex-wrap: wrap; }
.compare-tray__items { display: flex; gap: var(--space-2); }
.compare-tray__item { position: relative; width: 52px; height: 52px; border-radius: var(--radius-sm); overflow: hidden; border: 1px solid var(--border-hairline); background: var(--surface-sunken); }
.compare-tray__item img { width: 100%; height: 100%; object-fit: cover; }
.compare-tray__remove { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; border-radius: var(--radius-pill); border: 1px solid var(--border-hairline); background: var(--surface-page); color: var(--text-secondary); font-size: 11px; cursor: pointer; line-height: 1; }
.compare-tray__remove:hover { color: var(--gr-error); border-color: var(--gr-error); }
.compare-tray__slot { width: 52px; height: 52px; border-radius: var(--radius-sm); border: 1px dashed var(--border-hairline); }
.compare-tray__actions { display: flex; align-items: center; gap: var(--space-2); }
@media (max-width: 640px) { .compare-tray__inner { justify-content: center; } .compare-tray__slot { display: none; } }

/* ---- No-JS fallback note --------------------------------------------- */
.noscript-note {
  display: block;
  padding: var(--space-5);
  border: 1px dashed var(--border-hairline);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  color: var(--text-secondary);
  text-align: center;
}
.noscript-note a { font-weight: var(--fw-semibold); }

/* ---- Spacing helpers on the 8px grid --------------------------------- */
.stack-2 > * + * { margin-top: var(--space-2); }
.stack-3 > * + * { margin-top: var(--space-3); }
.stack-4 > * + * { margin-top: var(--space-4); }
.stack-5 > * + * { margin-top: var(--space-5); }
.stack-6 > * + * { margin-top: var(--space-6); }

/* ===== style.css (own rules) ===== */
/* =========================================================================
   GulfRabit · style.css — master stylesheet
   Imports every design-system partial in cascade order, then holds the base
   reset and print styles. Load this ONCE per page after Tailwind/Bootstrap so
   our tokens win over their defaults.

   Order matters: variables first (tokens), then base, then components.
   ========================================================================= */


/* ---- Base reset -------------------------------------------------------
 *
 * THIS USED TO SAY "light-touch; Tailwind's preflight handles most", and it
 * was telling the truth — which is the problem. The site loads
 * cdn.tailwindcss.com, a CSS compiler that runs in the browser before first
 * paint, and an inventory of every class in every page (tools/tailwind-
 * inventory.py) found it is used for exactly ONE utility: `sr-only`, twice.
 *
 * The rest of what the CDN was doing for us was invisible: preflight's reset.
 * Remove the script without replacing that and the whole site shifts — form
 * controls revert to the browser's font, buttons and tables grow default
 * borders, and every unstyled list and figure gets its margins back.
 *
 * So the reset below is now complete on its own. Everything here is what
 * preflight was silently providing. With that in place, dropping the CDN is a
 * one-line change — see §7e in context.md.
 */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { margin: 0; min-height: 100vh; overflow-x: hidden; }

img, picture, svg, video { max-width: 100%; display: block; }
img { height: auto; }

ul[role="list"], ol[role="list"] { list-style: none; margin: 0; padding: 0; }

/* ---- What Tailwind preflight was providing ----------------------------
   Each of these is load-bearing. They are listed separately from the block
   above so it stays obvious which came from where. */

/* Default margins on flow content. Headings and <p> are already zeroed in
   _typography.css; these are the ones nothing else covers. */
figure, blockquote, dl, dd, pre, fieldset { margin: 0; }
fieldset, legend { padding: 0; }

/* Form controls do NOT inherit the page font by default — a <select> or
   <input> reverts to the browser's UI font, which on Android is a different
   family at a different size, and the difference is obvious next to a label
   set in Inter. */
button, input, optgroup, select, textarea {
  font-family: inherit;
  font-size: 100%;
  font-weight: inherit;
  line-height: inherit;
  color: inherit;
  margin: 0;
  padding: 0;
}

/* Buttons and tables carry a UA border otherwise. Our components set their
   own where they want one; this stops the ones that do not from sprouting a
   grey 2px bevel. */
button, [type="button"], [type="reset"], [type="submit"] {
  appearance: button;
  background-color: transparent;
  background-image: none;
}
button, select { text-transform: none; }

table { border-collapse: collapse; border-color: inherit; text-indent: 0; }

/* Chrome shows a resize handle and a spinner that no design here asks for. */
textarea { resize: vertical; }
::-webkit-inner-spin-button, ::-webkit-outer-spin-button { height: auto; }
::-webkit-search-decoration { -webkit-appearance: none; }

/* Placeholder colour is otherwise a browser opinion, and Safari's is much
   darker than Chrome's — the same field reads as filled on one and empty on
   the other. */
input::placeholder, textarea::placeholder { opacity: 1; color: var(--text-muted); }

/* ---- What Bootstrap's Reboot was providing ----------------------------
   Same story as preflight: the grid and the components are unused, but the
   reset underneath was load-bearing. */

/* THE IMPORTANT ONE.
 *
 * `hidden` is an attribute, and the UA stylesheet implements it as
 * `[hidden] { display: none }` — a rule any class with a `display` beats,
 * because they have the same specificity and ours comes later.
 *
 * `.acat-new { display: flex }` is used as `<form class="acat-new" hidden>`.
 * So the New category, New product and New coupon forms were hidden ONLY
 * because Reboot adds `!important` to that rule. Remove Bootstrap without
 * this line and all three sit permanently open.
 *
 * 231 elements across the site carry the attribute. Three components had
 * already patched it individually — .tab-panel[hidden], .checkout-step[hidden]
 * and .announce-bar[hidden] — which is the same bug being found and fixed
 * locally three times instead of once here.
 *
 * !important is right for this. `hidden` is the platform's own way of saying
 * "not rendered"; a component that wants to override it should not be using
 * the attribute at all. */
[hidden] { display: none !important; }

/* Reboot styles these; the UA defaults are inconsistent between browsers and
   in Safari's case ugly. */
hr {
  margin: var(--space-6) 0;
  border: 0;
  border-top: 1px solid var(--border-hairline);
  opacity: 1;                 /* Firefox and Chrome disagree on the default */
}

caption {
  padding: var(--space-3) 0;
  color: var(--text-muted);
  text-align: left;           /* the UA centres it, which reads as a title */
}

label { display: inline-block; }
[role="button"] { cursor: pointer; }
summary { display: list-item; cursor: pointer; }

/* Visually hidden, still read aloud. The ONLY Tailwind utility this site
   actually used, so it moves here rather than the CDN staying for it. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; }

::selection { background: rgba(27,180,212,.3); color: var(--text-primary); }

/* Keep the sticky header offset when jumping to in-page anchors */
:target { scroll-margin-top: calc(var(--header-h) + var(--space-4)); }

/* ---- Print --------------------------------------------------------------
   Order confirmation / invoices should print clean on white. */
@media print {
  .site-header, .site-footer, .cart-drawer, .toast-stack, .announce-bar,
  .no-print { display: none !important; }
  body { background: #fff; color: #000; }
  .canvas-dark, .canvas-panel { background: #fff !important; color: #000 !important; }
  a { color: #000; text-decoration: underline; }
}
