﻿/* ==========================================================================
   COMPONENTS
   Contents:
     1. Buttons
     2. Badges & chips
     2b. Home hero
     3. Cards
     4. Offer card (the deal tile)
     5. Price
     6. Category & feature tiles
     7. Navigation & dropdowns
     8. Breadcrumb, pagination, tabs
     9. Alerts & notices
    10. Tables
    11. Drawer / offcanvas / modal
    12. Tooltip, spinner, misc
    13. Advertising slot
    14. Filter panel
    15. Offer page — saving and action cluster
    16. Branch card
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. BUTTONS
   Base geometry from the source design: pill radius, 15px/500 label,
   0.5px tracking, 0.21s ease-in-out on everything.
   -------------------------------------------------------------------------- */
.btn {
  --btn-bg: var(--color-primary);
  --btn-color: var(--color-primary-fg);
  --btn-border: transparent;
  --btn-bg-hover: var(--color-primary-hover);
  --btn-color-hover: var(--color-primary-fg);
  --btn-border-hover: transparent;

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 17px var(--space-8);
  border: 1px solid var(--btn-border);
  border-radius: var(--radius-control);
  background-color: var(--btn-bg);
  color: var(--btn-color);
  font-family: inherit;
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  line-height: 1;
  letter-spacing: var(--ls-wide);
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: var(--transition);
  -webkit-appearance: none;
  appearance: none;
}
.btn:hover {
  background-color: var(--btn-bg-hover);
  color: var(--btn-color-hover);
  border-color: var(--btn-border-hover);
}
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.btn:disabled, .btn.is-disabled {
  opacity: 0.55;
  pointer-events: none;
}
.btn .icon { font-size: var(--fs-base); }

/* Variants */
.btn--primary {
  --btn-bg: var(--color-primary);
  --btn-bg-hover: var(--color-primary-hover);
}
.btn--primary:hover { box-shadow: var(--shadow-brand); }

.btn--secondary {
  --btn-bg: var(--color-secondary);
  --btn-bg-hover: var(--color-heading);
}

.btn--accent {
  --btn-bg: var(--color-accent);
  --btn-color: var(--color-accent-fg);
  --btn-bg-hover: var(--color-accent-hover);
  --btn-color-hover: var(--color-accent-fg);
  font-weight: var(--fw-semibold);
}

/* Inverse pair: a bar-coloured button stays dark in both schemes rather than
   inverting to near-white with white text written over it. */
.btn--dark {
  --btn-bg: var(--color-inverse-surface);
  --btn-color: var(--color-inverse-heading);
  --btn-bg-hover: var(--color-primary);
  --btn-color-hover: var(--color-primary-fg);
}

.btn--danger {
  --btn-bg: var(--color-danger);
  --btn-color: var(--color-sale-fg);
  --btn-bg-hover: color-mix(in oklab, var(--color-danger) 84%, #000);
  --btn-color-hover: var(--color-sale-fg);
}

/* Outline: transparent with a hairline border — the "All Categories" style */
.btn--outline {
  --btn-bg: transparent;
  --btn-color: var(--color-heading);
  --btn-border: var(--color-border);
  --btn-bg-hover: var(--color-primary);
  --btn-color-hover: var(--color-primary-fg);
  --btn-border-hover: var(--color-primary);
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  padding: var(--space-3) var(--space-6);
}

/* Soft: low-emphasis grey fill. Pill like every other button — the size is
   what carries "low emphasis" here, not a different corner. */
.btn--soft {
  --btn-bg: var(--color-surface-alt);
  --btn-color: var(--color-heading);
  --btn-bg-hover: var(--color-primary);
  --btn-color-hover: var(--color-primary-fg);
  border-radius: var(--radius-control);
  padding: var(--space-2) var(--space-4);
  font-size: var(--fs-sm);
  font-weight: var(--fw-normal);
  letter-spacing: var(--ls-normal);
}

/* WhatsApp, in WhatsApp's own green. A service mark rather than the
   deployment's colour — see the note on `--color-whatsapp` in tokens.css — and
   the foreground is COMPUTED from it, which is what puts black on the green
   instead of the 2.0:1 white the service's own button uses. */
.btn--whatsapp {
  --btn-bg: var(--color-whatsapp);
  --btn-color: var(--color-whatsapp-fg);
  --btn-bg-hover: color-mix(in oklab, var(--color-whatsapp) 86%, #000);
  --btn-color-hover: var(--color-whatsapp-fg);
  font-weight: var(--fw-semibold);
}

/* Tinted brand wash */
.btn--tint {
  --btn-bg: var(--color-primary-soft);
  --btn-color: var(--color-primary);
  --btn-bg-hover: var(--color-primary);
  --btn-color-hover: var(--color-primary-fg);
}

/* Bare text button with a sliding arrow */
.btn--link {
  --btn-bg: transparent;
  --btn-color: var(--color-primary);
  --btn-bg-hover: transparent;
  --btn-color-hover: var(--color-primary-hover);
  padding: 0;
  border-radius: 0;
  font-weight: var(--fw-semibold);
}
.btn--link:active { transform: none; }
.btn--link .icon { transition: transform var(--dur) var(--ease); }
.btn--link:hover .icon { transform: translateX(calc(4px * var(--flip))); }

/* Sizes */
.btn--xs { padding: var(--space-2) var(--space-4); font-size: var(--fs-xs); }
.btn--sm { padding: var(--space-3) var(--space-5); font-size: var(--fs-base); }
.btn--lg { padding: 20px var(--space-9); font-size: var(--fs-lg); }
.btn--block { width: 100%; }

/* Square icon-only button */
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  color: var(--color-body);
  cursor: pointer;
  transition: var(--transition);
}
.btn-icon:hover {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-primary-fg);
}
.btn-icon--round  { border-radius: var(--radius-full); }
.btn-icon--bare   { border-color: transparent; background: transparent; }
.btn-icon--sm     { width: 34px; height: 34px; }
.btn-icon--lg     { width: 52px; height: 52px; }

/* Header action with a counter bubble */
.action-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  color: var(--color-heading);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  transition: var(--transition);
}
.action-btn:hover { color: var(--color-primary); background: var(--color-primary-soft); }
.action-btn__count {
  position: absolute;
  top: -2px; inset-inline-start: 26px;
  min-width: 18px; height: 18px;
  display: grid;
  place-items: center;
  padding: 0 4px;
  border-radius: var(--radius-pill);
  background: var(--color-primary);
  color: var(--color-primary-fg);
  font-size: 10px;
  font-weight: var(--fw-semibold);
  line-height: 1;
}
.action-btn__label { display: flex; flex-direction: column; line-height: 1.35; }
.action-btn__label small { color: var(--color-meta); font-size: var(--fs-xs); }

/* --------------------------------------------------------------------------
   2. BADGES & CHIPS
   -------------------------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 3px var(--space-2);
  border-radius: var(--radius-xs);
  background: var(--color-surface-alt);
  color: var(--color-heading);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: 1.5;
}
.badge--primary { background: var(--color-primary); color: var(--color-primary-fg); }
.badge--accent  { background: var(--color-accent);  color: var(--color-accent-fg); }
.badge--sale    { background: var(--color-sale);    color: var(--color-sale-fg); }
.badge--dark    { background: var(--color-inverse-surface); color: var(--color-inverse-heading); }
.badge--outline {
  background: transparent;
  border: 1px solid var(--color-border);
  color: var(--color-body);
}
.badge--pill { border-radius: var(--radius-pill); padding-inline: var(--space-3); }

/* A sentence that script has turned into a running clock. Belongs to no single
   component on purpose: it rides the card's urgency pill and the offer page's
   badge alike, and both only ever ask it for figures that do not dance.

   Tabular figures, because a chip that breathes once a second as the digit
   widths change is a flicker, not a timer. Nothing here is physical: the
   composed string is digits and a translated unit letter, and the bidi
   algorithm lays it out correctly in both directions. */
.countdown {
  font-variant-numeric: tabular-nums;
  /* The seconds change under the pointer; selecting them by accident on a drag
     is noise, and there is nothing here worth copying. */
  user-select: none;
}

/* The signature arrow-notched sale flag */
.badge--flag {
  padding-block: 3px;
  padding-inline: 11px var(--space-2);   /* the wider side is where the notch bites in */
  clip-path: polygon(15% 0%, 100% 0%, 100% 100%, 15% 100%, 0% 50%);
}

/* Removable filter chip */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding-block: var(--space-1);
  padding-inline: var(--space-4) var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  font-size: var(--fs-sm);
  color: var(--color-heading);
  transition: var(--transition);
}
.chip:hover { border-color: var(--color-primary); }
.chip__remove {
  display: grid;
  place-items: center;
  width: 18px; height: 18px;
  border-radius: var(--radius-full);
  background: var(--color-surface-alt);
  color: var(--color-muted);
  cursor: pointer;
  transition: var(--transition);
}
.chip__remove:hover { background: var(--color-sale); color: var(--color-sale-fg); }
.chip__remove .icon { font-size: 9px; }

/* A chip that is a LINK, not a removable filter. The base chip's inline padding
   is deliberately lopsided — 16px in front, 8px behind — to leave room for the
   × ; without one, that gap reads as a typo. */
.chip--plain { padding-inline: var(--space-4); }
.chip--plain:hover { border-color: var(--color-primary); color: var(--color-primary); }

/* "Clear all" reads as an action rather than as another narrowing, so it drops
   the pill's fill and keeps only the outline. */
.chip--clear {
  padding-inline: var(--space-4);
  border-style: dashed;
  color: var(--color-primary);
  font-weight: var(--fw-semibold);
}

/* The chips themselves. Wrap rather than scroll: a filter a visitor cannot see
   is a filter they will not think to remove. */
.filter-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  flex: 1 1 260px;
  min-width: 0;
}

/* One row holding the filter trigger and the active chips side by side. Sharing
   a wrapping row rather than stacking two blocks saves about 30px above the fold
   on a phone, which is half a card.

   A real element rather than `display: contents` on the group: an element with
   a role still has to have a box to be reliably announced. */
.browse__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-5);
}

/* --------------------------------------------------------------------------
   THE HOME HERO — the "savings search engine"

   The band used to be a heading, a count and 90px of air, which read as a page
   that had not finished loading. What was missing was not decoration: it was
   the ONE THING the page is for. A directory's front page is a search box, and
   everything else on this band exists to explain it.

   The two blooms are the only ornament, they are drawn from the brand's own
   accent and primary, and they are `pointer-events: none` — nothing here is
   an image request, so the band costs nothing to paint.
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  isolation: isolate;
  padding-block: var(--space-12) var(--space-11);
  background: var(--color-surface-tint);
  text-align: center;
  overflow: hidden;
}
.hero::before,
.hero::after {
  content: "";
  position: absolute;
  z-index: -1;
  width: 420px;
  aspect-ratio: 1;
  border-radius: var(--radius-full);
  pointer-events: none;
  /* `transparent` in a gradient is transparent BLACK in sRGB, which grey-hazes
     the edge of a coloured bloom. Mixing toward the band's own colour instead
     fades to nothing. */
  background: radial-gradient(circle,
              color-mix(in oklab, var(--bloom) 24%, transparent) 0%,
              color-mix(in oklab, var(--bloom)  0%, transparent) 70%);
}
.hero::before { --bloom: var(--color-accent);  top: -160px; inset-inline-start: -120px; }
.hero::after  { --bloom: var(--color-primary); bottom: -200px; inset-inline-end: -140px; }

.hero__inner {
  position: relative;
  max-width: 760px;
  margin-inline: auto;
}
/* 2.5rem at the top, and it CLAMPS rather than stepping at a breakpoint: the
   heading is one line of a tagline a deployment writes, and a deployment's
   tagline can be twice as long as this one's. */
.hero__title {
  margin-bottom: var(--space-3);
  font-size: clamp(1.75rem, 5.4vw, 2.5rem);
  line-height: 1.15;
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-tight);
  text-wrap: balance;
}
.hero__count {
  margin-bottom: 0;
  font-size: var(--fs-lg);
  color: var(--color-meta-strong);
}
.hero__search { margin-top: var(--space-7); }

/* A line of what the search actually reaches, under the box. Not navigation —
   the categories have their own row below — but proof the box has something
   behind it. */
.hero__hints {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-5);
  font-size: var(--fs-sm);
  color: var(--color-meta);
}

@media (max-width: 767px) {
  .hero { padding-block: var(--space-9) var(--space-8); }
  .hero::before, .hero::after { width: 260px; }
}

/* --------------------------------------------------------------------------
   3. CARDS
   -------------------------------------------------------------------------- */
.card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  transition: var(--transition);
}
.card--hover:hover {
  border-color: transparent;
  box-shadow: var(--shadow-card-hover);
  transform: translateY(-3px);
}
/* Flat is the deliberate opt-out — a panel that is part of the page rather
   than an object sitting on it. It has to clear the shadow explicitly now. */
.card--flat   { border: 0; background: var(--color-surface-alt); box-shadow: none; }
.card--raised { border: 0; box-shadow: var(--shadow-card-hover); }
.card__media { position: relative; overflow: hidden; background: var(--color-surface-alt); }
.card__media img { width: 100%; transition: transform var(--dur-slow) var(--ease-out); }
.card--hover:hover .card__media img { transform: scale(1.06); }
.card__body { padding: var(--space-5); flex: 1 1 auto; }
.card__title {
  margin-bottom: var(--space-2);
  font-size: var(--fs-h5);
  font-weight: var(--fw-semibold);
}
.card__title a { color: inherit; }
.card__title a:hover { color: var(--color-primary); }
.card__text { margin-bottom: 0; font-size: var(--fs-base); line-height: var(--lh-snug); }
.card__footer {
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--color-border-soft);
}
.card__meta {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
  font-size: var(--fs-sm);
  color: var(--color-meta);
}
.card__meta > * { display: inline-flex; align-items: center; gap: var(--space-1); }

/* --------------------------------------------------------------------------
   4. OFFER CARD — the deal tile

   The one unit this whole product is a grid of, and it is NOT a product card.
   Nothing here is bought: what the tile has to answer, in the half second it
   gets, is "how much is it now, how much is off, who is offering it, and how
   long have I got".

   THE HIERARCHY, largest first:

     1. THE PRICE NOW  — the biggest thing on the tile, in the sale red. This is
                         a change: the percentage flag used to be the largest
                         thing. A percentage is a ratio, and a ratio needs the
                         other number to mean anything; the money does not.
     2. THE DISCOUNT   — a yellow pill floating on the photograph. Still the
                         first thing seen, because colour outranks size at a
                         glance, but no longer the biggest.
     3. THE MERCHANT   — a 32px disc straddling the bottom edge of the photo,
                         with the name beside it. A discount from nobody is not
                         worth a phone call. A verified tick sits here.
     4. THE TITLE      — what the deal is, and still the main link.

   THE FRAME IS A PHYSICAL CARD. It was flat until D-69 — no border, no shadow,
   the offer floating on the page — and flat turned out to be the wrong call for
   a grid this dense: with no edge, a card's image read as belonging to the row
   above it and the eye had to do the grouping the layout should have done. So
   every offer now sits on `--color-surface` with a 12px corner and one wide,
   low-contrast lift. ONE shadow, shared through a token: the noise the flat
   version was avoiding comes from forty DIFFERENT shadows, not from forty.

   Hover deepens that lift rather than adding a second one, and it is pointer
   devices only — a touch device never hovers, and half this site's traffic is
   a phone.

   THE PHOTOGRAPH IS FULL-BLEED. The card carries no padding of its own; the
   image runs edge to edge into the card's own top corners and `overflow: hidden`
   does the clipping. That is what makes it a tile rather than a framed picture.

   THE MERCHANT MARK IS A MONOGRAM, not a logo file — see D-70. The disc is
   sized, positioned and ringed exactly as a logo would be, so dropping an
   `<img>` into it is a view change and nothing more. What is missing is the
   data: `OfferCard` carries no merchant logo URL, and putting one there means
   twenty extra image requests for a 32px square on every grid.
   -------------------------------------------------------------------------- */
.offer-card {
  /* One knob. Three rules read it — the disc itself, the space the body leaves
     above it, and the indent that keeps the merchant name clear of it — and
     they are only ever correct together. */
  --offer-avatar: 32px;

  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  /* Clips the full-bleed photograph to the card's own corner. */
  overflow: hidden;
  transition: border-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}

/* Pointer devices only. `any-hover` rather than `hover` so a laptop with a
   touchscreen still gets it, and a phone never does. */
@media (any-hover: hover) {
  .offer-card:hover {
    border-color: var(--color-border);
    box-shadow: var(--shadow-card-hover);
    transform: translateY(-3px);
  }
  .offer-card:hover .offer-card__media img { transform: scale(1.05); }
}

/* Focus follows the same shape as hover, so keyboard and mouse see one card. */
.offer-card:focus-within {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-card-hover);
}

/* ---- THE PLATE -----------------------------------------------------------
   A positioning context for the two things that float on the photograph and
   for the merchant disc that hangs off the bottom of it. The disc cannot live
   inside `.offer-card__media`: that element clips its own overflow so the
   hover zoom stays inside the frame, and it would clip the disc with it. */
.offer-card__plate { position: relative; }

.offer-card__media {
  position: relative;
  display: block;
  background: var(--color-surface-alt);
  overflow: hidden;
}
/*
   Cover, not contain: an offer's image is a photograph of a deal, not a cut-out
   packshot, and letterboxing it wastes the half of the card people look at.
   4:3 rather than square — it fits more card in less column on a phone.
*/
.offer-card__media img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out);
}
/* No image: the plate stays, at the same ratio. A grid where some cards are
   shorter than others reads as broken rather than as sparse. */
.offer-card__media--empty { aspect-ratio: 4 / 3; }

/* ---- 2. THE DISCOUNT -----------------------------------------------------
   A pill floating clear of the photograph's corner, not a flag welded to it.

   THE CORNER IS LOGICAL, NOT PHYSICAL. `inset-inline-start` puts it top-RIGHT
   on the Arabic site — the deployment's default language and the corner the
   brief asked for — and top-LEFT in English, which is where a Latin reader's
   eye starts. A physical `right` here would park it in the English page's
   trailing corner, which is the last place anyone looks. See rule 7. */
.offer-card__deal {
  position: absolute;
  top: var(--space-3);
  inset-inline-start: var(--space-3);
  z-index: 2;
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  padding: 6px var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--color-accent-fg);
  font-weight: var(--fw-bold);
  line-height: 1;
  /* The digits read left-to-right in Arabic too, and the minus sign belongs on
     the same side as them whichever way the page runs. */
  font-variant-numeric: tabular-nums;
}
.offer-card__deal-value { font-size: var(--fs-md); letter-spacing: var(--ls-tight); }
.offer-card__deal-unit {
  font-size: 10px;
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
}

/* ---- URGENCY -------------------------------------------------------------
   One pill, three readings, and the colour is the whole difference: the sale red
   for a window that is genuinely closing, a quiet chip for an offer that is
   running with a date on it, grey for one that has closed. An open-ended offer
   gets none at all.

   The RED IS STILL THE SCARCE ONE. Since the countdown started running on every
   dated card (D-83) the pill itself is no longer the signal — but a red pill on
   every card in a grid would say nothing, so the loud variant stays inside the
   last three days and the rest of the grid takes the calm one below.

   The DARKER red, not the price red: this is small white text on a fill, where
   4.5:1 is the bar rather than 3:1. Sits on the trailing side so it can never
   collide with the discount pill, whichever way the page runs. */
.offer-card__urgency {
  position: absolute;
  bottom: var(--space-3);
  inset-inline-end: var(--space-3);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 3px var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--color-sale-strong);
  color: var(--color-sale-fg);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  line-height: 1.5;
}
.offer-card__urgency .icon { font-size: 12px; }

/* An offer that is running, with weeks rather than days left. The card's own
   surface and its own ink: a chip that reads as information sitting on the
   photograph, not as a warning. It carries the same clock and the same running
   digits — what it does not carry is the sale colour, which stays the property
   of the last three days.

   The card surface rather than a scrim, because this pill has to be legible over
   a photograph nobody vetted, and mixing a translucent dark over an unknown image
   is how small white text ends up at 4.4:1 on a pale sky. */
.offer-card__urgency--calm {
  background: var(--color-surface);
  color: var(--color-heading);
  font-weight: var(--fw-medium);
  /* The tight one. A pill this small needs the edge lifted off the photograph,
     not a drop shadow under it. */
  box-shadow: var(--shadow-xs);
}

/* An offer whose window has closed. Greyed rather than hidden: a card can be
   rendered from a cached response on the day an offer lapses. */
.offer-card__urgency--ended {
  background: var(--color-inverse-surface);
  color: var(--color-inverse-heading);
}

/* ---- 3. THE MERCHANT -----------------------------------------------------
   The disc straddles the photograph's bottom edge: half on the picture, half on
   the card. The ring is the card's own surface rather than white, so it stays a
   cut-out in both colour schemes instead of a white halo in the dark one. */
.offer-card__avatar {
  position: absolute;
  z-index: 2;
  inset-block-end: calc(var(--offer-avatar) / -2);
  inset-inline-start: var(--space-4);
  display: grid;
  place-items: center;
  width: var(--offer-avatar);
  height: var(--offer-avatar);
  border-radius: var(--radius-full);
  border: 2px solid var(--color-surface);
  background: var(--color-primary-soft);
  color: var(--color-primary);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  line-height: 1;
  text-transform: uppercase;
  overflow: hidden;
}
/* Ready for the logo the payload does not carry yet — see D-70. */
.offer-card__avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.offer-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  flex: 1 1 auto;
  /* Only 8px at the top: the merchant row is indented past the disc rather than
     pushed below it, so the half that hangs down sits BESIDE the name. */
  padding: var(--space-2) var(--space-4) var(--space-4);
}

.offer-card__merchant {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  /* Clears the disc. The disc and this row share the same `--space-4` inline
     offset, so what is left to clear is the disc itself plus a gap. */
  padding-inline-start: calc(var(--offer-avatar) + var(--space-2));
  min-height: calc(var(--offer-avatar) / 2);
  color: var(--color-heading);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}
.offer-card__merchant:hover { color: var(--color-primary); }

.offer-card__merchant-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The tick is a trust signal, so it is the brand colour and it is not optional
   furniture: it appears only on merchants the platform actually verified. */
.offer-card__verified {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 15px; height: 15px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-primary-fg);
  font-size: 9px;
}

/* ---- 4. THE TITLE -------------------------------------------------------- */
.offer-card__title {
  margin: 0;
  font-size: var(--fs-base);
  font-weight: var(--fw-normal);
  line-height: var(--lh-snug);
  color: var(--color-body);
}
.offer-card__title a { color: inherit; }
.offer-card__title a:hover { color: var(--color-primary); }

/* ---- 1. PRICE AND WINDOW -------------------------------------------------
   The bottom third of the tile, and the part the hierarchy is built around. */
.offer-card__foot {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  margin-top: auto;
  padding-top: var(--space-2);
}

/* The money, at the top of the type scale and in the price red. Unconditional,
   not gated on there being a "was" price: every offer in this directory is a
   discount, and a tile that shows the red only when the merchant happened to
   publish both numbers reads as two different products. */
.offer-card .price { align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.offer-card .price__now {
  color: var(--color-sale);
  font-size: var(--fs-h4);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-tight);
  line-height: 1.1;
}
.offer-card .price__unit {
  color: var(--color-sale);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}
/* Struck through and grey, one step down: it is context for the red, not a
   number anyone is being asked to read. */
.offer-card .price__was {
  color: var(--color-meta);
  font-size: var(--fs-sm);
}

/* What the visitor keeps, as a quiet brand-tinted chip rather than a second red
   line. Two reds stacked on one tile is two alarms; this is the reassurance
   under the alarm, so it takes the primary's soft wash and the primary's ink —
   and it follows a deployment that rebrands itself, because both are derived. */
.offer-card__save {
  margin: 0;
  display: inline-flex;
  align-items: center;
  padding: 3px var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--color-primary-soft);
  color: var(--color-primary);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  line-height: 1.5;
}
.offer-card__until {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--color-meta);
}


/* --------------------------------------------------------------------------
   5. PRICE

   No ratings here, and none coming. Offers are not rated on this platform -
   the nearest real action a visitor has is to report an offer as wrong. The
   `.rating` and `.stars` components the store theme shipped were removed with
   the rest of the commerce residue; their presence was never permission.
   -------------------------------------------------------------------------- */
.price {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--color-heading);
}
.price__now { color: var(--color-heading); }
.price__was {
  font-size: var(--fs-base);
  font-weight: var(--fw-normal);
  color: var(--color-meta);
  text-decoration: line-through;
}
.price__unit { font-size: var(--fs-xs); font-weight: var(--fw-normal); color: var(--color-meta); }
.price--sale .price__now { color: var(--color-sale); }
.price--lg { font-size: var(--fs-h4); }

/* --------------------------------------------------------------------------
   6. CATEGORY & FEATURE TILES
   -------------------------------------------------------------------------- */
/*
   A tile is a WASH, not a box. The row used to be a line of white cards with a
   grey disc in each — identical, and identically forgettable. Four soft tints
   cycling down the row give the eye something to count by, which is what makes
   a category row scannable rather than a list you read.

   EVERY WASH IS MIXED FROM A SEMANTIC TOKEN, never a literal. A deployment that
   brands itself purple gets a pale purple, a pale amber, a pale blue and a pale
   red — the same four RELATIONSHIPS, in its own colours — and the same rule
   holds in dark, because the mix is toward `--color-surface` and that inverts
   with the scheme. A hard-coded `#fff8d1` would have been a light-mode-only
   decision baked into a file that serves both. See D-72.
*/
.category-tile {
  --tile-wash: var(--color-surface-alt);

  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--radius-card);
  background: var(--tile-wash);
  text-align: center;
  transition: var(--transition);
}
.category-tile:nth-child(4n + 1) { --tile-wash: color-mix(in oklab, var(--color-accent)  18%, var(--color-surface)); }
.category-tile:nth-child(4n + 2) { --tile-wash: color-mix(in oklab, var(--color-primary) 14%, var(--color-surface)); }
.category-tile:nth-child(4n + 3) { --tile-wash: color-mix(in oklab, var(--color-info)    14%, var(--color-surface)); }
.category-tile:nth-child(4n + 4) { --tile-wash: color-mix(in oklab, var(--color-sale)    12%, var(--color-surface)); }

.category-tile:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-card-hover);
}
.category-tile:focus-visible { outline: var(--focus-ring); outline-offset: 3px; }

/* The glyph sits in the middle of the wash with nothing drawn behind it: a disc
   inside a tinted tile is two backgrounds arguing. Scaling it on hover is the
   whole hover — the tile lifts, the icon grows, and neither changes colour. */
.category-tile__icon {
  display: grid;
  place-items: center;
  width: 56px; height: 56px;
  color: var(--color-heading);
  font-size: 34px;
  transition: transform var(--dur) var(--ease-out);
}
.category-tile:hover .category-tile__icon,
.category-tile:focus-visible .category-tile__icon { transform: scale(1.18); }

/* Honour a visitor who asked for less movement: the lift and the scale are both
   decoration, and neither carries information the tile does not already say. */
@media (prefers-reduced-motion: reduce) {
  .category-tile,
  .category-tile__icon { transition: none; }
  .category-tile:hover { transform: none; }
  .category-tile:hover .category-tile__icon,
  .category-tile:focus-visible .category-tile__icon { transform: none; }
}
.category-tile__name {
  margin: 0;
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--color-heading);
}
.category-tile__count { font-size: var(--fs-xs); color: var(--color-meta); }

/* Trust / USP strip: free shipping, returns, support… */
.feature-box {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  transition: var(--transition);
}
.feature-box--bordered { border: 1px solid var(--color-border-soft); }
.feature-box:hover { box-shadow: var(--shadow-sm); }
.feature-box__icon {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 54px; height: 54px;
  border-radius: var(--radius-full);
  background: var(--color-primary-soft);
  color: var(--color-primary);
  font-size: 24px;
}
.feature-box__title {
  margin: 0 0 var(--space-1);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--color-heading);
}
.feature-box__text { margin: 0; font-size: var(--fs-base); line-height: var(--lh-snug); }

/* Promo banner with copy over an image */
.promo {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-3);
  min-height: 240px;
  padding: var(--space-8);
  border-radius: var(--radius-2xl);
  background: var(--color-surface-tint) center / cover no-repeat;
  overflow: hidden;
}
.promo__eyebrow {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--color-primary);
}
.promo__title { margin: 0; font-size: var(--fs-h3); line-height: var(--lh-h3); max-width: 18ch; }
.promo__text  { margin: 0; max-width: 32ch; }
.promo > * { position: relative; z-index: 1; }
.promo--dark { background-color: var(--color-inverse-surface); color: var(--color-inverse-content); }
.promo--dark .promo__title { color: var(--color-inverse-heading); }
.promo--dark .promo__eyebrow { color: var(--color-accent); }

/* --------------------------------------------------------------------------
   7. NAVIGATION & DROPDOWNS
   -------------------------------------------------------------------------- */
.nav {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  list-style: none;
  margin: 0;
  padding: 0;
}
.nav > li { position: relative; margin: 0; }
.nav > li > a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  color: var(--color-heading);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  transition: var(--transition);
}
.nav > li > a:hover,
.nav > li.is-active > a { color: var(--color-primary); }
.nav > li.has-children > a::after {
  content: "";
  width: 10px; height: 10px;
  background: currentColor;
  -webkit-mask: center / contain no-repeat var(--chevron);
  mask: center / contain no-repeat var(--chevron);
  --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9.5 6 6 6-6'/%3E%3C/svg%3E");
  transition: transform var(--dur) var(--ease);
}
.nav > li.has-children:hover > a::after { transform: rotate(180deg); }

/* Submenu */
.nav .submenu {
  position: absolute;
  top: 100%; inset-inline-start: 0;
  z-index: var(--z-dropdown);
  min-width: 220px;
  margin: 0;
  padding: var(--space-2);
  list-style: none;
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-popover);
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: var(--transition);
}
.nav li:hover > .submenu,
.nav li:focus-within > .submenu {
  opacity: 1;
  visibility: visible;
  transform: none;
}
.nav .submenu li { margin: 0; }
.nav .submenu a {
  display: block;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--color-body);
  font-size: var(--fs-base);
  transition: var(--transition);
}
.nav .submenu a:hover {
  background: var(--color-primary-soft);
  color: var(--color-primary);
  padding-inline-start: var(--space-4);
}
.nav .submenu .submenu { top: calc(var(--space-2) * -1); inset-inline-start: 100%; }

/* Generic dropdown panel */
.dropdown { position: relative; }
.dropdown__panel {
  position: absolute;
  top: calc(100% + var(--space-2)); inset-inline-end: 0;
  z-index: var(--z-dropdown);
  min-width: 240px;
  padding: var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-popover);
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: var(--transition);
}
.dropdown.is-open > .dropdown__panel,
.dropdown:focus-within > .dropdown__panel {
  opacity: 1;
  visibility: visible;
  transform: none;
}

/* Vertical sidebar menu */
.menu-list { list-style: none; margin: 0; padding: 0; }
.menu-list li { margin: 0; border-bottom: 1px solid var(--color-border-soft); }
.menu-list li:last-child { border-bottom: 0; }
.menu-list a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  color: var(--color-body);
  font-size: var(--fs-base);
  transition: var(--transition);
}
.menu-list a:hover { color: var(--color-primary); }
.menu-list .count { color: var(--color-meta); font-size: var(--fs-xs); }

/* --------------------------------------------------------------------------
   8. BREADCRUMB, PAGINATION, TABS
   -------------------------------------------------------------------------- */
.breadcrumb {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--fs-base);
}
.breadcrumb li { margin: 0; display: inline-flex; align-items: center; gap: var(--space-2); }
.breadcrumb li + li::before {
  content: "/";
  color: var(--color-meta);
}
.breadcrumb a { color: var(--color-body); }
.breadcrumb a:hover { color: var(--color-primary); }
.breadcrumb [aria-current="page"] { color: var(--color-heading); font-weight: var(--fw-medium); }

.page-hero {
  padding-block: var(--space-10);
  background: var(--color-surface-tint);
  text-align: center;
}
.page-hero__title { margin-bottom: var(--space-3); }
.page-hero .breadcrumb { justify-content: center; }

/* A list page's heading. Start-aligned, shallow, and the count sits ON the same
   baseline as the title rather than under it — this band is the last thing
   between the header and the first card, and a centred hero with 56px of air
   above and below pushes the results off a phone screen entirely. */
.page-hero--tight {
  padding-block: var(--space-4);
  text-align: start;
}
.page-hero--tight .container {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-4);
}
.page-hero--tight .page-hero__title {
  margin-bottom: 0;
  font-size: var(--fs-h3);
}
.page-hero--tight p { margin-bottom: 0; }

.pagination {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  list-style: none;
  margin: 0;
  padding: 0;
}
.pagination li { margin: 0; }
.pagination a, .pagination span {
  display: grid;
  place-items: center;
  min-width: 42px; height: 42px;
  padding: 0 var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  color: var(--color-heading);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  transition: var(--transition);
}
.pagination a:hover {
  border-color: var(--color-primary);
  background: var(--color-primary);
  color: var(--color-white);
}
.pagination .is-current {
  border-color: var(--color-primary);
  background: var(--color-primary);
  color: var(--color-primary-fg);
}
.pagination .is-disabled { color: var(--color-meta); pointer-events: none; }

.tabs {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-6);
}
.tabs__btn {
  position: relative;
  padding: var(--space-3) var(--space-5);
  border: 0;
  background: none;
  color: var(--color-body);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: var(--transition);
}
.tabs__btn::after {
  content: "";
  position: absolute;
  inset: auto 0 -1px 0;
  height: 2px;
  background: var(--color-primary);
  transform: scaleX(0);
  transition: transform var(--dur) var(--ease);
}
.tabs__btn:hover { color: var(--color-heading); }
.tabs__btn[aria-selected="true"] { color: var(--color-primary); }
.tabs__btn[aria-selected="true"]::after { transform: scaleX(1); }

/* Pill tab set (product carousels, category filters) */
.tabs--pills { border-bottom: 0; }
.tabs--pills .tabs__btn {
  border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-5);
  font-size: var(--fs-base);
  background: var(--color-surface-alt);
}
.tabs--pills .tabs__btn::after { display: none; }
.tabs--pills .tabs__btn[aria-selected="true"] {
  background: var(--color-primary);
  color: var(--color-primary-fg);
}

/* Accordion */
.accordion { border: 1px solid var(--color-border-soft); border-radius: var(--radius-md); overflow: hidden; }
.accordion__item + .accordion__item { border-top: 1px solid var(--color-border-soft); }
.accordion__btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  width: 100%;
  padding: var(--space-4) var(--space-5);
  background: var(--color-surface);
  color: var(--color-heading);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  text-align: start;
  cursor: pointer;
  transition: var(--transition);
}
.accordion__btn:hover { color: var(--color-primary); }
.accordion__btn .icon { transition: transform var(--dur) var(--ease); flex-shrink: 0; }
.accordion__btn[aria-expanded="true"] { color: var(--color-primary); }
.accordion__btn[aria-expanded="true"] .icon { transform: rotate(180deg); }
.accordion__panel { padding: 0 var(--space-5) var(--space-5); font-size: var(--fs-base); }

/* --------------------------------------------------------------------------
   9. ALERTS & NOTICES
   -------------------------------------------------------------------------- */
.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-border-soft);
  border-inline-start: 3px solid var(--color-primary);
  border-radius: var(--radius);
  background: var(--color-surface-alt);
  font-size: var(--fs-base);
  line-height: var(--lh-snug);
}
.alert__icon { flex-shrink: 0; color: var(--color-primary); }
.alert--success { border-inline-start-color: var(--color-success); background: var(--color-primary-soft); }
.alert--success .alert__icon { color: var(--color-success); }
.alert--warning { border-inline-start-color: var(--color-warning); background: var(--color-accent-soft); }
.alert--warning .alert__icon { color: var(--color-accent-hover); }
.alert--danger  { border-inline-start-color: var(--color-danger); background: color-mix(in srgb, var(--color-danger) 8%, transparent); }
.alert--danger .alert__icon { color: var(--color-danger); }
.alert--info    { border-inline-start-color: var(--color-info); background: rgb(47 128 237 / 7%); }
.alert--info .alert__icon { color: var(--color-info); }

/* --------------------------------------------------------------------------
   10. TABLES
   -------------------------------------------------------------------------- */
.table-wrap { overflow-x: auto; }
.table { width: 100%; font-size: var(--fs-base); }
.table th, .table td {
  padding: var(--space-4);
  border-bottom: 1px solid var(--color-border-soft);
  text-align: start;
  vertical-align: middle;
}
.table th {
  color: var(--color-heading);
  font-weight: var(--fw-semibold);
  background: var(--color-surface-alt);
  white-space: nowrap;
}
.table thead th:first-child { border-start-start-radius: var(--radius); }
.table thead th:last-child  { border-start-end-radius: var(--radius); }
.table tbody tr { transition: var(--transition); }
.table tbody tr:hover { background: var(--color-surface-alt); }
.table--bordered th, .table--bordered td { border: 1px solid var(--color-border); }
.table--center th, .table--center td { text-align: center; }

/* --------------------------------------------------------------------------
   11. DRAWER / OFFCANVAS / MODAL
   -------------------------------------------------------------------------- */
.backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  background: var(--color-scrim);
  opacity: 0;
  visibility: hidden;
  transition: var(--transition);
}
.backdrop.is-open { opacity: 1; visibility: visible; }

.drawer {
  position: fixed;
  top: 0; bottom: 0; inset-inline-end: 0;
  z-index: calc(var(--z-drawer) + 1);
  display: flex;
  flex-direction: column;
  width: min(var(--drawer-width), 92vw);
  background: var(--color-surface);
  box-shadow: var(--shadow-lg);
  transform: translateX(calc(100% * var(--flip)));
  transition: transform var(--dur-slow) var(--ease-out);
}
/* Opens from the inline-start edge instead — the start side follows the reading direction. */
.drawer--start {
  inset-inline-end: auto; inset-inline-start: 0;
  transform: translateX(calc(-100% * var(--flip)));
}
.drawer.is-open { transform: none; }
.drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5);
  border-bottom: 1px solid var(--color-border-soft);
}
.drawer__title { margin: 0; font-size: var(--fs-h5); }
.drawer__body { flex: 1 1 auto; overflow-y: auto; padding: var(--space-5); }
.drawer__foot { padding: var(--space-5); border-top: 1px solid var(--color-border-soft); }

.modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  place-items: center;
  padding: var(--space-5);
  opacity: 0;
  visibility: hidden;
  transition: var(--transition);
}
.modal.is-open { opacity: 1; visibility: visible; }
.modal__dialog {
  width: 100%;
  max-width: 560px;
  max-height: 88vh;
  overflow-y: auto;
  padding: var(--space-7);
  background: var(--color-surface);
  border-radius: var(--radius-2xl);
  box-shadow: var(--shadow-lg);
  transform: translateY(16px) scale(0.98);
  transition: transform var(--dur-slow) var(--ease-out);
}
.modal.is-open .modal__dialog { transform: none; }
.modal__dialog--lg { max-width: 900px; }

/* --------------------------------------------------------------------------
   12. TOOLTIP, SPINNER, MISC
   -------------------------------------------------------------------------- */
[data-tooltip] { position: relative; }
[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;  /* physical on purpose: pairs with translate(-50%) to centre in both directions */
  z-index: var(--z-dropdown);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-xs);
  background: var(--color-inverse-surface);
  color: var(--color-inverse-heading);
  font-size: var(--fs-xs);
  font-weight: var(--fw-normal);
  line-height: 1.6;
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, 4px);
  transition: var(--transition);
  pointer-events: none;
}
[data-tooltip]:hover::after,
[data-tooltip]:focus-visible::after {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
}

.spinner {
  display: inline-block;
  width: 20px; height: 20px;
  border: 2px solid color-mix(in srgb, var(--color-primary) 25%, transparent);
  border-top-color: var(--color-primary);
  border-radius: var(--radius-full);
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.skeleton {
  background: linear-gradient(90deg,
    var(--color-surface-alt) 25%,
    var(--color-border-soft) 37%,
    var(--color-surface-alt) 63%);
  background-size: 400% 100%;
  border-radius: var(--radius);
  animation: shimmer 1.4s ease infinite;
}
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }

/* Countdown timer for deal sections */
.countdown { display: flex; gap: var(--space-2); }
.countdown__unit {
  min-width: 52px;
  padding: var(--space-2);
  border-radius: var(--radius);
  background: var(--color-surface-alt);
  text-align: center;
}
.countdown__num {
  display: block;
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  line-height: 1.2;
  color: var(--color-heading);
}
.countdown__label { font-size: 10px; text-transform: uppercase; color: var(--color-meta); letter-spacing: var(--ls-wide); }

/* Social row */
.social { display: flex; gap: var(--space-2); }
.social a {
  display: grid;
  place-items: center;
  width: 38px; height: 38px;
  border-radius: var(--radius-full);
  background: var(--color-surface-alt);
  color: var(--color-body);
  transition: var(--transition);
}
.social a:hover { background: var(--color-primary); color: var(--color-primary-fg); transform: translateY(-2px); }

/* Back-to-top */
.to-top {
  position: fixed;
  inset-inline-end: var(--space-6); bottom: var(--space-6);
  z-index: var(--z-sticky);
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  border: 0;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-primary-fg);
  box-shadow: var(--shadow-md);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transition: var(--transition);
}
.to-top.is-visible { opacity: 1; visibility: visible; }
.to-top:hover { background: var(--color-primary-hover); }


/* --------------------------------------------------------------------------
   12b. THE LISTING TICKER
   A narrow strip under the header inviting a business to put its offers on the
   site — the one piece of chrome addressed to a merchant rather than a visitor.

   It is text and two links, moved by ONE CSS animation over a duplicated run.
   No <marquee>, no timer, no script: the strip is complete and readable before
   the animation does anything, and it stays complete if the animation never
   runs at all.

   Accent-soft rather than the brand fill: it has to be noticed and it is not
   the page. A strip in the deployment's full primary competes with the header
   above it and with every button below.
   -------------------------------------------------------------------------- */
.ticker {
  position: relative;

  /* Clips the pause button's scrim to the strip. Without it that shadow spreads
     16px in every direction, including down past the border into the page — a
     pale smudge under one end of the band, visible in dark mode first. */
  overflow: hidden;

  background: var(--color-accent-soft);
  color: var(--color-heading);
  border-bottom: 1px solid var(--color-border-soft);
  font-size: var(--fs-sm);
  line-height: 1.6;
}

/* The pause control. WCAG 2.2.2 asks for a way to stop movement that runs for
   more than five seconds, and this runs forever — hover is not that mechanism
   on a touch screen, and the OS preference is not a per-page choice.

   Over the strip rather than in it, at the trailing edge, so the message keeps
   the full width and the button does not travel with the track. It ships
   `hidden` and script unhides it. */
.ticker__pause {
  position: absolute;
  z-index: 1;
  inset-block: 0;
  inset-inline-end: 0;
  padding-inline: var(--space-3);
  border: 0;
  background: var(--color-accent-soft);
  color: var(--color-meta);
  font: inherit;
  font-size: var(--fs-xs);
  cursor: pointer;

  /* A soft edge so the message slides under it rather than colliding with it.
     Physical, and deliberately so: the fade has to be on whichever side the
     button is, which `inset-inline-end` has already decided. */
  box-shadow: 0 0 var(--space-4) var(--space-4) var(--color-accent-soft);
}

.ticker__pause:hover { color: var(--color-heading); }

/* Paused by the button, as against paused by a hover or by the OS preference —
   this one has to survive the pointer leaving. */
.ticker.is-paused .ticker__track { animation-play-state: paused; }

/* The mask, and the reason nothing needs a fixed width: the track inside is as
   wide as its content and this clips it. */
.ticker__viewport {
  overflow: hidden;
  /* Not about reading direction: the strip's text runs off both edges, and the
     clip has to be the same on each. */
  white-space: nowrap;
}

/* Two identical runs side by side. The animation travels exactly half the
   track, which lands the second run where the first started — so the loop has
   no seam and no empty stretch waiting for the message to come round. */
.ticker__track {
  display: inline-flex;
  animation: ticker-scroll 38s linear infinite;
}

/* MOVING TEXT WITH LINKS IN IT IS A TARGET THAT RUNS AWAY. Pausing on hover is
   what makes the two links clickable; pausing on focus-within is the same
   courtesy for a keyboard, which would otherwise have to catch a moving box. */
.ticker:hover .ticker__track,
.ticker:focus-within .ticker__track { animation-play-state: paused; }

.ticker__run {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding-block: var(--space-2);
  padding-inline-end: var(--space-8);
}

/* No padding on the viewport to make room for the pause button, and that is
   worth stating so nobody adds one: `overflow: hidden` clips at the PADDING
   box, so an overflowing track renders straight through it. The button's own
   box-shadow scrim is what separates the two. */

.ticker__icon { color: var(--color-accent); }

.ticker__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-weight: var(--fw-semibold);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* THE MARK IS GREEN, THE WORDS ARE NOT. WhatsApp green is a service mark and
   the theme owns it as a token (D-74) — but it is a mid-green, and mid-green
   text on this strip's pale tint is somewhere around 2:1. So the icon wears the
   mark and the label keeps the strip's own colour, which is the only version
   that is both recognisable and readable. */
.ticker__link--whatsapp .icon { color: var(--color-whatsapp); }

/* Half the track, in the direction the page reads. There is no logical form of
   translateX, so `--flip` carries the direction — the theme's one exception,
   and this is exactly the case it exists for: in Arabic the message has to
   travel the other way or it reads backwards out of the wrong edge. */
@keyframes ticker-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-50% * var(--flip))); }
}

/* Somebody who asked their device for less movement gets a strip that does not
   move. The sentence, both links and the colour are all still there — nothing
   this animation carries is information, so there is nothing to replace it
   with. */
@media (prefers-reduced-motion: reduce) {
  .ticker__track { animation: none; }

  /* Standing still, a track twice as wide as the viewport would hide the second
     copy's content off the edge — and on a narrow screen the FIRST copy's links
     too. Scrolling it by hand is what a static strip can honestly offer. */
  .ticker__viewport { overflow-x: auto; }
  .ticker__track { display: flex; }
  .ticker__run:last-child { display: none; }

  /* Nothing is moving, so a control offering to stop it would be a button that
     does nothing and an `aria-pressed` that says something untrue. The
     `!important` overrides the inline `hidden` the script removes — the script
     cannot see this media query, and it should not have to. */
  .ticker__pause { display: none !important; }
}

/* --------------------------------------------------------------------------
   13. ADVERTISING SLOT
   A paid banner, injected into a page after it loads by the storefront's
   uncached fragment endpoint. Nothing here is drawn until that fragment
   arrives — an empty slot has no box, no border and no reserved height, so a
   page with nothing sold in it looks like a page with no advertising rather
   than like one with a hole in it.
   -------------------------------------------------------------------------- */
.banner-slot {
  display: grid;
  gap: var(--space-4);
  margin-block: var(--space-6);
}

.banner {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--color-surface-alt);
}

.banner img {
  display: block;
  width: 100%;
  height: auto;
}

/* ---- the booked display size --------------------------------------------
   Staff give a banner a maximum width, and optionally the height its creative
   has at that width. Both are blank by default, and blank is the rule above:
   fill the slot.

   `--banner-width` is a MAXIMUM, never a width. The image is still 100% of
   whatever box it ends up in, so a 728px leaderboard on a 375px phone shrinks
   instead of making the page scroll sideways — which a fixed width would.

   `margin-inline: auto` is the other half of the feature and not a detail.
   A creative narrower than its slot has to sit somewhere, and leaving it to
   the flow would put it at whichever edge the reading starts from — different
   in the two languages, for a picture that has no reading direction. An
   advertiser who bought a slot bought the middle of it. */
.banner--sized,
.banner--sized-ratio,
.banner--sized-wide {
  max-inline-size: var(--banner-width);
  margin-inline: auto;
}

/* The width and height together are a ratio, and the ratio reserves the box
   before the image arrives — which is what stops an advertisement pushing the
   article underneath it down the page as it loads.

   `object-fit: contain` rather than the default `fill`: the numbers describe
   the creative, so ordinarily the ratio matches and this changes nothing. When
   somebody mistypes one, it letterboxes against the slot's own background
   instead of stretching an advertiser's artwork out of shape. */
.banner--sized-ratio img,
.banner--sized-wide img {
  object-fit: contain;
}

.banner--sized-ratio img {
  aspect-ratio: var(--banner-ratio);
}

/* A banner with a narrow crop is TWO pictures, and only the wide one's size was
   booked — the crop is a different photograph whose ratio nobody recorded. So
   the reservation starts where <picture> stops choosing the crop, at the same
   768px the source's media query names. Below it the banner has no reserved
   box, which is exactly what it had before this existed. */
@media (min-width: 768px) {
  .banner--sized-wide img { aspect-ratio: var(--banner-ratio); }
}

/* Said in words as well as in rel="sponsored". Placed over the image at the
   trailing edge, so it mirrors with the page and never covers the part of a
   creative the advertiser put their name on — which in both directions is the
   side the reading starts from. */
.banner__label {
  position: absolute;
  inset-block-start: var(--space-2);
  inset-inline-end: var(--space-2);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  background: rgb(0 0 0 / 0.55);   /* a scrim over an image the theme does not choose */
  color: #fff;                     /* stated as a literal: it sits on that scrim in both schemes */
  font-size: var(--fs-xs);
  line-height: 1.6;
  letter-spacing: 0.02em;
}

/* --------------------------------------------------------------------------
   14. FILTER PANEL — a sidebar on a desktop, a drawer on a phone, and the
   same markup either way with NO JavaScript involved.

   The problem this solves was measured: stacked above the results on a 375px
   viewport, the filter form was 894px tall and the first offer card began
   1,361px down the page. A visitor had to scroll past every control to reach
   the thing they came for.

   HOW IT OPENS. A checkbox — `.filters-toggle` — sits as the first child of
   `.shell`, visually hidden but still focusable, and every rule below reacts to
   `:checked` through a sibling selector. The visible controls are `<label>`s
   pointing at it: the trigger above the results, the close button in the
   panel's head, and the scrim behind it. Three ways to shut it, no script, and
   it works on the crawler's page too.

   WHY NOT `<details>`. Its open state cannot be forced back on at desktop
   widths without `::details-content`, which is too new to rely on. Why not the
   theme's own `.drawer`: that one is driven by `site.js` toggling a class, and
   the filters must work with script off — which is exactly the case where a
   visitor most needs the list to be usable.

   ABOVE 992px none of this applies. The checkbox is inert, the panel is a
   static block in the grid's first column, and the trigger, the scrim and the
   head are not rendered at all.
   -------------------------------------------------------------------------- */

/* The panel's own frame, in both layouts. */
.filters__panel {
  background: var(--color-surface);
  border-radius: var(--radius-lg);
}
.filters__form { padding: var(--space-5); }
.filters__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-5);
}

/* Desktop: an ordinary sidebar. The drawer's furniture is not drawn. */
.filters__scrim,
.filters__head,
.filters-trigger { display: none; }

.filters__panel { border: 1px solid var(--color-border-soft); }

@media (max-width: 991px) {
  /* The aside stops being a grid track and becomes the overlay itself. It is
     pointer-transparent while closed so the page underneath stays usable. */
  .filters {
    position: fixed;
    inset: 0;
    z-index: var(--z-drawer);
    pointer-events: none;
  }

  .filters__scrim {
    display: block;
    position: absolute;
    inset: 0;
    background: var(--color-scrim);
    opacity: 0;
    transition: opacity var(--dur) var(--ease);
  }

  /* Opens from the inline-start edge. `--flip` is the theme's one direction
     multiplier: the transform follows the reading direction without a [dir] rule
     and without a second stylesheet. */
  .filters__panel {
    position: absolute;
    top: 0; bottom: 0; inset-inline-start: 0;
    display: flex;
    flex-direction: column;
    width: min(var(--drawer-width), 88vw);
    border: 0;
    border-radius: 0;
    box-shadow: var(--shadow-lg);
    overflow-y: auto;
    overscroll-behavior: contain;
    transform: translateX(calc(-100% * var(--flip)));
    transition: transform var(--dur-slow) var(--ease-out);
  }

  .filters__head {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-4) var(--space-5);
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border-soft);
  }
  .filters__title { margin: 0; font-size: var(--fs-h5); }

  /* The apply button stays reachable at the bottom of a long form. */
  .filters__actions {
    position: sticky;
    bottom: 0;
    margin-inline: calc(var(--space-5) * -1);
    margin-bottom: calc(var(--space-5) * -1);
    padding: var(--space-4) var(--space-5);
    background: var(--color-surface);
    border-top: 1px solid var(--color-border-soft);
  }

  .filters-trigger { display: inline-flex; }

  /* ---- the open state ---- */
  .filters-toggle:checked ~ .filters { pointer-events: auto; }
  .filters-toggle:checked ~ .filters .filters__scrim { opacity: 1; }
  .filters-toggle:checked ~ .filters .filters__panel { transform: none; }

  /* The focus ring belongs on the thing a sighted keyboard user can see, which
     is the label — the checkbox itself is off-screen. */
  .filters-toggle:focus-visible ~ .browse .filters-trigger {
    outline: var(--focus-ring);
    outline-offset: 2px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .filters__panel,
  .filters__scrim { transition: none; }
}

/* --------------------------------------------------------------------------
   15. OFFER PAGE — the saving, and the action cluster

   This page is the only one on the site with a measured outcome: a visitor
   leaves for the merchant, and the click is what the platform sells. Everything
   here exists to make that easier to do and harder to miss.
   -------------------------------------------------------------------------- */

/* ---- the saving ----------------------------------------------------------
   Price and saving in one block, because they are one fact. The number a
   visitor weighs is not the new price — it is what the new price is instead of. */
.savings {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  align-items: flex-start;
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-lg);
  background: var(--color-primary-soft);
  border-inline-start: 3px solid var(--color-primary);
}
.savings__line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}
.savings__amount {
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  color: var(--color-sale);
}
.savings__percent {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--color-accent-fg);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}

/* ---- the action cluster --------------------------------------------------
   The page's whole purpose and the platform's only measured outcome.

   TWO TIERS, AND THE SPLIT IS THE POINT. Call and WhatsApp are the conversion:
   they are large filled pills with icons, and on a phone they are what the
   fixed bottom bar contains. Directions and Website are useful and are not the
   outcome, so they sit in the flow above as quiet outline buttons and scroll
   away like everything else.

   The old arrangement put all four in the bar as equal icon-over-label columns,
   which made the two things this page exists to produce a quarter of a 375px
   row each — the same size as "Website". A bar with two 48px targets in it
   converts; a bar with four 88px ones is a toolbar. See D-73. */
.offer-actions__inner {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}
.offer-actions__btn { flex: 0 1 auto; }

/* The second tier. Never fixed, never in the bar. */
.offer-actions__more {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

@media (max-width: 767px) {
  .offer-actions {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: var(--z-sticky);
    margin: 0;
    padding: var(--space-3);
    padding-bottom: max(var(--space-3), env(safe-area-inset-bottom));
    background: var(--color-surface);
    border-top: 1px solid var(--color-border-soft);
    box-shadow: 0 -6px 24px rgb(0 0 0 / 12%);
  }

  /* Equal halves, never wrapping. With at most two buttons here now, each one
     gets half a 375px screen — a real target with a real label beside its icon,
     rather than a column of icon-over-caption. */
  .offer-actions__inner {
    flex-wrap: nowrap;
    gap: var(--space-2);
  }

  /* TWO CLASSES, DELIBERATELY. These buttons carry `.btn--lg` so they are large
     in the flow of the page, and `.btn--lg` sets `padding: 20px 44px`. A lone
     `.offer-actions__btn` ties it on specificity and only wins on source order,
     which is not a thing to rely on in a file this size — and it lost: two
     buttons on a 375px screen spent 176px on padding and clipped the WhatsApp
     label down to an ellipsis. Scoping under `.offer-actions` settles it. */
  .offer-actions .offer-actions__btn {
    flex: 1 1 0;
    min-width: 0;
    padding: var(--space-4) var(--space-3);
    font-size: var(--fs-md);
    letter-spacing: var(--ls-normal);
  }
  .offer-actions .offer-actions__btn .icon { font-size: 18px; }

  /* One action fills the bar. "Full-width on mobile" is literal when there is
     only one thing to be full width of. */
  .offer-actions .offer-actions__btn:only-child { flex: 1 1 100%; }

  .offer-actions__btn > span:not(.icon) {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

/* ---- 15b. SAVE AND SHARE -------------------------------------------------
   The quiet row under the offer's actions: keep this one, or send it to
   somebody. Both are secondary to the conversion pair above, so they are a
   single line of link-weight controls rather than a second set of buttons.

   Wrapping, not scrolling: on a narrow screen the share marks drop to their own
   line under "Save", which reads as two thoughts rather than one cut off. */
.offer-share {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

/* The label and the marks are ONE flex item, not two, and that is what makes
   the wrap behave. As siblings they wrap independently: on a narrow screen the
   word "share" is left stranded at the end of the first line while the four
   marks it labels drop to the second. Grouped, they move together. */
.offer-share__group {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);

  /* Pushed to the trailing edge where there is room, so the marks sit at the
     far side of the line from the save link and the two do not read as one run
     of five controls. Logical, so it follows the reading direction. */
  margin-inline-start: auto;
}

.offer-share__targets {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.offer-share__label {
  color: var(--color-meta);
  font-size: var(--fs-sm);
}

/* ---- 16. BRANCH CARD -----------------------------------------------------
   Where to actually use the offer, and the page's trust section: a merchant
   with three addresses printed on it is a merchant that exists.

   The address is what the card is FOR, so it is set as content rather than as
   a caption, and the directions button is the card's own call to action — the
   one branch action that can only be answered per branch, since a "directions"
   link that does not know which branch it means has nowhere to send anyone. */
.branch-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  height: 100%;
  padding: var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
@media (any-hover: hover) {
  .branch-card:hover { border-color: var(--color-border); box-shadow: var(--shadow-card-hover); }
}
.branch-card:focus-within { border-color: var(--color-primary); box-shadow: var(--shadow-card-hover); }

.branch-card__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}
/* A pin, in the brand's soft wash. It is the only ornament on the card and it
   is what makes a column of three read as three places rather than three
   paragraphs. */
.branch-card__pin {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 40px; height: 40px;
  border-radius: var(--radius-full);
  background: var(--color-primary-soft);
  color: var(--color-primary);
  font-size: 18px;
}
.branch-card__name {
  margin: 0;
  font-size: var(--fs-h5);
  font-weight: var(--fw-semibold);
  color: var(--color-heading);
}
.branch-card__address {
  margin: 0;
  font-size: var(--fs-base);
  line-height: var(--lh-snug);
  color: var(--color-body);
}
/* Pushed to the foot so a row of branches with addresses of different lengths
   still lines its buttons up. */
.branch-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: auto;
  padding-top: var(--space-2);
}
