/* ==========================================================================
   Satya Since 1973 — the premium pass: rich Indian luxury

   The direction, chosen by the client: gold on deep maroon, ornament rather
   than restraint, textured panels, decorative dividers. The reference points
   are the Indian luxury houses — Tanishq, Manyavar — not a Scandinavian
   lifestyle brand.

   This file deliberately reverses an earlier minimal pass. Two things were
   wrong with it: the cards had no presence, and the page was too empty. So the
   cards get a frame, a mat and a gold edge, and the whole layout tightens.

   Loaded last, so it is the final word on everything it touches.
   ========================================================================== */

:root {
  /* Warmer, deeper ground than the near-white cream. */
  --s-cream: #fbf5ec;
  --s-ivory: #fffdf8;
  --s-max: 88rem;

  /* One gold gradient, used wherever an edge or a rule needs to catch light. */
  --s-gold-edge: linear-gradient(135deg, var(--s-gold-600), var(--s-gold-300) 45%, var(--s-gold-600));
}

body { background-color: var(--s-cream); }

/* --------------------------------------------------------------------------
   Tighter, fuller

   The client's note was that the page felt empty. Sections lose roughly a
   third of their padding and the product grid closes up, so a screen carries
   more of the shop.
   -------------------------------------------------------------------------- */

.s-section { padding-block: var(--s-8); }
@media (min-width: 48rem) { .s-section { padding-block: var(--s-12); } }

/* The column gap is set through `--s-col-gap`, never through `gap` directly.

   The card width is `calc((100% - 4 * var(--s-col-gap)) / 5)`, so a `gap`
   override that the basis does not know about breaks the arithmetic: setting
   `gap: 20px` here while the basis still assumed 16px made a row of five come
   to 1344px inside a 1328px rail, and the fifth card wrapped. Five across
   silently became four, with a gap where the fifth should have been. */
.s-prail, .s-grid { --s-col-gap: var(--s-4); gap: var(--s-6) var(--s-col-gap); }
@media (min-width: 62rem) { .s-prail, .s-grid { --s-col-gap: var(--s-5); gap: var(--s-8) var(--s-col-gap); } }

/* --------------------------------------------------------------------------
   A temple-jaali texture

   A single small SVG tile, repeated. At four per cent opacity it is a texture
   you feel rather than a pattern you read, which is the difference between a
   rich panel and a busy one.
   -------------------------------------------------------------------------- */

.s-textured { position: relative; }
.s-textured::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.07;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 48 48'%3E%3Cg fill='none' stroke='%23e5bc45' stroke-width='1'%3E%3Cpath d='M24 2 46 24 24 46 2 24Z'/%3E%3Cpath d='M24 14 34 24 24 34 14 24Z'/%3E%3Ccircle cx='24' cy='24' r='3'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 48px 48px;
}
.s-textured > * { position: relative; z-index: 1; }

/* --------------------------------------------------------------------------
   The product card — framed, matted, gold-edged
   -------------------------------------------------------------------------- */

.s-card {
  overflow: hidden;
  border: 1px solid var(--s-sand);
  border-radius: var(--s-radius);
  background-color: var(--s-ivory);
  box-shadow: 0 1px 3px rgb(93 0 3 / 0.05);
  transition: box-shadow var(--s-base) var(--s-ease),
              transform var(--s-base) var(--s-ease),
              border-color var(--s-base) var(--s-ease);
}

/* The gold hairline along the top edge. A pseudo-element rather than a border,
   so it can carry the gradient a border cannot. */
.s-card::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  block-size: 2px;
  z-index: 3;
  background-image: var(--s-gold-edge);
  opacity: 0;
  transition: opacity var(--s-base) var(--s-ease);
}

@media (hover: hover) {
  .s-card:hover {
    transform: translateY(-4px);
    border-color: var(--s-gold-400);
    box-shadow: 0 16px 38px rgb(93 0 3 / 0.16);
  }
  .s-card:hover::before { opacity: 1; }
}

/* The picture sits on a mat rather than bleeding to the card edge — the way a
   piece is presented in a case, not stacked on a shelf. */
.s-card__figure {
  margin: var(--s-3) var(--s-3) 0;
  border-radius: var(--s-radius-sm);
  background-color: var(--s-white);
  border: 1px solid var(--s-sand-tint);
  max-block-size: none;
}

.s-card__body {
  padding: var(--s-3) var(--s-4) var(--s-4);
  gap: 0.3rem;
  text-align: center;
}

/* One line, always. "Traditional Ceremony and Wedding Essentials" wrapped to
   two and pushed that card's name, price and button a whole line below its
   neighbours' — one ragged tile in an otherwise even row. */
.s-card__cat {
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  color: var(--s-gold-700);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-inline-size: 100%;
}

.s-card__name {
  font-family: var(--s-font-display);
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: 0;
  -webkit-line-clamp: 2;
  min-block-size: 2.6em;
  color: var(--s-maroon-900);
}

/* A short gold rule between the name and the price — the ornament that makes
   the block read as a label on a piece rather than a row in a list. */
.s-card__price-row {
  justify-content: center;
  padding-block-start: var(--s-2);
  margin-block-start: var(--s-1);
  position: relative;
  /* The price and the struck-through MRP are one statement and stay on one
     line. With the green "% off" beside them as well this row wrapped on a
     phone and broke "10% off" across two lines, which the client photographed
     on 12 September 2026. The repeat is gone; this keeps the rest together.

     Together — but never outside the card. `nowrap` on the whole row held the
     two on one line even when one line could not hold them: a product whose
     sizes cost different amounts prints a range ("₹1,250 – ₹9,999"), and with
     an MRP beside it the row ran out past the card's edge at 1024px and on
     every phone. The browser sweep of 13 September 2026 measured it. So each
     amount keeps itself whole, and only when the two genuinely do not fit does
     the MRP step beneath the price. Nothing shrinks and nothing is hidden, and
     every card the shop has today still reads on one line.

     An amount never breaks — "₹1,250" has no space in it to break at. A range
     can, at the spaces around its dash, and only when even the range alone is
     wider than the card: "₹1,250 – ₹9,999" is about 150px at this size, and a
     phone card is 136–164px. Measured, not guessed. */
  flex-wrap: wrap;
  column-gap: var(--s-2);
  row-gap: 0;
}

.s-card__price-row > .s-card__mrp {
  white-space: nowrap;
}
.s-card__price-row::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 50%;
  translate: -50% 0;
  inline-size: 2.25rem;
  block-size: 1px;
  background-image: var(--s-gold-edge);
}

/*
 * The price is read, not admired.
 *
 * This was set in the display serif, which is beautiful on a heading and wrong
 * on a number: Cormorant's figures are narrow and old-style, so "₹600.00" on a
 * card read as decoration rather than as money, and did not match the same price
 * on the product page two taps later. The client photographed exactly that on
 * 12 September 2026 and asked for the product page's font here.
 *
 * `--s-font-body` is what `.s-pdp__price` uses. Tabular figures on top, so a
 * column of prices lines up at the decimal point instead of shuffling.
 */
.s-card__price {
  font-family: var(--s-font-body);
  font-size: 1.2rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  color: var(--s-maroon-700);
}

/* The crossed-out price beside it belongs to the same number, so it follows. */
.s-card__mrp {
  font-family: var(--s-font-body);
  font-variant-numeric: tabular-nums;
}

/* The grid is tightest around 1024px, not on a phone: the main column narrows
   but the number of cards in it does not, so the row drops to about 113px
   against a phone's 130. The gap closes there rather than the type shrinking —
   the client asked for this price to be easier to read, so making it smaller
   was the wrong way round. What actually made it fit is `money_short()`: the
   paise are printed when there are any and left out when there are none. */
@media (max-width: 74.99rem) {
  .s-card__price-row { gap: var(--s-1); }
}
.s-card__stock { text-align: center; }

/* The buy control stays visible. Hiding it behind a hover was the minimal
   pass; a shop that wants to sell keeps it on the tile. */
.s-card__cta { margin-block-start: var(--s-3); }
.s-card__cta .s-btn {
  min-block-size: 2.5rem;
  border-radius: var(--s-radius-pill);
  border: 1px solid var(--s-gold-500);
  background-color: transparent;
  color: var(--s-maroon-800);
  font-size: var(--s-text-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transition: background-color var(--s-fast) var(--s-ease), color var(--s-fast) var(--s-ease);
}
.s-card__cta .s-btn:hover {
  background-image: var(--s-gold-edge);
  border-color: transparent;
  color: var(--s-maroon-900);
}

/* Quick view centres on the picture, in the brand's own maroon. */
@media (hover: hover) {
  .s-card__quick {
    inset-block-end: auto;
    inset-block-start: 50%;
    inset-inline: 50%;
    translate: -50% -50%;
    inline-size: max-content;
    padding-inline: var(--s-5);
    min-block-size: 2.4rem;
    background-color: rgb(93 0 3 / 0.92);
    color: var(--s-gold-300);
    border: 1px solid var(--s-gold-500);
  }
  .s-card:hover .s-card__quick, .s-card__quick:focus-visible { opacity: 1; translate: -50% -50%; }
  .s-card__quick:hover { background-color: var(--s-maroon-900); color: var(--s-gold-300); }
}

/* --------------------------------------------------------------------------
   Section headings, with ornament
   -------------------------------------------------------------------------- */

.s-head { margin-block-end: var(--s-6); }
.s-head:not(.s-head--centre) { align-items: baseline; }

.s-head__eyebrow {
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.22em;
  color: var(--s-gold-700);
  margin-block-end: var(--s-2);
}

.s-head__title {
  font-weight: 600;
  color: var(--s-maroon-900);
  letter-spacing: -0.005em;
}
@media (min-width: 48rem) { .s-head__title { font-size: 2.25rem; } }

.s-head__more {
  font-size: var(--s-text-xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--s-maroon-700);
  border-block-end: 1px solid var(--s-gold-500);
  padding-block-end: 3px;
}
.s-head__more:hover { color: var(--s-gold-700); }

/* The gold rule under a centred heading gains a diamond at its middle. */
.s-rule {
  position: relative;
  inline-size: 5rem;
  block-size: 1px;
  background-image: var(--s-gold-edge);
  overflow: visible;
}
.s-rule::after {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  inline-size: 7px;
  block-size: 7px;
  translate: -50% -50%;
  rotate: 45deg;
  background-color: var(--s-gold-500);
}

/* --------------------------------------------------------------------------
   Chrome
   -------------------------------------------------------------------------- */

.s-header { background-color: var(--s-ivory); border-block-end: 1px solid var(--s-sand-soft); }

/* One line under the header, not two.
 *
 * The category bar closes the header with a 2px gold rule. The header then drew
 * its own 1px cream hairline directly beneath it, so the join read as a
 * two-tone stripe — a gold line with a paler line under it — sitting between
 * the menu and the category circles. The client photographed it on
 * 11 September 2026: "header aur category ke card ke beech me alag color dikh
 * raha hai".
 *
 * Only where the bar is actually there. Below 62rem it is `display: none` and
 * the header's own hairline is the only thing separating it from the page, so
 * that width keeps it. `:has()` rather than the media query alone, because a
 * shop with no categories has no bar at any width — and a browser too old to
 * understand `:has()` simply keeps the line it has today.
 */
@media (min-width: 62rem) {
  .s-header:has(.s-catbar) { border-block-end: 0; }
}

/* The category bar becomes a maroon band with gold type — the deep header the
   brand already uses on its own site, rather than a pale strip. */
.s-catbar {
  background-color: var(--s-maroon-800);
  border-block-start: 0;
  border-block-end: 2px solid var(--s-gold-600);
}
.s-catbar__link { color: var(--s-gold-100); font-weight: 500; }
.s-catbar__link:hover { background-color: rgb(255 255 255 / 0.10); color: var(--s-gold-300); }
.s-catbar__link.is-active { background-color: var(--s-maroon-950); color: var(--s-gold-300); }

/* ---------------------------------------------------------------------------
   The category bar on the page's edges, highlighted or not

   The client asked on 15 September 2026 for the left and right edges to be
   exact at every screen size "whether it is active or not". A filled pill
   cannot do both: with its box on the edge its word sits 16px inside it, and
   with the word on the edge the highlighted pill reaches 16px past it (tried,
   and rejected the same day).

   So the "you are here" mark is a gold line under the word instead of a
   filled pill, and the links carry no side padding: the first word starts on
   the left edge and the last ends on the right, highlighted or not. The gap
   between links is widened by exactly the padding taken away, so every name
   needs the same room as before and the same categories fit before "More".
--------------------------------------------------------------------------- */

@media (min-width: 62rem) {
  .s-catbar__list { column-gap: calc(var(--s-4) * 2 + var(--s-1)); }

  .s-catbar__link {
    position: relative;
    padding-inline: 0;
    border-radius: 0;
  }

  .s-catbar__link:hover,
  .s-catbar__link.is-active { background-color: transparent; }

  .s-catbar__link::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0.5rem;
    block-size: 2px;
    border-radius: 1px;
    background-color: var(--s-gold-400);
    opacity: 0;
    transform: scaleX(0.4);
    transition: opacity var(--s-fast) var(--s-ease), transform var(--s-fast) var(--s-ease);
  }

  .s-catbar__link:hover::after,
  .s-catbar__link:focus-visible::after { opacity: 0.6; transform: scaleX(1); }

  .s-catbar__link.is-active::after { opacity: 1; transform: scaleX(1); }
}

.s-catstrip { background-color: var(--s-ivory); border-block-end: 1px solid var(--s-sand-soft); }
.s-catstrip .s-wrap { padding-block: var(--s-6); }

.s-cattile__figure {
  border: 2px solid var(--s-gold-400);
  padding: 3px;
  background-color: var(--s-white);
}
.s-cattile__figure img { border-radius: 50%; }
.s-cattile:hover .s-cattile__figure { border-color: var(--s-maroon-700); }
.s-cattile__name { font-weight: 600; color: var(--s-maroon-900); }

/* ---------------------------------------------------------------------------
   The category circles start on the page's left edge

   Measured on 15 September 2026: every tile started on the edge, but its
   circle was narrower than the tile and sat in the middle of it — 8px in on a
   phone, 14px in on a computer — so the first circle stood in from the logo,
   the search box and the category bar above it, and the client saw it.

   The circle now fills its tile, so the first one starts exactly on the edge
   at every width. Each name was already given the tile's width, so a name is
   never wider than its circle and stays centred under it. The number of
   circles in view and the space between them are unchanged; the circles are a
   little larger.

   The arrow is set to the middle of the larger circle: half the tile's width,
   less half the arrow's own size, at each of the rail's three sizes.
--------------------------------------------------------------------------- */

.s-catrail .s-cattile__figure {
  inline-size: 100%;
  block-size: auto;
  aspect-ratio: 1;
}

/* The leading 1rem is the room the track keeps above the tiles for their hover
   lift (its `padding-block-start`); measured, the circles start that far
   below the rail's own top. */
.s-catrail .s-rail-arrow { inset-block-start: calc(1rem + 5.25rem / 2 - 2.125rem / 2); }

@media (min-width: 48rem) {
  .s-catrail .s-rail-arrow { inset-block-start: calc(1rem + 7rem / 2 - 2.125rem / 2); }
}

@media (min-width: 62rem) {
  .s-catrail .s-rail-arrow { inset-block-start: calc(1rem + 8rem / 2 - 2.5rem / 2); }
}

/* --------------------------------------------------------------------------
   Trust strip — a maroon band, not a pale gap
   -------------------------------------------------------------------------- */

.s-trust {
  background-color: var(--s-maroon-900);
  border: 0;
  color: var(--s-gold-100);
}
.s-trust__list { padding-block: var(--s-6); }
.s-trust__icon {
  background-color: rgb(214 166 46 / 0.14);
  border: 1px solid rgb(214 166 46 / 0.4);
  color: var(--s-gold-300);
}
.s-trust__title { color: var(--s-white); font-weight: 700; }
.s-trust__note { color: rgb(255 255 255 / 0.62); }

/* --------------------------------------------------------------------------
   Category tiles and promotional panels
   -------------------------------------------------------------------------- */

.s-cat {
  border: 1px solid var(--s-sand);
  background-color: var(--s-ivory);
}
.s-cat:hover { border-color: var(--s-gold-400); }
.s-cat__figure { margin: var(--s-2) var(--s-2) 0; border-radius: var(--s-radius-sm); overflow: hidden; }
.s-cat__name { font-family: var(--s-font-display); font-size: var(--s-text-md); font-weight: 600; color: var(--s-maroon-900); }
.s-cat__count { color: var(--s-gold-700); font-weight: 600; }

.s-promo { border: 1px solid var(--s-gold-600); }
.s-promo__title { font-weight: 600; }

/* --------------------------------------------------------------------------
   Slider
   -------------------------------------------------------------------------- */

.s-slider { border: 1px solid var(--s-gold-600); }
.s-slider__title { font-weight: 600; }
.s-mosaic__tile { border: 1px solid var(--s-gold-600); }

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

.s-btn--gold { background-image: var(--s-gold-edge); color: var(--s-maroon-900); font-weight: 700; letter-spacing: 0.06em; }
.s-btn--primary { border: 1px solid var(--s-gold-500); }
.s-btn--primary:hover { background-color: var(--s-maroon-800); }

/* --------------------------------------------------------------------------
   Listing furniture
   -------------------------------------------------------------------------- */

.s-filters { border-color: var(--s-sand); background-color: var(--s-ivory); }
.s-filters__title { color: var(--s-maroon-800); font-weight: 700; }
.s-toolbar { margin-block-end: var(--s-5); }

.s-footer { border-block-start: 3px solid var(--s-gold-600); }

/* --------------------------------------------------------------------------
   A product band on one line

   The merchant's second layout choice, and a real one: the same card at the
   same size, on a single line that scrolls sideways. Useful where a band is
   long and the page below it matters more — a phone reaches the next section
   in one thumb flick instead of four.

   `overflow-x: auto` forces `overflow-y` to clip, so a card that lifts on hover
   would be sheared off. The padding gives it room and the negative margin takes
   the height back (PROJECT_HANDOFF.md §5).
   -------------------------------------------------------------------------- */

.s-prail--row {
  flex-wrap: nowrap;
  justify-content: flex-start;
  overflow-x: auto;
  padding-block: var(--s-2);
  margin-block: calc(var(--s-2) * -1);
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
}

.s-prail--row > * {
  flex: 0 0 auto;
  inline-size: 14rem;
  scroll-snap-align: start;
}

@media (min-width: 62rem) {
  .s-prail--row > * { inline-size: 16rem; }
}

/* ==========================================================================
   A page the merchant wrote — About, contact, the policies, the questions
   --------------------------------------------------------------------------
   Same framing vocabulary as the cards: ivory panel, gold hairline, a gold rule
   under the heading. Nothing here is a new visual idea; a policy page that
   looked like a different website would be a worse answer than a plain one
   (MASTER_PROMPT.md §7a).

   Two columns where there is a second column to draw, one where there is not —
   `s-doc--wide`. Prose is held to a readable measure either way, because a
   200-character line at 1920px is unreadable; the aside is what uses the rest
   of the width rather than leaving it blank (DEVELOPMENT_RULES.md §6).
   ========================================================================== */

.s-pagehead { margin-block-end: var(--s-8); }

.s-pagehead__title {
  font-family: var(--s-font-display);
  font-size: clamp(1.75rem, 1.2rem + 2.4vw, 2.75rem);
  line-height: 1.15;
  color: var(--s-maroon-700);
  margin: 0;
}

.s-pagehead__title::after {
  content: "";
  display: block;
  inline-size: 3.5rem;
  block-size: 2px;
  margin-block-start: var(--s-3);
  background: linear-gradient(90deg, var(--s-gold-600), var(--s-gold-300));
}

.s-pagehead__lede {
  max-inline-size: 46rem;
  margin-block: var(--s-4) 0;
  font-size: var(--s-text-lg);
  line-height: 1.6;
  color: var(--s-ink-soft);
}

.s-pagehead__stamp {
  margin-block: var(--s-3) 0;
  font-size: var(--s-text-sm);
  color: var(--s-muted);
}

.s-doc { display: grid; gap: var(--s-6); align-items: start; }

@media (min-width: 62rem) {
  .s-doc { grid-template-columns: minmax(0, 1fr) 18rem; gap: var(--s-10); }
  .s-doc--wide { grid-template-columns: minmax(0, 1fr); }
}

.s-doc__body {
  max-inline-size: 52rem;
  padding: var(--s-6);
  background-color: var(--s-cream);
  border: 1px solid var(--s-gold-300);
  border-radius: var(--s-radius);
}

@media (min-width: 48rem) {
  .s-doc__body { padding: var(--s-8); }
}

/* The aside sticks past the fold on a tall policy, and stops sticking on a
   phone, where a pinned block would eat a third of the screen. */
.s-doc__aside { display: grid; gap: var(--s-4); }

@media (min-width: 62rem) {
  .s-doc__aside { position: sticky; inset-block-start: var(--s-6); }
}

.s-aside__card {
  padding: var(--s-5);
  background-color: var(--s-surface);
  border: 1px solid var(--s-line);
  border-radius: var(--s-radius);
}

.s-aside__title {
  font-family: var(--s-font-display);
  font-size: var(--s-text-md);
  color: var(--s-maroon-700);
  margin: 0 0 var(--s-3);
}

.s-aside__list { display: grid; gap: var(--s-2); margin: 0; padding: 0; list-style: none; }

.s-aside__list a {
  display: block;
  padding-block: var(--s-1);
  font-size: var(--s-text-sm);
  color: var(--s-ink-soft);
  text-underline-offset: 0.15em;
}

.s-aside__list a:hover { color: var(--s-maroon-700); text-decoration: underline; }

/* ---- Questions and answers ---------------------------------------------- */

.s-faqs { margin-block-start: var(--s-8); }
.s-faqs:first-child { margin-block-start: 0; }

.s-faqs__heading {
  font-family: var(--s-font-display);
  font-size: var(--s-text-xl);
  color: var(--s-maroon-700);
  margin-block: var(--s-8) var(--s-4);
  scroll-margin-block-start: var(--s-8);
}

.s-faqs__heading:first-child { margin-block-start: 0; }

.s-faq { border-block-end: 1px solid var(--s-line); }

.s-faq__q {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-4);
  padding-block: var(--s-4);
  font-family: var(--s-font-display);
  font-size: var(--s-text-lg);
  line-height: 1.35;
  color: var(--s-ink);
  cursor: pointer;
  list-style: none;
}

.s-faq__q::-webkit-details-marker { display: none; }

/* A plus that becomes a minus. Drawn in CSS rather than as an icon file, so it
   is right at every size and needs no request of its own. */
.s-faq__q::after {
  content: "+";
  flex: none;
  font-family: var(--s-font-body);
  font-size: var(--s-text-xl);
  line-height: 1;
  color: var(--s-gold-600);
  transition: rotate var(--s-base) var(--s-ease-out);
}

.s-faq[open] > .s-faq__q::after { content: "−"; }
.s-faq__q:hover { color: var(--s-maroon-700); }
.s-faq__a { padding-block-end: var(--s-5); }

/* With no second column to draw, the words are centred rather than left with an
   unused third beside them. Prose stays capped either way — a 200-character
   line at 1920px is unreadable — so centring is the honest answer to "could
   that space say something useful?": it could not, so nothing is left dangling
   beside it. The same rule a short row of cards follows (MASTER_PROMPT.md §7a). */
.s-doc--wide .s-doc__body { margin-inline: auto; }

/* ==========================================================================
   Checkout, and the order a customer reads back
   --------------------------------------------------------------------------
   Same two-column shape as the bag, so a shopper moving from one to the other
   is not re-learning the page: the things they are deciding on the left, the
   money on the right, and the money sticks past the fold on a desktop.

   Fields are deliberately plain. A checkout is the one page on a shop where
   decoration costs money — every extra thing to look at is another moment to
   change your mind — so this is the brand's colours and nothing else.
   ========================================================================== */

.s-checkout { display: grid; gap: var(--s-6); align-items: start; }

@media (min-width: 62rem) {
  .s-checkout { grid-template-columns: minmax(0, 1fr) 22rem; gap: var(--s-8); }
}

.s-checkout__main { display: grid; gap: var(--s-6); min-inline-size: 0; }

/* Two columns of fields where there is room, one where there is not. A field
   that wants the whole row says so with `--full`. */
.s-fields { display: grid; gap: var(--s-4); }

@media (min-width: 34rem) {
  .s-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .s-field--full { grid-column: 1 / -1; }
}

.s-field { display: grid; gap: var(--s-1); margin: 0; min-inline-size: 0; }

.s-field label {
  font-size: var(--s-text-sm);
  font-weight: 600;
  color: var(--s-ink);
}

.s-field input,
.s-field textarea {
  inline-size: 100%;
  padding: var(--s-3);
  font: inherit;
  font-size: var(--s-text-md);
  color: var(--s-ink);
  background-color: var(--s-surface);
  border: 1px solid var(--s-line);
  border-radius: var(--s-radius-sm);
}

.s-field input:focus-visible,
.s-field textarea:focus-visible {
  outline: 2px solid var(--s-gold-500);
  outline-offset: 1px;
  border-color: var(--s-gold-500);
}

/* --------------------------------------------------------------------------
   Every form control, in the shop's own hand

   The inputs were dressed and the selects were not, so a "Featured" box on the
   listing and the search box in the header looked like they came from two
   different websites. One rule for all of them: the ivory ground, the gold
   hairline, the maroon text and the same gold focus ring the cards use.

   The chevron is an inline SVG in the background rather than the browser's own
   arrow — `appearance: none` removes that, and without a replacement the box
   reads as a text field the customer cannot type in.
   -------------------------------------------------------------------------- */

/* `#e8d9bd` — the same warm sand the product cards are edged with, not
   `--s-gold-300`.

   Gold-300 is a pale yellow. At rest, on a cream panel, it reads as a
   highlighter drawn round the box rather than as an edge, and it was the one
   thing on the page shouting. Gold belongs to hover and focus, where it means
   something. */
.s-field select,
.s-select,
.s-input,
.s-field input,
.s-field textarea {
  background-color: var(--s-white);
  border: 1px solid var(--s-sand);
  border-radius: var(--s-radius-sm);
  color: var(--s-maroon-900);
  font: inherit;
}

.s-field select:hover,
.s-select:hover,
.s-input:hover { border-color: var(--s-gold-500); }

.s-field select:focus-visible,
.s-select:focus-visible,
.s-input:focus-visible {
  outline: 2px solid var(--s-gold-500);
  outline-offset: 1px;
  border-color: var(--s-gold-500);
}

.s-field select,
.s-select {
  appearance: none;
  /* `--s-9` was never a token — the scale is 1,2,3,4,5,6,8,10,12,16,20. A
     custom property that does not resolve makes the whole declaration *invalid
     at computed-value time*, and that is not a parse error the cascade can fall
     back from: the property is thrown away and takes its initial value. So
     every select on this shop has been `padding: 0` — the words jammed against
     the left border and the chevron sitting on top of them. Reported by the
     client as the sort box looking "chipka hua". Grep for a token before using
     one. */
  padding: var(--s-3) var(--s-10) var(--s-3) var(--s-4);
  font-size: var(--s-text-sm);
  font-weight: 600;
  cursor: pointer;

  /* A maroon chevron, sized and placed so it never sits on the text. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5 6 6.5l5-5' stroke='%235D0003' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--s-4) center;
}

/* A disabled control has to *look* unavailable, or a shopper taps it twice and
   concludes the shop is broken. */
.s-field select:disabled,
.s-select:disabled,
.s-input:disabled,
.s-field input:disabled {
  background-color: var(--s-cream);
  color: var(--s-muted);
  cursor: not-allowed;
}

/* Tick boxes and radios take the brand's gold rather than the browser's blue —
   the one-line change that stops a filter panel looking like a system dialog. */
.s-check input[type="checkbox"],
.s-check input[type="radio"],
.s-facet input[type="checkbox"],
.s-filters input[type="checkbox"],
.s-sheet input[type="checkbox"] {
  accent-color: var(--s-gold-700);
  inline-size: 1.05rem;
  block-size: 1.05rem;
}

.s-field__help { font-size: var(--s-text-xs); color: var(--s-muted); }

.s-field__error {
  font-size: var(--s-text-xs);
  font-weight: 600;
  color: var(--s-danger);
}

.s-optional { font-weight: 400; color: var(--s-muted); }

/* A tick box or a radio and its label on one line, with the label doing the
   clicking — a 14px control is not a tap target. */
.s-check { display: flex; align-items: flex-start; gap: var(--s-3); margin-block: 0 var(--s-4); }
.s-check input { margin-block-start: 0.25rem; inline-size: 1.1rem; block-size: 1.1rem; accent-color: var(--s-maroon-700); flex: none; }
.s-check label { display: grid; gap: 2px; cursor: pointer; font-size: var(--s-text-md); }

.s-check--pay {
  padding: var(--s-4);
  border: 1px solid var(--s-line);
  border-radius: var(--s-radius-sm);
}
.s-check--pay:has(input:checked) {
  border-color: var(--s-gold-500);
  background-color: var(--s-gold-100);
}

.s-sumline {
  display: flex;
  justify-content: space-between;
  gap: var(--s-3);
  padding-block: var(--s-2);
  font-size: var(--s-text-sm);
  border-block-end: 1px solid var(--s-line-soft);
}
.s-sumline__version, .s-sumline__qty { color: var(--s-muted); }

.s-cart__total--grand {
  padding-block-start: var(--s-3);
  border-block-start: 1px solid var(--s-line);
}

/* ---- The order a customer reads back ------------------------------------- */

.s-orderstate { display: flex; align-items: flex-start; gap: var(--s-3); margin-block: 0 var(--s-5); }
.s-orderstate > span:last-child { display: grid; gap: 2px; }

.s-orderstate__dot {
  flex: none;
  inline-size: 0.6rem;
  block-size: 0.6rem;
  margin-block-start: 0.4rem;
  border-radius: 50%;
  background-color: var(--s-success);
}

.s-address { margin: 0; line-height: 1.7; color: var(--s-ink-soft); }

.s-timeline { display: grid; gap: var(--s-4); margin: 0; padding: 0; list-style: none; }
.s-timeline li { display: grid; gap: 2px; padding-inline-start: var(--s-4); border-inline-start: 2px solid var(--s-gold-300); }

/* The one page that hands the customer to a payment window. Deliberately bare:
   there is nothing to decide here and nothing to read, so there is nothing to
   look at either. */
.s-paybox {
  max-inline-size: 28rem;
  margin-inline: auto;
  padding: var(--s-6);
  background-color: var(--s-cream);
  border: 1px solid var(--s-gold-300);
  border-radius: var(--s-radius);
  text-align: center;
}
.s-paybox .s-rule { margin-inline: auto; }

/* Sign in, sign up, and the two password screens. One narrow column, centred:
   there is one thing to do on each and nothing to compare, so the width the
   catalogue needs would only make the form harder to read. */
.s-authbox {
  max-inline-size: 30rem;
  margin-inline: auto;
  padding: var(--s-6);
  background-color: var(--s-cream);
  border: 1px solid var(--s-gold-300);
  border-radius: var(--s-radius);
}
.s-authbox .s-fields { margin-block-start: var(--s-5); }
.s-authbox .s-head__title { margin: 0; }

/* --------------------------------------------------------------------------
   Back to the top

   Hidden until the page has scrolled two screens, so it is never a button
   sitting in the way of a shop somebody has only just opened. Sits clear of the
   phone's bottom navigation bar.
   -------------------------------------------------------------------------- */

.s-totop {
  position: fixed;
  inset-block-end: calc(var(--s-6) + env(safe-area-inset-bottom, 0px));
  inset-inline-end: var(--s-5);
  z-index: 70;
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  border: 1px solid var(--s-gold-500);
  border-radius: 50%;
  background-color: var(--s-maroon-800);
  color: var(--s-gold-300);
  cursor: pointer;
  box-shadow: 0 6px 20px rgb(93 0 3 / 0.28);

  /* Out of the way and out of the tab order until it is actually offered. */
  opacity: 0;
  visibility: hidden;
  translate: 0 0.5rem;
  transition: opacity var(--s-base) var(--s-ease),
              visibility var(--s-base) var(--s-ease),
              translate var(--s-base) var(--s-ease);
}

.s-totop.is-on { opacity: 1; visibility: visible; translate: 0 0; }
.s-totop:hover { background-color: var(--s-maroon-900); }

/* Above the phone's bottom navigation, which owns the bottom of the screen. */
@media (max-width: 62rem) {
  .s-totop { inset-block-end: calc(4.75rem + env(safe-area-inset-bottom, 0px)); }
}

@media (prefers-reduced-motion: reduce) {
  .s-totop { transition: none; }
}

/* --------------------------------------------------------------------------
   Reviews

   Framed like everything else on this storefront — ivory panel, gold hairline —
   because a review is a piece of the shop's own content, not a widget bolted on.
   The shop's reply is inset and tinted, so it reads as an answer rather than as
   a second review.
   -------------------------------------------------------------------------- */

/* `auto-fit`, not `auto-fill`, and a centred short row.

   `auto-fill` keeps the empty columns, so a product with one review drew a
   narrow card against three columns of nothing — the blank-third the client is
   most emphatic about (`DEVELOPMENT_RULES.md §6`). `auto-fit` collapses the
   empty tracks, and centring means two reviews sit together in the middle
   rather than hugging the left edge. Same rule as the product cards: never
   deform one to fill a row, centre the row instead. */
/* The track has a **fixed** maximum, not `1fr`.

   With `1fr` the tracks stretch to fill the row, so a product with one review
   drew a single 1328px-wide band of text — unreadable, and the opposite of the
   `auto-fill` version's blank third. A fixed 26rem keeps every review the same
   size whatever the row holds, and `justify-content: center` centres a short
   row. That is the client's own card rule, applied here: never deform one to
   fill a row, centre the row instead. */
.s-reviews {
  display: grid;
  gap: var(--s-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 26rem));
  justify-content: center;
}

.s-review {
  padding: var(--s-5);
  background-color: var(--s-ivory);
  border: 1px solid var(--s-sand);
  border-radius: var(--s-radius);
}

.s-review__stars { color: var(--s-gold-700); letter-spacing: 0.1em; }

.s-review__title {
  font-family: var(--s-font-display);
  font-size: 1.0625rem;
  color: var(--s-maroon-900);
  margin-block-start: var(--s-2);
}

.s-review__body {
  margin-block-start: var(--s-2);
  font-size: var(--s-text-sm);
  line-height: 1.6;
  /* A review is somebody's typing: their paragraph breaks are part of what they
     said, and collapsing them turns a considered note into a wall. */
  white-space: pre-line;
}

.s-review__meta {
  margin-block-start: var(--s-3);
  font-size: var(--s-text-xs);
  color: var(--s-muted);
}

.s-review__verified { color: var(--s-maroon-700); font-weight: 600; }

.s-review__reply {
  margin-block-start: var(--s-4);
  padding: var(--s-3) var(--s-4);
  border-inline-start: 2px solid var(--s-gold-500);
  background-color: var(--s-cream);
  border-radius: var(--s-radius-sm);
}
.s-review__reply .s-review__meta { margin-block-start: 0; font-weight: 600; color: var(--s-maroon-700); }

/* --------------------------------------------------------------------------
   Discount codes on the bag

   A label, a row of input + button, and one message line. The message is drawn
   only when there is something to say — an empty strip reserved for a reason
   that usually does not exist is the "never render empty" rule broken on the
   page a shopper is most impatient on.
   -------------------------------------------------------------------------- */

.s-coupon { margin-block-start: var(--s-4); }
.s-coupon__label {
  display: block;
  font-size: var(--s-text-xs);
  font-weight: 600;
  color: var(--s-maroon-800);
  margin-block-end: var(--s-2);
}
.s-coupon__row { display: flex; gap: var(--s-2); align-items: stretch; }

/* Dressed like the rest of the shop, not like a browser default.

   It was a plain box against ivory panels and gold hairlines — the one control
   on the page that looked borrowed. It now takes the same ground, the same gold
   edge and the same focus ring as every other field, plus the letter-spacing a
   code deserves: a discount code is read character by character, and tracking
   is what stops O and 0 being retyped three times. */
.s-coupon__input {
  flex: 1 1 auto;
  /* Without this the input will not shrink below its intrinsic size and the
     button is pushed out of the panel at 320 — the min-content family again. */
  min-inline-size: 0;
  padding: var(--s-3);
  font: inherit;
  font-size: var(--s-text-md);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--s-maroon-900);
  background-color: var(--s-white);
  border: 1px solid var(--s-sand);
  border-radius: var(--s-radius-sm);
}
.s-coupon__input::placeholder {
  font-weight: 400;
  letter-spacing: normal;
  color: var(--s-muted);
}
.s-coupon__input:focus-visible {
  outline: 2px solid var(--s-gold-500);
  outline-offset: 1px;
  border-color: var(--s-gold-500);
}

/* The button matches the outlined gold "ADD TO BAG" the cards carry, so the two
   controls in this row read as a pair rather than as a field with a link
   floating beside it. */
.s-coupon__row .s-btn {
  flex: none;
  padding-inline: var(--s-5);
  border: 1px solid var(--s-gold-500);
  border-radius: var(--s-radius-sm);
  background-color: transparent;
  color: var(--s-maroon-800);
  font-size: var(--s-text-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
/* No gold on hover while it cannot be pressed (an empty box). */
.s-coupon__row .s-btn:hover:not(:disabled) {
  background-image: var(--s-gold-edge);
  border-color: transparent;
  color: var(--s-maroon-900);
}

.s-coupon__ok,
.s-coupon__bad {
  margin-block-start: var(--s-2);
  font-size: var(--s-text-xs);
  line-height: 1.4;
}
.s-coupon__ok { color: var(--s-maroon-700); font-weight: 600; }
.s-coupon__bad { color: #92400e; }

.s-coupon__clear {
  margin-block-start: var(--s-2);
  border: 0;
  background: none;
  padding: 0;
  font-size: var(--s-text-xs);
  color: var(--s-muted);
  text-decoration: underline;
  cursor: pointer;
}
.s-coupon__clear:hover { color: var(--s-maroon-700); }

/* --------------------------------------------------------------------------
   Signing in with Google or Facebook

   The buttons sit **above** the email form, because somebody who has an account
   elsewhere wants the one-tap route and should not have to read past a password
   field to find it.

   The two brand colours are the providers' own, and are the one place in this
   storefront that is not maroon and gold. That is deliberate: a Google button
   that does not look like a Google button reads as a phishing page, and these
   are the marks people check before handing over an account.
   -------------------------------------------------------------------------- */

.s-oauth { display: grid; gap: var(--s-3); margin-block-start: var(--s-5); }

.s-oauth__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  font-weight: 600;
  border-width: 1px;
  border-style: solid;
  min-height: 2.75rem;
}

/* The provider's mark sits before the words, the size Google's guidance gives
   it, and never shrinks when the label wraps on a narrow phone. */
.s-oauth__mark { flex: none; width: 20px; height: 20px; }

/* Google's own guidance: their wordmark on white, with a visible border so the
   button still has an edge on a cream panel. */
/* `.s-btn.s-oauth__btn--…`: two classes, because the plain `.s-btn` rule that
   comes later in the cascade was painting both buttons white-on-cream —
   measured on the sign-in page, the Facebook button had `#fff` behind it. */
.s-btn.s-oauth__btn--google {
  background-color: #fff;
  border-color: #dadce0;
  color: #3c4043;
}
.s-btn.s-oauth__btn--google:hover { background-color: #f8f9fa; color: #3c4043; }

.s-btn.s-oauth__btn--facebook {
  background-color: #1877f2;
  border-color: #1877f2;
  color: #fff;
}
.s-btn.s-oauth__btn--facebook:hover { background-color: #166fe0; color: #fff; }

.s-oauth__note {
  margin-block-start: var(--s-3);
  font-size: var(--s-text-xs);
}

/* "or", with a rule either side. A grid rather than pseudo-elements, so the
   rules shrink with the panel instead of overflowing it at 320. */
.s-orbar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: var(--s-3);
  margin-block-start: var(--s-5);
  font-size: var(--s-text-xs);
  text-transform: uppercase;
  letter-spacing: var(--s-tracking-caps);
  color: var(--s-muted);
}
.s-orbar::before,
.s-orbar::after {
  content: "";
  block-size: 1px;
  background-color: var(--s-gold-300);
}

/* ==========================================================================
   The mega menu — sub-collections under each heading in the category bar
   --------------------------------------------------------------------------
   CSS only. It opens on hover and on keyboard focus with no JavaScript at all,
   which means it works with scripts blocked and every link in it is in the page
   for a crawler to follow — AJAX enhances, it never becomes the only way to
   read something (MASTER_PROMPT.md §8).
   ========================================================================== */

.s-catbar__item--has-menu { position: relative; }

.s-mega {
  position: absolute;
  inset-block-start: 100%;
  inset-inline-start: 0;
  z-index: var(--s-z-header, 60);
  min-inline-size: 15rem;
  padding: var(--s-3) 0;
  background-color: var(--s-surface);
  border: 1px solid var(--s-gold-300);
  border-block-start: 0;
  border-radius: 0 0 var(--s-radius) var(--s-radius);
  box-shadow: 0 12px 28px rgb(29 23 22 / 0.16);

  /* Hidden by clipping rather than by `display`, so it can fade and so a
     keyboard tabbing into it still finds real, focusable links. */
  opacity: 0;
  visibility: hidden;
  translate: 0 -4px;
  transition: opacity var(--s-fast) var(--s-ease-out), translate var(--s-fast) var(--s-ease-out), visibility var(--s-fast);
}

.s-catbar__item--has-menu:hover > .s-mega,
.s-catbar__item--has-menu:focus-within > .s-mega {
  opacity: 1;
  visibility: visible;
  translate: 0 0;
}

.s-mega__list { margin: 0; padding: 0; list-style: none; }

.s-mega__list a {
  display: block;
  padding: var(--s-2) var(--s-4);
  font-size: var(--s-text-sm);
  color: var(--s-ink-soft);
  white-space: nowrap;
}

.s-mega__list a:hover,
.s-mega__list a:focus-visible {
  background-color: var(--s-gold-100);
  color: var(--s-maroon-700);
}

/* "All of Puja Samagri" — the parent is still reachable from its own dropdown,
   which is the link people look for and the one a hover menu usually eats. */
.s-mega__all {
  margin-block-start: var(--s-2);
  padding-block-start: var(--s-2);
  border-block-start: 1px solid var(--s-line-soft);
}
.s-mega__all a { font-weight: 600; color: var(--s-maroon-700); }

/* On a phone there is no hover and the bar scrolls sideways, so a dropdown
   would be a trap. The phone menu already lists everything; this is desktop
   furniture. */
@media (max-width: 61.99rem) {
  .s-mega { display: none; }
}

/* The bar itself has to own a stacking context, or its dropdowns lose.

   `.s-mega` carried a z-index and was still painted under the category rail
   below it: a z-index on a child does nothing against a later sibling unless an
   ancestor is positioned, and `.s-catbar` was `position: static`. The dropdown
   was in the page, the right size, in the right place, and invisible. Caught by
   a screenshot, which is exactly what screenshots are for — DOM geometry said
   it was fine. */
.s-catbar { position: relative; z-index: var(--s-z-header, 60); }

/* And the bar must stop clipping where the dropdown exists.

   `.s-catbar__list` is `overflow-x: auto` so the bar scrolls sideways on a
   phone — and `overflow-x: auto` forces `overflow-y` to clip, which ate the
   dropdown whole. This is the trap already written down in
   PROJECT_HANDOFF.md §5, met from the other side: there the fix was padding, and
   here it is not scrolling at all above the width where the bar fits.

   Below 62rem it goes back to scrolling, which is exactly where `.s-mega` is
   hidden anyway — so nothing is ever clipped and nothing ever overflows. */
@media (min-width: 62rem) {
  /* Not scrolling means the bar has to fit some other way, and wrapping is that
     way. Simply switching the clip off let the bar push the whole page sideways
     — a horizontal scrollbar on the document, which is the one thing this
     project refuses everywhere else. Two tidy lines beat one line you have to
     drag. */
  .s-catbar__list { overflow: visible; flex-wrap: wrap; row-gap: 0; }

  /* One line, once JavaScript has had a chance to fold the overflow away.

     The client asked for a single row, and a shop with ten long category names
     cannot have one by wrapping or by scrolling: wrapping gives two lines, and
     `overflow-x` clips the mega menu (the trap in PROJECT_HANDOFF.md §5, met
     twice). So the bar keeps the names that fit and puts the rest under
     **More** — which is what every large shop does, Amazon included.

     `nowrap` is applied only under `[data-catbar-ready]`, a flag the script sets
     **after** it has moved the overflow. With scripts blocked the attribute is
     never added and the bar wraps exactly as before: two tidy lines, every link
     present and crawlable. It degrades rather than breaking. */
  .s-catbar__list[data-catbar-ready] { flex-wrap: nowrap; }

  /* And the names spread across the whole bar rather than huddling on the left.

     Folding the overflow away leaves whatever did not divide evenly as a band of
     bare maroon after **More**, which is exactly the empty space the client has
     asked not to see anywhere. `space-between` hands that slack back to the gaps
     between the names, so the row starts at one edge of the bar and finishes at
     the other.

     Only under `[data-catbar-ready]`: the script has already decided how many
     names fit by measuring their own widths, and spreading them afterwards
     changes only the space between them, never how much room they need. With
     scripts blocked the bar still wraps, left-aligned, which is right for two
     ragged lines. */
  .s-catbar__list[data-catbar-ready] { justify-content: space-between; }
}

/* The overflow dropdown hangs from the right, because it is the last item on
   the bar and a left-aligned panel there would run off the page. */
.s-catbar__item--more > .s-mega {
  inset-inline-start: auto;
  inset-inline-end: 0;
  border-radius: 0 0 var(--s-radius) var(--s-radius);
}

/* The chevron turns over when the panel is open, so the control says which way
   it is pointing rather than sitting still. */
.s-catbar__more-mark {
  margin-inline-start: var(--s-1);
  font-size: 0.7em;
  transition: rotate var(--s-fast) var(--s-ease);
}
.s-catbar__item--more:hover .s-catbar__more-mark,
.s-catbar__item--more:focus-within .s-catbar__more-mark { rotate: 180deg; }

/* --------------------------------------------------------------------------
   The listing toolbar's pair of controls
   -------------------------------------------------------------------------- */

/* **Filters** and the sort box are one pair and have to read as one.

   They did not. The button is `.s-btn--sm` — 2.25rem tall, a pill, bordered in
   `--s-line`, small caps-ish type — and the select took the form default:
   2.875rem tall, a small radius, a warmer border, heavier type. Side by side on
   a phone they looked like two controls borrowed from two different shops,
   which is exactly what the client said when they saw them. One height, one
   radius, one border, for both. */
.s-toolbar__right > .s-btn,
.s-toolbar__right .s-sort {
  /* A fixed height rather than a minimum, and no vertical padding to fight it.
     A minimum left the select free to grow to whatever its own line box asked
     for — 47px against the button's 44 — and three pixels of difference between
     two controls sitting side by side is exactly the mismatch being fixed. */
  block-size: 2.875rem;
  min-block-size: 2.875rem;
  padding-block: 0;
  border-radius: var(--s-radius-pill);
  border-color: var(--s-sand);
  font-size: var(--s-text-sm);
  font-weight: 600;
  letter-spacing: normal;
}

/* The pill's own curve needs more room at the ends than a rectangle does, or
   the first letter sits on the arc. */
.s-toolbar__right > .s-btn { padding-inline: var(--s-5); }

/* Only as much room on the right as the chevron actually needs. It was
   `--s-10`, which left a visible band of nothing between the word and the mark
   — the client pointed straight at it. The chevron is 12px wide sitting 16px in,
   so 2rem clears it with a hair to spare and nothing more. */
.s-toolbar__right .s-sort { padding-inline: var(--s-5) var(--s-8); }

.s-toolbar__right > .s-btn:hover,
.s-toolbar__right .s-sort:hover { border-color: var(--s-gold-500); }

/* --------------------------------------------------------------------------
   Scrollbars on the shop's own panels

   The filter column and the phone sheet scroll, and they were drawing the
   operating system's own bar — a wide grey slab down the inside edge of a
   maroon-and-gold panel. It is the one piece of chrome on these screens that
   nothing had ever been said about.

   Both spellings, deliberately. Chrome 121 and Firefox take `scrollbar-width` /
   `scrollbar-color`; older Chrome and Safari take the `::-webkit-` pseudos, and
   where a browser understands the standard properties it ignores the pseudos,
   so the two can never both apply and disagree.
   -------------------------------------------------------------------------- */

/* Both spellings, side by side, and no `@supports` cleverness.

   Going thinner than `thin` in Chrome needs the `::-webkit-` pseudos, and Chrome
   ignores those the moment `scrollbar-width` is set — so an earlier pass handed
   the standard properties back inside `@supports selector(::-webkit-scrollbar)`
   to let the pseudos win. It cost Firefox its gold bar and looked worse in the
   browser the client actually watches. Four pixels was not worth that: `thin`
   plus a gold thumb, in whichever spelling the browser understands. */
.s-filters > .s-filterform,
.s-sheet__body {
  scrollbar-width: thin;
  scrollbar-color: var(--s-gold-500) transparent;
}

.s-filters > .s-filterform::-webkit-scrollbar,
.s-sheet__body::-webkit-scrollbar { inline-size: 6px; }

.s-filters > .s-filterform::-webkit-scrollbar-track,
.s-sheet__body::-webkit-scrollbar-track { background: transparent; }

.s-filters > .s-filterform::-webkit-scrollbar-thumb,
.s-sheet__body::-webkit-scrollbar-thumb {
  border-radius: var(--s-radius-pill);
  background-color: var(--s-gold-500);
}

.s-filters > .s-filterform::-webkit-scrollbar-thumb:hover,
.s-sheet__body::-webkit-scrollbar-thumb:hover { background-color: var(--s-gold-600); }

/* Half the row each, on a phone.

   Below 62rem is exactly where both controls exist — above it the filter column
   is on the page and the Filters button is not drawn, so there is no pair to
   balance. Two pills of different widths huddled at the right-hand end read as
   leftovers; one row, split evenly, reads as a control bar. The count takes the
   line above, which `.s-toolbar`'s own wrapping already gives it. */
@media (max-width: 61.999rem) {
  /* A two-column grid, not `flex: 1 1 0` on each.

     Equal flex factors did *not* give equal widths here — measured 187px against
     145px at 375 — so the row read as two different controls rather than a pair.
     `1fr 1fr` is the only thing that says "exactly half each" and means it. */
  .s-toolbar__right {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--s-3);
    flex: 1 1 100%;
    margin-inline-start: 0;
  }

  /* The select is wrapped in its GET form, so the form is the grid item; the
     select fills it. And a grid item will not shrink below its min-content
     without being told it may — the same family as the note in filters.css. */
  .s-toolbar__right > * { min-inline-size: 0; }
  .s-toolbar__right .s-sort { inline-size: 100%; }
}

/* ==========================================================================
   Session 10 — the phone, put right

   Reported by the client from a real phone, and all of it was real. The page
   was being *zoomed out by the browser* on a product page, which is what a
   document wider than its viewport looks like on Android: at 375px the document
   measured 480px.
   ========================================================================== */

/* ---- The section head was 105px of the overflow ---------------------------

   `.s-head` is a flex row — the title block on the left, a "See all" style link
   on the right — and that link carried `flex: none`. On a category with a long
   name the link is "MORE IN TRADITIONAL PACKING AND GIFT ITEMS", 402px wide at
   its uppercase, letter-spaced max-content size, and `flex: none` means it will
   not shrink for anybody. The row could not fit, so the document grew and the
   phone zoomed the whole page out to compensate.

   The title block had the matching half of the bug: no `min-inline-size: 0`, so
   it was squeezed to its min-content instead — "YOU MAY ALSO LIKE" and "Goes
   well with this" stacked one word per line down a narrow column, which is what
   the client photographed.

   Wrapping is the fix, not shrinking: the link keeps its own line rather than
   breaking mid-phrase.
   ------------------------------------------------------------------------- */

.s-head {
  flex-wrap: wrap;
  min-inline-size: 0;
}

.s-head > * { min-inline-size: 0; }

/* Give the title block the room and let the link fall under it when there is
   not enough for both. */
.s-head:not(.s-head--centre) > :first-child { flex: 1 1 14rem; }

.s-head__more {
  flex: 0 1 auto;
  max-inline-size: 100%;
  overflow-wrap: anywhere;
}

/* ---- The header does not follow you down a phone -------------------------

   `.s-header` is `position: sticky` at every width. On a phone it is the logo,
   the account, saved and bag icons **and** the search box stacked — about a
   quarter of the screen, held there for every one of the shopper's scrolls,
   on a layout that already carries a persistent bottom navigation with Home,
   Shop, Bag, Saved and Account on it. Two permanent bars is one too many, and
   the one that costs the most room is the one that duplicates the other.

   It stays sticky from 62rem up, where there is no bottom navigation and the
   header is a single short row.
   ------------------------------------------------------------------------- */

@media (max-width: 61.99rem) {
  .s-header { position: static; }
}

/* `base.css` also pins the link to one line with `white-space: nowrap`, which
   is the last thing keeping 402px of text in a 343px box. Right for a two-word
   "See all"; wrong the moment the label carries a category name the merchant
   chose. It wraps now, and the underline follows each line. */
.s-head__more {
  white-space: normal;
  text-wrap: balance;
}

/* ==========================================================================
   A link has to look like a link

   `base.css` opens with `a { color: inherit; text-decoration: none }`, which is
   right for the hundreds of anchors that are cards, nav items and buttons — and
   wrong for the handful written as ordinary prose links. "Manage your
   addresses" and "No account yet? Create one" both rendered as plain grey text
   that nothing marked as clickable, which is exactly what the client reported.

   Scoped to `a:not([class])` so it can only ever reach an anchor nobody has
   styled on purpose. Anything with a class keeps whatever it was given.
   ========================================================================== */

.s-link,
.s-note a:not([class]),
.s-panel p a:not([class]),
.s-prose a:not([class]),
.s-form a:not([class]),
.s-auth a:not([class]) {
  color: var(--s-maroon-700);
  font-weight: 600;
  text-decoration: underline;
  text-decoration-color: var(--s-gold-500);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color 140ms ease, text-decoration-color 140ms ease;
}

.s-link:hover,
.s-note a:not([class]):hover,
.s-panel p a:not([class]):hover,
.s-prose a:not([class]):hover,
.s-form a:not([class]):hover,
.s-auth a:not([class]):hover {
  color: var(--s-gold-700);
  text-decoration-color: currentColor;
}

.s-link:focus-visible,
a:not([class]):focus-visible {
  outline: 2px solid var(--s-maroon-700);
  outline-offset: 2px;
  border-radius: 2px;
}

/* ==========================================================================
   An invalid field has to look invalid

   The checkout already printed a red sentence under a field the server had
   refused — and left the box itself looking exactly like the boxes that were
   fine, so on a long form the shopper had to read every line to find the one
   that was wrong. The client asked for the box to be marked too.

   `:has()` does the server-rendered half without touching a single template:
   any field that carries an error message is a field whose control is wrong.
   `.is-invalid` is what the script adds before the form is ever sent.
   ========================================================================== */

.s-field:has(.s-field__error) input,
.s-field:has(.s-field__error) select,
.s-field:has(.s-field__error) textarea,
.s-field input.is-invalid,
.s-field select.is-invalid,
.s-field textarea.is-invalid,
input.is-invalid,
select.is-invalid,
textarea.is-invalid {
  border-color: var(--s-danger);
  background-color: #fff6f6;
}

.s-field:has(.s-field__error) input:focus,
.s-field input.is-invalid:focus,
input.is-invalid:focus,
select.is-invalid:focus,
textarea.is-invalid:focus {
  border-color: var(--s-danger);
  box-shadow: 0 0 0 3px rgb(185 28 28 / 0.16);
}

/* The label goes with it, so the eye lands on the name of the thing that is
   wrong rather than only on the box. */
.s-field:has(.s-field__error) label,
.s-field:has(input.is-invalid) label,
.s-field:has(select.is-invalid) label,
.s-field:has(textarea.is-invalid) label {
  color: var(--s-danger);
}

.s-field__error {
  display: flex;
  align-items: flex-start;
  gap: 0.35rem;
}

/* A mark before the message, so the error is findable without relying on
   colour alone. */
.s-field__error::before {
  content: "!";
  flex: none;
  display: grid;
  place-items: center;
  inline-size: 1rem;
  block-size: 1rem;
  border-radius: 50%;
  background-color: var(--s-danger);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1;
}

/* The summary at the top of a refused form. */
.s-formerror {
  display: grid;
  gap: var(--s-2);
  margin-block-end: var(--s-5);
  padding: var(--s-4);
  border: 1px solid var(--s-danger);
  border-inline-start-width: 3px;
  border-radius: var(--s-radius);
  background-color: #fff6f6;
}

.s-formerror__title {
  margin: 0;
  font-weight: 700;
  color: var(--s-danger);
}

.s-formerror ul { margin: 0; padding-inline-start: var(--s-5); font-size: var(--s-text-sm); }
.s-formerror a { color: var(--s-danger); text-decoration: underline; }

/* ==========================================================================
   Reviews, as a carousel

   Asked for by the client in session 10. The wrapping grid was the honest
   layout for three reviews and the wrong one for twenty: five cards drew as
   three across and two beneath, of unequal height, with a ragged bottom row —
   which is what "alignment perfect nahi hai" was.

   A rail fixes both. It is the same carousel the product rails already use, so
   a shopper who has scrolled one on this shop has already learnt this one, and
   it is honest about there being more: a row that runs off the edge says "keep
   going" in a way a grid never does.
   ========================================================================== */

.s-reviews {
  display: grid;
  grid-auto-flow: column;
  /* A whole number of cards, never a sliced one.

     This was `minmax(min(20rem, 82vw), 24rem)` — a fixed width, which at any
     given viewport leaves a half card hanging off the right edge. The half was
     deliberate, as the "there is more" hint; the client's answer was that a cut
     card is what ruins the finish, so the hint is the arrows now and the cards
     divide the row exactly. One column of gap for each gap between cards. */
  /* The grid rule further up this file still declares an explicit
     `grid-template-columns: repeat(auto-fit, minmax(…, 26rem))`, and nothing
     here overrode it — a different property. So the first three cards landed
     in explicit 416px tracks and every card after them in the implicit tracks
     below, at 432px: six identical cards, two different widths, and a row that
     never ended where the maths said it would. Cleared, so every column comes
     from `grid-auto-columns`. */
  grid-template-columns: none;

  --s-reviews-per-row: 1;
  grid-auto-columns: calc(
    (100% - (var(--s-reviews-per-row) - 1) * var(--s-4)) / var(--s-reviews-per-row)
  );
  gap: var(--s-4);
  justify-content: start;

  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: 0;

  /* No break-out, and this is the other half of "no cut cards".
     It used to pull itself a gutter wider than the wrap on each side so the
     strip could run to the screen edge. But `100%` in the column maths above
     is the content box — the wrap width — so three cards ended exactly at the
     wrap's right edge while the visible track carried on for one more gutter,
     and a gutter-wide sliver of the fourth card showed through. Sitting inside
     the wrap makes the row end where the heading above it ends, which is what
     was asked for anyway. */
  padding-block-end: var(--s-2);

  scrollbar-width: none;
}

.s-reviews::-webkit-scrollbar { display: none; }

.s-reviews > * { scroll-snap-align: start; }

/* Equal height, and the meta line pinned to the bottom of each card, so a
   three-line review and a nine-line one still end level. This is the alignment
   the grid could not give. */
.s-review {
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  block-size: 100%;
  min-inline-size: 0;

  /* Positioned, and this is not decoration — it is what stops the carousel
     widening the whole page.

     Each card carries a `.s-visually-hidden` span for the star rating, and that
     class is `position: absolute !important`. With every ancestor static, its
     containing block is the **initial** containing block — the viewport — not
     the card. In a grid the cards all sit at x ≈ 0, so nobody noticed. In a
     rail the fourth card sits at x ≈ 1100, its hidden span is placed there
     against the viewport, and the document's scroll width grows to match: a
     375px phone reporting a 1436px page and zooming itself out, which is the
     exact fault this session started by fixing.

     `overflow: hidden` on the rail does not help, because an absolutely
     positioned box is only clipped by an ancestor that is its containing
     block. Making the card one is the fix. */
  position: relative;
}

.s-review__body { overflow-wrap: anywhere; }
.s-review__meta { align-self: end; }

/* A reply is its own block and must not be squashed by the 1fr row above it. */
.s-review:has(.s-review__reply) { grid-template-rows: auto auto 1fr auto auto; }

/* Two across once there is room for two to still be readable, three on a
   desktop. Deliberately conservative: a review is a paragraph, and four across
   turns each one into a narrow column of broken words. */
@media (min-width: 48rem) { .s-reviews { --s-reviews-per-row: 2; } }
@media (min-width: 75rem) { .s-reviews { --s-reviews-per-row: 3; } }

/* The rail the arrows are positioned against. `.s-reviews` breaks out of the
   wrap with negative margins, so the arrows must hang off this instead or they
   would sit inside the first and last card. */
.s-reviewrail { position: relative; }

/* Clear of the arrows on a phone and a tablet.

   Below 62rem the arrows sit inside the rail — hanging them off the edge there
   would widen the page — and they covered the end of the text in the card
   beneath them: "feels" at 768 pixels, and a line of the first review at 375
   (measured 14 September 2026: the next arrow at 706–740 over a card ending at
   740 with 20px of padding, so 14px of it lay on the words). Pulling the row in
   by half an arrow on each side leaves each arrow over a card's padding only. */
@media (max-width: 61.999rem) {
  .s-reviewrail .s-reviews { margin-inline: calc(2.125rem / 2); }
}

@media (prefers-reduced-motion: no-preference) {
  .s-reviews { scroll-behavior: smooth; }
}

/* ==========================================================================
   The rating line — the shop's own gold, not grey small print

   It was inline grey text at the smallest size on the page: the single most
   persuasive thing a product page can say, styled as a footnote. It is the
   brand's gold now, on a soft gold panel, with the figure carried at the size
   it deserves — and it is still only ever drawn when real approved reviews
   exist, which has not changed.
   ========================================================================== */

.s-rating {
  display: inline-flex;
  align-items: baseline;
  gap: var(--s-2);
  margin-block: var(--s-2) 0;
  padding: 0.3rem var(--s-3);
  border: 1px solid var(--s-gold-300, var(--s-sand));
  border-radius: var(--s-radius-pill, 999px);
  background-color: var(--s-cream, var(--s-sand-pale));
  text-decoration: none;
  color: inherit;
}

.s-rating__stars {
  color: var(--s-gold-700);
  letter-spacing: 0.08em;
  font-size: var(--s-text-sm);
  line-height: 1;
}

.s-rating__score {
  font-family: var(--s-font-display);
  font-size: 1.0625rem;
  font-weight: 700;
  color: var(--s-maroon-900);
  line-height: 1;
}

.s-rating__count {
  font-size: var(--s-text-xs);
  color: var(--s-maroon-700);
  font-weight: 600;
}

a.s-rating:hover { border-color: var(--s-gold-500); background-color: #fbf1de; }

/* The same treatment on a product card, at card scale.

   `justify-content: center` is not decoration. `.s-card__body` is a grid with
   `text-align: center`, and a grid item declared `inline-flex` is **blockified**
   to `flex` — so it filled the column and pinned its stars to the left while the
   category, the name, the price and the button above and below it were all
   centred. `text-align` cannot reach the children of a flex container, so the
   centring has to be said again here. */
.s-card__rating {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  font-size: var(--s-text-xs);
}

.s-card__rating .s-card__stars { color: var(--s-gold-700); letter-spacing: 0.06em; }
.s-card__rating .s-card__score { font-weight: 700; color: var(--s-maroon-900); }
.s-card__rating .s-card__count { color: var(--s-muted); }

/* ==========================================================================
   Session 10 — the order page

   Two faults the client photographed, and neither was cosmetic.
   ========================================================================== */

/* ---- A button with no variant was invisible ------------------------------

   `.s-btn` sets shape, size and weight — and `border: 1px solid transparent`
   with no background and no colour of its own. Every call site that reaches for
   a plain `.s-btn` therefore renders **centred text**: "Tax invoice", "Return
   or exchange something" and "Cancel this order" all sat in the order summary
   looking like captions, with the space around them reading as a gap rather
   than as a control.

   Measured, not guessed: `background-color: rgba(0,0,0,0)`,
   `border-color: rgba(0,0,0,0)`.

   The base now carries the outlined look, so a `.s-btn` without a modifier is
   still a button. Every existing variant sets its own background and border, so
   none of them changes. This is the same trap `.adm-button--danger` sprang in
   the admin (PROJECT_HANDOFF.md §5) — a modifier written first and a base
   nobody checked.
   ------------------------------------------------------------------------- */

/* Named exclusions, not `:not([class*="s-btn--"])`.

   This sheet loads after `base.css`, and `.s-btn` and `.s-btn--primary` have
   the same specificity — so a bare `.s-btn` rule here would repaint the primary
   button too, and "Send my review" turned white the first time this was
   written. Excluding every modifier is wrong the other way: `--block` and
   `--sm` are size, not colour, and "Tax invoice" is `s-btn s-btn--block` and
   still needs the default look. So the four **colour** variants are named, and
   nothing else is touched. The Google / Facebook sign-in buttons are the
   fifth: they carry their providers' own colours (see `.s-oauth__btn--…`). */
.s-btn:not(.s-btn--primary):not(.s-btn--gold):not(.s-btn--ghost):not(.s-btn--onDark):not(.s-oauth__btn) {
  background-color: var(--s-surface);
  border-color: var(--s-line);
  color: var(--s-ink);
}

.s-btn:not(.s-btn--primary):not(.s-btn--gold):not(.s-btn--ghost):not(.s-btn--onDark):not(.s-oauth__btn):hover:not(:disabled) {
  border-color: var(--s-maroon-700);
  color: var(--s-maroon-700);
}

/* ---- Fields in a row must line up ----------------------------------------

   `.s-field` is a grid, and its rows stretch by default. "How many stars?" has
   two rows (label, select) and "Your name" beside it has three (label, input,
   help line) — so in a row of equal height the select stretched into the space
   the help line occupies next door: **13px lower and 10px taller than the input
   beside it**. That is the "text box upar niche" the client saw.

   Packing the rows to the top is the whole fix. The controls then sit directly
   under their labels, at the same height, whether or not a field carries help
   text underneath.
   ------------------------------------------------------------------------- */

.s-field {
  align-content: start;
}

/* And the same nominal height for every control, so a select and a text box in
   one row are the same object at a glance. */
.s-field > input,
.s-field > select,
.s-field > .s-select,
.s-field > textarea {
  min-block-size: 3.125rem;
}

.s-field > textarea { min-block-size: 7rem; }

/* ---- The summary panel ---------------------------------------------------

   Its actions were spaced by inline margins written when they were invisible.
   With the buttons visible the rhythm is the panel's own.
   ------------------------------------------------------------------------- */

.s-summary .s-btn + .s-btn,
.s-summary form + .s-btn,
.s-summary .s-btn + form,
.s-summary .s-cart__total--grand + .s-btn,
.s-summary .s-cart__total--grand + form {
  margin-block-start: var(--s-4);
}

/* The grand total is what the eye should land on first in this panel. */
.s-summary .s-cart__total--grand {
  margin-block-start: var(--s-3);
  padding-block-start: var(--s-3);
  border-block-start: 1px solid var(--s-line);
}

/* The error summary is prepended into the form, and most forms here are the
   two-column `.s-fields` grid — so without this it becomes a grid item and
   draws in one narrow column. It is about the whole form; it takes the whole
   width. */
.s-formerror { grid-column: 1 / -1; }

/* ==========================================================================
   Ask about this on WhatsApp

   Drawn only when the merchant has switched it on and saved a number — see
   `WhatsAppEnquiry`. WhatsApp's own green is used deliberately: it is the one
   colour on this page that is not the shop's, and that is the point, because
   the button is a promise about which app is going to open. It is kept quiet —
   an outline rather than a filled block — so it sits below "Add to bag" in the
   order of things rather than competing with it.
   ========================================================================== */

.s-whatsapp {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  margin-block-start: var(--s-3);
  min-block-size: 2.875rem;
  padding-inline: var(--s-5);
  border: 1px solid #25d366;
  border-radius: var(--s-radius-pill);
  background-color: #f3fdf7;
  color: #0b6b34;
  font-size: var(--s-text-sm);
  font-weight: 600;
  letter-spacing: var(--s-tracking-wide);
  text-decoration: none;
  transition:
    background-color var(--s-fast) var(--s-ease),
    border-color var(--s-fast) var(--s-ease),
    color var(--s-fast) var(--s-ease);
}

.s-whatsapp:hover {
  background-color: #25d366;
  border-color: #25d366;
  color: #fff;
}

.s-whatsapp:focus-visible {
  outline: 2px solid #0b6b34;
  outline-offset: 2px;
}

.s-whatsapp svg { flex: none; }

/* Full width, at every size.

   It sits under "Add to bag" and "Save", both of which run the width of the
   buy column. Left as an inline-flex it stopped 279px short of their right
   edge on a 1280 screen — three stacked controls and one ending in the middle,
   which is the client's "three buttons are not aligned". Measured on the page:
   683→1225, 683→1225, 683→946.

   It stays a quiet outline rather than a filled block, so matching their width
   does not make it compete with "Add to bag" for attention. */
.s-whatsapp { inline-size: 100%; }

/* ==========================================================================
   "Add this shop to your home screen"

   A small card above the bottom navigation, on a phone, once. Not a modal and
   not a banner across the top: a shopper reading a product page should never
   have the thing they are reading covered by an advertisement for the shop they
   are already in.

   It carries `hidden` in the markup and only the script takes it off, and only
   after the browser has confirmed the shop can genuinely be installed.
   ========================================================================== */

.s-install {
  position: fixed;
  z-index: 60;
  inset-inline: var(--s-3);
  /* Above the bottom navigation, clear of the phone's own home bar. */
  inset-block-end: calc(4.25rem + env(safe-area-inset-bottom, 0px));

  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3);

  border: 1px solid var(--s-gold-500);
  border-radius: var(--s-radius-lg, 14px);
  background-color: var(--s-white);
  box-shadow: 0 12px 32px rgb(36 0 1 / 0.22);

  /* Slid out and faded until the script opens it. `visibility` as well as the
     transform, so nothing here is reachable by a keyboard while it is closed. */
  transform: translateY(120%);
  opacity: 0;
  visibility: hidden;
  transition:
    transform 260ms var(--s-ease),
    opacity 200ms var(--s-ease),
    visibility 0s linear 260ms;
}

.s-install.is-open {
  transform: translateY(0);
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

/* `hidden` must actually hide it — a display or flex declaration out-specifies
   the attribute's own UA rule, which this project has already been bitten by
   on the filter badge (PROJECT_HANDOFF.md §5). */
.s-install[hidden] { display: none; }

.s-install__icon {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  overflow: hidden;
  border: 1px solid var(--s-line);
  border-radius: 12px;
  background-color: var(--s-ivory);
}

.s-install__icon img { inline-size: 100%; block-size: 100%; object-fit: contain; padding: 3px; }

.s-install__text { display: grid; gap: 1px; min-inline-size: 0; }

.s-install__title {
  font-size: var(--s-text-sm);
  font-weight: 700;
  color: var(--s-maroon-900);
  line-height: 1.25;
}

.s-install__note { font-size: var(--s-text-xs); color: var(--s-muted); }

.s-install__actions {
  flex: none;
  display: grid;
  gap: 2px;
  justify-items: center;
}

.s-install__no {
  border: 0;
  background: none;
  padding: 2px 4px;
  font-size: var(--s-text-2xs);
  color: var(--s-muted);
  text-decoration: underline;
  cursor: pointer;
}

.s-install__no:hover { color: var(--s-maroon-700); }

/* Narrow phones: the card becomes two rows rather than squeezing the wording
   down to one word per line. */
@media (max-width: 26rem) {
  .s-install { flex-wrap: wrap; }
  .s-install__text { flex: 1 1 8rem; }
  .s-install__actions { inline-size: 100%; grid-auto-flow: column; justify-content: end; align-items: center; gap: var(--s-3); }
}

/* Never on a desktop: there is no bottom navigation to sit above, and nobody
   asked to be offered an app on a 27-inch screen. The footer button is not
   restricted this way — somebody who goes looking should find it. */
@media (min-width: 62rem) {
  .s-install { display: none; }
}

/* ---- The permanent way in, in the footer -------------------------------- */

/* The install button sits between the footer's columns and the copyright
   line, and it had space above it and none below — so it read as if it were
   glued to the rule under it rather than standing in its own row. The space
   is now even on both sides of it. */
.s-footer__install {
  padding-block: var(--s-5);
  text-align: center;
}

.s-footer__install [hidden] { display: none; }

/* ---- Two corrections after measuring the card on a 375px phone ----------

   **Above the bottom navigation, not under it.** The nav is `z-index: 110` and
   the card was 60, so the two cleared each other by six pixels and only by
   luck: a longer invite line, or a larger phone font, and the card would have
   slid underneath the bar with its buttons unreachable.

   **One row, not two.** The wrap breakpoint was 26rem, which is wider than the
   phone it was written for — so at 375px the card wrapped to two rows and stood
   118px tall on top of a 62px navigation. That is a fifth of the screen for an
   offer the shopper did not ask for, which is the same complaint the header
   already earned. It wraps below 20rem now, where a single row genuinely cannot
   fit.
   ------------------------------------------------------------------------- */

.s-install { z-index: 120; }

@media (max-width: 26rem) {
  .s-install { flex-wrap: nowrap; }
  .s-install__text { flex: 1 1 auto; }
  .s-install__actions { inline-size: auto; grid-auto-flow: row; justify-content: center; gap: 2px; }
}

@media (max-width: 20rem) {
  .s-install { flex-wrap: wrap; }
  .s-install__actions { inline-size: 100%; grid-auto-flow: column; justify-content: end; align-items: center; gap: var(--s-3); }
}

/* The build credit in the footer. Quieter than the copyright line beside it —
   it is a signature, not a claim on the shop. */
.s-footer__credit { font-size: var(--s-text-xs); opacity: 0.75; }
.s-footer__credit a { color: var(--s-gold-300); text-decoration: underline; text-underline-offset: 3px; }
.s-footer__credit a:hover { color: var(--s-gold-100); }

/* --------------------------------------------------------------------------
   A "Save" button that admits the thing is already saved

   The card's heart has said so since it was built — `.s-card__wish.is-on`
   fills it — but the product page and the quick view carry a word instead, and
   the word never changed. A shopper who had just saved something was shown a
   button reading "Save", pressed it again, and quietly *un*saved it. Reported
   by the client, and the reason the whole saved state is now rendered by the
   server rather than left to script.
   -------------------------------------------------------------------------- */

.s-btn--ghost.is-on {
  border-color: var(--s-maroon-700);
  background-color: var(--s-maroon-50, #fbf3f3);
  color: var(--s-maroon-700);
  font-weight: 600;
}

/* No heart glyph on the button. It was added so the state read at a glance and
   the client found it cheap-looking beside the serif type — the word carries it,
   and the card already has a real heart. */

/* --------------------------------------------------------------------------
   Flash messages — the shop answering something the shopper just did

   There was nowhere for these to go. Every `->with('status', …)` on the
   storefront was set and discarded, which is why asking for a password-reset
   link looked like a page that reloaded and forgot the address it was given.

   It lives in the toast stack and floats over the page. As a banner inside the
   page it pushed everything below it down the instant it appeared — the client
   photographed the category circles jumping on 11 September 2026. A message
   about what just happened must not move what the shopper is reading.

   It still is not a toast in one respect: no timer. A toast reports what the
   page did on its own and may be missed; this answers something the shopper
   did, so it waits for the × .
   -------------------------------------------------------------------------- */

.s-flash {
  /* The stack lets clicks through; this one has a button in it, so it takes
     its own. */
  pointer-events: auto;

  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: start;
  gap: var(--s-3);
  max-inline-size: 26rem;
  padding: var(--s-4) var(--s-5);
  border-radius: var(--s-radius);
  border: 1px solid;
  box-shadow: var(--s-shadow-lg, 0 10px 30px rgb(46 0 2 / 0.16));
  font-size: var(--s-text-sm);
  line-height: 1.5;
  animation: s-toast-in 0.18s ease-out both;
}

/* Brand, not the browser-default green.

   The client asked for this in as many words about the add-to-bag notice:
   "the notification background should not be green; it should use a brown /
   related brand shade matching the website's theme". The toast was changed and
   this banner — the same message in a different shape, on the bag, the account
   and the forgotten-password screen — was left behind, still the one green
   thing on a maroon-and-gold shop. Found by opening the screen.

   Gold on cream rather than maroon on cream: `--s-flash--bad` already owns the
   maroon end, and a shopper has to be able to tell "that worked" from "that
   did not" without reading. */
.s-flash--ok  { background-color: var(--s-gold-100); border-color: var(--s-sand-warm); color: var(--s-gold-700); }
.s-flash--bad { background-color: #fdf3f3; border-color: #f0cfcf; color: var(--s-maroon-800); }

.s-flash__mark svg { inline-size: 1.15rem; block-size: 1.15rem; display: block; }
.s-flash__text { margin: 0; }

.s-flash__close {
  border: 0;
  background: none;
  padding: 0;
  color: inherit;
  opacity: 0.55;
  cursor: pointer;
  line-height: 0;
}
.s-flash__close:hover { opacity: 1; }

/* The cross is 15px, which a finger on a phone keeps missing. It stays that
   size; an invisible ring around it takes the tap. */
.s-flash__close { position: relative; }
.s-flash__close::after { content: ""; position: absolute; inset: -0.75rem; }
.s-flash__close svg { inline-size: 0.95rem; block-size: 0.95rem; }

/* --------------------------------------------------------------------------
   The trust strip — a title, then what it promises, each on its own line

   Both were inline spans, so they ran together as one sentence and wrapped
   wherever the line happened to end: "Authentic samagri Sourced the way it
   always has been", and "Here to help +91" with the number stranded on the
   next line. A title is a title.
   -------------------------------------------------------------------------- */

.s-trust__title,
.s-trust__note { display: block; }

.s-trust__title { margin-block-end: 0.15rem; }

/* A phone number is one thing and must never be broken across two lines. */
.s-trust__note { text-wrap: pretty; }
.s-trust__note a[href^="tel:"],
.s-trust__phone { white-space: nowrap; }

/* Room to breathe inside the panels on a product page.

   `padding-block` alone left the text sitting against the hairline that
   separates one block from the next, which is what the client photographed —
   "the text is attached to the box". */
.s-pdp__block { padding-block: var(--s-6) var(--s-6); }
.s-pdp__block > :last-child { margin-block-end: 0; }

/* --------------------------------------------------------------------------
   A textarea that cannot be dragged out of its own form

   `resize: vertical` was set on `.s-textarea`, but the checkout's box is a
   plain `textarea` inside `.s-field`, which never picked the rule up — so it
   kept the browser default `resize: both` and could be dragged sideways over
   the order summary beside it.
   -------------------------------------------------------------------------- */

textarea,
.s-field textarea {
  resize: vertical;
  max-inline-size: 100%;
}

/* --------------------------------------------------------------------------
   The eye inside a password box
   -------------------------------------------------------------------------- */

.s-reveal { position: relative; display: block; }
.s-reveal > input { inline-size: 100%; padding-inline-end: 2.9rem; }

.s-reveal__btn {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-end: 0.6rem;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  inline-size: 2rem;
  block-size: 2rem;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--s-muted);
  cursor: pointer;
}
.s-reveal__btn:hover { color: var(--s-maroon-700); }
.s-reveal__btn svg { inline-size: 1.15rem; block-size: 1.15rem; }

/* --------------------------------------------------------------------------
   The quick view's close button had to be able to receive a tap

   `.s-modal` is `pointer-events: none` so that a click on the backdrop reaches
   the scrim behind it and dismisses the sheet. Everything inside inherits that,
   and the close button sits **outside** `.s-modal__panel` — the one child that
   turns pointer events back on — so the X was drawn, hovered nothing and did
   nothing. On a desktop a shopper could still tap the backdrop and get away
   with it; on a phone the X is the only obvious way out, and it was dead. The
   client found it there.
   -------------------------------------------------------------------------- */

.s-modal__close { pointer-events: auto; }

/* --------------------------------------------------------------------------
   "Saved" turns into "Remove" under the cursor

   A button that says "Saved" and does the opposite when pressed is a small
   trap. Hovering an already-saved item now says what pressing it will do.
   The swap is CSS, so it costs nothing and cannot fall out of step with the
   state the script paints.
   -------------------------------------------------------------------------- */

.s-wish-hover { display: none; }

.s-btn.is-on:hover [data-wish-label],
.s-btn.is-on:focus-visible [data-wish-label] { display: none; }

.s-btn.is-on:hover .s-wish-hover,
.s-btn.is-on:focus-visible .s-wish-hover { display: inline; }

/* The heart on a card carries no words, so it says it in the tooltip and to a
   screen reader instead — `state.js` keeps both in step. */

/* --------------------------------------------------------------------------
   Room inside the trust strip

   `padding-block` was declared on `.s-trust__list` in two stylesheets and lost
   to `ul[role="list"] { padding: 0 }` in the reset both times — an attribute
   selector beats a class. The reset is narrowed now; this states the intent
   plainly and gives the strip more room on a desktop, where the band is wide
   and four short lines floating in it looked cramped.
   -------------------------------------------------------------------------- */

.s-trust__list {
  padding-block: var(--s-6);
  row-gap: var(--s-5);
}

@media (min-width: 48rem) {
  .s-trust__list { padding-block: var(--s-8, 2rem); }
}

/* --------------------------------------------------------------------------
   The trust strip, looked at rather than measured

   Padding alone did not fix it. The icon was vertically centred against a text
   block that is often four lines tall on a phone, so it floated beside the
   middle of the sentence while the title sat above it — which is what reads as
   "chipka hua", stuck together. The icon belongs level with the title.

   The rows also needed real air between them, and the whole band needs more of
   it on a phone than a desktop, because every item wraps there.
   -------------------------------------------------------------------------- */

.s-trust__item {
  align-items: start;
  gap: var(--s-3);
}

/* Level with the cap-height of the title, not the top of its line box. */
.s-trust__icon { margin-block-start: 0.1rem; flex: none; }

.s-trust__list {
  padding-block: var(--s-8);
  row-gap: var(--s-7, 1.75rem);
  column-gap: var(--s-5);
}

.s-trust__title { line-height: 1.35; margin-block-end: 0.3rem; }
.s-trust__note { line-height: 1.5; }

@media (min-width: 48rem) {
  .s-trust__list { padding-block: var(--s-10, 2.5rem); row-gap: var(--s-6); }
}

/* --------------------------------------------------------------------------
   The account page — a hall, not a list

   "My account" used to be the orders list with the details squeezed into a
   sidebar, so the page meant two things and orders had no home of their own.
   The client asked for My orders as its own place; this is what is left: three
   ways through, and the details underneath.
   -------------------------------------------------------------------------- */

.s-accountgrid {
  display: grid;
  gap: var(--s-4);
  margin-block-end: var(--s-8);
}

@media (min-width: 48rem) {
  .s-accountgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.s-accountcard {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-5);
  background-color: var(--s-surface);
  border: 1px solid var(--s-line);
  border-radius: var(--s-radius);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--s-fast) var(--s-ease),
              box-shadow var(--s-fast) var(--s-ease),
              transform var(--s-fast) var(--s-ease);
}

.s-accountcard:hover {
  border-color: var(--s-gold-500);
  box-shadow: var(--s-shadow);
  transform: translateY(-2px);
}

.s-accountcard__mark {
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  border-radius: 50%;
  background-color: var(--s-cream);
  border: 1px solid var(--s-line);
  color: var(--s-maroon-800);
}
.s-accountcard__mark svg { inline-size: 1.25rem; block-size: 1.25rem; }

.s-accountcard__body { display: grid; gap: 0.2rem; min-inline-size: 0; }
.s-accountcard__title { font-weight: 700; color: var(--s-maroon-900); }
.s-accountcard__note { font-size: var(--s-text-xs); color: var(--s-muted); text-wrap: pretty; }

.s-accountcard__go { color: var(--s-gold-700); font-size: 1.1rem; }
.s-accountcard:hover .s-accountcard__go { color: var(--s-maroon-700); }

.s-accountdetails .s-address { margin-block-end: var(--s-4); }
.s-accountdetails .s-address:last-of-type { margin-block-end: 0; }

.s-address__default {
  margin-inline-start: var(--s-2);
  font-size: var(--s-text-xs);
  color: var(--s-muted);
  font-weight: 400;
}

/* The footer address is a link to a map — underlined only on hover, so the
   block still reads as an address rather than as a wall of links. */
.s-footer__address { color: inherit; text-decoration: none; }
.s-footer__address:hover { color: var(--s-gold-300); text-decoration: underline; }

/* --------------------------------------------------------------------------
   The map on the Contact page
   -------------------------------------------------------------------------- */

.s-map { margin-block-start: var(--s-10); }

.s-map__title {
  font-family: var(--s-font-display);
  font-size: var(--s-text-2xl);
  color: var(--s-maroon-900);
  margin-block-end: var(--s-4);
}

/* A ratio rather than a fixed height, so it is a map on a phone and a map on a
   desktop rather than a letterbox on one of them. */
.s-map__frame {
  position: relative;
  aspect-ratio: 16 / 10;
  border: 1px solid var(--s-line);
  border-radius: var(--s-radius);
  overflow: hidden;
  box-shadow: var(--s-shadow-sm);
}

@media (min-width: 62rem) { .s-map__frame { aspect-ratio: 21 / 8; } }

.s-map__frame iframe {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  border: 0;
}

.s-map__note { margin-block-start: var(--s-3); font-size: var(--s-text-sm); }

/* ==========================================================================
   One vertical rhythm, everywhere

   The client asked for this in plain words: "har section, buttons, links ke
   bich me ek standard spacing to maintain rakh". Until now spacing was set
   where somebody noticed it was missing — a `style="margin-block-start:…"`
   here, nothing there — so the same kind of gap was three different sizes on
   three pages, and several places had none at all: a heading against its rule,
   a coupon box against the Checkout button, "What you ordered" against the note
   above it.

   One scale, applied to the containers rather than to individual elements, so a
   block added next month is spaced without anybody remembering to space it.
   ========================================================================== */

:root {
  --s-rhythm: var(--s-5);        /* between things inside a panel   */
  --s-rhythm-lg: var(--s-8);     /* between one panel and the next  */
}

/* ---- Inside a panel, a card, a form box ------------------------------- */

.s-panel > * + *,
.s-authbox > * + *,
.s-summary > * + * {
  margin-block-start: var(--s-rhythm);
}

/* A title and the rule under it belong together — they are one unit, and the
   rule was sitting on the descenders of the heading above it. */
.s-panel > .s-rule,
.s-authbox > .s-rule,
.s-summary > .s-rule {
  margin-block-start: var(--s-3);
}

.s-head__title + .s-rule { margin-block-start: var(--s-3); }

/* A section heading needs air above it, more than below: it belongs to what
   follows it, not to what came before. */
.s-panel > .s-pdp__block-title + *,
.s-summary > .s-pdp__block-title + * {
  margin-block-start: var(--s-3);
}

.s-panel > * + .s-pdp__block-title,
.s-summary > * + .s-pdp__block-title {
  margin-block-start: var(--s-rhythm-lg);
}

/* ---- Between panels --------------------------------------------------- */

.s-checkout__main > .s-panel + .s-panel,
.s-doc__body > * + .s-panel { margin-block-start: var(--s-rhythm-lg); }

/* ---- The bag's order summary ------------------------------------------ */

/* The coupon row, the delivery note and the Checkout button were touching:
   three separate things reading as one block. */
.s-coupon { margin-block-start: var(--s-rhythm); }
.s-coupon__row { margin-block-start: var(--s-2); }
.s-summary .s-btn--block { margin-block-start: var(--s-rhythm); }
.s-summary .s-note { margin-block-start: var(--s-3); }

/* ---- Buttons and links that follow one another ------------------------ */

.s-btn + .s-btn,
form + form,
.s-btn + form,
form + .s-btn { margin-block-start: var(--s-3); }

/* …but never beside each other in a row. A row of controls spaces itself with
   its gap, and the margin above dropped the second button 6px below the first
   wherever two sat side by side: "Save" beside "Add to bag" on every product
   page and in the quick view, the two buttons on the not-found page, and
   "Show results" beside "Clear all" in the phone's filter sheet (measured
   14 September 2026). One class more than the rule above, and after it, so it
   wins in every stylesheet order. */
.s-pdp__buy > .s-btn + .s-btn,
.s-qv__buy > .s-btn + .s-btn,
.s-notfound__actions > .s-btn + .s-btn,
.s-sheet__foot > .s-btn + .s-btn { margin-block-start: 0; }

/* ---- The confirmation page -------------------------------------------- */

/* "What you ordered" sat against the cash-on-delivery note above it. */
.s-callout + .s-pdp__block-title { margin-block-start: var(--s-rhythm-lg); }
.s-callout { margin-block: var(--s-4); }

/* ==========================================================================
   Things a finger has to hit

   Found by measuring every link and button at 360px rather than by looking at
   them: the carousel dots were **8 pixels** tall, every footer link was 16, the
   phone number in the trust strip 15, and "See all" 23. All of them fine under
   a mouse and all of them a guess under a thumb — the usual guidance is a 44px
   target, and a shopper who misses twice stops trying.

   Nothing here changes how anything *looks*. The dot stays 8px of paint with a
   44px catchment around it, and the extra room on the text links is vertical
   padding on coarse pointers only, so the desktop footer is untouched.
   ========================================================================== */

/* The dot keeps its size; the button around it grows an invisible catchment.
   `::after` rather than padding, because padding would push the dots apart and
   the row is a deliberate shape. */
.s-slider__dot { position: relative; }

.s-slider__dot::after {
  content: "";
  position: absolute;
  inset-block: -1.1rem;
  inset-inline: -0.5rem;
  /* Nothing to see — this exists purely to be tapped. */
}

@media (pointer: coarse) {
  /* A column of 16px links is a column of near-misses. The padding also gives
     the footer the breathing room the client asked for. */
  .s-footer__list a,
  .s-footer__links a,
  .s-footer nav a {
    display: inline-block;
    padding-block: 0.55rem;
  }

  /*
   * A bigger tap target, WITHOUT flattening the row.
   *
   * This was `inline-block`, added so the padding below would apply — but the
   * padding applies to `inline-flex` just as well, and `inline-block` threw away
   * the flex row these links are built as. On a touch screen the little phone
   * and envelope icons stopped sitting beside their number and address and
   * stacked above them instead, which is exactly what the client photographed
   * on 12 September 2026. The postal address escaped it only because it carries
   * its own `display:flex`.
   */
  .s-trust__note a[href^="tel:"],
  .s-trust__phone,
  .s-footer a[href^="tel:"],
  .s-footer a[href^="mailto:"] {
    display: inline-flex;
    align-items: center;
    gap: var(--s-2);
    padding-block: 0.5rem;
  }

  .s-head__more {
    display: inline-block;
    padding-block: 0.5rem;
  }

  /* Anything that is a bare underlined action in a list — Update, Remove,
     "Suggest this one" — is the same problem in a different place. */
  .s-line__remove {
    display: inline-block;
    padding-block: 0.45rem;
  }

  /* The breadcrumb and the rating badge. Small by convention on a desktop,
     and still something a thumb is asked to hit. */
  .s-crumbs a,
  nav[aria-label="Breadcrumb"] a {
    display: inline-block;
    padding-block: 0.7rem;
  }

  .s-rating { padding-block: 0.35rem; }
}

/* ==========================================================================
   A menu as deep as the merchant built it, and a "More" that can be reached
   ==========================================================================

   Two things the client asked for, and both were real:

   * The dropdown drew one level of children and stopped. A category inside a
     category inside a category existed, held products, and nothing on the bar
     reached it. `menu-branch.blade.php` now includes itself, so a branch goes
     as deep as it goes; these rules make each generation open beside its
     parent rather than under it.

   * With enough categories the "More" panel grew taller than the window and
     the last few could not be reached at all. It now stops at the height of
     the viewport and scrolls inside itself.
*/

/* A row that has more beneath it: the flyout hangs off the row. */
.s-mega__item--has-menu { position: relative; }

/* Second generation and beyond — beside the parent, not below it. */
.s-mega--nested {
  inset-block-start: calc(var(--s-2) * -1);
  inset-inline-start: 100%;
  border-block-start: 1px solid var(--s-gold-300);
  border-radius: var(--s-radius);
  translate: -4px 0;
}

.s-mega__item--has-menu:hover > .s-mega--nested,
.s-mega__item--has-menu:focus-within > .s-mega--nested {
  opacity: 1;
  visibility: visible;
  translate: 0 0;
}

/* Near the right-hand edge a flyout would run off the page, so from the third
   generation it opens back towards the middle. Cheap, and it costs nothing
   when there is room, because by then the panel is already far right. */
.s-mega--nested .s-mega--nested .s-mega--nested {
  inset-inline-start: auto;
  inset-inline-end: 100%;
  translate: 4px 0;
}
.s-mega__item--has-menu:hover > .s-mega--nested .s-mega--nested .s-mega--nested,
.s-mega__item--has-menu:focus-within > .s-mega--nested .s-mega--nested .s-mega--nested {
  translate: 0 0;
}

/* The row itself: the name on the left, the "there is more" mark on the right. */
.s-mega__list a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}

.s-mega__mark {
  flex: none;
  font-size: 0.85em;
  line-height: 1;
  color: var(--s-muted);
}

.s-mega__item--has-menu:hover > a .s-mega__mark,
.s-mega__item--has-menu:focus-within > a .s-mega__mark { color: var(--s-maroon-700); }

/* A heading with children but no page of its own. */
.s-mega__heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-2) var(--s-4);
  font-size: var(--s-text-sm);
  color: var(--s-ink-soft);
  white-space: nowrap;
  cursor: default;
}

/* "No products in this category" — said before the shopper clicks, rather than
   letting them arrive at an empty page and wonder whether the shop is broken. */
.s-mega__none {
  display: block;
  padding: 0 var(--s-4) var(--s-2);
  font-size: var(--s-text-xs, 0.75rem);
  font-style: italic;
  color: var(--s-muted);
  white-space: nowrap;
}

/* A top-level category that is empty has no list to hang a note in, so it gets
   a small panel of its own. */
.s-mega--note { min-inline-size: 0; padding: var(--s-3) 0; }
.s-mega--note .s-mega__none { padding-block: 0; }

/* --------------------------------------------------------------------------
   "More", when there is a lot of it
   -------------------------------------------------------------------------- */

/* The panel stops at the window and scrolls, so the last category is reachable
   however many the merchant has. `overscroll-behavior` keeps the wheel inside
   the panel instead of scrolling the page out from under the pointer once the
   list bottoms out. */
.s-catbar__item--more > .s-mega {
  max-block-size: min(70vh, 32rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* A flyout inside a scrolling panel would be clipped by that same scroll, so
   inside "More" the children open underneath their parent instead, indented.
   Less pretty than a flyout and it is the only version that can actually be
   used. */
.s-catbar__item--more .s-mega--nested {
  position: static;
  opacity: 1;
  visibility: visible;
  translate: none;
  border: 0;
  box-shadow: none;
  padding: 0;
  min-inline-size: 0;
  background: none;
}

.s-catbar__item--more .s-mega__list--nested a,
.s-catbar__item--more .s-mega__list--nested .s-mega__heading {
  padding-inline-start: var(--s-6);
  font-size: var(--s-text-xs, 0.75rem);
}

.s-catbar__item--more .s-mega__list--nested .s-mega__list--nested a {
  padding-inline-start: var(--s-8, 2rem);
}

/* Inside "More" the flyout is already open, so the mark would be pointing at
   nothing. */
.s-catbar__item--more .s-mega__mark { display: none; }

/* A scrolled panel needs a scrollbar somebody can see without it shouting. */
.s-catbar__item--more > .s-mega { scrollbar-width: thin; }
.s-catbar__item--more > .s-mega::-webkit-scrollbar { inline-size: 8px; }
.s-catbar__item--more > .s-mega::-webkit-scrollbar-thumb {
  background-color: var(--s-line);
  border-radius: 999px;
}
.s-catbar__item--more > .s-mega::-webkit-scrollbar-thumb:hover { background-color: var(--s-gold-300); }

/* A category on the bar that has more inside it says so, rather than leaving
   the shopper to find out by hovering. Pointing down, because that is where
   its panel opens; the rows inside that panel use "›" because theirs open
   sideways. An arrow that points the wrong way is worse than none. */
.s-catbar__mark {
  margin-inline-start: var(--s-1);
  font-size: 0.7em;
  line-height: 1;
  transition: rotate var(--s-fast) var(--s-ease);
}
.s-catbar__item--has-menu:hover > .s-catbar__link .s-catbar__mark,
.s-catbar__item--has-menu:focus-within > .s-catbar__link .s-catbar__mark { rotate: 180deg; }

/* A colour choice shows its colour.

   Two shades of blue written out are two identical buttons, and picking
   between them is exactly what the shopper came to this block to do. The dot
   carries the colour; the name stays beside it, because a swatch alone is
   unusable to anyone who cannot see it and is ambiguous to everyone else. */
.s-choice {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
}

.s-choice__dot {
  inline-size: 1rem;
  block-size: 1rem;
  border-radius: 50%;
  background-color: var(--s-choice-colour, transparent);
  /* A white or cream swatch on a white button would otherwise be invisible. */
  box-shadow: inset 0 0 0 1px rgb(29 23 22 / 0.22);
  flex: none;
}

.s-choice[aria-pressed="true"] .s-choice__dot,
.s-choice.is-on .s-choice__dot {
  box-shadow: inset 0 0 0 1px rgb(29 23 22 / 0.35), 0 0 0 2px var(--s-surface), 0 0 0 4px var(--s-maroon-700);
}

/* --------------------------------------------------------------------------
   The phone menu, with the whole tree in it
   --------------------------------------------------------------------------

   It listed top-level categories and stopped, so on a phone — which is how
   most of this shop is visited — a sub-category could not be reached from the
   menu at all. Each generation is a <details>, indented by its depth. */

.s-menu__branch > summary {
  list-style: none;
  cursor: pointer;
}
.s-menu__branch > summary::-webkit-details-marker { display: none; }

.s-menu__link {
  padding-inline-start: calc(var(--s-4) + (var(--s-menu-indent, 0) * var(--s-4)));
}

.s-menu__chev {
  transition: rotate var(--s-fast) var(--s-ease);
}
.s-menu__branch[open] > summary .s-menu__chev { rotate: 180deg; }

/* Its own page, offered separately — tapping the name opens the branch, so
   without this line the branch's own category becomes unreachable. */
.s-menu__link--all {
  font-weight: 600;
  color: var(--s-maroon-700);
}

.s-menu__none {
  font-size: var(--s-text-xs, 0.75rem);
  font-style: italic;
  color: var(--s-muted);
}

/* --------------------------------------------------------------------------
   The saved button on a touch screen

   The word swaps from "Saved" to "Remove" on hover, which is a fine idea with
   a mouse and a broken one under a finger. A tap leaves `:hover` stuck on the
   button, so it reads "Remove"; the next tap anywhere else clears the hover
   and it flips back to "Saved" on its own. The client filmed exactly that and
   wrote it down twice.

   Where there is no hover at all, there is no swap: a saved item offers
   "Remove", an unsaved one offers "Save", and the word only ever changes when
   the shopper changes the thing it describes.
   -------------------------------------------------------------------------- */

@media (hover: none) {
  .s-btn.is-on [data-wish-label] { display: none; }
  .s-btn.is-on .s-wish-hover { display: inline; }

  /* And nothing may swap it back when the finger moves elsewhere. */
  .s-btn.is-on:hover [data-wish-label],
  .s-btn.is-on:focus-visible [data-wish-label] { display: none; }
  .s-btn.is-on:hover .s-wish-hover,
  .s-btn.is-on:focus-visible .s-wish-hover { display: inline; }
}

/* The quick view's other pictures.

   A product's second and third photographs were only on the full page, and the
   card is what a shopper taps from the shelf — so a merchant who uploaded four
   pictures saw one and concluded the upload had failed. The strip is drawn only
   when there is more than one. */
.s-quick__thumbs {
  display: flex;
  gap: var(--s-2);
  margin: var(--s-3) 0 0;
  padding: 0;
  list-style: none;
  overflow-x: auto;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

.s-quick__thumb {
  display: block;
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--s-radius-sm, 6px);
  background: none;
  cursor: pointer;
  line-height: 0;
  /* A finger needs something to land on; 72px is comfortably over the mark. */
  inline-size: 72px;
  block-size: 72px;
  overflow: hidden;
}

.s-quick__thumb img { inline-size: 100%; block-size: 100%; object-fit: cover; }
.s-quick__thumb.is-on { border-color: var(--s-maroon-700); }
.s-quick__thumb:focus-visible { outline: 2px solid var(--s-gold-500); outline-offset: 2px; }

/* The category's own picture, beside its name in the menu.

   The client asked for the uploaded picture to appear wherever the category
   does. A round 28px thumbnail is enough to recognise a collection by; larger
   would turn a menu into a catalogue. */
.s-mega__pic,
.s-menu__pic {
  inline-size: 28px;
  block-size: 28px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
  /* A pale photograph on a pale panel needs an edge to sit on. */
  box-shadow: inset 0 0 0 1px rgb(29 23 22 / 0.12);
}

.s-mega__list a { gap: var(--s-2); }

.s-menu__name {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-inline-size: 0;
}

/* "No products in this category", under the name it is about.

   Placed at row level it became a second column and squeezed a two-line
   category name into half the width; making the row wrap instead pushed the
   chevron onto a line of its own. It belongs inside the name, which is what it
   describes, so the row stays two columns and the note sits under the words.
*/
.s-menu__label { display: block; min-inline-size: 0; }

/* Spacing only. Whether it is SHOWN is decided further down, by hover on a
   desktop and by the first tap on a phone — a `display` here would be more
   specific than those rules and would win over both. */
.s-menu__label .s-menu__none { margin-block-start: 2px; }

/* --------------------------------------------------------------------------
   "No products in this category" — said when asked, not shouted

   Standing permanently under a category name it is clutter: a menu of ten
   collections carrying a sentence about the one that is empty. The client asked
   for it on hover on a desktop and on a tap on a phone, which is exactly when
   the shopper is deciding whether to go there.
   -------------------------------------------------------------------------- */

.s-menu__none,
.s-mega__none { display: none; }

/* Desktop: while the pointer or the keyboard is on that row. */
.s-menu__link:hover .s-menu__label .s-menu__none,
.s-menu__link:focus-visible .s-menu__label .s-menu__none,
.s-mega__list li:hover > .s-mega__none,
.s-mega__list li:focus-within > .s-mega__none { display: block; }

/* A top-level empty category on the bar has a panel of its own; that panel
   only exists while the row is hovered, so its note is always wanted. */
.s-mega--note .s-mega__none { display: block; }

/* Phone: revealed by the first tap. `shop.js` adds this and holds the
   navigation back once, so the shopper is told before they travel rather than
   after they arrive. */
.s-menu__link.is-telling .s-menu__label .s-menu__none { display: block; }

/* ==========================================================================
   Offers, listed where the bag can see them
   ==========================================================================

   A discount box a shopper must already know a code to use is a discount most
   shoppers never get. These are the offers the shop is running: the ones this
   bag can use are a tap away, and the ones it cannot are greyed out with the
   reason — "spend ₹260 more and delivery is free" is the most useful sentence
   a bag page can carry, and hiding it would be hiding the sale.
*/

.s-offers { margin-block-start: var(--s-4); }

.s-offers__title {
  margin: 0 0 var(--s-2);
  font-size: var(--s-text-sm);
  font-weight: 600;
  color: var(--s-ink);
}

.s-offers__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--s-2);
}

.s-offer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-3);
  border: 1px dashed var(--s-gold-500);
  border-radius: var(--s-radius);
  background-color: var(--s-gold-100);
}

/* Not gone — just plainly not available yet. */
.s-offer.is-out {
  border-style: solid;
  border-color: var(--s-line);
  background-color: var(--s-surface);
  opacity: 0.72;
}

.s-offer.is-on {
  border-style: solid;
  border-color: var(--s-maroon-700);
  background-color: var(--s-maroon-100);
}

.s-offer__main { min-inline-size: 0; }

.s-offer__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-2);
  margin: 0;
}

.s-offer__code {
  font-family: var(--s-font-mono, ui-monospace, monospace);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--s-maroon-700);
}

.s-offer__saving { font-size: var(--s-text-sm); font-weight: 600; color: var(--s-ink); }

.s-offer__flag {
  margin: var(--s-1) 0 0;
  font-size: var(--s-text-xs, 0.75rem);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--s-gold-700);
}

.s-offer__note {
  margin: var(--s-1) 0 0;
  font-size: var(--s-text-xs, 0.75rem);
  color: var(--s-muted);
}

/* The useful half of a refusal: how much more, not just "no". */
.s-offer__why {
  margin: var(--s-1) 0 0;
  font-size: var(--s-text-xs, 0.75rem);
  color: var(--s-ink-soft);
}

.s-offer__action { flex: none; }

.s-offer__locked,
.s-offer__applied {
  font-size: var(--s-text-xs, 0.75rem);
  font-weight: 600;
  color: var(--s-muted);
  white-space: nowrap;
}

/* On the bag — the word the client asked for on 15 September 2026, set in the
   same capitals and weight as the Apply button it stands in for. */
.s-offer__applied {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--s-maroon-700);
}

@media (max-width: 26rem) {
  .s-offer { flex-wrap: wrap; }
  .s-offer__action { inline-size: 100%; }
}

/* ==========================================================================
   The category bar is one row from the very first frame
   ==========================================================================

   Measured, not guessed: before the fold script runs, ten long category names
   wrapped to THREE rows and the bar stood 134px tall; a moment later the script
   folded the overflow into "More" and it snapped back to 46px. So every visitor
   watched the header shrink by ninety pixels as the page settled, and the
   client's requirement was exactly that the header must not grow.

   The bar is now `nowrap` from the start and clips what has not been folded
   yet, so the height is 46px on the first paint and never changes. The clip is
   removed the instant the script marks itself done — it must be, because
   `overflow` on this element eats the dropdown whole (PROJECT_HANDOFF.md §5,
   met three times now).

   With scripts blocked there is no fold, so the `<noscript>` rule in the layout
   puts wrapping back: two or three tidy rows with every link present and
   crawlable, which is the right answer when nothing can fold anything.
*/
@media (min-width: 62rem) {
  .s-catbar__list:not([data-catbar-ready]) {
    flex-wrap: nowrap;
    /* Only while the fold is pending. A dropdown cannot be open yet: opening
       one needs a hover, and a hover needs a person, and by then this is gone. */
    overflow: hidden;
  }

  /* Once folded, nothing overflows — and nothing may clip the mega menu. */
  .s-catbar__list[data-catbar-ready] { overflow: visible; }
}

/* --------------------------------------------------------------------------
   A category page, with the category's picture
   --------------------------------------------------------------------------

   The client asked for a category's photograph to be shown "wherever the
   category is shown". It already was — in the menu, in the rail, in the share
   card — everywhere except the one page actually about it.

   `.s-head` is `justify-content: space-between`, which is right when the row
   holds a heading and a control on the far end and wrong the moment a picture
   joins the heading: the two would fly apart. Hence a modifier rather than
   `:has()`, so the rule is exact and no old browser has to support anything.
   -------------------------------------------------------------------------- */
.s-head--pic { justify-content: flex-start; align-items: center; }

/* The selector is long on purpose. `.s-head:not(.s-head--centre) > :first-child`
   hands the first child `flex: 1 1 14rem` — written when the first child was
   always the heading block, and it now catches the picture, which stretched a
   circular 72px thumbnail into an 834px ellipse. Measured on the page, not
   guessed. Beating it needs three classes, so three classes it is. */
.s-head:not(.s-head--centre) > .s-head__pic {
  flex: none;
  inline-size: 3.5rem;
  block-size: 3.5rem;
  border-radius: 50%;
  object-fit: cover;
  /* The client's photographs are 500×500 on a pale ground; a ring gives the
     circle an edge without a border that would read as a frame.

     The ring was asked for as `--s-gold-200`, and the gold scale has no 200 —
     it runs 700, 600, 500, 400, 300, 100. An undefined custom property takes
     its whole declaration down with it, so this photograph had neither the ring
     nor the shadow beneath it: measured on a category page as `box-shadow:
     none`, not guessed. 300 is the pale gold either side of where 200 would
     have sat. */
  box-shadow: 0 0 0 1px var(--s-gold-300), 0 2px 8px rgb(0 0 0 / 0.06);
}

@media (min-width: 48rem) {
  .s-head:not(.s-head--centre) > .s-head__pic { inline-size: 4.5rem; block-size: 4.5rem; }
}


/* --------------------------------------------------------------------------
   A row that is all actions
   --------------------------------------------------------------------------

   `.s-line__foot` is `space-between`, which is right in the bag — the name on
   the left and the price on the right — and wrong on the addresses screen,
   where every child is a link. With three actions it looked deliberate; with
   two, "Update" and "Remove" sat pinned to opposite edges of the card with a
   200px hole between them. Seen on a phone, which is where the client is
   looking.

   Grouped at the start with a real gap, so they read as a set of things to do
   to this address rather than as two unrelated corners.
   -------------------------------------------------------------------------- */
.s-line__foot--actions {
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-5);
}

/* ---------------------------------------------------------------------------
   "Add to home screen" — a slim bar across the very top

   The offer has moved several times: a gold button at the foot of the footer, a
   bar under the header, then an "Install App" button in the header itself. On
   15 September 2026 the client sent a picture of what they wanted instead: a
   thin bar above everything — the shop's icon, the words, an "Add" button in
   the same gold the Install App button wore, and an × to close it.

   A soft gold ground with maroon words, not maroon: it sits directly above the
   maroon announcement bar, and the same colour twice read as one heavy block
   (seen on 15 September 2026). Both colours are the shop's own tokens, so
   nothing new comes onto the page, and the gold edge under it separates the two
   bars. One line on a phone; the words give way (a second line, on the
   narrowest screens) before the Add button or the × shrink.

   Never `hidden` in the markup — shown on every visit until the shop is
   installed (the client's request of 12 September 2026). Taken away by
   `pwa.js` once the shop is the installed app; closed by its × for the rest of
   the visit, through `s-appbar-closed` on `<html>`, which the layout's head sets
   before the first paint.
--------------------------------------------------------------------------- */

.s-appbar {
  background-color: var(--s-gold-100);
  border-block-end: 1px solid var(--s-sand-warm);
  color: var(--s-maroon-800);
}

.s-appbar__inner {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-block-size: 2.75rem;
  padding-block: 0.25rem;
}

.s-appbar__icon {
  flex: none;
  inline-size: 1.75rem;
  block-size: 1.75rem;
  border-radius: 0.375rem;
  object-fit: cover;
  background-color: var(--s-ivory);
}

.s-appbar__text {
  flex: 1 1 auto;
  min-inline-size: 0;
  margin: 0;
  font-size: var(--s-text-xs);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: var(--s-tracking-wide);
  text-transform: uppercase;
}

.s-appbar__add {
  flex: none;
  min-block-size: 2rem;
}

.s-appbar__close {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  /* Pulled out by the space around the × inside its 44px circle, so the ×
     itself ends on the page's edge line — the same line the search box and
     the pictures end on. */
  margin-inline-end: -0.875rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: inherit;
  cursor: pointer;
}

.s-appbar__close svg { inline-size: 1rem; block-size: 1rem; }
.s-appbar__close:hover { background-color: rgb(71 0 2 / 0.08); }
.s-appbar__close:focus-visible { outline: 2px solid var(--s-maroon-700); outline-offset: -4px; }

/* A 375px phone: the words on one line, as in the client's picture. The
   button gives up some of its side padding too, so a longer word typed into
   "What the button says" still leaves the words room on a 320px phone. */
@media (max-width: 30rem) {
  .s-appbar__inner { gap: var(--s-2); }
  .s-appbar__text { font-size: 0.6875rem; letter-spacing: 0.03em; }
  .s-appbar__add { padding-inline: var(--s-3); letter-spacing: 0.02em; }
}

.s-appbar-closed .s-appbar { display: none; }

/* ---------------------------------------------------------------------------
   One left edge and one right edge down a phone's page

   Measured on a 370px phone on 15 September 2026: the bar's icon, the search
   box and the pictures start 16px from the left, but the three lines of the
   menu button started at 27px, and the bag icon ended 27px from the right —
   because each sits in the middle of a 44px tap circle. The client saw the
   left side as not lined up.

   The circles keep their full 44px (a thumb still has the same target); they
   are only moved out by the 11px of space around the icon inside them, so the
   icon itself sits on the page's 16px edge. Phones and tablets only: from
   62rem up there is no menu button and the header is laid out differently.

   The category circles are left as they are on purpose: each name is wider
   than its circle, so moving the circles to the edge would push the names
   past it.
--------------------------------------------------------------------------- */

@media (max-width: 61.99rem) {
  .s-header__burger { margin-inline-start: -0.6875rem; }
}

/* The account, saved and bag icons: the bag ends on the right edge at every
   width (it ended 11px inside it on a 1440px screen too). */
.s-header__actions { margin-inline-end: -0.6875rem; }

/* The category bar is NOT moved out to put its words on the edge. That was
   tried on 15 September 2026 and the client rejected it at once: the
   highlighted "Home" then reached 16px past the logo's edge. Each link is a
   pill, and the pills — the highlighted one included — start on the left
   edge and end on the right edge, with the words inside them. */

/* A one-sentence announcement that needs two lines on a phone splits into two
   even lines, instead of leaving its last word ("Trust") alone on the second. */
.s-announce p { text-wrap: balance; }

/* Opened from the home screen, the offer has nothing left to offer — hidden in
   CSS too, so it does not paint for a moment before `pwa.js` runs. */
@media (display-mode: standalone) {
  .s-appbar { display: none; }
}

/* ---------------------------------------------------------------------------
   The quick view's × sits on the card, not in the corner of the browser

   The close button was a child of the overlay — which is the whole window — so
   on a desktop it floated in the far top-right of the screen while the card it
   closes sat in the middle. The client circled the gap on 12 September 2026.

   It cannot live inside the panel: `shop.js` fills that with `innerHTML` and
   would throw the button away every time the quick view opens. So both share a
   frame the exact size of the panel, and the × pins to the frame.
--------------------------------------------------------------------------- */

.s-modal__frame {
  position: relative;
  pointer-events: none;     /* the overlay behind it still closes on a click */
  inline-size: 100%;
  max-inline-size: 56rem;
}

/* The panel already carries its own max width; inside the frame it fills it. */
.s-modal__frame .s-modal__panel { max-inline-size: none; }

/* Pinned to the panel's own top-right, and tucked just inside it so the circle
   never hangs off the card on a narrow phone. */
.s-modal__frame .s-modal__close {
  inset-block-start: var(--s-3);
  inset-inline-end: var(--s-3);
}

/* ---------------------------------------------------------------------------
   The checkout's coupon area

   It used to list every code the shop runs, above the total, on the one screen
   a shopper reaches with their mind made up — and the box below said "Apply"
   whether or not a code was already on, with the way to take one off as a small
   link underneath the thing it undid. All three were on the client's list of
   12 September 2026.

   Now: a line pointing at the offers page, the code that IS on with its own
   Remove above the box, and the box below only ever for typing a new one.
--------------------------------------------------------------------------- */

.s-coupon__seeall { margin-block: var(--s-3) 0; }

.s-coupon__on {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
  margin-block-start: var(--s-3);
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--s-sand-warm);
  border-radius: var(--s-radius);
  background-color: var(--s-gold-100);
}

/* Stored, but not taking anything off yet — the bag is under its minimum, or
   the code has stopped working since it was typed. Gold says "you are saving
   money"; this one must not, because the total has not moved. */
.s-coupon__on--waiting {
  border-color: var(--s-line, #e5e0d8);
  background-color: transparent;
}

.s-coupon__on--waiting .s-coupon__onnote { color: var(--s-ink-500, #6b6257); }

.s-coupon__oncode {
  font-family: var(--s-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--s-maroon-800);
}

.s-coupon__onnote {
  flex: 1 1 auto;
  font-size: var(--s-text-sm);
  color: var(--s-gold-700);
}
