/* OneSunny — clean and natural.
 *
 * The brief (Jacelyn, 2026-09-06) was two words: clean, and natural. What that
 * means here, so the next person adding a page keeps it:
 *
 *   CLEAN    — one ground colour, one accent, no shadows, no gradients, no
 *              borders where whitespace will do. Every product photo already
 *              has a white studio background, so the page stays out of its way.
 *   NATURAL  — a warm paper ground rather than white, a sage green rather than
 *              a brand blue, and a serif for headings. The goods are roots,
 *              flowers, berries and dried fruit; the palette is taken from them.
 *
 * ⛔ NO WEBFONTS, ON PURPOSE. The type is a system stack, so there is no
 * external request, nothing to consent to, and no flash of unstyled text on a
 * Singapore mobile connection. If a brand face is ever bought, self-host it —
 * do not add a Google Fonts tag.
 */

:root {
  /* Ground and ink */
  /* ⭐ EVERY COLOUR BELOW IS TAKEN FROM ONE SUNNY'S OWN LOGO, not chosen to
     look natural and hoped to match. The mark was sampled pixel by pixel
     (bin/ has no script for it — it was a one-off count): 54.5% of its ink is
     #106838, a deep forest green, with #40A840 on the sun rays. The palette
     that preceded this was a muted olive sage, invented before the logo was
     available, and it simply was not the brand's green.

     ⛔ THE GROUND IS TINTED, NOT WHITE. Against 426 photographs shot on white
     studio backgrounds a white page reads as one continuous blank sheet with
     nothing for the cards to sit on. Cards stay pure white; the page does not.

     Contrast was checked, not assumed — every pairing used for text clears
     WCAG AA on its own ground: primary on white 6.86:1, primary on paper
     6.41:1, sale green on white 5.15:1, ink on paper 15.06:1, body 7.28:1,
     white on the footer 12.60:1. If you change one, re-check it. */
  /* Deepened from #F5F8F4 on 2026-09-06. At a whisper away from white the
     ground did no work: white cards sat on it invisibly and the page read as
     one flat sheet. This is still quiet, but a card now looks like a card. */
  --paper:   #F2F6EC;   /* page — the logo green, warmed and brought up */
  --card:    #FFFFFF;   /* product cards, so photos sit on true white */
  --ink:     #16241B;   /* headings — near-black with the brand's green in it */
  --body:    #47533F;   /* body copy */
  --muted:   #68745E;   /* captions, meta — dark enough to still be readable */
  --rule:    #DFE7D3;   /* hairlines */

  /* The brand greens themselves */
  --leaf:       #106838;  /* THE logo green — buttons, links, the promo bar */
  --leaf-dk:    #0B4F2A;  /* hover */
  --leaf-bright:#2A7D33;  /* the sun-ray green, darkened to carry text: prices */
  --leaf-pale:  #DCEBCF;  /* the hero panel — the richest green on a light ground */
  /* ☀️ THE ONE WARM COLOUR ON THE SITE, AND IT IS USED IN EXACTLY ONE PLACE.
     Jacelyn, 2026-09-06: the reviews band "look dark", and she wants the site to
     feel warm — "like what the name suggested one sunny" (一米阳光, a metre of
     sunshine). A first pass warmed every ground on the page and she narrowed it:
     "as in change just the review section". So the palette above is untouched
     and this single yellow-leaning green carries the warmth, which also keeps
     the band distinct from the two cooler --leaf-pale bands above it.
     ⛔ Warm here means yellow-green, NOT brown. Brown was tried and rejected. */
  --leaf-soft:  #E7F0D8;  /* the reviews band, and nothing else */

  /* ☀️ THE ONE WARM ACCENT, AND IT IS DECORATIVE ONLY. "One Sunny" is 一米阳光,
     a metre of sunshine, and a page of greens and white had nothing sunny in
     it. This is used for the star glyphs and nothing else.
     ⛔ NEVER FOR TEXT. Measured 2.22:1 on white — it fails AA outright, which
     is fine for a star sitting beside an aria-label and unacceptable for a
     price. Sale prices stay --leaf-bright, which clears 5.15:1. */
  --sun:        #E3A21B;
  --leaf-deep:  #0C3B24;  /* footer */

  /* ⛔ NO BROWN, AND NO AMBER EITHER. A kraft/bark pair was added on 2026-09-06
     and removed the same day — "the brown is ugly, just remove". The orange
     --amber that used to mark sale prices went with it: it was the same warm
     family and the logo contains nothing like it. Sale prices are the sun-ray
     green now. --sand-deep survives only as a warm-neutral HAIRLINE. */
  --sand-deep: #DAE4CB;  /* border-only neutral, never a fill */

  --max:     1180px;
  --gutter:  clamp(1.25rem, 4vw, 3rem);

  --serif: Georgia, 'Iowan Old Style', 'Times New Roman', serif;
  --sans:  system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--body);
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }

a { color: var(--leaf); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--leaf-dk); }

h1, h2, h3 {
  font-family: var(--serif);
  font-weight: 400;          /* the serif carries the weight; bold looks heavy */
  color: var(--ink);
  line-height: 1.2;
  margin: 0 0 0.5em;
  letter-spacing: -0.005em;
}
h1 { font-size: clamp(2rem, 4.5vw, 3rem); }
h2 { font-size: clamp(1.5rem, 3vw, 2.125rem); }
h3 { font-size: 1.125rem; }

.wrap { max-width: var(--max); margin-inline: auto; padding-inline: var(--gutter); }
.section { padding-block: clamp(3rem, 7vw, 5.5rem); }
.section--tight { padding-block: clamp(2rem, 4vw, 3rem); }

/* A small tracked label above a heading. Used once per section, never twice. */
.eyebrow {
  margin: 0 0 0.75rem;
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--leaf);
}

.lede { font-size: 1.0625rem; max-width: 46ch; }

/* Reasons-to-buy under the hero lede. A list, not a paragraph: a shopper scans
   these, and three short lines are read where three clauses are not. */
.ticks { list-style: none; margin: 1.25rem 0 0; padding: 0; display: grid; gap: 0.45rem; max-width: 46ch; }
.ticks li { position: relative; padding-left: 1.5rem; font-size: 0.9375rem; }
.ticks li::before {
  content: ""; position: absolute; left: 0; top: 0.55em;
  width: 0.5rem; height: 0.5rem; border-radius: 50%;
  background: var(--leaf);
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ------------------------------------------------------------- buttons --- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 2.75rem;             /* comfortable touch target */
  padding: 0.7rem 1.5rem;
  border: 1px solid var(--leaf);
  border-radius: var(--r-pill);
  background: var(--leaf);
  color: #fff;
  font: inherit;
  font-size: 0.9375rem;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.2s var(--ease), border-color 0.2s var(--ease);
}
.btn:hover { background: var(--leaf-dk); border-color: var(--leaf-dk); color: #fff; }
.btn--ghost { background: transparent; color: var(--leaf); }
.btn--ghost:hover { background: var(--leaf); color: #fff; }
.btn--wide { width: 100%; }
.btn[disabled] { opacity: 0.45; cursor: not-allowed; }

/* ------------------------------------------------------------- header ---- */

.masthead {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: clamp(1rem, 3vw, 2.5rem);
  padding: 1rem var(--gutter);
  background: color-mix(in srgb, var(--paper) 92%, transparent);
  backdrop-filter: saturate(140%) blur(8px);
  border-bottom: 1px solid transparent;
  transition: border-color 0.3s var(--ease);
}
.masthead.is-stuck { border-bottom-color: var(--rule); }

.brand {
  flex: 1 1 0;                     /* zero basis, matched by .masthead__actions,
                                      so the nav between them is truly centred */
  min-width: 0;
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}
/* Height-driven with width:auto, so the lockup keeps its own proportions and
   can never be stretched by a layout change. The logo is a STACKED lockup —
   mark above wordmark — so it needs more height than a horizontal one would;
   2.75rem is the smallest at which "One Sunny" is still legible. */
.brand__logo { display: block; height: 3.25rem; width: auto; }
.footer__logo { display: block; height: 3.25rem; width: auto; margin-bottom: 0.35rem; }

.nav { display: flex; gap: clamp(1rem, 2.2vw, 1.75rem); font-size: 0.9375rem; }
.nav a { color: var(--body); text-decoration: none; white-space: nowrap; }
.nav a:hover, .nav a[aria-current] { color: var(--ink); }

.masthead__actions {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
}
.iconlink {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem;
  width: 2.75rem; height: 2.75rem;
  color: var(--body); text-decoration: none;
  position: relative;
}
.iconlink:hover { color: var(--ink); }
.iconlink svg {
  width: 1.25rem; height: 1.25rem; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
}
.iconlink__label { display: none; }
.cart-count {
  position: absolute; top: 0.3rem; right: 0.15rem;
  min-width: 1.15rem; height: 1.15rem;
  display: grid; place-items: center;
  padding-inline: 0.3rem;
  border-radius: 999px;
  background: var(--leaf); color: #fff;
  font-size: 0.6875rem; line-height: 1;
}

.nav-toggle { display: none; }

/* --------------------------------------------------------------- hero ---- */

.hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: clamp(1.5rem, 4vw, 3.5rem);
}
.hero__copy { padding-block: clamp(2.5rem, 6vw, 5rem); }

/* A block of sage behind the hero photograph, bleeding off the right edge of
   the screen. This is where most of the "more green" lives: a tint alone still
   read as a white page, so the top of the site now has one solid area of colour
   with the product sitting in it.

   ⛔ The bleed is measured from --gutter and --max, NOT written as
   calc(50% - 50vw). This is a grid ITEM, so a percentage margin resolves
   against its own grid area rather than the container, and the 50%-50vw form
   overruns the screen edge — invisible behind overflow clipping, but it throws
   off anything centred inside. */
.hero__shot {
  position: relative;
  align-self: stretch;
  display: grid;
  place-items: center;
  background: var(--leaf-pale);
  margin-right: calc(-1 * (var(--gutter) + max(0px, (100vw - var(--max)) / 2)));
  padding: clamp(1.5rem, 3.5vw, 3rem) clamp(1.25rem, 3vw, 2.5rem);
  /* Rounded on the left only: the right edge runs off the screen, so a radius
     there would curve away from an edge nobody can see and leave a sliver of
     page showing through. */
  border-radius: var(--r-lg) 0 0 var(--r-lg);
}
.hero__shot img {
  width: 100%; max-width: 26rem; height: auto;
  border-radius: var(--r);
  background: var(--card);
}
.hero__note {
  margin: 0.75rem 0 0;
  text-align: center;
  font-size: 0.8125rem;
  color: var(--muted);
}

/* --------------------------------------------------------- product grid -- */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: clamp(1rem, 2.5vw, 1.75rem);
}

.card { display: flex; flex-direction: column; text-decoration: none; }
.card__shot {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  overflow: hidden;
  aspect-ratio: 1 / 1;             /* Shopee photos are square; this stops the
                                      grid jumping while they load */
}
.card__shot img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 0.4s var(--ease);
}
.card__shot {
  transition: box-shadow 0.3s var(--ease), border-color 0.3s var(--ease);
}
.card:hover .card__shot img { transform: scale(1.03); }
.card:hover .card__shot {
  border-color: var(--sand-deep);
  box-shadow: 0 6px 18px -8px rgba(45, 55, 38, 0.26);
}
.card__name {
  margin: 0.75rem 0 0.25rem;
  font-family: var(--sans);
  font-size: 0.9375rem;
  line-height: 1.45;
  color: var(--ink);
  /* Titles vary from three words to a full line; clamping keeps every card
     the same height without truncating so hard that the weight is lost. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.card__meta { font-size: 0.8125rem; color: var(--muted); margin-bottom: 0.35rem; }
.card__price { font-size: 0.9375rem; color: var(--ink); }
.card__was { color: var(--muted); text-decoration: line-through; margin-left: 0.4rem; font-size: 0.8125rem; }
.card__price--offer { color: var(--leaf-bright); }

/* -------------------------------------------------------------- bands ---- */

.band { background: var(--card); border-block: 1px solid var(--rule); }
/* .band--leaf carried --leaf-pale, the cool green, and was used once — on the
   "Why choose One Sunny" section directly above the reviews band. Two pale
   greens back to back, one cool and one warm, fought each other (Jacelyn,
   2026-09-06: "it doesnt gel well with review section"). That section now sits
   on the page's own ground and its white cards carry it, which leaves the
   reviews band as the single block of colour before the footer.
   Kept as a class because the hero panel and any future band may want it. */
.band--leaf { background: var(--leaf-pale); border-block: 0; }

/* Pure white, for a section whose own cards are white. On this site that is a
   deliberate near-invisible pairing: the cards are told apart from the ground
   by their shadow alone, which is exactly the effect the Jin Chun reference
   uses. If the cards ever lose that shadow, this ground has to change too or
   they disappear. */
.band--white { background: var(--card); }

/* A photographic ground for the why-choose band, from One Sunny's own cover
   art — chrysanthemum and wolfberry brewing in glass.
   
   ⛔ THE WASH IS APPLIED TWICE, ON PURPOSE. The JPEG is already blended 55%
   toward white and desaturated, and a further white layer sits over it here.
   Belt and braces because this band carries white cards with body copy on them:
   if the gradient is ever dropped from this rule the file underneath is still
   pale enough to read against, rather than suddenly putting dark text over a
   photograph.
   
   background-attachment stays scroll, not fixed: a fixed background judders on
   iOS and is repainted on every scroll frame on Android. */
.band--photo {
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.42), rgba(255, 255, 255, 0.42)),
    url('https://onesunny-assets.sgp1.cdn.digitaloceanspaces.com/site/why-bg.jpg');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
/* Kept as an alias so the markup did not have to be edited back: the record
   band asks for --sand and now gets the page's own paper, which reads as a
   quiet step between two green blocks rather than a third colour. */
.band--sand { background: var(--paper); border-block: 1px solid var(--rule); }

.pills { display: flex; flex-wrap: wrap; gap: 0.5rem; list-style: none; margin: 0; padding: 0; }
.pill {
  display: inline-block;
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--sand-deep);
  border-radius: var(--r-pill);
  background: var(--card);
  color: var(--body);
  font-size: 0.875rem;
  text-decoration: none;
}
.pill:hover, .pill[aria-current] {
  border-color: var(--leaf); color: var(--leaf); background: var(--leaf-pale);
}

/* ------------------------------------------------------------- footer ---- */

/* ⛔ NO margin-top. It carried clamp(3rem, 6vw, 5rem) until 2026-09-06, which
   showed as a band of bare page between the reviews section and the footer —
   invisible while every section ended on the page's own ground, obvious the
   moment the reviews band got a colour of its own. Every page already ends with
   something that has its own bottom padding (.section, .pdp, or an explicit
   padding-bottom on the last wrap), so the space was doubling up rather than
   providing anything. If a new page ever ends flush against the footer, give
   THAT page's last block padding rather than putting this back — a margin here
   reopens the gap under every coloured section on the site. */
.footer { background: var(--leaf-deep); color: #C6CDB8; }
.footer__grid {
  max-width: var(--max); margin-inline: auto; padding: clamp(2.5rem, 5vw, 4rem) var(--gutter);
  display: grid; grid-template-columns: 1.6fr repeat(3, 1fr); gap: 2rem;
}
.footer a { color: #C3C8BA; text-decoration: none; }
.footer a:hover { color: #fff; }
.footer h2 { font-family: var(--sans); font-size: 0.75rem; letter-spacing: 0.16em;
             text-transform: uppercase; color: #fff; margin-bottom: 0.9rem; }
.footer__col { display: flex; flex-direction: column; gap: 0.55rem; align-items: flex-start; }
.footer__legal {
  max-width: var(--max); margin-inline: auto;
  padding: 1.25rem var(--gutter) 2rem;
  border-top: 1px solid #414737;
  font-size: 0.8125rem; color: #8E9484;
}

/* ------------------------------------------------------------ responsive -- */

@media (max-width: 900px) {
  .hero { grid-template-columns: 1fr; }
  .hero__copy { padding-bottom: 0; }
  /* Stacked, the panel bleeds BOTH ways so it spans the phone edge to edge —
     a colour block that stops short of one side looks like a mistake. */
  .hero__shot {
    margin-inline: calc(-1 * var(--gutter));
    padding-inline: var(--gutter);
    border-radius: 0;          /* full-bleed on a phone: nothing to round */
  }
  .footer__grid { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 640px) {
  .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; }
  .footer__grid { grid-template-columns: 1fr; }

  /* Nav collapses behind the hamburger. Gated on [data-nav], which site.js
     sets — with JS off the button never appears and the nav stays a visible
     row, so no link is ever trapped behind a control that cannot open. */
  .masthead[data-nav] .nav-toggle {
    order: 5;
    display: inline-flex; align-items: center; justify-content: center;
    width: 2.75rem; height: 2.75rem;
    padding: 0; border: 0; background: none; color: var(--ink); cursor: pointer;
  }
  .masthead[data-nav] .nav-toggle svg {
    width: 1.375rem; height: 1.375rem;
    fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round;
  }
  .masthead[data-nav] .nav-toggle__close,
  .masthead[data-nav] .nav-toggle[aria-expanded="true"] .nav-toggle__open { display: none; }
  .masthead[data-nav] .nav-toggle[aria-expanded="true"] .nav-toggle__close { display: block; }

  .masthead[data-nav] .nav {
    display: none;
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; gap: 0;
    padding: 0.25rem var(--gutter) 0.75rem;
    background: var(--paper);
    border-bottom: 1px solid var(--rule);
  }
  .masthead[data-nav] .nav[data-open] { display: flex; }
  .masthead[data-nav] .nav a { padding-block: 0.7rem; }
  .masthead[data-nav] .nav a + a { border-top: 1px solid var(--rule); }

  .nav { font-size: 0.9375rem; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
  .card:hover .card__shot img { transform: none; }
}

/* ----------------------------------------------------- product detail ---- */

.pdp {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  gap: clamp(1.5rem, 4vw, 3.5rem);
  align-items: start;
  padding-bottom: clamp(2.5rem, 5vw, 4rem);
}
.pdp__hero {
  width: 100%; height: auto;
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--r);
}
.pdp__thumbs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));
  gap: 0.5rem;
  margin-top: 0.6rem;
}
.pdp__thumbs img {
  width: 100%; aspect-ratio: 1/1; object-fit: cover;
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-sm);
}
.pdp__price { font-size: 1.5rem; color: var(--ink); margin: 0 0 1.5rem; }
.pdp__form { display: grid; gap: 1rem; max-width: 22rem; }
.pdp__field { display: grid; gap: 0.35rem; font-size: 0.875rem; color: var(--body); }
.pdp__field select, .pdp__field input {
  font: inherit; font-size: 1rem; color: var(--ink);
  padding: 0.7rem 0.8rem;
  background: var(--card);
  border: 1px solid var(--rule); border-radius: var(--r-sm);
  appearance: none; -webkit-appearance: none;
}
.pdp__field select:focus, .pdp__field input:focus { border-color: var(--leaf); outline-offset: 1px; }
.pdp__field--qty { max-width: 7rem; }

.specs { margin: 2.5rem 0 0; display: grid; grid-template-columns: 8.5rem 1fr; gap: 0.5rem 1rem; font-size: 0.9375rem; }
.specs dt { color: var(--muted); }
.specs dd { margin: 0; color: var(--body); }

/* The written description, the questions, and the line that says One Sunny
   makes no health claim. Held to a reading measure rather than the full page
   width - this is the only long-form prose on the site, and a line of 100
   characters is where people stop reading it. */
.pdp__copy {
  padding-bottom: clamp(2.5rem, 5vw, 4rem);
  border-top: 1px solid var(--rule);
  padding-top: clamp(2rem, 4vw, 3rem);
}
.pdp__copy section { max-width: 44rem; margin: 0 0 2rem; }
.pdp__copy h2 { font-size: 1.125rem; color: var(--ink); margin: 0 0 0.6rem; }
.pdp__copy p { color: var(--body); line-height: 1.7; margin: 0 0 0.75rem; }
.pdp__aka { font-size: 0.9375rem; color: var(--muted); }
.pdp__aka strong { color: var(--body); font-weight: 600; }
.pdp__qa { margin: 0 0 1.25rem; }
.pdp__qa h3 { font-size: 1rem; color: var(--ink); margin: 0 0 0.3rem; font-weight: 600; }
.pdp__qa p { margin: 0; }
.pdp__note {
  max-width: 44rem; margin: 2rem 0 0;
  font-size: 0.8125rem; color: var(--muted); line-height: 1.6;
}

/* ------------------------------------------------------------ basket ---- */

.basket { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--rule); }
.basket__row {
  display: grid;
  grid-template-columns: 5rem 1fr 5rem 6rem;
  gap: 1rem;
  align-items: center;
  padding: 1.15rem 0;
  border-bottom: 1px solid var(--rule);
}
.basket__shot img {
  width: 100%; aspect-ratio: 1/1; object-fit: cover;
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-sm);
}
.basket__qty input {
  width: 100%; font: inherit; text-align: center;
  padding: 0.5rem; border: 1px solid var(--rule); border-radius: var(--r-sm); background: var(--card);
}
.basket__total { text-align: right; color: var(--ink); }
.basket__foot {
  display: flex; flex-wrap: wrap; gap: 1rem;
  align-items: flex-start; justify-content: space-between;
  padding-top: 1.5rem;
}
.basket__sum { margin: 0; text-align: right; font-size: 1.125rem; color: var(--ink); }

@media (max-width: 640px) {
  .pdp { grid-template-columns: 1fr; }
  /* The photo, then what it is, then the controls — quantity and line total
     share a row so a basket line is three rows, not four. */
  .basket__row { grid-template-columns: 4rem 1fr auto; gap: 0.75rem; }
  .basket__qty { grid-column: 2; }
  .basket__total { grid-column: 3; }
}

/* ---------------------------------------------------------- checkout ---- */

.checkout {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: start;
}
.checkout__form { display: grid; gap: 1rem; }
.checkout__sum {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  padding: 1.25rem 1.4rem;
  position: sticky; top: 5.5rem;
}
.err { display: block; color: #A6402C; font-size: 0.8125rem; font-style: normal; }

/* Used for the "payment not connected" state. Amber rather than red: it is a
   statement of fact about the shop's setup, not an error the shopper caused. */
.notice {
  margin: 0;
  padding: 0.9rem 1.1rem;
  background: var(--card);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--ink);
  border-radius: var(--r-sm);
  font-size: 0.9375rem;
}
.notice--bad { background: #FBEEEA; border-left-color: #A6402C; }

@media (max-width: 800px) {
  .checkout { grid-template-columns: 1fr; }
  .checkout__sum { position: static; order: -1; }
}

/* ------------------------------------------------------ track record ---- */

.record {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: 1.25rem 2rem;
  text-align: center;
}
.record__item { display: grid; gap: 0.15rem; }
.record__item strong {
  font-family: var(--serif);
  font-size: clamp(1.5rem, 3vw, 2rem);
  font-weight: 400;
  color: var(--leaf-dk);
  line-height: 1.1;
}
.record__item span { font-size: 0.875rem; color: var(--body); }

@media (max-width: 560px) {
  /* Two up rather than one, so the band stays a band and does not become a
     column of three headlines taller than the hero above it. */
  .record { grid-template-columns: repeat(2, minmax(0, 1fr)); text-align: left; }
}

/* ------------------------------------------------------- promo bar ------ */

/* Above the sticky masthead, not inside it: it scrolls away after it has been
   read instead of taking a strip off every screen for the whole visit. */
.promobar {
  margin: 0;
  padding: 0.55rem var(--gutter);
  background: var(--leaf);
  color: #fff;
  text-align: center;
  font-size: 0.875rem;
  letter-spacing: 0.01em;
}

/* The free-delivery nudge in the basket. Amber while there is a gap to close,
   green once it is won — the colour IS the message, so it reads at a glance
   without anyone parsing the sentence. */
/* Two states, told apart by FILL rather than by reading the sentence: an
   unfinished job sits on white, a won one sits in the brand's own pale green. */
.freeship {
  margin: 1.25rem 0 0;
  padding: 0.8rem 1.1rem;
  border-radius: var(--r-sm);
  background: var(--card);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--leaf-bright);
  font-size: 0.9375rem;
}
.freeship--won {
  background: var(--leaf-pale);
  border-left-color: var(--leaf);
}

/* -------------------------------------------------- why buy at One Sunny -- */

/* Modelled on the "Why Choose Jin Chun" block Jacelyn pointed at
   (jinchunpl.com, 2026-09-06): a centred heading, then white rounded cards each
   led by a large centred icon above a serif title and centred copy.
   Rendered in One Sunny's greens rather than Jin Chun's gold.
   ⚠️ Six cards here against their three — the reasons are all evidence-backed
   and none was worth cutting to match a layout, so they run 3 across and wrap
   to a second row. */

.why {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.5rem;
  margin-top: 2.5rem;
}
.why__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 2rem 1.5rem 1.75rem;
  background: var(--card);
  border-radius: var(--r);
  /* ⚠️ A RESTING SHADOW, WHICH THE REST OF THIS SITE DELIBERATELY AVOIDS. The
     header comment sets "no shadows" as the rule and the product grid keeps it
     — a soft lift only on hover. These cards are the one exception, because the
     reference's whole effect is white cards floating off the ground, and a
     flat white card on a near-white band simply disappears. Kept very soft;
     do not spread it to other components. */
  box-shadow: 0 10px 26px -18px rgba(22, 36, 27, 0.45);
}
.why__icon {
  display: block;
  width: 2.75rem; height: 2.75rem;
  margin-bottom: 1rem;
  color: var(--leaf);
  stroke-width: 1.25;   /* thinner at this size, or the glyph reads as heavy */
}
.why__item h3 {
  margin: 0 0 0.5rem;
  font-family: var(--serif);
  font-size: 1.1875rem;
  font-weight: 400;
  color: var(--ink);
}
.why__item p { margin: 0; font-size: 0.9375rem; }

/* ------------------------------------------------------------- quotes ---- */

/* Modelled on the "What People Are Saying" block Jacelyn pointed at
   (jinchunpl.com, 2026-09-06): a dark ground at the foot of the page, a centred
   heading, and pale cards each opening with an oversized quote mark above
   centred text. Rendered in One Sunny's own greens rather than Jin Chun's
   brown-and-red, so it reads as this brand quoting its customers rather than as
   a borrowed component. */
/* ⛔ NOT --leaf-deep. This band was the dark green the footer uses, and against
   a page of sunlit paper it read as a hole rather than a section — Jacelyn:
   "currently it look dark". It is now the warmest green on the site, one step
   deeper than the other bands so it still separates, with the cards staying
   white so the quotes lift off it. The footer keeps the dark green: one deep
   anchor at the very bottom is grounding, two is heavy. */
.says { background: var(--leaf-soft); color: var(--body); }
.says__head {
  color: var(--ink);
  text-align: center;
  margin-bottom: 0.75rem;
}
.says__sub {
  max-width: 52ch;
  margin: 0 auto;
  text-align: center;
  font-size: 0.9375rem;
  color: var(--body);
}

/* Each row clips its own track; the section itself must not, or the sticky
   masthead's blur and the page's own overflow start fighting it. */
.marquee {
  overflow: hidden;
  margin-top: 1.25rem;
  /* ⛔ NO EDGE FADE. A linear-gradient mask softened both ends here until
     2026-09-06 — Jacelyn: "remove the gradient at the side". Cards now enter
     and leave at a clean edge. Do not reinstate it as a "polish": on this dark
     green ground the fade read as a pale smear rather than as depth. */
}
.quotes {
  display: flex;
  flex-wrap: nowrap;
  width: max-content;
  margin: 0;
  padding: 0;
  list-style: none;
  /* 140s, not 70: one row now carries all 18 reviews, so the track is twice
     as long and the same duration would have doubled the speed. ~42px/s is
     slow enough to read a card as it passes. */
  animation: quotes-left 140s linear infinite;
  will-change: transform;
}
/* Spacing as margin, NOT gap. gap leaves no trailing space after the last card,
   so half the track's width lands mid-gap and the seam stutters once per lap —
   the same trap as any -50% marquee. */
.quotes > .quote { margin-right: 1.25rem; }
/* A second, opposite-direction row lived here until 2026-09-06 — Jacelyn: "one
   row will do". The .marquee__track--back modifier and its quotes-right
   keyframes went with it rather than being left unused. */
@keyframes quotes-left { from { transform: translateX(0); } to { transform: translateX(-50%); } }

.says .marquee:hover .quotes,
.says .quotes:focus-within { animation-play-state: paused; }
.quote {
  display: flex;
  flex-direction: column;
  align-items: center;
  background: var(--card);
  /* Fixed width: in a scrolling row a card cannot size itself to the text or
     every lap changes length and the loop stops matching. */
  flex: 0 0 19rem;
  gap: 0.6rem;
  padding: 1.6rem 1.5rem 1.5rem;
  border: 0;
  border-radius: var(--r);
  text-align: center;
}
/* The oversized opening quote. Decorative, so it is aria-hidden in the markup —
   a screen reader announcing a lone quotation mark before every testimonial is
   noise, and the <blockquote> already says what this is. */
.quote__mark {
  display: block;
  font-family: var(--serif);
  font-size: 3rem;
  line-height: 0.7;
  color: var(--leaf);
  margin-bottom: 0.25rem;
}
.quote__text {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.65;
  color: var(--body);
}
/* Pushed to the bottom of the card, so the stars and the name sit on one line
   across a row however long each quote runs. Without this they float directly
   under their own text and the row reads as ragged. */
.quote__stars { margin: auto 0 0; padding-top: 0.6rem; color: var(--sun); letter-spacing: 0.12em; font-size: 0.9375rem; }
.quote__by { margin: 0; font-size: 0.8125rem; color: var(--muted); }

/* .btn--onDark lived here while the reviews band was dark green and the normal
   green button vanished into it. The band is light now, so the standard button
   works and the variant went rather than being left unused. */

@media (max-width: 640px) {
  .quote { flex-basis: 16rem; }
}

/* ⛔ REDUCED MOTION GETS A REAL LAYOUT, NOT A STOPPED ANIMATION. The global
   rule near the top of this file kills animations with a 0.01ms duration, which
   for a marquee means it snaps straight to translateX(-50%) and parks there
   with half the reviews off screen. So motion-averse visitors get a plain grid
   of the first row's reviews instead, and the duplicate copies are hidden. */
@media (prefers-reduced-motion: reduce) {
  .marquee { overflow: visible; }
  .quotes {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
    gap: 1.25rem;
    width: auto;
    animation: none;
  }
  .quotes > .quote { margin-right: 0; flex-basis: auto; }
  .quotes > .quote[aria-hidden="true"] { display: none; }
}
