/* =====================================================================
   Dehydrated Fruit Store — shared styles (storefront + admin)
   Warm, natural, artisanal. Fraunces for display, Inter for body.

   Read this before editing:

   * This file is loaded by BOTH the storefront and the admin panel.
     --shadow, --shadow-lg and --radius are kept as aliases of the newer
     elevation/shape scales purely because admin.html's inline styles ask
     for them by name. Renaming them silently flattens the admin.

   * Dark mode is scoped to html[data-store], which only the storefront
     sets. The admin's inline table colours are hardcoded light, so
     letting it inherit a dark palette would make it unreadable.

   * Everything from "v4: walking fruit" downwards belongs to the mascot
     system and is generated in part from js/mascots.js. Leave it alone.
   ===================================================================== */

:root {
  /* ----- palette -----
     Every colour below is sampled from the Tooty mark (logo.png): the
     wordmark green, the passion-fruit terracotta, the pineapple olive and
     the tomato red of the apricot outlines. Nothing here is invented, so
     the page and the logo cannot drift apart.

     The grounds are near-white rather than cream: the mark is drawn for a
     white background, and at this lightness --bg and --surface are almost
     the same value, so cards are separated by their border and shadow
     rather than by a change of tone. Lighten --line and they vanish. */
  --bg:        #fdfdfb;
  --bg-sunk:   #f4f6ef;
  --surface:   #ffffff;
  --surface-2: #fdfdfa;
  --ink:       #1d2419;
  --ink-2:     #3f4d38;
  --muted:     #65705d;
  --line:      #eaece2;
  --line-strong:#d7dbcb;
  --brand:     #4c6b43;   /* the wordmark green — buttons and chrome */
  --brand-dark:#3a5333;
  --brand-soft:#eef2e7;
  --on-brand:  #f8f5ec;
  /* Checkout and "place order" are terracotta, not another green: with
     the chrome already green, the buy action needs to step forward
     rather than blend into it. */
  --accent:    #b75323;
  --accent-dark:#a1491f;
  --danger:    #c8463d;   /* the mark's own tomato red */
  --ok:        #4c6b43;
  --warn:      #a15b12;
  /* Stays dark in both themes: it is always white text on a solid chip,
     so it must not lighten the way --danger does for dark backgrounds.
     Tomato at this value clears 4.5:1 against white; the lighter zest
     below does not, which is why the sale chip uses this one. */
  --danger-solid: #c8463d;

  /* ----- fruit accents -----
     Decoration only — rules under headings, the hero glow, small marks.
     Never text on a light ground, and never a ground for small text. */
  --zest:      #cf5e28;   /* passion fruit */
  --olive:     #cfd073;   /* pineapple crown */

  /* --bg doubles as the backdrop's stand-in: it is what shows when the
     video is blocked, still loading, or switched off for reduced motion, so
     it is kept a hair off BG in tooty-video/src/ground.tsx (#ffffff) rather
     than some other tone. Drift far from that white and the page will
     visibly change colour the moment the loop appears. */

  /* ----- materials: translucent chrome that content scrolls under ----- */
  --glass:      rgba(253, 253, 251, .80);
  --glass-edge: rgba(255, 255, 255, .55);
  --scrim:      rgba(18, 24, 15, .48);

  /* ----- elevation: three steps, no more ----- */
  --e1: 0 1px 2px rgba(40, 50, 30, .05), 0 2px 8px rgba(40, 50, 30, .05);
  --e2: 0 2px 6px rgba(40, 50, 30, .07), 0 14px 34px rgba(40, 50, 30, .09);
  --e3: 0 6px 16px rgba(40, 50, 30, .10), 0 30px 64px rgba(40, 50, 30, .16);
  --shadow:    var(--e1);    /* kept: admin.html asks for these by name */
  --shadow-lg: var(--e3);

  /* ----- shape ----- */
  --r-sm: 10px; --r-md: 14px; --r-lg: 18px; --r-xl: 26px; --r-pill: 999px;
  --radius: var(--r-lg);     /* kept: admin.html asks for this by name */

  /* ----- space: rem so a larger text setting scales the layout too ----- */
  --s1: .25rem; --s2: .5rem; --s3: .75rem; --s4: 1rem;
  --s5: 1.5rem; --s6: 2rem;  --s7: 3rem;   --s8: 4.5rem;

  /* ----- motion -----
     Springs live in js/motion.js and drive anything the user can grab.
     These curves are only for the small, uninterruptible things: hovers,
     colour changes, presses. --ease-in is the inverse of --ease-out so a
     reversible transition retraces its own path. */
  --ease-out: cubic-bezier(.22, 1, .36, 1);
  --ease-in:  cubic-bezier(.64, 0, .78, 0);
  --t-fast: 120ms; --t-med: 240ms; --t-slow: 380ms;

  --font:      "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
  --display:   "Fraunces", Georgia, "Times New Roman", serif;
}

/* Dark mode, storefront only — see the note at the top of this file. */
@media (prefers-color-scheme: dark) {
  html[data-store] {
    --bg:        #12160f;
    --bg-sunk:   #0d100b;
    --surface:   #1b2117;
    --surface-2: #232a1e;
    --ink:       #f0f2e6;
    --ink-2:     #cdd4c2;
    --muted:     #98a48d;
    --line:      #2b3325;
    --line-strong:#414c39;
    --brand:     #9dbc84;   /* the leaf green lifted, to read on near-black */
    --brand-dark:#b8d3a0;
    --brand-soft:#26301f;
    --on-brand:  #161c12;   /* dark ink on the lighter brand keeps contrast */
    --accent:    #e8834a;   /* terracotta lifted to match */
    --accent-dark:#f2a074;
    --danger:    #e58275;
    --ok:        #9dbc84;
    --warn:      #e0b055;
    /* --danger-solid, --zest, --olive and the --hero-* set are
       deliberately not overridden: they are fixed chips in both themes. */

    --glass:      rgba(18, 22, 15, .74);
    --glass-edge: rgba(255, 255, 255, .08);
    --scrim:      rgba(0, 0, 0, .6);

    --e1: 0 1px 2px rgba(0, 0, 0, .4), 0 2px 8px rgba(0, 0, 0, .3);
    --e2: 0 2px 6px rgba(0, 0, 0, .45), 0 14px 34px rgba(0, 0, 0, .4);
    --e3: 0 6px 16px rgba(0, 0, 0, .5), 0 30px 64px rgba(0, 0, 0, .55);
  }
}

* { box-sizing: border-box; }
/* The browser hides [hidden] with display:none, but any rule that sets its
   own display wins on specificity and the element stays on screen. That was
   quietly keeping the floating WhatsApp button (display:flex) visible even
   when it was switched off in Settings. Make hiding mean hiding. */
[hidden] { display: none !important; }
html { scroll-behavior: smooth; }
/* Reserve the scrollbar's width permanently. Without it, locking the body
   when the cart opens removes the scrollbar and the whole page jumps
   sideways by ~15px underneath the panel. */
html { scrollbar-gutter: stable; }
html[data-store] { color-scheme: light dark; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--font);
  color: var(--ink);
  background: var(--bg);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
/* Opening the cart or a modal locks the page behind it, otherwise the
   background scrolls under the customer's finger on a phone. */
body.is-locked { overflow: hidden; }
img { max-width: 100%; display: block; }
button { font-family: inherit; cursor: pointer; }
a { color: var(--brand-dark); }

/* Tracking is size-specific: large display type reads too loose at its
   default spacing and needs pulling in, body text does not. One global
   letter-spacing would be wrong at one end or the other. */
h1, h2, h3 {
  font-family: var(--display); font-weight: 600;
  font-optical-sizing: auto; letter-spacing: -.015em;
}
.display {
  font-family: var(--display); font-weight: 600; font-optical-sizing: auto;
  font-size: clamp(2.1rem, 1.15rem + 3.9vw, 3.7rem);
  line-height: 1.02; letter-spacing: -.03em;
  margin: 0;
  text-wrap: balance;
}
.lede {
  font-size: clamp(1rem, .95rem + .35vw, 1.15rem);
  line-height: 1.55; color: var(--ink-2);
  text-wrap: pretty;
}

.wrap { max-width: 1140px; margin: 0 auto; padding: 0 clamp(1rem, 4vw, 2rem); }

:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 4px; }

/* Skip link — the first stop for a keyboard, and invisible otherwise. */
.skip-link {
  position: absolute; inset-inline-start: var(--s4); top: -100px; z-index: 100;
  background: var(--surface); color: var(--ink); font-weight: 600; font-size: 14px;
  padding: var(--s3) var(--s4); border-radius: var(--r-sm); box-shadow: var(--e2);
  transition: top var(--t-fast) var(--ease-out);
}
.skip-link:focus-visible { top: var(--s4); }

/* The press-feedback rules live at the end of the storefront section, in
   "press feedback", because they have to outrank the :hover transforms
   they are competing with. */

/* ---------- buttons ---------- */
.btn {
  border: 0; border-radius: var(--r-pill); padding: 12px 22px;
  font-size: 15px; font-weight: 600; letter-spacing: -.005em;
  /* inline-flex + no underline so an <a class="btn"> (the hero call to
     action) is indistinguishable from a real button. */
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  text-decoration: none; text-align: center;
  background: var(--brand); color: var(--on-brand);
  transition: background var(--t-fast) ease, transform var(--t-fast) var(--ease-out),
              box-shadow var(--t-med) var(--ease-out), filter var(--t-fast) ease;
  box-shadow: 0 1px 2px rgba(40,50,30,.16);
}
@media (hover: hover) {
  .btn:hover { background: var(--brand-dark); box-shadow: 0 4px 14px -2px rgba(60,85,50,.42); }
}
.btn:active { transform: scale(.975); }
.btn:disabled {
  background: var(--line); color: var(--muted);
  cursor: not-allowed; box-shadow: none; transform: none;
}
.btn.ghost { background: transparent; color: var(--ink); border: 1px solid var(--line-strong); box-shadow: none; }
.btn.ghost:hover { background: var(--bg-sunk); border-color: var(--muted); }
.btn.green { background: var(--accent); color: var(--on-brand); }
.btn.green:hover { background: var(--accent-dark); box-shadow: 0 4px 14px -2px rgba(176,77,30,.42); }
.btn.danger { background: transparent; color: var(--danger); border: 1px solid var(--line); box-shadow: none; }
.btn.danger:hover { background: var(--brand-soft); border-color: var(--danger); }
.btn.sm { padding: 8px 15px; font-size: 13px; }
.btn.lg { padding: 15px 30px; font-size: 16px; }
.btn.block { display: flex; width: 100%; }
/* WhatsApp green is a brand colour on a solid chip, so — like
   --danger-solid — it stays put in dark mode rather than lightening. */
.btn.wa { background: #25d366; color: #fff; }
.btn.wa:hover { background: #1da851; box-shadow: 0 4px 14px -2px rgba(37,211,102,.5); }
.btn.wa svg { flex: none; }

/* ---------- promo bar ----------
   Scrolls away with the page on purpose: it is a notice, not chrome, and
   a sticky one would eat a strip of every screen. store.js unhides it
   once it knows the real free-delivery threshold. */
.promo-bar {
  background: var(--brand); color: var(--on-brand);
  font-size: 12.5px; font-weight: 600; letter-spacing: .01em;
}
.promo-bar .wrap {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  flex-wrap: wrap; padding-top: 9px; padding-bottom: 9px; text-align: center;
}
.promo-bar .dot { opacity: .5; }
/* Three claims on one line is one too many on a phone; the middle one
   (the live delivery threshold) is the one worth keeping. */
@media (max-width: 640px) {
  .promo-bar span:not(#promo-delivery) { display: none; }
}
/* In dark mode --brand is the lifted sun, and a full-bleed strip of it
   above a near-black page is a headlight. Same chip, tuned down: cocoa
   ground with the gold text the rest of the dark palette already uses.
   Scoped to [data-store] like every other dark rule, so the admin — which
   has no promo bar anyway — cannot inherit it. */
@media (prefers-color-scheme: dark) {
  html[data-store] .promo-bar {
    background: var(--brand-soft); color: var(--brand-dark);
    border-bottom: 1px solid var(--line);
  }
}

/* ---------- header ----------
   A floating translucent layer with the page running underneath, rather
   than an opaque bar that eats a strip of the screen. The 1px rule is
   not drawn until something has actually scrolled beneath it — a divider
   under a header with nothing behind it is a line for its own sake. */
.site-header {
  position: sticky; top: 0; z-index: 30;
  background: var(--glass);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border-bottom: 1px solid transparent;
  transition: border-color var(--t-med) ease, box-shadow var(--t-med) ease;
}
.site-header[data-scrolled] {
  border-bottom-color: var(--line);
  box-shadow: 0 10px 28px -24px rgba(40,50,30,.9);
}
.site-header .wrap {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s4);
  padding-top: var(--s3); padding-bottom: var(--s3);
}
.header-actions { display: flex; align-items: center; gap: var(--s2); }
/* One-page shop, so these are anchors down the page. Hidden below 860px
   rather than folded into a burger: three in-page links are not worth a
   menu, and the same three destinations are all reachable from the hero
   and the footer. */
.site-nav { display: flex; align-items: center; gap: var(--s5); }
.site-nav a {
  position: relative; color: var(--ink-2); text-decoration: none;
  font-size: 14px; font-weight: 600; padding: 4px 0;
  transition: color var(--t-fast) ease;
}
.site-nav a::after {
  content: ""; position: absolute; inset-inline-start: 0; bottom: 0;
  width: 100%; height: 2px; border-radius: 2px; background: var(--zest);
  /* Grows from the centre, which needs no RTL counterpart — and note that
     transform-origin takes no logical keyword, so `inline-start` here
     would be dropped as invalid and silently land back on centre anyway. */
  transform: scaleX(0);
  transition: transform var(--t-med) var(--ease-out);
}
.site-nav a:hover { color: var(--ink); }
.site-nav a:hover::after { transform: scaleX(1); }
@media (max-width: 860px) { .site-nav { display: none; } }
.brand {
  display: flex; align-items: center; gap: 11px; min-width: 0;
  text-decoration: none; color: inherit;
}
/* The mark is a detailed drawing, so it gets no plinth, no border and no
   shadow — a chip around it would fight the line art at this size. It is
   drawn on white in the artwork itself, which is why it needs none. */
.brand-mark {
  width: 46px; height: 46px; flex: none;
  object-fit: contain;
  transition: transform var(--t-med) var(--ease-out);
}
@media (hover: hover) {
  .brand:hover .brand-mark { transform: scale(1.06) rotate(-3deg); }
}
.brand h1 {
  font-size: clamp(1.15rem, 1.05rem + .4vw, 1.4rem); margin: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.brand .tag { color: var(--muted); font-size: 13px; }
.cart-btn {
  position: relative; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-pill); padding: 10px 17px; font-weight: 600; font-size: 14px;
  color: var(--ink); display: inline-flex; align-items: center; gap: 8px;
  transition: border-color var(--t-fast) ease, box-shadow var(--t-med) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
.cart-btn:hover { border-color: var(--brand); box-shadow: 0 3px 12px -2px rgba(60,85,50,.25); }
.cart-btn .count {
  position: absolute; top: -7px; inset-inline-end: -7px; min-width: 21px; height: 21px;
  padding: 0 6px; border-radius: var(--r-pill); background: var(--brand); color: var(--on-brand);
  font-size: 12px; line-height: 21px; text-align: center; font-weight: 700;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 2px var(--bg);
}
/* The badge pops when the number changes, so adding from the bottom of a
   long page still reads as "that went in". store.js re-triggers it. */
.cart-btn .count.bump { animation: countBump 420ms var(--ease-out); }
@keyframes countBump {
  0%   { transform: scale(1); }
  32%  { transform: scale(1.38); }
  100% { transform: scale(1); }
}
@media (max-width: 420px) {
  .cart-btn .label { display: none; }
  .cart-btn { padding: 10px 13px; }
}

/* ---------- shop ----------
   Products first, immediately under the header, with the loop behind them
   as a stack of bands. See the long note in index.html for why it is a
   stack of scrolling bands and not one fixed backdrop — briefly: fixed
   pins the animation to the viewport so it travels with the reader, and a
   viewport-tall cover crop blows the artwork up on a phone. */
.shop { position: relative; isolation: isolate; }
.shop > .wrap { position: relative; }

.shop-bg {
  /* Height comes from the bands, NOT from the section (no bottom: 0). The
     mask below has to fade the stack's own bottom edge; when it was tied to
     the section height instead, the gradient started below where the bands
     actually ended and the last one just stopped dead. */
  position: absolute; z-index: -1; top: 0; inset-inline: 0;
  /* Clips the stack to the section. Without it the bands overflow past the
     shop on a wide screen — three of them are 2400px tall at laptop width
     against a ~1100px section — and land on top of the footer, which
     vanished behind the loop. They cover rather than slide behind because
     .shop is positioned, so it paints after in-flow blocks like the footer.

     max-height rather than bottom: 0 because the two cases differ: on a
     phone the stack is SHORTER than the section and the mask below has to
     fade the stack's own end, while on a laptop it is longer and simply
     needs cutting off. Sizing to content with a ceiling does both. */
  max-height: 100%;
  overflow: hidden;
  display: flex; flex-direction: column;
  -webkit-mask-image: linear-gradient(to bottom, #000 72%, transparent);
          mask-image: linear-gradient(to bottom, #000 72%, transparent);
}
/* height:auto — the band is exactly the video's own aspect ratio, so the
   frame is shown whole and nothing is cropped.

   It used to be a fixed height with object-fit:cover, and that was the bug
   behind the halved drawings: cover showed only the middle ~61% of each
   frame, so every band boundary was a hard jump between two different crops
   and any fruit crossing an edge was sliced. Showing whole frames means a
   seam is the frame's own white margin meeting the next one's — invisible,
   provided the composition keeps its fruit clear of the top and bottom
   edges, which is exactly what the note in InkBloom.tsx enforces.

   The artwork scales with the width now, which is also what keeps it small
   on a phone: at 430px wide a band is 242px tall and the fruit renders at
   about a fifth of frame size. */
.bg-loop {
  flex: none;
  width: 100%; height: auto;
  pointer-events: none;
}
/* Same rule the mascots follow: render nothing rather than freeze a moving
   thing mid-stride. The page falls back to the flat --bg underneath. */
@media (prefers-reduced-motion: reduce) {
  .shop-bg { display: none; }
}
/* The loop is drawn on white and cannot sit on a dark page — it would be a
   lit rectangle behind the products. */
@media (prefers-color-scheme: dark) {
  html[data-store] .shop-bg { display: none; }
}

/* Only ever read by a screen reader: see the heading note in index.html. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ---------- trust strip ----------
   Four claims that hold for every product in the shop, so they are static
   copy rather than anything read out of the database. Sits on the sunk
   tone so it reads as a band between the hero and the grid. */
.usps { background: var(--bg-sunk); border-bottom: 1px solid var(--line); }
.usp-list {
  list-style: none; margin: 0; padding: var(--s5) 0;
  display: grid; gap: var(--s4);
  grid-template-columns: repeat(4, 1fr);
}
.usp {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  text-align: center; padding-inline: var(--s2);
}
/* Each icon carries one colour out of the mark, so the row reads as the
   logo's palette rather than four arbitrary tints. The colours are only
   ever line work on the sunk ground — never a background for text — so
   the lighter two are safe here where they would fail as fills. */
.usp-ico {
  display: inline-flex; margin-bottom: 8px;
  color: var(--brand);
}
.usp-ico svg { width: 26px; height: 26px; }
.fruit-green .usp-ico { color: var(--brand); }
.fruit-zest  .usp-ico { color: var(--zest); }
.fruit-sage  .usp-ico { color: #7da260; }
.fruit-olive .usp-ico { color: #a8a94b; }
/* The two lightest fruit colours lose their footing on the dark ground,
   so they lift the way --brand does rather than staying put. */
@media (prefers-color-scheme: dark) {
  html[data-store] .fruit-zest  .usp-ico { color: #e8834a; }
  html[data-store] .fruit-sage  .usp-ico { color: #9dbc84; }
  html[data-store] .fruit-olive .usp-ico { color: var(--olive); }
}
.usp strong { font-size: 13.5px; color: var(--ink); letter-spacing: -.01em; }
.usp span:last-child { font-size: 12px; color: var(--muted); line-height: 1.35; }
/* Two up on a phone rather than four squeezed columns of one word each. */
@media (max-width: 620px) {
  /* Two up, and noticeably tighter: this strip sits between the loop and
     the products, so every pixel it spends is a pixel of scrolling before
     anyone sees something they can buy. */
  .usp-list {
    grid-template-columns: repeat(2, 1fr);
    padding: var(--s4) 0; gap: var(--s3); row-gap: var(--s4);
  }
  .usp-ico { margin-bottom: 4px; }
  .usp-ico svg { width: 22px; height: 22px; }
  .usp strong { font-size: 12.5px; }
  .usp span:last-child { font-size: 11.5px; }
}

/* ---------- section heading above the grid ---------- */
/* Only the product count lives here now. Its padding is what leaves a strip
   of the loop showing above the first row of cards — without it the drawing
   would be entirely hidden behind them. */
.section-head {
  display: flex; align-items: center; justify-content: flex-end;
  gap: var(--s4); flex-wrap: wrap;
  padding-top: var(--s5);
}
.section-head .count { color: var(--muted); font-size: 13.5px; font-variant-numeric: tabular-nums; }

/* ---------- product grid ---------- */
.grid {
  display: grid; gap: clamp(.75rem, 1.6vw, 1.25rem);
  padding: var(--s3) 0 var(--s8);
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  /* The hero's "Shop the range" jumps here; without this the sticky
     header lands on top of the first row of cards. */
  scroll-margin-top: 5rem;
}
@media (max-width: 720px) { .grid { grid-template-columns: repeat(2, 1fr); } }

.card {
  position: relative;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--e1);
  display: flex; flex-direction: column;
  transition: transform var(--t-med) var(--ease-out),
              box-shadow var(--t-med) var(--ease-out),
              border-color var(--t-med) ease;
}
/* Lift on hover only where there is a real pointer. On a touch screen
   :hover sticks after a tap and leaves the card floating. */
@media (hover: hover) {
  .card:hover { transform: translateY(-6px); box-shadow: var(--e2); border-color: var(--line-strong); }
  .card:hover .photo img { transform: scale(1.05); }
}
/* The invisible "open this product" button, stretched over the card. It
   is the first child so everything else paints above it; .buy is raised
   explicitly so the Add control still receives its own taps. */
.card-open {
  position: absolute; inset: 0; z-index: 1;
  background: none; border: 0; padding: 0;
  border-radius: inherit;
}
.card-open:focus-visible { outline-offset: -3px; }
.card .buy { position: relative; z-index: 2; }

/* Square rather than 4:3: a jar photographed head-on fills a square and
   gets cropped through its middle by a landscape frame. */
.card .photo { aspect-ratio: 1 / 1; overflow: hidden; background: var(--bg-sunk); }
/* Sold out reads at a glance, not just in the label. */
.card.is-out .photo img { filter: saturate(.3) opacity(.55); }
.card.is-out h3, .card.is-out .price { color: var(--muted); }
.card .photo img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform var(--t-slow) var(--ease-out);
}
.card .photo.empty {
  display: flex; align-items: center; justify-content: center;
  color: var(--muted); font-size: 13px;
}
.card .body {
  padding: var(--s3) var(--s4) var(--s4);
  display: flex; flex-direction: column; gap: var(--s2); flex: 1;
}
.card h3 { margin: 0; font-size: 1rem; line-height: 1.25; }
/* Two lines, then ellipsis — ragged card heights across a grid read as
   an accident rather than a choice. */
.card .desc {
  color: var(--muted); font-size: 12.5px; margin: 0; flex: 1; line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.card .row { display: flex; align-items: center; justify-content: space-between; gap: var(--s2); flex-wrap: wrap; }
.card .row.head { align-items: flex-start; }
.card .buy { margin-top: var(--s1); }
.card .buy .btn, .card .buy .qty { width: 100%; }

.price {
  font-size: 1.0625rem; font-weight: 700; font-variant-numeric: tabular-nums;
  letter-spacing: -.015em;
}
.price-row { display: flex; align-items: baseline; gap: var(--s2); }

/* Stock is a hierarchy decision: "in stock" is the normal case and should
   stay quiet, while "only 2 left" and "sold out" are the ones that need
   to be seen. Only those two get a filled pill. */
.stock { font-size: 11px; font-weight: 700; padding: 3px 9px; border-radius: var(--r-pill); white-space: nowrap; }
.stock.in  { background: none; padding: 3px 0; color: var(--muted); font-weight: 600; }
.stock.low { background: var(--brand-soft); color: var(--brand-dark); }
.stock.out { background: var(--line); color: var(--muted); }

.qty {
  display: flex; align-items: center; justify-content: space-between;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill); overflow: hidden; background: var(--surface);
  height: 42px;
}
.qty button {
  background: none; border: 0; width: 40px; height: 100%; font-size: 18px; line-height: 1;
  color: var(--ink); flex: none;
  transition: background var(--t-fast) ease, transform var(--t-fast) var(--ease-out);
}
.qty button:hover:not(:disabled) { background: var(--bg-sunk); }
.qty button:active:not(:disabled) { transform: scale(.88); }
.qty button:disabled { color: var(--line-strong); cursor: not-allowed; }
.qty span {
  min-width: 32px; text-align: center; font-weight: 700; font-size: 15px;
  font-variant-numeric: tabular-nums;
}
.qty.sm { height: 34px; }
.qty.sm button { width: 32px; font-size: 16px; }

/* ---------- loading skeletons ----------
   Shown instead of a spinner so the page arrives at its final shape and
   does not jump when the products land. The shimmer is a plain animation,
   so the global reduced-motion rule flattens it to a static block. */
.skeleton { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg); overflow: hidden; }
.skeleton .sk-photo { aspect-ratio: 1 / 1; }
.skeleton .sk-body { padding: var(--s3) var(--s4) var(--s4); display: flex; flex-direction: column; gap: var(--s2); }
.sk-line { height: 11px; border-radius: var(--r-pill); }
.sk-line.short { width: 45%; }
.sk-line.tall { height: 38px; border-radius: var(--r-pill); margin-top: var(--s2); }
.sk-photo, .sk-line {
  background: linear-gradient(100deg, var(--bg-sunk) 30%, var(--surface-2) 50%, var(--bg-sunk) 70%);
  background-size: 220% 100%;
  animation: skShimmer 1.5s linear infinite;
}
@keyframes skShimmer { from { background-position: 180% 0; } to { background-position: -80% 0; } }

/* ---------- toast ---------- */
.toast {
  position: fixed; left: 50%; bottom: 28px; transform: translate(-50%, 20px) scale(.96);
  background: var(--ink); color: var(--bg); padding: 12px 22px; border-radius: var(--r-pill);
  font-size: 14px; font-weight: 600; opacity: 0; pointer-events: none;
  transition: opacity var(--t-med) var(--ease-out), transform var(--t-med) var(--ease-out);
  z-index: 80; box-shadow: var(--e3);
}
.toast.show { opacity: 1; transform: translate(-50%, 0) scale(1); }

/* ---------- cart drawer / bottom sheet ----------
   One panel, two shapes: a side panel where there is room for one, and a
   bottom sheet on a phone where the reachable part of the screen is the
   bottom. js/motion.js drives the transform, so it can be dragged and
   thrown; the transforms below are only the parked, closed position for
   before the script runs. */
.overlay {
  position: fixed; inset: 0; background: var(--scrim);
  opacity: 0; pointer-events: none; z-index: 40;
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
}
.overlay.open { pointer-events: auto; }
.drawer {
  position: fixed; z-index: 50; background: var(--bg);
  display: flex; flex-direction: column;
  will-change: transform;
}
/* touch-action belongs on the grab handles alone. Putting "none" on the
   drawer itself also disables it for every descendant — including the
   scrolling item list, which then cannot be scrolled by finger at all. */
.drawer [data-sheet-grab] { touch-action: none; }
.drawer[data-dragging] { user-select: none; }
.drawer header {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--s4) var(--s5) var(--s3); flex: none;
}
.drawer header h3 { margin: 0; font-size: 1.3rem; }
/* The grab handle. Also the hit target the sheet is dragged by, which is
   why it is a generous strip rather than just the 5px bar you can see. */
.sheet-grip { display: none; flex: none; padding: 10px 0 2px; cursor: grab; }
.sheet-grip::before {
  content: ""; display: block; width: 42px; height: 5px; margin: 0 auto;
  border-radius: var(--r-pill); background: var(--line-strong);
}
.drawer[data-dragging] .sheet-grip { cursor: grabbing; }

@media (min-width: 721px) {
  .drawer {
    top: 0; inset-inline-end: 0; height: 100%; width: min(420px, 92vw);
    border-inline-start: 1px solid var(--line); box-shadow: var(--e3);
    transform: translateX(100%);
  }
  [dir="rtl"] .drawer { transform: translateX(-100%); }
}
@media (max-width: 720px) {
  .drawer {
    inset-inline: 0; bottom: 0; max-height: 86vh;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    box-shadow: var(--e3); transform: translateY(100%);
  }
  .drawer .sheet-grip { display: block; }
  .drawer header { padding-top: var(--s2); }
  /* Keeps the footer clear of the home indicator / gesture bar. */
  .drawer footer { padding-bottom: max(var(--s5), env(safe-area-inset-bottom)); }
}

.x { background: none; border: 0; font-size: 24px; color: var(--muted); line-height: 1; border-radius: 50%; }
.x:hover { color: var(--ink); }
.drawer .items { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 0 var(--s5); }
.line { display: flex; gap: var(--s3); padding: var(--s3) 0; border-bottom: 1px solid var(--line); align-items: center; }
.line:last-child { border-bottom: 0; }
.line .thumb {
  width: 62px; height: 62px; border-radius: var(--r-md);
  background: var(--bg-sunk) center/cover; flex: none; border: 1px solid var(--line);
}
.line .info { flex: 1; min-width: 0; }
.line .info h4 { margin: 0 0 2px; font-size: 15px; font-family: var(--font); font-weight: 600; }
.line .info small { color: var(--muted); font-variant-numeric: tabular-nums; }
.line .remove {
  background: none; border: 0; color: var(--muted); font-size: 13px; padding: var(--s1) var(--s2);
  border-radius: var(--r-sm); transition: color var(--t-fast) ease, background var(--t-fast) ease;
}
.line .remove:hover { color: var(--danger); background: var(--brand-soft); }
.drawer footer {
  padding: var(--s4) var(--s5) var(--s5); border-top: 1px solid var(--line);
  background: var(--surface); flex: none;
}
.totals { font-size: 14px; color: var(--muted); }
.totals .grand {
  display: flex; justify-content: space-between; color: var(--ink);
  font-size: 1.25rem; font-weight: 700; margin-top: var(--s2);
  padding-top: var(--s2); border-top: 1px solid var(--line);
}
.totals .lineitem { display: flex; justify-content: space-between; padding: 2px 0; }
.totals .grand span:last-child, .totals .lineitem span:last-child { font-variant-numeric: tabular-nums; }
.empty-cart { text-align: center; color: var(--muted); padding: var(--s8) var(--s5); }
.empty-cart .emoji { font-size: 2.5rem; display: block; margin-bottom: var(--s3); opacity: .65; }

/* ---------- forms / modal ---------- */
.field { margin-bottom: 14px; }
.field label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 5px; }
.field input, .field textarea, .field select {
  width: 100%; padding: 11px 13px; border: 1px solid var(--line); border-radius: 10px;
  font-size: 15px; font-family: inherit; background: var(--surface); color: var(--ink);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.field textarea { resize: vertical; min-height: 70px; }
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px rgba(76,107,67,.16);
}
.field .hint, .hint { display: block; font-size: 12px; color: var(--muted); margin-top: 5px; }
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; }
@media (max-width: 560px) { .two-col { grid-template-columns: 1fr; } }

.pay-options { display: flex; flex-direction: column; gap: var(--s2); }
.pay-option {
  display: flex; align-items: center; gap: 11px; padding: 13px 15px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
  font-size: 14.5px; font-weight: 500; cursor: pointer;
  transition: border-color var(--t-fast) ease, background var(--t-fast) ease,
              transform var(--t-fast) var(--ease-out);
}
.pay-option:hover, .size-option:hover { border-color: var(--brand); }
.pay-option:active, .size-option:active { transform: scale(.99); }
.pay-option:has(input:checked), .size-option:has(input:checked) { border-color: var(--brand); background: var(--brand-soft); }
.pay-option input { accent-color: var(--brand); width: 17px; height: 17px; margin: 0; flex: none; }

/* ---------- product sizes (variants) ----------
   A row of pills rather than pay-options' stacked column — sizes read
   better side by side, and there are rarely more than two or three. The
   border/hover/checked look is shared with .pay-option (see the combined
   selectors just above) so the two pickers read as the same control. */
.size-options { display: flex; gap: var(--s2); flex-wrap: wrap; margin: var(--s3) 0 var(--s4); }
.size-option {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 9px 16px; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-pill); font-size: 14px; font-weight: 600; cursor: pointer;
  transition: border-color var(--t-fast) ease, background var(--t-fast) ease,
              transform var(--t-fast) var(--ease-out);
}
.size-option input { position: absolute; opacity: 0; width: 0; height: 0; }
.size-option .size-price { font-size: 11.5px; font-weight: 600; color: var(--muted); }
.size-option:has(input:checked) .size-price { color: var(--brand-dark); }
.size-option.is-out { opacity: .5; cursor: not-allowed; }

/* The photo underneath crossfades to the chosen size's own picture, if it
   has one, rather than snapping straight to it. store.js drops the class
   on the current <img>, swaps its src once it's transparent, then lifts
   the class again on load. */
.detail-photo img.is-swapping { opacity: 0; }
.detail-photo img { transition: opacity var(--t-med) ease; }

.pay-box {
  background: var(--brand-soft); border: 1px solid var(--line-strong); border-radius: var(--r-md);
  padding: var(--s4); margin: var(--s3) 0; font-size: 14px;
}
.pay-box p { margin: 4px 0; }
.pay-target {
  font-size: 1.15rem; font-weight: 700; letter-spacing: .02em;
  background: var(--surface); border-radius: var(--r-sm); padding: 10px 14px; text-align: center;
  user-select: all; margin: var(--s2) 0;
}

.modal {
  position: fixed; inset: 0; z-index: 60; display: none;
  align-items: center; justify-content: center;
  padding: clamp(.75rem, 3vw, 2rem); overflow-y: auto;
  background: var(--scrim);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  animation: scrimIn var(--t-med) ease;
}
.modal.open { display: flex; }
@keyframes scrimIn { from { opacity: 0; } to { opacity: 1; } }
.modal .panel {
  background: var(--bg); border-radius: var(--r-xl); width: 520px; max-width: 100%;
  margin: auto; box-shadow: var(--e3); padding: clamp(1.25rem, 3vw, 1.75rem);
  border: 1px solid var(--line);
  /* Grows out of whatever was tapped to open it, so the panel and its
     trigger stay visibly related. store.js writes the two custom
     properties from the card's position; the 50% fallback keeps modals
     opened from nothing (checkout, confirmation) centred. */
  transform-origin: var(--origin-x, 50%) var(--origin-y, 50%);
  animation: materialize var(--t-med) var(--ease-out);
}
/* Blur and scale together, so the panel reads as a material arriving
   rather than a rectangle being faded up. */
@keyframes materialize {
  from { opacity: 0; transform: scale(.93); filter: blur(6px); }
  to   { opacity: 1; transform: none;       filter: blur(0); }
}
.modal .panel h3 { margin: 0 0 var(--s4); font-size: 1.4rem; }

/* Mixed against --surface rather than hardcoded, so the same three notes
   stay legible on the storefront's dark palette instead of turning into
   bright pills on a near-black page. */
.note { font-size: 13px; padding: 10px 14px; border-radius: var(--r-sm); margin: 10px 0; }
.note.err { background: color-mix(in srgb, var(--danger) 16%, var(--surface)); color: var(--danger); }
.note.ok  { background: color-mix(in srgb, var(--ok) 16%, var(--surface));     color: var(--ok); }
.note.info{ background: color-mix(in srgb, var(--warn) 18%, var(--surface));   color: var(--warn); }

/* Confirmation screen: WhatsApp is the primary action once an order is
   placed, because with no card gateway the next real step is talking to
   the shop. "Continue shopping" drops to a ghost beside it. */
.confirm-actions { display: flex; flex-direction: column; gap: var(--s2); margin-top: var(--s4); }
.wa-hint {
  text-align: center; font-size: 13.5px; color: var(--ink-2);
  margin: var(--s4) 0 0;
}

.center-msg { text-align: center; color: var(--muted); padding: var(--s8) var(--s5); grid-column: 1 / -1; }
.spinner {
  width: 26px; height: 26px; border: 3px solid var(--line); border-top-color: var(--brand);
  border-radius: 50%; margin: 0 auto 12px; animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------- contact band ----------
   Cocoa panel, so it borrows the hero's fixed palette for the same
   reason the hero does. Deliberately no email capture: the shop has no
   way to send to a list, and a box that silently drops addresses is
   worse than no box. */
/* No longer on the hero's palette. Following it to white would have left
   nothing between the grid and the footer, so the band keeps its own soft
   ground — and being on theme tokens, it now follows dark mode properly,
   which it never did while it was pinned to the fixed hero colours. */
.contact-band {
  background: var(--bg-sunk); color: var(--ink); text-align: center;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  position: relative; isolation: isolate; overflow: hidden;
}
.contact-band::before {
  content: ""; position: absolute; z-index: -1; inset: 0;
  background:
    radial-gradient(58% 76% at 50% 0%, rgba(207,94,40,.10), transparent 72%),
    radial-gradient(46% 60% at 8% 100%, rgba(125,162,96,.12), transparent 74%);
}
.contact-band .wrap { padding-top: clamp(2.5rem, 5vw, 3.75rem); padding-bottom: clamp(2.5rem, 5vw, 3.75rem); }
.contact-band h2 { margin: 0; color: var(--ink); font-size: clamp(1.4rem, 1.1rem + 1.1vw, 2rem); }
.contact-band p { margin: var(--s3) auto 0; max-width: 34rem; color: var(--muted); font-size: 14.5px; }
.contact-actions {
  margin-top: var(--s5); display: flex; gap: var(--s3);
  justify-content: center; flex-wrap: wrap;
}

/* ---------- footer ---------- */
footer.site-footer {
  border-top: 1px solid var(--line); padding: var(--s7) 0 var(--s5); color: var(--muted);
  font-size: 13.5px; background: var(--surface);
}
.footer-cols {
  display: grid; gap: var(--s6);
  grid-template-columns: 1.6fr 1fr 1fr;
  text-align: start;
}
footer.site-footer strong {
  color: var(--ink); font-size: 1.15rem; font-family: var(--display); font-weight: 600;
  display: block; margin-bottom: var(--s2);
}
.footer-col h3 {
  margin: 0 0 var(--s2); font-size: 12px; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-2);
  font-family: var(--font);
}
footer.site-footer p { margin: 6px 0 0; max-width: 34rem; line-height: 1.6; }
/* Selector matches `footer.site-footer p` above rather than the bare
   class: this line is a rule across the whole footer, and the measure it
   would otherwise inherit left the border-top — and the centring — a
   third of the way in. A lone .footer-bottom loses that fight. */
footer.site-footer .footer-bottom {
  margin-top: var(--s6); padding-top: var(--s4); border-top: 1px solid var(--line);
  font-size: 12.5px; text-align: center; color: var(--muted); max-width: none;
}
@media (max-width: 720px) {
  .footer-cols { grid-template-columns: 1fr; gap: var(--s5); text-align: center; }
  .footer-brand p, footer.site-footer p { margin-inline: auto; }
}

/* ---------- v3: language toggle ---------- */
.lang-btn {
  background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
  padding: 9px 13px; font-weight: 700; font-size: 14px; color: var(--ink);
  transition: border-color .15s ease;
}
.lang-btn:hover { border-color: var(--brand); }

/* ---------- v3: sale prices ---------- */
.price-old { color: var(--muted); font-weight: 500; font-size: .8em; }
.card .photo { position: relative; }
/* The mark's tomato red, which clears 4.5:1 against white text — the
   lighter zest orange does not, so it stays decorative. Fixed in both
   themes for the same reason --danger-solid is. */
.sale-badge {
  position: absolute; top: 10px; inset-inline-start: 10px;
  background: var(--danger-solid); color: #fff; font-size: 11px; font-weight: 800;
  padding: 4px 10px; border-radius: var(--r-pill); box-shadow: 0 2px 8px rgba(15,20,12,.28);
  letter-spacing: .02em;
}

/* ---------- v3: free delivery nudge ---------- */
.free-del {
  font-size: 13px; font-weight: 600; text-align: center;
  background: var(--brand-soft); color: var(--brand-dark);
  border-radius: var(--r-md); padding: 10px 14px; margin-bottom: var(--s3);
}
.free-del.reached { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent-dark); }
.discount-line { color: var(--accent-dark); font-weight: 600; }

/* ---------- v3: WhatsApp button ---------- */
.whatsapp-btn {
  position: fixed; z-index: 35;
  bottom: calc(22px + env(safe-area-inset-bottom));
  inset-inline-end: 22px;
  width: 54px; height: 54px; border-radius: 50%;
  background: #25d366; color: #fff; display: flex; align-items: center; justify-content: center;
  box-shadow: 0 4px 14px rgba(37,211,102,.45);
  transition: transform var(--t-med) var(--ease-out), box-shadow var(--t-med) var(--ease-out);
  /* Caught on a real screenshot: a phone-width grid puts the full-width
     "Add to cart" button of the right-hand card directly under this FAB.
     border-radius only changes what paints; the element still hit-tests
     as its full square, so the transparent corners beside the circle were
     silently swallowing taps meant for the button peeking out from under
     them. clip-path makes the hit area match what is actually visible. */
  clip-path: circle(50%);
}
.whatsapp-btn:hover { transform: scale(1.08); box-shadow: 0 6px 20px rgba(37,211,102,.55); }
.whatsapp-btn:active { transform: scale(.94); }

/* ---------- v3: product detail modal ----------
   .detail-photo MUST stay position:relative and keep its height: it is
   the container the walking-fruit strip absolutely positions itself
   into. See the mascot block further down. */
/* Scoped as .modal .detail-panel on purpose. A bare .detail-panel loses
   the padding fight with .modal .panel, which is more specific, and the
   product photo ends up inset instead of running edge to edge. */
.modal .detail-panel { position: relative; padding: 0; overflow: hidden; }
.detail-x {
  position: absolute; top: 12px; inset-inline-end: 12px; z-index: 3;
  background: rgba(255,255,255,.82); color: #2b2620;
  backdrop-filter: blur(10px) saturate(160%); -webkit-backdrop-filter: blur(10px) saturate(160%);
  border-radius: 50%; width: 36px; height: 36px; font-size: 22px;
  display: flex; align-items: center; justify-content: center;
  box-shadow: var(--e1);
  transition: background var(--t-fast) ease, transform var(--t-fast) var(--ease-out);
}
.detail-x:hover { background: #fff; transform: scale(1.06); }
.detail-photo { position: relative; max-height: 340px; overflow: hidden; background: var(--bg-sunk); }
.detail-photo img { width: 100%; max-height: 340px; object-fit: cover; }
.detail-body { padding: var(--s5) clamp(1.25rem, 4vw, 1.75rem) clamp(1.25rem, 4vw, 1.75rem); }
.detail-body h3 { margin: 0; font-size: clamp(1.4rem, 1.2rem + .8vw, 1.75rem); }
.detail-desc { color: var(--ink-2); font-size: 14.5px; line-height: 1.6; margin: var(--s3) 0; }
.detail-meta { font-size: 13px; margin: 6px 0; color: var(--muted); }
.detail-meta strong { color: var(--ink-2); }
/* The buy row sits on a tinted plinth so it reads as the action, not as
   one more line of product copy. */
.detail-buy {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s4);
  flex-wrap: wrap; margin-top: var(--s4); padding-top: var(--s4);
  border-top: 1px solid var(--line);
}
.detail-buy .price { font-size: 1.375rem; }
.detail-buy #detail-control { flex: 1; min-width: 160px; display: flex; justify-content: flex-end; }
.detail-buy #detail-control .btn { min-width: 160px; }

/* ---------- v3: footer social links ---------- */
/* Start-aligned with the footer column it lives in; the phone breakpoint
   above centres the whole column, so it follows. */
.social-links { display: flex; gap: var(--s3); justify-content: flex-start; margin-top: var(--s4); }
@media (max-width: 720px) { .social-links { justify-content: center; } }
.social-links a {
  color: var(--muted); display: inline-flex; padding: 10px;
  border: 1px solid var(--line); border-radius: 50%; background: var(--bg);
  transition: color var(--t-fast) ease, border-color var(--t-fast) ease,
              transform var(--t-fast) var(--ease-out);
}
.social-links a:hover { color: var(--brand); border-color: var(--brand); transform: translateY(-2px); }

/* ---------- v3: RTL (Arabic) ----------
   The drawer's side is handled by the logical properties in the drawer
   block above; motion.js flips the drag direction to match. Arabic has
   no display face here and negative tracking pulls its joins apart, so
   the tightened headline spacing is switched off. */
[dir="rtl"] body, [dir="rtl"] { font-family: var(--font); }
[dir="rtl"] .display, [dir="rtl"] .card h3, [dir="rtl"] .drawer h3,
[dir="rtl"] .modal h3, [dir="rtl"] .section-head h2,
[dir="rtl"] h1, [dir="rtl"] h2, [dir="rtl"] h3 { letter-spacing: 0; }

/* ---------- accessibility preferences ----------
   Reduced motion is handled by the global rule at the very bottom of
   this file, plus an early return in js/motion.js. These two cover the
   other preferences a translucent, layered design has to answer for. */
@media (prefers-reduced-transparency: reduce) {
  .site-header { background: var(--surface); backdrop-filter: none; -webkit-backdrop-filter: none; }
  .site-header { border-bottom-color: var(--line); }
  .modal, .overlay { backdrop-filter: none; -webkit-backdrop-filter: none; }
  .detail-x { background: #fff; backdrop-filter: none; -webkit-backdrop-filter: none; }
}
@media (prefers-contrast: more) {
  :root { --line: #b9a98f; --line-strong: #8d7c62; --muted: #5c5348; }
  .card, .pay-option, .modal .panel { border-width: 2px; }
  .stock.in { color: var(--ink-2); }
}

/* ---------- small screens ----------
   Sits here, above "press feedback", because that block has to stay the
   last thing before the mascot CSS — see the note there.

   The goal of the whole block is the same as the short hero: get a product
   card on screen without a scroll, and make everything on it comfortably
   tappable once it is there. */
@media (max-width: 620px) {
  /* The grid used to open a full 1.5rem below its heading and close 4.5rem
     above the footer. On a phone that is most of a screen spent on air. */
  .section-head { padding-top: var(--s4); }
  .grid { padding: var(--s2) 0 var(--s6); }
  .card .body { padding: var(--s3); gap: 6px; }
  .card .desc { font-size: 12px; }
  .site-header .wrap { padding-top: var(--s2); padding-bottom: var(--s2); }
  .brand-mark { width: 38px; height: 38px; }
}

/* iOS Safari zooms the whole page in when a focused field's text is under
   16px, and then leaves the customer zoomed in for the rest of checkout.
   16px here is not a design choice — it is the threshold. */
@media (max-width: 820px) {
  .field input, .field textarea, .field select { font-size: 16px; }
}

/* Touch pointers: the 40px controls sized for a mouse are below the ~44px
   a fingertip needs, and the grey webkit tap flash fights the press
   feedback this page already does for itself. */
@media (pointer: coarse) {
  html { -webkit-tap-highlight-color: transparent; }
  .btn.sm { padding: 12px 16px; font-size: 14px; }
  .qty { height: 46px; }
  .qty button { width: 46px; }
  .qty.sm { height: 40px; }
  .qty.sm button { width: 40px; }
  .lang-btn { padding: 11px 14px; }
  .x { min-width: 44px; min-height: 44px; }
}

/* Phones with a home indicator: the last row of the footer, and the cart
   sheet's own footer, would otherwise sit under it. */
footer.site-footer { padding-bottom: calc(var(--s5) + env(safe-area-inset-bottom)); }
.drawer footer { padding-bottom: calc(var(--s4) + env(safe-area-inset-bottom)); }

/* ---------- press feedback ----------
   Acknowledge the press on pointer-down rather than on the click; store.js
   stamps [data-press] from one delegated listener.

   This block sits last on purpose. A bare [data-press] is one class-worth
   of specificity and loses to .card:hover, so a pressed card visibly did
   nothing on a desktop. Matching the components by name ties the
   specificity and lets source order settle it. */
.btn[data-press],
.cart-btn[data-press],
.lang-btn[data-press],
.pay-option[data-press],
.x[data-press],
.qty button[data-press] {
  transform: scale(.965);
  transition-duration: var(--t-fast);
}
/* The card is already lifted on hover; the press pushes it back towards
   the page rather than replacing the lift outright. */
.card[data-press] {
  transform: translateY(-1px) scale(.985);
  box-shadow: var(--e1);
  transition-duration: var(--t-fast);
}
.whatsapp-btn[data-press] { transform: scale(.92); transition-duration: var(--t-fast); }

/* ---------- v4: walking fruit mascots (product detail) ----------
   A troupe of fruit characters walks in from the left, crosses the bottom
   of the product photo and leaves on the right; one of them stops part
   way, waves, then follows the others out. After they exit the strip sits
   empty for a moment and the pass begins again.

   Two things are worth knowing before editing:

   1. The walk-cycle keyframes at the bottom of this block are GENERATED
      from the joint-angle table in js/mascots.js. The body's up-and-down
      bob is derived by forward kinematics so the lower foot always meets
      the ground, which is what keeps the walk from looking like it floats.
      Change the table, not these numbers.

   2. Travel distance and step timing are set from JS (see layout() in
      mascots.js) because they depend on how wide the photo is. Step length
      is matched to walking speed so the feet do not skate.            */

.tooty-strip {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 52px; overflow: hidden; pointer-events: none; z-index: 1;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
}
/* A soft scrim keeps the little guys readable on top of busy photos. */
.tooty-strip::before {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(252,251,245,.6), rgba(252,251,245,0));
}

.tooty-walker {
  position: absolute; left: 0; bottom: 6px; width: 0; height: 0;
  animation-name: tootyPass;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  will-change: transform;
}
/* JS sets animation-name per role: tootyPass, tootyHold or tootyToss. */
.tooty-actor { position: absolute; left: 0; bottom: 0; }
.tooty-char  { height: 100%; width: auto; display: block; overflow: visible; }

/* In Arabic the troupe walks the other way and the characters turn round. */
.tooty-strip.tooty-rtl .tooty-walker { animation-direction: reverse; }
.tooty-strip.tooty-rtl .tooty-char   { transform: scaleX(-1); }

/* --startx / --holdx / --endx are written by JS from the strip's width. */
@keyframes tootyPass {
  0%   { transform: translateX(var(--startx)); }
  68%  { transform: translateX(var(--endx)); }
  100% { transform: translateX(var(--endx)); }
}
/* Same pace as the others, with a stop in the middle for the skit, so the
   actors never drift out of step with the group however long it runs. */
@keyframes tootyHold {
  0%   { transform: translateX(var(--startx)); }
  32%  { transform: translateX(var(--holdx)); }
  50%  { transform: translateX(var(--holdx)); }
  86%  { transform: translateX(var(--endx)); }
  100% { transform: translateX(var(--endx)); }
}
/* The thrown one stops with the others, then leaves early and fast. */
@keyframes tootyToss {
  0%   { transform: translateX(var(--startx)); }
  32%  { transform: translateX(var(--holdx)); }
  42%  { transform: translateX(var(--holdx)); }
  48%  { transform: translateX(var(--tossx)); }
  100% { transform: translateX(var(--tossx)); }
}

/* ----- the rig -----
   Colours and strokes live on the SVG as presentation attributes (see
   mascots.js). These rules only MOVE things, never draw them. */
.tooty-char g { transform-box: view-box; }
.tooty-limb, .tooty-bob, .tooty-lean, .tooty-shadow {
  animation-duration: var(--step);
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  /* Offsets each character's legs so the troupe doesn't march in lockstep.
     Anyone with a part in the skit keeps --phase at 0 — their stop is
     lined up with the walk cycle, and a delay here would throw that out. */
  animation-delay: var(--phase, 0s);
}
.tooty-lean   { transform-origin: 20px 30px;   animation-name: tootyLean; }
.tooty-bob    { transform-origin: 20px 26px;   animation-name: tootyBob;
                animation-duration: var(--step); }
.tooty-shadow { transform-origin: 20px 47.6px; animation-name: tootyShade; }

.tooty-thigh-a { transform-origin: 17px 27.4px; animation-name: tootyThighA; }
.tooty-thigh-b { transform-origin: 23px 27.4px; animation-name: tootyThighB; }
.tooty-shin-a  { transform-origin: 17px 36px;   animation-name: tootyShinA; }
.tooty-shin-b  { transform-origin: 23px 36px;   animation-name: tootyShinB; }
.tooty-foot-a  { transform-origin: 17px 43.4px; animation-name: tootyFootA; }
.tooty-foot-b  { transform-origin: 23px 43.4px; animation-name: tootyFootB; }
.tooty-arm-a   { transform-origin: 11.6px 19px; animation-name: tootyArmA; }
.tooty-arm-b   { transform-origin: 28.4px 19px; animation-name: tootyArmB; }
.tooty-fore-a  { transform-origin: 8.6px 24.2px;  animation-name: tootyForeA; }
.tooty-fore-b  { transform-origin: 31.4px 24.2px; animation-name: tootyForeB; }

/* --- generated from the walk-cycle table in mascots.js --- */
@keyframes tootyThighA {
  0% { transform: rotate(24.0deg); }
  25% { transform: rotate(4.0deg); }
  50% { transform: rotate(-24.0deg); }
  75% { transform: rotate(-3.0deg); }
  100% { transform: rotate(24.0deg); }
}
@keyframes tootyThighB {
  0% { transform: rotate(-24.0deg); }
  25% { transform: rotate(-3.0deg); }
  50% { transform: rotate(24.0deg); }
  75% { transform: rotate(4.0deg); }
  100% { transform: rotate(-24.0deg); }
}
@keyframes tootyShinA {
  0% { transform: rotate(10.0deg); }
  14% { transform: rotate(26.0deg); }
  28% { transform: rotate(32.0deg); }
  42% { transform: rotate(12.0deg); }
  50% { transform: rotate(0.0deg); }
  72% { transform: rotate(2.0deg); }
  86% { transform: rotate(5.0deg); }
  100% { transform: rotate(10.0deg); }
}
@keyframes tootyShinB {
  0% { transform: rotate(0.0deg); }
  22% { transform: rotate(2.0deg); }
  36% { transform: rotate(5.0deg); }
  50% { transform: rotate(10.0deg); }
  64% { transform: rotate(26.0deg); }
  78% { transform: rotate(32.0deg); }
  92% { transform: rotate(12.0deg); }
  100% { transform: rotate(0.0deg); }
}
@keyframes tootyFootA {
  0% { transform: rotate(-16.0deg); }
  14% { transform: rotate(-2.0deg); }
  28% { transform: rotate(6.0deg); }
  46% { transform: rotate(2.0deg); }
  50% { transform: rotate(-3.0deg); }
  76% { transform: rotate(-10.0deg); }
  100% { transform: rotate(-16.0deg); }
}
@keyframes tootyFootB {
  0% { transform: rotate(-3.0deg); }
  26% { transform: rotate(-10.0deg); }
  50% { transform: rotate(-16.0deg); }
  64% { transform: rotate(-2.0deg); }
  78% { transform: rotate(6.0deg); }
  96% { transform: rotate(2.0deg); }
  100% { transform: rotate(-3.0deg); }
}
@keyframes tootyArmA {
  0% { transform: rotate(-16.0deg); }
  25% { transform: rotate(-2.0deg); }
  50% { transform: rotate(16.0deg); }
  75% { transform: rotate(2.0deg); }
  100% { transform: rotate(-16.0deg); }
}
@keyframes tootyArmB {
  0% { transform: rotate(16.0deg); }
  25% { transform: rotate(2.0deg); }
  50% { transform: rotate(-16.0deg); }
  75% { transform: rotate(-2.0deg); }
  100% { transform: rotate(16.0deg); }
}
@keyframes tootyForeA {
  0% { transform: rotate(12.0deg); }
  25% { transform: rotate(18.0deg); }
  50% { transform: rotate(6.0deg); }
  75% { transform: rotate(14.0deg); }
  100% { transform: rotate(12.0deg); }
}
@keyframes tootyForeB {
  0% { transform: rotate(6.0deg); }
  25% { transform: rotate(14.0deg); }
  50% { transform: rotate(12.0deg); }
  75% { transform: rotate(18.0deg); }
  100% { transform: rotate(6.0deg); }
}
@keyframes tootyBob {
  0% { transform: translateY(-0.99px); }
  12.5% { transform: translateY(-0.16px); }
  25% { transform: translateY(0.50px); }
  37.5% { transform: translateY(0.42px); }
  50% { transform: translateY(-0.99px); }
  62.5% { transform: translateY(-0.16px); }
  75% { transform: translateY(0.50px); }
  87.5% { transform: translateY(0.42px); }
  100% { transform: translateY(-0.99px); }
}
@keyframes tootyShade {
  0% { transform: scale(1.069); }
  12.5% { transform: scale(1.011); }
  25% { transform: scale(0.965); }
  37.5% { transform: scale(0.971); }
  50% { transform: scale(1.069); }
  62.5% { transform: scale(1.011); }
  75% { transform: scale(0.965); }
  87.5% { transform: scale(0.971); }
  100% { transform: scale(1.069); }
}
@keyframes tootyLean {
  0% { transform: rotate(1.0deg); }
  50% { transform: rotate(-1.0deg); }
  100% { transform: rotate(1.0deg); }
}

/* ----- the skits -----
   While a skit runs, `is-acting` freezes every joint of the characters
   taking part, and the act-* rules below take over just the joints that
   move. Every act keyframe starts AND ends on the pose the walk cycle
   holds at 25% (the moment the legs pass each other), which JS lines the
   stop up with — so nothing snaps on the way in or out. Those anchor
   numbers are the walk table sampled at 25%; change the table and they
   need recomputing. */
.is-acting .tooty-limb,
.is-acting .tooty-bob,
.is-acting .tooty-lean,
.is-acting .tooty-shadow { animation-play-state: paused; }

/* --- one stops and waves (the default) --- */
.act-wave .tooty-arm-a  { animation: tootyWaveArm  var(--act) ease-in-out 1 both; }
.act-wave .tooty-fore-a { animation: tootyWaveFore var(--act) ease-in-out 1 both; }

@keyframes tootyWaveArm {
  0% { transform: rotate(-2deg); }   8% { transform: rotate(64deg); }
  16% { transform: rotate(120deg); } 28% { transform: rotate(126deg); }
  40% { transform: rotate(112deg); } 52% { transform: rotate(126deg); }
  64% { transform: rotate(112deg); } 76% { transform: rotate(126deg); }
  88% { transform: rotate(58deg); }  100% { transform: rotate(-2deg); }
}
@keyframes tootyWaveFore {
  0% { transform: rotate(18deg); }   8% { transform: rotate(24deg); }
  16% { transform: rotate(34deg); }  28% { transform: rotate(2deg); }
  40% { transform: rotate(64deg); }  52% { transform: rotate(2deg); }
  64% { transform: rotate(64deg); }  76% { transform: rotate(2deg); }
  88% { transform: rotate(26deg); }  100% { transform: rotate(18deg); }
}

/* --- cinnamon: one lies down, the other seasons it --- */
/* act-lounge shares every limb keyframe with act-lie — legs out, arms
   relaxed — and differs only in the body angle further down, because one
   lies on the ground and the other reclines on a deck chair. */
.act-lie .tooty-thigh-a, .act-lounge .tooty-thigh-a { animation: tootyLieThighA var(--act) ease-in-out 1 both; }
.act-lie .tooty-thigh-b, .act-lounge .tooty-thigh-b { animation: tootyLieThighB var(--act) ease-in-out 1 both; }
.act-lie .tooty-shin-a,  .act-lounge .tooty-shin-a  { animation: tootyLieShinA  var(--act) ease-in-out 1 both; }
.act-lie .tooty-shin-b,  .act-lounge .tooty-shin-b  { animation: tootyLieShinB  var(--act) ease-in-out 1 both; }
.act-lie .tooty-foot-a,  .act-lounge .tooty-foot-a  { animation: tootyLieFootA  var(--act) ease-in-out 1 both; }
.act-lie .tooty-foot-b,  .act-lounge .tooty-foot-b  { animation: tootyLieFootB  var(--act) ease-in-out 1 both; }
.act-lie .tooty-arm-a,   .act-lounge .tooty-arm-a   { animation: tootyLieArmA   var(--act) ease-in-out 1 both; }
.act-lie .tooty-arm-b,   .act-lounge .tooty-arm-b   { animation: tootyLieArmB   var(--act) ease-in-out 1 both; }
.act-lie .tooty-lean    { animation: tootyLieLean   var(--act) ease-in-out 1 both; }
/* Its own pivot, so the recline can be written as plain numbers rather than
   compensating for the walk cycle's origin. */
.act-lounge .tooty-lean { transform-origin: 20px 20px;
                          animation: tootyLoungeLean var(--act) ease-in-out 1 both; }

@keyframes tootyLoungeLean {
  0%, 8%    { transform: translate(0, 0) rotate(0deg); }
  22%, 80%  { transform: translate(-1.5px, 11.5px) rotate(-22deg); }
  91%, 100% { transform: translate(0, 0) rotate(0deg); }
}

@keyframes tootyLieThighA { 0% { transform: rotate(4deg); }    7%,86% { transform: rotate(-94deg); } 100% { transform: rotate(4deg); } }
@keyframes tootyLieThighB { 0% { transform: rotate(-3deg); }   7%,86% { transform: rotate(-90deg); } 100% { transform: rotate(-3deg); } }
@keyframes tootyLieShinA  { 0% { transform: rotate(31.4deg); } 7%,86% { transform: rotate(2deg); }   100% { transform: rotate(31.4deg); } }
@keyframes tootyLieShinB  { 0% { transform: rotate(2.3deg); }  7%,86% { transform: rotate(4deg); }   100% { transform: rotate(2.3deg); } }
@keyframes tootyLieFootA  { 0% { transform: rotate(5.3deg); }  7%,86% { transform: rotate(-22deg); } 100% { transform: rotate(5.3deg); } }
@keyframes tootyLieFootB  { 0% { transform: rotate(-10deg); }  7%,86% { transform: rotate(-20deg); } 100% { transform: rotate(-10deg); } }
@keyframes tootyLieArmA   { 0% { transform: rotate(-2deg); }  10%,86% { transform: rotate(78deg); }  100% { transform: rotate(-2deg); } }
@keyframes tootyLieArmB   { 0% { transform: rotate(2deg); }   10%,86% { transform: rotate(-62deg); } 100% { transform: rotate(2deg); } }
/* The body waits for the legs to be out of the way, and gets up first —
   moving both together drove the feet clean through the floor. */
@keyframes tootyLieLean {
  0%, 8%    { transform: translate(0, 0) rotate(0deg); }
  20%, 80%  { transform: translate(0, 19px) rotate(-14deg); }
  91%, 100% { transform: translate(0, 0) rotate(0deg); }
}

/* The sprinkler holds the shaker out level at full stretch and shakes it.
   Shoulder and elbow swing in the SAME direction so the movements add up —
   the hand travels about 7 units, which is what makes the shake visible at
   this size. Angles come from solving for where the hand has to be. */
.act-sprinkle .tooty-arm-a  { animation: tootySprinkleArm  var(--act) ease-in-out 1 both; }
.act-sprinkle .tooty-fore-a { animation: tootySprinkleFore var(--act) ease-in-out 1 both; }
.act-sprinkle .tooty-shaker { animation: tootyShakerIn var(--act) linear 1 both; }
.act-sprinkle .tooty-grain  { animation: tootyGrain calc(var(--act) / 6) linear infinite; }

@keyframes tootySprinkleArm {
  0% { transform: rotate(-2deg); }  12% { transform: rotate(90deg); }
  22% { transform: rotate(99deg); }  30% { transform: rotate(81deg); }
  38% { transform: rotate(99deg); }  46% { transform: rotate(81deg); }
  54% { transform: rotate(99deg); }  62% { transform: rotate(81deg); }
  70% { transform: rotate(99deg); }  78% { transform: rotate(84deg); }
  88% { transform: rotate(90deg); } 100% { transform: rotate(-2deg); }
}
@keyframes tootySprinkleFore {
  0% { transform: rotate(18deg); }  12% { transform: rotate(16deg); }
  22% { transform: rotate(40deg); }  30% { transform: rotate(-8deg); }
  38% { transform: rotate(40deg); }  46% { transform: rotate(-8deg); }
  54% { transform: rotate(40deg); }  62% { transform: rotate(-8deg); }
  70% { transform: rotate(40deg); }  78% { transform: rotate(4deg); }
  88% { transform: rotate(16deg); } 100% { transform: rotate(18deg); }
}
@keyframes tootyShakerIn { 0%, 9% { opacity: 0; } 13%, 86% { opacity: 1; } 93%, 100% { opacity: 0; } }
/* Cinnamon leaves the shaker sideways and only then falls, so it carries
   across onto the one lying down instead of dribbling straight off the cap. */
@keyframes tootyGrain {
  0%   { opacity: 0;   transform: translate(0, 0) scale(.6); }
  14%  { opacity: 1;   transform: translate(-4.5px, .5px) scale(1); }
  45%  { opacity: 1;   transform: translate(-9px, 6px) scale(1); }
  82%  { opacity: .9;  transform: translate(-11.6px, 15px) scale(.95); }
  100% { opacity: 0;   transform: translate(-12.8px, 22px) scale(.85); }
}

/* --- bananas: one throws the other like a frisbee ---
   The thrower grips the RIM of the slice and sweeps it up its right side.
   The arm angles are a smooth monotone sweep and the slice's offsets are
   computed from wherever the hand lands at each beat — chasing exact hand
   positions instead made the elbow snap 80 degrees between poses. The slice
   crouches, is lifted clear of the ground (its shadow goes with it), dips
   once for anticipation, then is flung. */
.act-toss .tooty-arm-b  { animation: tootyTossArm  var(--act) ease-in-out 1 both; }
.act-toss .tooty-fore-b { animation: tootyTossFore var(--act) ease-in-out 1 both; }
.act-toss .tooty-lean   { animation: tootyTossLean var(--act) ease-in-out 1 both; }

@keyframes tootyTossArm {
  0%   { transform: rotate(2deg); }    10% { transform: rotate(-40deg); }
  20%  { transform: rotate(-52deg); }  30% { transform: rotate(-64deg); }
  38%  { transform: rotate(-58deg); }  46% { transform: rotate(-76deg); }
  52%  { transform: rotate(-92deg); }  56% { transform: rotate(-108deg); }
  64%  { transform: rotate(-124deg); } 74% { transform: rotate(-136deg); }
  100% { transform: rotate(2deg); }
}
@keyframes tootyTossFore {
  0%   { transform: rotate(14deg); }   10% { transform: rotate(66deg); }
  20%  { transform: rotate(60deg); }   30% { transform: rotate(52deg); }
  38%  { transform: rotate(56deg); }   46% { transform: rotate(42deg); }
  52%  { transform: rotate(26deg); }   56% { transform: rotate(10deg); }
  64%  { transform: rotate(-4deg); }   74% { transform: rotate(-10deg); }
  100% { transform: rotate(14deg); }
}
@keyframes tootyTossLean {
  0%  { transform: rotate(0deg); }   30% { transform: rotate(-4deg); }
  38% { transform: rotate(-8deg); }  56% { transform: rotate(7deg); }
  74% { transform: rotate(3deg); }  100% { transform: rotate(0deg); }
}

.act-tossed .tooty-thigh-a { animation: tootyTuckThighA var(--act) ease-out 1 both; }
.act-tossed .tooty-thigh-b { animation: tootyTuckThighB var(--act) ease-out 1 both; }
.act-tossed .tooty-shin-a  { animation: tootyTuckShinA  var(--act) ease-out 1 both; }
.act-tossed .tooty-shin-b  { animation: tootyTuckShinB  var(--act) ease-out 1 both; }
.act-tossed .tooty-arm-a   { animation: tootyTuckArmA   var(--act) ease-out 1 both; }
.act-tossed .tooty-arm-b   { animation: tootyTuckArmB   var(--act) ease-out 1 both; }
.act-tossed .tooty-lean    { transform-origin: 20px 17px;
                             animation: tootyFly var(--act) linear 1 both; }
.act-tossed .tooty-shadow  { animation: tootyFlyShadow var(--act) ease-out 1 both; }

@keyframes tootyTuckThighA { 0% { transform: rotate(4deg); }   16%,100% { transform: rotate(48deg); } }
@keyframes tootyTuckThighB { 0% { transform: rotate(-3deg); }  16%,100% { transform: rotate(44deg); } }
@keyframes tootyTuckShinA  { 0% { transform: rotate(31.4deg); } 16%,100% { transform: rotate(58deg); } }
@keyframes tootyTuckShinB  { 0% { transform: rotate(2.3deg); } 16%,100% { transform: rotate(56deg); } }
@keyframes tootyTuckArmA   { 0% { transform: rotate(-2deg); }  16%,100% { transform: rotate(-38deg); } }
@keyframes tootyTuckArmB   { 0% { transform: rotate(2deg); }   16%,100% { transform: rotate(-34deg); } }
/* Everything up to 56% is exactly where the thrower's hand is; past that it
   is airborne and the walker's tootyToss carries it off to the right. */
@keyframes tootyFly {
  0%   { transform: translate(0, 0) rotate(0deg); }
  10%  { transform: translate(1.4px, 6.6px) rotate(-4deg); }
  20%  { transform: translate(3.4px, 5.1px) rotate(-8deg); }
  30%  { transform: translate(5.2px, 3px) rotate(-12deg); }
  38%  { transform: translate(4.4px, 4.1px) rotate(-6deg); }
  46%  { transform: translate(6.5px, .2px) rotate(24deg); }
  52%  { transform: translate(6.7px, -4.3px) rotate(70deg); }
  56%  { transform: translate(4.8px, -8.7px) rotate(120deg); }
  74%  { transform: translate(10px, -20px) rotate(500deg); }
  100% { transform: translate(15px, -28px) rotate(900deg); }
}
@keyframes tootyFlyShadow { 0%, 24% { opacity: .16; } 50%, 100% { opacity: 0; } }

/* --- the fruit lying down closes its eyes and breathes ------------------- */
.act-lie .tooty-eyes,      .act-lounge .tooty-eyes      { animation: tootyEyesOpen var(--act) linear 1 both; }
.act-lie .tooty-eyes-shut, .act-lounge .tooty-eyes-shut { animation: tootyEyesShut var(--act) linear 1 both; }
.act-lie .tooty-body,      .act-lounge .tooty-body      { transform-origin: 20px 17px;
                            animation: tootyBreathe calc(var(--act) / 6) ease-in-out infinite; }

@keyframes tootyEyesOpen { 0%, 6% { opacity: 1; } 12%, 84% { opacity: 0; } 92%, 100% { opacity: 1; } }
@keyframes tootyEyesShut { 0%, 6% { opacity: 0; } 12%, 84% { opacity: 1; } 92%, 100% { opacity: 0; } }
@keyframes tootyBreathe  { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.035); } }

/* --- sun-drying: it lies in the sun and shrivels into its dried self ----- */
/* The sun and the beach kit only show while it is lying there, so they ride
   the act rather than the walker's whole cycle. The chair and parasol go up
   first — it settles onto them a beat later. */
.act-sundry .tooty-sun { animation: tootySunIn var(--act) linear 1 both; }
.act-sundry .tooty-beach { transform-origin: 20px 45px;
                           animation: tootyBeachUp var(--act) ease-out 1 both; }

@keyframes tootySunIn { 0%, 10% { opacity: 0; } 20%, 82% { opacity: 1; } 92%, 100% { opacity: 0; } }
@keyframes tootyBeachUp {
  0%, 2%    { opacity: 0; transform: scale(.25); }
  12%       { opacity: 1; transform: scale(1.06); }
  18%, 84%  { opacity: 1; transform: scale(1); }
  93%, 100% { opacity: 0; transform: scale(.25); }
}

/* Everything about actually being dried — smaller, darker, matte, creased,
   squashed — runs on the WALKER's whole cycle, not on the act. Tied to the
   act it all sprang back the instant the routine finished and the fruit
   walked away plump and shiny again, which rather missed the point. It only
   resets at 95%, by which time it is off the end of the strip. */
/* The shrink is scoped to .tooty-whole — the character — rather than to the
   actor, or the deck chair and parasol would shrink along with the fruit
   instead of it shrinking on them. */
.role-sundry .tooty-whole { transform-origin: 20px 45.4px; animation: tootyShrink var(--cycle) linear infinite; }
.role-sundry .tooty-dry   { transform-origin: 20px 28px; animation: tootyShrivel var(--cycle) linear infinite; }
.role-sundry .tooty-wrinkle { animation: tootyCrease var(--cycle) linear infinite; }
.role-sundry .tooty-gloss { animation: tootyMatte  var(--cycle) linear infinite; }

@keyframes tootyShrink {
  0%, 38%   { transform: scale(1); }
  47%, 88%  { transform: scale(.74); }
  95%, 100% { transform: scale(1); }
}
/* Colour-shifting the whole body is what sells it: a plain shrink just looks
   like a smaller fresh fruit. Deliberately gentle — pushed further it turned
   every fruit the same muddy brown and they stopped being recognisable. The
   creases, the matte finish and the squash carry most of the "dried" read,
   so the colour only has to nudge. */
@keyframes tootyShrivel {
  0%, 38%   { filter: none; transform: scaleY(1); }
  47%, 88%  { filter: saturate(.72) sepia(.22) brightness(.88) contrast(1.03);
              transform: scaleY(.82); }
  95%, 100% { filter: none; transform: scaleY(1); }
}
/* Each character carries a fruit-<id> class, so any fruit that browns badly
   can have its own strength. The mango's gradient already runs to a greenish
   gold, and the standard shift muddied it — this keeps it golden. */
.role-sundry .fruit-mango .tooty-dry { animation-name: tootyShrivelMango; }
@keyframes tootyShrivelMango {
  0%, 38%   { filter: none; transform: scaleY(1); }
  47%, 88%  { filter: saturate(.86) sepia(.10) brightness(.94) contrast(1.02);
              transform: scaleY(.84); }
  95%, 100% { filter: none; transform: scaleY(1); }
}

@keyframes tootyCrease { 0%, 40% { opacity: 0; } 50%, 88% { opacity: .72; } 95%, 100% { opacity: 0; } }
@keyframes tootyMatte  { 0%, 38% { opacity: 1; } 47%, 88% { opacity: .1; }  95%, 100% { opacity: 1; } }

@media (max-width: 480px) { .tooty-strip { height: 44px; } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  /* Smooth scrolling is vestibular motion too, and the hero's jump link
     is a full-page slide. js/motion.js settles its springs instantly for
     the same reason. */
  html { scroll-behavior: auto; }
}
