/* =========================================================================
   GulfRabit · Home module styles
   Only home-specific composition lives here (hero, trust strip layout,
   industry band, testimonials). Everything else reuses the shared design
   system. Load AFTER shared/css/style.css.
   ========================================================================= */

/* ---- Hero carousel ----------------------------------------------------
   The slides are finished campaign banners — headline, product shot and
   branding are painted into the artwork. Two consequences drive everything
   below.

   1. Nothing may cover the image. The old hero laid a 96%-white gradient over
      a decorative photo so authored copy could sit on top; do that to a
      finished banner and you have washed out the design. There is no scrim,
      no overlaid text and no overlaid button — every banner already paints
      its own SHOP NOW at the bottom-left, the whole slide is the link, and
      one of the four (figs) carries a full-width feature bar along the
      bottom that a centred overlay would sit on top of.

   2. Nothing may crop it either. `cover` is the usual carousel answer and it
      is wrong here: these four are 2.00, 1.50, 1.88 and 2.11 wide, so any
      single box crops at least three of them, and what gets cropped is the
      headline. `contain` fits each one whole; the leftover letterbox is
      filled with a blurred, over-scaled copy of the same slide, so the bars
      read as ambient colour from the artwork instead of dead space.

   The band under the artwork carries the dots. It is sized in one token so
   the viewport height can account for it. */
.hero {
  position: relative; overflow: hidden;
  --hero-h: clamp(170px, 50vw, 640px);
  --hero-progress: 3px;
}

.hero__viewport { position: relative; height: var(--hero-h); }

/* The two numbers the admin panel sets, with the authored defaults as
   fallbacks so the carousel behaves identically before the request answers,
   after it fails, and with modules/hero deleted entirely. */
.hero { --hero-interval: 6000ms; --hero-transition: 700ms; --hero-easing: var(--ease-out); }

.hero__slide {
  position: absolute; inset: 0;
  opacity: 0; visibility: hidden;
  transition: opacity var(--hero-transition) var(--hero-easing),
              visibility var(--hero-transition),
              transform var(--hero-transition) var(--hero-easing);
}
.hero__slide.is-active { opacity: 1; visibility: visible; }

/* ---- How one banner becomes the next ----------------------------------
   Chosen in the panel; written onto .hero as a data attribute. Each variant
   only adds a transform — opacity carries every one of them, so a slide is
   never left half-visible if a transform is unsupported. */

/* Sliding across: the outgoing banner leaves left, the incoming arrives from
   the right. Both offsets are on the same axis on purpose — a carousel whose
   slides pass through each other reads as a glitch, not a movement. */
.hero[data-hero-transition="slide"] .hero__slide { transform: translateX(6%); }
.hero[data-hero-transition="slide"] .hero__slide.is-active { transform: translateX(0); }

/* Zoom: the incoming banner settles from very slightly larger. 1.06 and not
   1.2 — this is a photograph of a product, and a big zoom makes it look like
   a stock-photo advert. */
.hero[data-hero-transition="zoom"] .hero__slide { transform: scale(1.06); }
.hero[data-hero-transition="zoom"] .hero__slide.is-active { transform: scale(1); }

/* Cutting straight. visibility is switched with no duration too, or the old
   slide lingers invisibly over the new one and swallows the click. */
.hero[data-hero-transition="none"] .hero__slide { transition: none; }

/* The slow drift, off unless the panel asks for it. Runs for the length of one
   slide and holds, so it is finished — not interrupted mid-glide — at the
   moment the next banner takes over. */
.hero.is-ken-burns .hero__slide.is-active .hero__art {
  animation: hero-drift var(--hero-interval) var(--hero-easing) forwards;
}
@keyframes hero-drift { from { transform: scale(1); } to { transform: scale(1.05); } }

/* Optional words over a banner, for artwork that does not already carry them.
   Most of these images have the headline baked in, so this is the exception —
   which is why it is opt-in per slide rather than a box every banner must fill.

   The scrim is not decoration. Merchant-supplied artwork can be any colour
   under this text, and white on a pale saffron photograph is unreadable; a
   gradient behind the words is the only way to keep the contrast honest
   without dictating what the picture may look like. */
.hero__words {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: grid; gap: var(--space-2);
  padding: var(--space-7) var(--space-5) var(--space-5);
  background: linear-gradient(to top, rgba(4, 25, 31, .78), rgba(4, 25, 31, .45) 45%, transparent);
  color: #fff;
  text-align: left;
}
.hero__words strong {
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 3.2vw, 2.25rem);
  line-height: var(--lh-tight);
}
.hero__words span { font-size: clamp(.875rem, 1.6vw, 1.0625rem); opacity: .92; }

.hero__media { position: absolute; inset: 0; overflow: hidden; background: var(--surface-band); }

/* <picture> exists only to offer the WebP source; it must not become a layout
   box. Without display:contents it sits between .hero__fit and .hero__art as
   an inline, shrink-wrapped element, and the artwork's height:100% then
   resolves against IT instead of the frame — the banner collapses. */
.hero picture { display: contents; }

/* Blurred fill for the letterbox. Over-scaled because a blur samples past its
   own edges and would otherwise leave a pale rim around the slide. Dimmed so
   the crisp artwork in front of it clearly reads as the foreground. */
.hero__backdrop {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  filter: blur(34px) saturate(1.15) brightness(.78);
  transform: scale(1.18);
}

/* The whole slide is the tap target — tapping anywhere on the banner buys,
   the SHOP NOW pill painted into the artwork included. There is no overlaid
   button: the painted pill is the only call to action. */
.hero__link { position: absolute; inset: 0; display: grid; place-items: center; }

/* THE ARTWORK FRAME.
 *
 * A `contain`-fitted <img> is letterboxed, and how much depends on the slide:
 * these four are 2.00, 1.50, 1.88 and 2.11 wide. This frame is exactly the
 * rendered artwork and nothing else: its width is whichever of the two limits
 * binds first, and --ar then fixes its height — so the blurred backdrop shows
 * only in the true letterbox, never behind the picture itself. */
.hero__fit {
  position: relative;
  display: block;
  width: min(100%, calc(var(--hero-h) * var(--arn)));
  aspect-ratio: var(--ar);
}
.hero__art { display: block; width: 100%; height: 100%; object-fit: contain; }

.hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-6); }

/* The arrows are gone. Two 38px circles existed to move a carousel that
   advances itself every 6s, and they cost more room than the control they
   offered was worth. What replaced them is the dot row doing a second job.

   A DOT ROW THAT IS ALSO A PROGRESS BAR.
   Four segments flush against the bottom of the artwork, and the active one
   fills left-to-right over exactly the autoplay interval. Dots say which of
   four you are on; this says that and when it will move, which is the
   question a carousel that advances on its own actually raises. It costs 3px
   where the old row cost 4px plus the space to sit clear of the button.

   They are still <button>s and still jump to their slide — the progress fill
   is a ::before inside, so tapping loses nothing. */
.hero__dots {
  position: absolute; z-index: 4;
  bottom: var(--space-3);
  left: 0; right: 0;
  display: flex; justify-content: center; gap: 4px;
}
/* THE BAR IS NOT THE BUTTON.
   26×3 is a target nobody can hit — under even WCAG 2.5.8's 24×24 floor. So
   the <button> is 26×24 and transparent, and the visible bar is a ::after
   inside it. The obvious fix — a pseudo-element stretching the old 3px
   button — could not work: the button carries overflow:hidden to clip the
   progress fill, so anything reaching past 3px was clipped away with it.
   The row grows to 24px and sits a little clear of the artwork's edge, which
   it wanted anyway. */
.hero__dot {
  position: relative;
  width: 26px; height: 24px;
  border: 0; background: none; padding: 0;
  cursor: pointer;
}
/* The track. Dark, not hairline-grey: these lie on the artwork, which is
   near-white on the acacia honey banner and near-black on the makhana one.
   An opaque dark bar with a light outline reads on both. */
.hero__dot::after {
  content: ""; position: absolute; z-index: 0;
  left: 0; right: 0; top: 50%;
  height: var(--hero-progress); margin-top: calc(var(--hero-progress) / -2);
  border-radius: var(--radius-pill);
  background: rgba(16,19,20,.42);
  box-shadow: 0 0 0 1px rgba(255,255,255,.45);
}
/* The fill starts the moment a dot gains .is-active — a CSS animation restarts
   when its element begins matching the selector, so the bar re-arms on every
   slide change without a line of JS. Same 6s as INTERVAL in home.js; if one
   moves, move the other.

   Centred with margin-top, NOT translateY: the animation owns `transform`,
   and a centring transform here would be overwritten the instant it ran. */
.hero__dot.is-active::before {
  content: ""; position: absolute; z-index: 1;
  left: 0; right: 0; top: 50%;
  height: var(--hero-progress); margin-top: calc(var(--hero-progress) / -2);
  border-radius: var(--radius-pill);
  background: var(--gr-gradient);
  transform-origin: left center;
  /* The SAME number the carousel's timer uses, read from the variable the page
     sets from the panel. These were a hardcoded 6s here and a hardcoded 6000
     in home.js — a pair with no reason to stay equal, and the bar finished
     early the moment anyone changed one of them. */
  animation: hero-progress var(--hero-interval) linear forwards;
}
/* Autoplay stops on hover, focus and a hidden tab. A bar that keeps filling
   while the slide it measures is parked is worse than no bar. */
.hero.is-paused .hero__dot.is-active::before { animation-play-state: paused; }

@keyframes hero-progress { from { transform: scaleX(0); } to { transform: scaleX(1); } }

@media (prefers-reduced-motion: reduce) {
  /* The global rule clamps this to 0.001ms, which reads as "already full" and
     happens to be the right answer — but say it outright rather than relying
     on a clamp to land somewhere useful. */
  .hero__dot.is-active::before { animation: none; transform: none; }
}

/* ---- Trust strip ------------------------------------------------------ */
.trust-band { border-top: 1px solid var(--border-hairline); border-bottom: 1px solid var(--border-hairline); background: var(--surface-band); }
.trust-band__inner { padding-block: var(--space-5); }

/* The clone home.js appends for the loop is a phone-only device. Hidden by
   default so the four-column desktop grid never sprouts eight cells. */
.trust-marquee__clone { display: none; }

/* ---- Trust strip on phones: one row, looping ---------------------------
 *
 * The shared .trust-strip is a 2×2 grid below 768px. Two rows of full-size
 * icons and 14/12 type is about 150px of the first screen spent on four short
 * claims, and it pushed Shop by Category off the fold entirely. Here the same
 * four items become a single ~40px row that travels.
 *
 * Nothing is truncated and no copy is dropped — the icons step 32→20px and the
 * type 14/12→11/10, and the loop carries whatever doesn't fit the viewport.
 * That is the point of looping it: on a 360px screen four chips of real copy
 * cannot all be on screen at once, so instead of shrinking the text until it
 * fits, the row shows ~2.5 at a time and keeps moving.
 */
@media (max-width: 767.98px) {
  .trust-band__inner { padding-block: var(--space-3); }

  .trust-marquee {
    --trust-icon: 20px;
    /* Deliberately below the type scale: --fs-12 is the smallest token and
       this is a supporting band, not body copy. 11/10 is what keeps a title
       and its subtitle on one 40px chip. */
    --trust-title: 0.6875rem;
    --trust-sub: 0.625rem;

    /* Base state is a finger-scrollable row. The animation is layered on top
       by .is-looping, which home.js only adds AFTER it has cloned the set —
       so a page with JS off gets a row it can swipe, never half a loop. */
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    /* Fade both ends, so a chip entering or leaving reads as travel rather
       than as a card being guillotined by the viewport edge. Kept to 14px and
       started at 40% rather than full transparency: a wider or harder fade
       washes out the leading chip's icon, which sits only 8px in from its own
       edge and is the first thing at rest on page load. */
    -webkit-mask-image: linear-gradient(90deg, rgba(0,0,0,.4), #000 14px, #000 calc(100% - 14px), rgba(0,0,0,.4));
            mask-image: linear-gradient(90deg, rgba(0,0,0,.4), #000 14px, #000 calc(100% - 14px), rgba(0,0,0,.4));
  }
  .trust-marquee::-webkit-scrollbar { display: none; }

  .trust-marquee .trust-strip { display: flex; flex-wrap: nowrap; width: max-content; gap: 0; }

  /* Spacing lives on the chip as a trailing margin instead of the flex `gap`.
     A cloned set gives eight items but only seven gaps, so with `gap` the
     halfway point of the track lands half a gap short of one full set and the
     loop hitches, once, every cycle. Margins make every item the same width
     including its space, and -50% is then exactly one set. */
  .trust-marquee .trust-item {
    flex: 0 0 auto;
    gap: var(--space-2);
    margin-inline-end: var(--space-2);
    padding: 0.375rem 0.75rem 0.375rem 0.5rem;
    border: 1px solid var(--border-hairline);
    border-radius: var(--radius-pill);
    background: var(--surface-page);
  }
  .trust-marquee .trust-item__icon { width: var(--trust-icon); height: var(--trust-icon); }
  .trust-marquee .trust-item__title { font-size: var(--trust-title); line-height: 1.3; white-space: nowrap; }
  .trust-marquee .trust-item__sub { font-size: var(--trust-sub); line-height: 1.3; white-space: nowrap; }

  .trust-marquee.is-looping { overflow: hidden; }
  .trust-marquee.is-looping .trust-item.trust-marquee__clone { display: flex; }
  .trust-marquee.is-looping .trust-strip {
    animation: trust-marquee 26s linear infinite;
    will-change: transform;
  }
}

/* One set is exactly half the cloned track, so -50% wraps seamlessly.
   home.js drives this animation's SPEED from scroll velocity: it drifts on its
   own and surges while the page is being scrolled, up or down alike. */
@keyframes trust-marquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

@media (prefers-reduced-motion: reduce) {
  /* The global reduced-motion rule clamps every animation to 0.001ms and a
     single iteration. Applied here that would snap the track to -50% and park
     it there — a band permanently frozen mid-loop. Stop the loop outright and
     give the row back to the finger. */
  .trust-marquee.is-looping { overflow-x: auto; }
  .trust-marquee.is-looping .trust-strip { animation: none !important; transform: none !important; }
  .trust-marquee.is-looping .trust-item.trust-marquee__clone { display: none; }
}

/* ---- Industry / B2B band --------------------------------------------- */
.industry-band {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--border-hairline);
  background:
    linear-gradient(90deg, rgba(255,255,255,.97), rgba(255,255,255,.82)),
    url("../../assets/images/hero/hero-industrial.svg") center/cover;
  padding: clamp(2rem, 5vw, 4rem);
}
.industry-band__grid { display: grid; gap: var(--space-6); grid-template-columns: 1fr; align-items: center; }
@media (min-width: 900px) { .industry-band__grid { grid-template-columns: 1.4fr 1fr; } }
.industry-stat { display: flex; flex-direction: column; }
.industry-stat__num { font-family: var(--font-display); font-size: var(--fs-31); color: var(--link); font-variant-numeric: tabular-nums; }
.industry-stat__label { font-size: var(--fs-12); color: var(--text-muted); text-transform: uppercase; letter-spacing: .08em; }

/* ---- Testimonials ----------------------------------------------------- */
.testimonials { position: relative; }
.testi-track { display: grid; grid-auto-flow: column; grid-auto-columns: 100%; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; }
.testi-track::-webkit-scrollbar { display: none; }
.testi-card { scroll-snap-align: center; padding: var(--space-2); }
.testi-card__inner { max-width: 760px; margin: 0 auto; text-align: center; padding: var(--space-7) var(--space-5); }
.testi-quote { font-family: var(--font-display); font-size: clamp(1.3rem, 3vw, var(--fs-31)); line-height: var(--lh-snug); font-weight: var(--fw-semibold); }
.testi-author { margin-top: var(--space-5); color: var(--text-muted); }
.testi-stars { color: var(--lime-ink); margin-bottom: var(--space-4); }

/* ---- Newsletter band -------------------------------------------------- */
.newsletter-band { position: relative; border-radius: var(--radius-lg); overflow: hidden; background: var(--gr-gradient); color: #04222b; padding: clamp(2rem, 5vw, 4rem); text-align: center; }
.newsletter-band .input-gr { background: rgba(255,255,255,.9); border-color: transparent; color: var(--gr-ink); }
.newsletter-band .btn-gr { background: var(--gr-ink); color: var(--text-inverse); border-color: transparent; }
.newsletter-band .btn-gr:hover { background: #000; }

/* ---- Featured brands (text logo-wall) -------------------------------- */
.brand-wall { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: clamp(1.5rem, 5vw, 3.5rem); padding: var(--space-6) var(--space-4); }
.brand-wall__item { font-family: var(--font-display); font-weight: var(--fw-semibold); font-size: clamp(1.1rem, 2.4vw, var(--fs-25)); color: var(--text-muted); letter-spacing: .01em; transition: color var(--dur-base) var(--ease-out); }
.brand-wall__item:hover { color: var(--text-primary); }

/* ---- Category grid on home ------------------------------------------- */
.home-cat-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(2, 1fr); }
@media (min-width: 768px)  {
  .home-cat-grid { grid-template-columns: repeat(4, 1fr); }
  /* There used to be a nudge here pushing the ninth of ten tiles into column 2,
     because two tiles hard left under a full row read as two that failed to
     load. The launch set is seven now, so the last row carries three of four
     columns — which reads as a row, not as breakage — and the rule was written
     to switch itself off at any count but ten. Removed rather than left as a
     selector that can never match. */
}
/* Clean icon-led tiles with a faint brand glow that lifts on hover. Phones
   only now — from 768px up the artwork covers the card and nothing of this
   can show through it. */
.home-cat-grid .category-card::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(120% 80% at 100% 0%, rgba(27,180,212,.12), transparent 58%);
  opacity: .65; transition: opacity var(--dur-base) var(--ease-out);
}
/* Guarded like every other hover rule: on touch it latches after a tap and
   one tile keeps a brighter glow than its neighbours for no reason. */
@media (hover: hover) {
  .home-cat-grid .category-card:hover::before { opacity: 1; }
}
.home-cat-grid .category-card > * { position: relative; z-index: 1; }

/* ---- Category tiles: light, square art, label under it ----------------
   Two wrong turns before this one, both worth naming so they are not retried.

   First the picture sat inset in white padding with its name captioned below
   — a photograph in a box, not a control, and the masters make it worse: each
   is a subject in its own dark vignette, half dark and half pale, so eight in
   a row read as unrelated images rather than a set.

   Then the picture became the whole tile with the name over a gradient. The
   contrast sampler was happy and the result was black bars across a white
   premium storefront: a number had been tuned instead of the page being
   looked at. Scrims belong over bright art; darkening an already-dark
   photograph to float type on it is the wrong tool.

   Neither was really a layout problem. The masters were replaced with the
   subject on a seamless white ground, shot as one set, and the tiles came
   right on their own — which is the actual lesson, and why the prompts and
   the reason they must match now live in tools/gen-category-images.py.

   What remains is deliberately plain: the art stays square and edge to edge,
   the name sits UNDERNEATH it, nothing is laid over the photograph, so
   nothing has to be darkened to stay readable. White art on a white card
   would dissolve, so the card's hairline, its shadow and the lift on hover
   are what make it a control rather than a picture. */
@media (min-width: 768px) {
  .home-cat-grid .category-card {
    min-height: 0;
    padding: 0;
    overflow: hidden;              /* the radius has to clip the artwork */
    justify-content: flex-start;
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
    box-shadow: 0 1px 3px rgba(16, 32, 44, .06);
  }
  .home-cat-grid .category-card__media {
    margin: 0;
    border-radius: 0;
    background: var(--surface-sunken);
  }
  .home-cat-grid .category-card__count { display: none; }
  .home-cat-grid .category-card__title {
    padding: 11px var(--space-2) 13px;
    text-align: center;
    /* The wordmark's colour, at the cut that clears 4.8:1 on white — raw
       --gr-cyan is 2.5:1 and would fail as body-sized type. */
    color: var(--link);
    font-size: 13px;
    line-height: 1.3;
    letter-spacing: -.01em;
    transition: color var(--dur-base) var(--ease-out);
  }
  /* The tile carries the brand on its edge too, but quietly: eight
     full-strength cyan rectangles in a row would shout over the artwork they
     are framing. A third of the way there at rest, all the way on hover. */
  .home-cat-grid .category-card {
    border-color: rgba(27, 180, 212, .34);
  }
  /* The photo answers the pointer. Slow and small — 600ms and 5% reads as the
     image settling, where a fast snap reads as a hover effect. */
  .home-cat-grid .category-card__img {
    transition: transform 600ms var(--ease-out);
  }
  /* The hover, in four parts that fire together: the tile lifts, its edge
     goes to full brand colour, the shadow it casts is tinted with that
     colour rather than grey, and the photograph pushes in slightly. The
     shadow is what does most of the work — a grey drop shadow reads as a box
     moving, a coloured one reads as the tile lighting up. */
  @media (hover: hover) {
    .home-cat-grid .category-card:hover {
      border-color: var(--gr-cyan);
      box-shadow: 0 10px 28px -6px rgba(27, 180, 212, .34),
                  0 2px 8px rgba(16, 32, 44, .06);
    }
    .home-cat-grid .category-card:hover .category-card__img { transform: scale(1.06); }
    .home-cat-grid .category-card:hover .category-card__title { color: var(--gr-cyan-dark); }
  }
  /* Touch has no hover to preview with, so the press is the whole feedback. */
  .home-cat-grid .category-card:active { border-color: var(--gr-cyan); }
  .home-cat-grid .category-card:focus-visible {
    outline: 2px solid var(--link);
    outline-offset: 3px;
  }
}

/* ---- One row of eight, desktop only -----------------------------------
   Eight across is the whole catalogue in one glance with no second row, and
   it keeps the shelves below above the fold. Only where there is room: under
   1200px eight columns are too narrow for these names. */
@media (min-width: 1200px) {
  .home-cat-grid { grid-template-columns: repeat(8, 1fr); gap: var(--space-3); }
  .home-cat-grid .category-card__title {
    /* Sized to keep the longest name — "Kitchen Appliances" — on one line at
       the narrowest desktop tile, 142px on a 1280px laptop. Measured. */
    font-size: 12.5px;
    padding: 10px 6px 12px;
  }
  .home-cat-grid .category-card__media .category-card__icon { width: 34px; height: 34px; }
  /* Proportional to the card: -4px on a tile this size is a jump. */
  .home-cat-grid .category-card:hover { transform: translateY(-3px); }
}

/* ---- Category section on phones --------------------------------------
   At 390px the two-up layout spent a full screen on eight tiles, and the
   header ate three lines before the first one (overline, title, then the
   View-all button wrapping under it). Phones get four tiles per row and a
   one-line header — picture and label, no blurb, which is all that fits in
   an 80px tile.

   Desktop keeps the eyebrow and the large cards — this whole block is
   scoped to .home-cat and below 768px, so no other section moves. */
@media (max-width: 767px) {
  /* Hiding the overline left .section-gr's 64px top padding sitting under the
     trust band as a stripe of nothing — the eyebrow it was cushioning is not
     there any more. The band's own border already separates the two sections;
     16px is all the gap that is needed on top of it. */
  .home-cat { padding-top: var(--space-4); }

  /* One line: title left, View all right, baselines aligned. */
  .home-cat .section-head {
    flex-wrap: nowrap;
    align-items: baseline;
    gap: var(--space-3);
    margin-bottom: var(--space-5);
  }
  .home-cat .section-head__titles { gap: 0; min-width: 0; }
  .home-cat .overline { display: none; }
  /* 75% of the 1.9rem phone h2 — still the loudest thing on the row. */
  .home-cat .section-head h2 {
    /* A touch larger than the sans it replaced: Playfair's x-height is smaller
       at the same px, so matching the old 1.425rem would have read as a step
       DOWN in the heading rather than a change of voice. */
    font-size: 1.55rem;
    line-height: 1.15;
    letter-spacing: -0.008em;
  }
  /* The button chrome reads as clutter next to a smaller title; a text
     link with a padded tap area carries it. */
  .home-cat .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;
  }
  .home-cat .section-head .btn-gr:hover,
  .home-cat .section-head .btn-gr:active { background: none; box-shadow: none; transform: none; }

  .home-cat-grid {
    grid-template-columns: repeat(4, 1fr);
    grid-auto-rows: 1fr;   /* every row matches, whether the label wraps or not */
    gap: var(--space-2);
  }
  .home-cat-grid .category-card {
    min-height: 0;
    justify-content: flex-start;
    align-items: center;
    text-align: center;
    gap: 6px;
    padding: 12px 6px 11px;
    border-radius: 12px;   /* between the token pair: 16px is a bubble at 80px wide */
  }
  /* The photo well fills the tile's inner width — about 68px at 390px — and
     stays square, so the same crop serves both breakpoints and no phone
     downloads the desktop tier (see the sizes= on the img).

     It costs roughly 30px per row over the 22px icon it replaced, which is
     the price of the section being a showcase again rather than a directory.
     The header above is still one line, so the first row of tiles is where
     it was. */
  .home-cat-grid .category-card__media {
    margin-bottom: 0;
    border-radius: 10px;   /* matches the 12px tile at this size */
  }
  .home-cat-grid .category-card__icon {
    width: 22px; height: 22px;
  }
  .home-cat-grid .category-card > .category-card__icon { margin-bottom: 0; }
  /* Two lines reserved so "Imported Food" and "Beauty" sit at the same
     height; anything longer clamps rather than pushing the tile taller. */
  .home-cat-grid .category-card__title {
    font-size: .6875rem;
    line-height: 1.25;
    letter-spacing: -0.005em;
    min-height: calc(2 * 1.25em);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  /* No room for "Dates · Coffee · Chocolate" in an 80px tile. */
  .home-cat-grid .category-card__count { display: none; }
}

/* ---- Mobile hero ------------------------------------------------------
   The standing rule here is that the shop must start near the fold: an
   earlier measurement at 390px found the hero 702px tall with the first
   product section only beginning at 1042px, more than a screen of scrolling
   before anything was for sale.

   The vw-based height above already satisfies that — 50vw of artwork plus a
   34px strip is ~229px at 390px wide — so there is no height override to
   make, and none of the old per-width button tuning survives: at 24px tall
   and its own label wide, the pill has clearance at 320px. */
@media (max-width: 639.98px) {
  /* Kept from the previous hero: full-width buttons on phones. Now only the
     industry band uses .hero__actions, and the rule still suits it. */
  .hero__actions { margin-top: var(--space-5); }
  .hero__actions .btn-gr { width: 100%; justify-content: center; }
}

/* Very small phones (380px and under). The strip is already at its 34px floor
   and the pill is sized by its own label, so only the progress row needs a
   word: four 26px segments plus gaps is 116px, which still centres cleanly at
   320px. Nothing to override. */

/* ---- Premium Picks · the shelf that advances itself -------------------
 *
 * On a phone the rail shows one and a half cards and nothing says the other
 * six exist. A scrollbar would say it; .snap-rail hides the scrollbar, and the
 * two arrows sit up in the section header where a thumb is not. So the shelf
 * moves on its own — one product every few seconds, always right to left,
 * which is the direction the remaining products are in.
 *
 * The travel is driven from home.js (a rAF tween, not scroll-behavior: smooth
 * — the browser picks its own duration for that, and the brief is exactly
 * 400ms). What CSS owns is the two things that make it look designed rather
 * than merely animated:
 *
 *   1. THE SNAP IS OFF FOR THE LENGTH OF A STEP. scroll-snap-type: mandatory
 *      re-snaps after EVERY programmatic scroll, so a tween that starts on a
 *      snap point has each of its frames dragged straight back to it — the rail
 *      holds perfectly still for 400ms and then teleports. .is-stepping is on
 *      only while home.js is writing scrollLeft; between steps the rail is
 *      parked exactly on a card boundary and snap has nothing to correct, so a
 *      finger arriving at any moment still gets a snapping rail.
 *
 *   2. THE RIGHT EDGE DISSOLVES. Without it the incoming card is sliced by the
 *      viewport mid-motion, and a hard vertical cut travelling across a moving
 *      row is the one thing that makes a carousel look cheap. A 56px fade lets
 *      the next product arrive out of the page instead of out of a guillotine.
 *      Purely decorative — where mask-image is unsupported the rail simply has
 *      a hard edge again, which is what it has today.
 */
[data-rail-autoplay].is-stepping { scroll-snap-type: none; }

@media (max-width: 767.98px) {
  [data-rail-autoplay] {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 56px), transparent 100%);
    mask-image: linear-gradient(to right, #000 calc(100% - 56px), transparent 100%);
  }

  /* Nothing autoplays under reduced motion — home.js never starts the timer —
     and a fade that softens an *incoming* card is meaningless on a rail that
     only moves when a finger moves it. It would just hide half a product. */
  @media (prefers-reduced-motion: reduce) {
    [data-rail-autoplay] {
      -webkit-mask-image: none;
      mask-image: none;
    }
  }
}
