/* The shop front.
   ===============

   The palette is taken from the shop's own ad: near-black ground, a lime that
   does the shouting, gold for the saving. One display face — Barlow Condensed,
   heavy and italic — because the ad's headlines are condensed and angry, and a
   system font cannot do that. Everything else is the system stack.

   Copy on this site is English, matching the shop's own ad creative.

   Dark only, deliberately. The brand is black; a light mode would be a second
   design pretending to be the same one.

   Still no framework and no carousel library. Nearly all of this traffic is a
   phone on mobile data arriving from a Facebook ad, and the offer has to be
   readable before anything finishes loading — which is why the font is
   display:swap and every image below the fold is lazy. */

@import url('https://fonts.googleapis.com/css2?family=Barlow+Condensed:ital,wght@0,700;0,800;1,800&display=swap');

:root {
  --ground: #0a0b0a;
  --raised: #141614;
  --line:   #262a26;
  --ink:    #f2f4f0;
  --dim:    #9aa196;

  /* The lime is the brand. It is used for one thing at a time: the button,
     the price, the saving. A page where everything shouts says nothing. */
  --lime:      #c3e600;
  --lime-ink:  #0a0b0a;
  --gold:      #f5c542;
  --hot:       #ff6a5c;

  --ph:    #ffcf70;
  --ph-bg: #2a2208;

  --display: 'Barlow Condensed', 'Arial Narrow', system-ui, sans-serif;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font: 16px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }

h1, h2, h3, .cta, .add, .brand, .price .now {
  font-family: var(--display);
  font-weight: 800;
  letter-spacing: .01em;
}

/* ---- the bar ----------------------------------------------------------- */

.bar {
  position: sticky; top: 0; z-index: 5;
  display: flex; align-items: center; gap: 12px;
  padding: 10px 16px;
  background: rgba(10, 11, 10, .92);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
}
.brand {
  font-size: 22px; text-transform: uppercase; font-style: italic;
  text-decoration: none; letter-spacing: .02em;
}
.announce {
  flex: 1 1 auto; margin: 0; text-align: center;
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--lime);
}
.cart-link {
  position: relative;
  display: inline-flex; align-items: center; gap: 8px;
  text-decoration: none; font-weight: 700; font-size: 14px;
  padding: 7px 12px 7px 14px; border: 1px solid var(--line); border-radius: 999px;
  white-space: nowrap;
  transition: border-color .2s, color .2s;
}

/* Something is in it. The whole pill answers, not just the number — a lime
   ring in the corner of a near-black bar is seen from the other side of the
   screen, and a lime digit on its own is not. */
.cart-link.has-items { border-color: var(--lime); color: var(--lime); }

main { max-width: 980px; margin: 0 auto; padding: 0 16px 40px; }

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

.hero {
  position: relative;
  padding: 34px 0 30px;
  text-align: center;
}

/* The hexagon dot field from the bottle label, drawn in CSS rather than
   shipped as an image. It is texture; it must not cost a request. */
.hero::before {
  content: '';
  position: absolute; inset: -10% -50% auto; height: 120%;
  background-image:
    radial-gradient(circle at 50% 50%, rgba(195, 230, 0, .09) 0 2px, transparent 3px);
  background-size: 26px 26px;
  pointer-events: none;
  mask-image: radial-gradient(ellipse at 50% 30%, #000 0%, transparent 70%);
  -webkit-mask-image: radial-gradient(ellipse at 50% 30%, #000 0%, transparent 70%);
}

.hero > * { position: relative; }

.hero h1 {
  font-size: clamp(38px, 11vw, 68px);
  line-height: .94;
  margin: 0 0 14px;
  text-transform: uppercase;
  font-style: italic;
}
/* The second half of the headline takes the brand colour, the way the ad
   splits SMASH | CLEAN. */
.hero h1 em { font-style: italic; color: var(--lime); }

.lede { font-size: 17px; color: var(--dim); margin: 0 auto 14px; max-width: 32ch; }

.offer {
  display: inline-block; margin: 0 0 22px; padding: 9px 16px;
  font-size: 13px; letter-spacing: .04em; text-transform: uppercase;
  border: 1px solid var(--line); border-radius: 999px;
}
.offer strong { color: var(--lime); }

.shot { margin: 28px 0 0; border-radius: 14px; border: 1px solid var(--line); }

.cta {
  display: inline-block; text-decoration: none;
  padding: 15px 40px; min-height: 52px;
  font-size: 22px; text-transform: uppercase; font-style: italic;
  color: var(--lime-ink); background: var(--lime);
  border-radius: 10px;
  transition: box-shadow .2s, transform .08s;
}
.cta:hover { box-shadow: 0 0 30px 0 rgba(195, 230, 0, .35); }
.cta:active { transform: translateY(1px); }

h2 {
  font-size: clamp(24px, 6vw, 34px);
  line-height: 1.05; margin: 0 0 12px;
  text-transform: uppercase; font-style: italic;
}
.dim { color: var(--dim); }

.band { padding: 30px 0; border-top: 1px solid var(--line); }
.band details { border-bottom: 1px solid var(--line); }
.band summary {
  cursor: pointer; font-weight: 700; padding: 14px 0;
  list-style: none;
}
.band summary::-webkit-details-marker { display: none; }
.band summary::before { content: '+ '; color: var(--lime); font-weight: 800; }
.band details[open] summary::before { content: '– '; }
.band details p { margin: 0 0 14px; color: var(--dim); }

.whys { list-style: none; margin: 18px 0 0; padding: 0; }
.whys li {
  padding: 13px 0 13px 30px; position: relative;
  border-top: 1px solid var(--line);
}
.whys li::before {
  content: '✓'; position: absolute; left: 0; top: 13px;
  color: var(--lime); font-weight: 800;
}
.whys strong { text-transform: uppercase; letter-spacing: .02em; }

.final { padding: 40px 0; text-align: center; border-top: 1px solid var(--line); }

/* ---- placeholders -----------------------------------------------------
   Loud on purpose. Nothing invented may reach the live site by accident,
   and the way to guarantee that is to make an empty slot impossible to miss. */

[data-placeholder] {
  background: var(--ph-bg);
  border: 1px dashed var(--ph);
  border-radius: 10px;
  padding: 16px;
  margin: 14px 0;
}
.ph-tag {
  display: inline-block; margin: 0 0 6px;
  font-size: 11px; font-weight: 800; letter-spacing: .08em;
  color: var(--ph);
}

/* ---- cards ------------------------------------------------------------- */

.shop { padding: 30px 0; border-top: 1px solid var(--line); }

.cards {
  display: grid; gap: 14px;
  /* Two across on a phone. Cards stacked full-width make a long scroll for
     someone still deciding whether to bother. */
  grid-template-columns: repeat(auto-fill, minmax(155px, 1fr));
  margin: 18px 0 26px;
}

.card {
  display: flex; flex-direction: column;
  border: 1px solid var(--line); border-radius: 14px;
  padding: 12px; background: var(--raised);
  transition: border-color .15s;
}
.card:hover { border-color: #3c423a; }
.card.is-out { opacity: .55; }

.card img, .noshot {
  border-radius: 10px; aspect-ratio: 1 / 1; object-fit: cover;
  background: #fff;
}
.noshot {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; background: var(--ph-bg); border: 1px dashed var(--ph);
  color: var(--ph); font-size: 11px; text-align: center; padding: 8px;
}

.card-name {
  font-size: 17px; margin: 12px 0 4px; line-height: 1.15;
  text-transform: uppercase;
}
.card-name a { text-decoration: none; }
.tagline { margin: 0 0 8px; color: var(--dim); font-size: 13px; }

.price { margin: 0 0 4px; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.price .now { font-size: 28px; line-height: 1; }
.price .was { color: var(--dim); text-decoration: line-through; font-size: 14px; }

.save {
  margin: 0 0 12px;
  font-size: 12px; font-weight: 800; letter-spacing: .06em;
  color: var(--gold); text-transform: uppercase;
}

.add {
  margin-top: auto; min-height: 48px; width: 100%;
  font-size: 18px; text-transform: uppercase; font-style: italic;
  color: var(--lime-ink); background: var(--lime);
  border: 0; border-radius: 10px; cursor: pointer;
}
.add:disabled { background: #22261f; color: var(--dim); cursor: default; font-style: normal; }

.upgrade {
  margin: 0 0 26px; padding: 16px 18px;
  background: var(--raised); border-radius: 12px;
  border-left: 4px solid var(--lime);
}
.upgrade strong { color: var(--lime); text-transform: uppercase; }

.product-top { padding: 24px 0; }
.product-top .card { max-width: 360px; }
.desc { margin: 18px 0; max-width: 60ch; }

:focus-visible { outline: 3px solid var(--lime); outline-offset: 2px; }

/* ---- foot -------------------------------------------------------------- */

.foot {
  display: flex; flex-wrap: wrap; gap: 16px; justify-content: center;
  padding: 26px 16px 44px;
  border-top: 1px solid var(--line);
  font-size: 14px;
}
.foot a { color: var(--dim); }

body.is-closed .add { pointer-events: none; }

/* ---- the cart page ------------------------------------------------------ */

.page-title {
  font-family: var(--display); font-weight: 800;
  font-size: clamp(28px, 8vw, 40px); margin: 26px 0 16px;
  text-transform: uppercase; font-style: italic;
}

.lines { list-style: none; margin: 0; padding: 0; }
.cart-line {
  display: grid; gap: 4px 12px; align-items: center;
  grid-template-columns: 1fr auto auto;
  padding: 16px 0; border-bottom: 1px solid var(--line);
}
.cart-line .grow { grid-row: 1; grid-column: 1; }
.l-name { margin: 0; font-weight: 700; }
.cart-line p { margin: 0; }
.l-total { font-weight: 800; white-space: nowrap; font-size: 18px; }

.qty { display: flex; align-items: center; gap: 2px; }
.step {
  width: 44px; height: 44px;
  font: inherit; font-size: 20px; line-height: 1;
  color: var(--ink); background: var(--raised);
  border: 1px solid var(--line); border-radius: 8px; cursor: pointer;
}
.step:disabled { opacity: .4; cursor: default; }
.qty .n { min-width: 32px; text-align: center; font-weight: 700; }

.link {
  grid-column: 1 / -1; justify-self: start;
  padding: 6px 0; font: inherit; font-size: 14px;
  color: var(--dim); background: none; border: 0;
  text-decoration: underline; cursor: pointer;
}

.rejected { list-style: none; margin: 16px 0; padding: 0; }
.gone {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  padding: 12px 14px; margin-bottom: 8px;
  color: var(--hot);
  border: 1px solid var(--hot); border-radius: 10px;
}
.gone button {
  min-height: 40px; padding: 0 14px; font: inherit; font-weight: 700;
  color: var(--ground); background: var(--hot);
  border: 0; border-radius: 8px; cursor: pointer;
}

.totals { margin: 24px 0 40px; }
.totals .row { display: flex; justify-content: space-between; margin: 0 0 8px; }
.totals .grand {
  font-family: var(--display); font-size: 26px; font-weight: 800;
  padding-top: 12px; border-top: 1px solid var(--line);
}
.discounts { list-style: none; margin: 0 0 8px; padding: 0; }
.discounts li {
  display: flex; justify-content: space-between;
  color: var(--gold); font-weight: 700;
}
.totals .cta { display: block; text-align: center; margin-top: 20px; }

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

#checkout { max-width: 480px; margin: 20px 0 40px; }
#checkout label { display: block; margin-bottom: 16px; font-weight: 600; }
#checkout input[type="text"],
#checkout input[type="tel"],
#checkout input:not([type]) {
  display: block; width: 100%; margin-top: 6px;
  padding: 13px 14px; min-height: 48px;
  font: inherit; color: var(--ink); background: var(--raised);
  border: 1px solid var(--line); border-radius: 10px;
}
#checkout input.is-bad { border-color: var(--hot); border-width: 2px; }

.err { display: block; margin-top: 5px; color: var(--hot); font-size: 14px; font-weight: 600; }
.err:empty { display: none; }

.tick { display: grid; grid-template-columns: auto 1fr; gap: 10px; align-items: start; }
.tick input { width: 24px; height: 24px; margin-top: 2px; accent-color: var(--lime); }
.tick .err { grid-column: 2; }

.form-error {
  padding: 12px 14px; margin: 0 0 14px;
  color: var(--hot); font-weight: 600;
  border: 1px solid var(--hot); border-radius: 10px;
}

#place {
  width: 100%; min-height: 56px;
  font-family: var(--display); font-size: 22px; font-weight: 800;
  text-transform: uppercase; font-style: italic;
  color: var(--lime-ink); background: var(--lime);
  border: 0; border-radius: 10px; cursor: pointer;
}
#place:disabled { opacity: .6; cursor: default; }

/* The honeypot. Off-screen rather than display:none, because some bots skip
   anything that is display:none — and it must stay reachable to them. */
.mw-extra { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.mini { list-style: none; margin: 0 0 10px; padding: 0; }
.mini li { display: flex; justify-content: space-between; margin-bottom: 6px; }
.mini .off, .mini li.off { color: var(--gold); font-weight: 700; }

/* ---- received ----------------------------------------------------------- */

.received { text-align: center; padding-top: 30px; }
.received h1 {
  font-size: clamp(28px, 8vw, 42px); line-height: 1.05;
  text-transform: uppercase; font-style: italic;
}
.received .band { text-align: left; }
.received ol { padding-left: 20px; }
.ref-label {
  margin: 26px 0 2px; color: var(--dim); font-size: 13px;
  text-transform: uppercase; letter-spacing: .08em;
}
.ref {
  margin: 0 0 8px;
  font-family: var(--display); font-size: 48px; font-weight: 800;
  letter-spacing: .04em; color: var(--lime);
}

/* ---- prose pages (FAQ, policies) ---------------------------------------- */

.prose { max-width: 680px; padding-top: 26px; }
.prose h1 {
  font-size: clamp(28px, 8vw, 40px); margin: 0 0 18px;
  text-transform: uppercase; font-style: italic;
}
.prose h2 { font-size: 20px; margin: 30px 0 8px; }
.prose p { margin: 0 0 12px; }
.prose details { border-bottom: 1px solid var(--line); }
.prose details p { color: var(--dim); }
.prose summary { cursor: pointer; font-weight: 600; padding: 14px 0; }
.prose .cta { margin: 26px 0; }

.policy-nav { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 24px; }
.policy-nav a {
  padding: 9px 15px; font-size: 14px; text-decoration: none;
  color: var(--dim); border: 1px solid var(--line); border-radius: 999px;
}
.policy-nav a.is-on {
  color: var(--lime-ink); background: var(--lime); border-color: var(--lime);
  font-weight: 700;
}

@media (min-width: 640px) {
  .cards { grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
}

/* ---- the hero, filling the screen --------------------------------------
   One screen, one offer. Everything a buyer needs to decide — what it is,
   what it costs them (nothing, until it arrives) and the button — sits above
   the fold on a 375x812 phone, which is what almost all of this traffic is.
   The image is capped rather than sized, so it can never push the button off. */

.hero {
  min-height: 100svh;
  display: grid; align-content: center;
  padding: 20px 0 28px;
}
.hero .shot {
  max-height: 42svh; width: auto; margin-inline: auto;
  object-fit: contain;
}

/* ---- a real countdown, or nothing --------------------------------------- */

.promo {
  justify-self: center;
  margin: 0 0 16px; padding: 7px 16px;
  font-family: var(--display); font-weight: 800; font-size: 15px;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--lime-ink); background: var(--gold);
  border-radius: 999px;
  font-variant-numeric: tabular-nums;
}

/* ---- badges ------------------------------------------------------------- */

.card { position: relative; }
.badge {
  position: absolute; top: 0; left: 12px;
  transform: translateY(-50%);
  z-index: 1;
  padding: 5px 11px;
  font-family: var(--display); font-weight: 800; font-size: 12px;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--lime-ink); background: var(--lime);
  border-radius: 999px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .5);
}

/* ---- cards arriving ----------------------------------------------------- */

.reveal { opacity: 0; transform: translateY(14px); }
.reveal.is-in {
  opacity: 1; transform: none;
  transition: opacity .45s ease, transform .45s cubic-bezier(.2, .7, .3, 1);
}
@media (prefers-reduced-motion: reduce) {
  .reveal, .reveal.is-in { opacity: 1; transform: none; transition: none; }
}

/* ---- the dock ----------------------------------------------------------- */

.dock {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  background: rgba(10, 11, 10, .95);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--line);
  animation: dock-in .25s ease both;
}
@keyframes dock-in { from { transform: translateY(100%); } to { transform: none; } }
@media (prefers-reduced-motion: reduce) { .dock { animation: none; } }

.dock-txt {
  flex: 1 1 auto; min-width: 0;
  font-size: 13px; color: var(--dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dock-cta {
  flex: 0 0 auto;
  padding: 13px 26px; min-height: 48px;
  display: inline-flex; align-items: center;
  font-family: var(--display); font-weight: 800; font-size: 19px;
  text-transform: uppercase; font-style: italic; text-decoration: none;
  color: var(--lime-ink); background: var(--lime);
  border-radius: 10px;
}

/* The dock covers the bottom of the page, so the footer needs room to clear
   it — otherwise the last link is permanently unreachable. */
body:has(.dock:not([hidden])) .foot { padding-bottom: 100px; }

/* ---- the button, breathing ---------------------------------------------
   A slow pulse on the one button that matters. Slow enough to notice only
   out of the corner of the eye, and gone entirely for anyone who has asked
   for less movement. */

@keyframes breathe {
  0%, 100% { box-shadow: 0 0 0 0 rgba(195, 230, 0, .40); }
  50%      { box-shadow: 0 0 26px 4px rgba(195, 230, 0, .22); }
}
.hero .cta { animation: breathe 3.2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .hero .cta { animation: none; } }

/* ---- fitting one phone screen ------------------------------------------
   Measured on a 375x812 phone, which is what most of this traffic is. The
   sticky bar was wrapping its own name onto two lines and eating 91px of the
   screen before the offer had said anything. */

.brand { white-space: nowrap; font-size: 19px; }

/* The announcement repeats what the offer pill says two inches below it.
   On a narrow screen it is the thing to drop. */
.announce { display: none; }
@media (min-width: 560px) { .announce { display: block; } }

.hero { min-height: calc(100svh - 58px); padding: 10px 0 24px; }
.hero h1 { font-size: clamp(34px, 10.5vw, 68px); margin-bottom: 10px; }
.hero .lede { margin-bottom: 10px; font-size: 16px; }
.hero .offer { margin-bottom: 16px; }
.hero .shot { max-height: 36svh; margin-top: 18px; }

/* ---- nothing may push the page sideways ---------------------------------
   The hero's dot texture was inset -50% on each side so it would reach past
   the content column to the screen edge. On a phone that made the document
   531px wide inside a 375px window: the whole page scrolled sideways, and the
   dock's buy button sat off the right edge where nobody could reach it.

   It only ever needed to cover main's own 16px padding. `clip` on the body is
   the belt to that braces — and `clip` rather than `hidden`, because `hidden`
   turns the body into a scroll container and breaks the sticky bar. */

html, body { overflow-x: clip; }
.hero::before { inset: -10% -16px auto; }

/* The reveal starts things invisible, which means a script failure would
   leave the whole catalogue hidden with no way back. So the hiding only
   applies once the script has run far enough to say so: no script, no
   animation, and every card simply visible. */
.reveal { opacity: 1; transform: none; }
.js .reveal:not(.is-in) { opacity: 0; transform: translateY(14px); }

/* ---- the reveal, made incapable of hiding anything ----------------------
   It used to fade in from opacity 0. That is a common pattern and it is a
   trap: a transition only finishes if the browser is painting, and if it
   stalls — an offscreen renderer, a cheap phone under load, an in-app browser
   having a bad day — the product cards stay invisible and the shop is silently
   dead. This traffic arrives inside Facebook's browser, so that is not a
   hypothetical.

   So the entrance moves the card and never fades it. If nothing animates at
   all, the worst case is a card sitting 14px lower than it should, fully
   readable and fully buyable. */

.reveal { opacity: 1 !important; }
.js .reveal:not(.is-in) { transform: translateY(14px); }
.reveal.is-in { transform: none; transition: transform .45s cubic-bezier(.2, .7, .3, 1); }
@media (prefers-reduced-motion: reduce) {
  .reveal, .js .reveal:not(.is-in) { transform: none; transition: none; }
}

/* ---- the real badge ----------------------------------------------------
   The wizard on the chrome hexagon — Merlin, which is where the name comes
   from. It carries the word MERLIN inside it, so the bar does not repeat it
   as text; the alt attribute does that job for a screen reader and for
   Google. Lifted out of the shop's own logo sheet, with the outer white
   flood-filled away so the chrome's own highlights survived. */

.brand { display: flex; align-items: center; padding: 2px 0; }
.brand img { height: 38px; width: auto; display: block; }

@media (max-width: 380px) { .brand img { height: 32px; } }

/* ---- placeholders are for the owner, not the customer -------------------
   These started as loud yellow boxes so nothing invented could reach the live
   site by accident. That worked — but they were reaching the live site
   themselves, and a shop that looks half-built does not get bought from.

   So they are hidden by default and shown on ?draft=1, which puts .draft on
   the root element. The owner still sees exactly what is missing; the
   customer sees a finished page. Nothing invented has been added to fill the
   gaps — the gaps are simply not shown. */

[data-placeholder] { display: none; }
.draft [data-placeholder] { display: block; }
.draft p[data-placeholder] { display: block; }

/* ---- the price, before the scroll --------------------------------------- */

.from-price {
  margin: 0 0 20px;
  font-family: var(--display); font-weight: 800;
  font-size: 19px; letter-spacing: .02em; text-transform: uppercase;
  color: var(--lime);
}

/* ---- three true things, where the deciding happens ---------------------- */

.trust {
  list-style: none; margin: 0 0 22px; padding: 0;
  display: grid; gap: 10px;
  grid-template-columns: repeat(3, 1fr);
}
.trust li {
  padding: 12px 10px; text-align: center;
  background: var(--raised); border-radius: 10px;
}
.trust strong {
  display: block; font-size: 13px; text-transform: uppercase;
  letter-spacing: .03em; color: var(--lime);
}
.trust span { display: block; margin-top: 3px; font-size: 11px; color: var(--dim); line-height: 1.35; }

/* `.dock { display: flex }` outranks the browser's own [hidden] { display:
   none }, so setting .hidden on it did nothing and the bar sat over the hero
   from the first paint — covering the offer it was meant to follow. Anything
   given a display here has to say what hidden means for it too. */
.dock[hidden] { display: none; }

/* ---- movement that lifts the product ------------------------------------
   Every rule here moves or fades something that is already on the screen and
   staying there. Nothing starts hidden, nothing is revealed by an animation,
   and every property animated is transform or opacity — the two the browser
   can hand to the compositor. On the cheap Android phone this page is mostly
   read on, anything else drops frames.

   All of it is off for anyone who has asked their device to stop animating. */

/* The photo, framed so it can grow inside the card rather than over it. */
.card-photo {
  overflow: hidden; border-radius: 10px;
  background: #fff;
}
.card-photo img, .card-photo .noshot {
  border-radius: 0;
  transition: transform .45s cubic-bezier(.2, .7, .3, 1);
}
.card:hover .card-photo img { transform: scale(1.06); }
.card:active .card-photo img { transform: scale(1.02); }

/* The cards arrive one after another rather than all at once. Six is as far
   as it goes; past that the last card waits too long to be worth it. */
.cards .reveal:nth-child(2).is-in { transition-delay: .07s; }
.cards .reveal:nth-child(3).is-in { transition-delay: .14s; }
.cards .reveal:nth-child(4).is-in { transition-delay: .21s; }
.cards .reveal:nth-child(5).is-in { transition-delay: .28s; }
.cards .reveal:nth-child(6).is-in { transition-delay: .35s; }

/* The card answers when it is added to, because on a phone the thumb is over
   the button at the moment it is pressed and the button cannot be seen. */
@keyframes added {
  0%   { transform: none; }
  35%  { transform: scale(1.03); }
  100% { transform: none; }
}
.card.just-added { animation: added .45s ease; border-color: var(--lime); }

/* The cart count, so a tap registers somewhere the eye is already looking. */
@keyframes bump {
  0%   { transform: none; }
  30%  { transform: scale(1.5); }
  100% { transform: none; }
}
/* Not there at all when the cart is empty. A badge showing a zero takes up
   the room of a signal and carries none, and the eye learns to skip that
   corner. When it appears, it means something. */
#cart-count { display: none; }
.cart-link.has-items #cart-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 21px; height: 21px; padding: 0 6px;
  border-radius: 999px;
  background: var(--lime); color: var(--lime-ink);
  font-size: 12px; font-weight: 800; line-height: 1;
}
#cart-count.bump { animation: bump .4s cubic-bezier(.2, .8, .3, 1); }

/* A slow shine across a badge. It is the one thing on the card that is there
   to be noticed, and a still badge stops being seen after a second. */
.badge { overflow: hidden; }
.badge::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(105deg, transparent 35%, rgba(255,255,255,.75) 50%, transparent 65%);
  transform: translateX(-120%);
  animation: shine 3.6s ease-in-out infinite;
}
@keyframes shine {
  0%, 55% { transform: translateX(-120%); }
  80%, 100% { transform: translateX(120%); }
}

/* The hero bottle drifting, barely. Enough that the page is not a photograph
   of a page. */
@keyframes float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-7px); }
}
.hero .shot { animation: float 6s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .card-photo img, .card-photo .noshot,
  .card.just-added, #cart-count.bump,
  .badge::after, .hero .shot {
    animation: none !important;
    transition: none !important;
    transform: none !important;
  }
  .cards .reveal.is-in { transition-delay: 0s !important; }
}

/* ---- the first screen, made to land ------------------------------------
   Somebody arriving from an ad decides in about a second whether this is a
   real shop. The headline does most of that work, so it is set as large as
   the line will take — a poster, not a paragraph. */

.hero { padding-top: 4px; }

.eyebrow {
  margin: 0 0 14px;
  font-family: var(--display); font-weight: 700;
  font-size: 13px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--dim);
}
.eyebrow sup { font-size: .6em; letter-spacing: 0; }

.hero h1 {
  /* 13vw fills the line on a phone; the cap keeps it sane on a laptop. */
  font-size: clamp(42px, 13vw, 92px);
  line-height: .86;
  letter-spacing: -0.015em;
  margin: 0 0 16px;
  text-wrap: balance;
}

/* A glow behind the type, the colour of the brand, so the headline sits on
   something rather than floating on flat black. */
/* Kept inside the hero rather than spilling 10% past each edge. The wider
   version made the document 393px in a 375px window and the page scrolled
   sideways again — and `overflow-x: clip` on the body did not catch it. A
   glow that never overflows needs nothing to catch it. */
.hero::after {
  content: '';
  position: absolute; left: 0; right: 0; top: 12%;
  height: 55%;
  background: radial-gradient(60% 100% at 50% 40%, rgba(195, 230, 0, .18), transparent 70%);
  pointer-events: none; z-index: 0;
}

.hero .lede { font-size: 17px; max-width: 30ch; }
.hero .shot { max-height: 34svh; }

/* The headline arrives. It is already on screen at full opacity before this
   runs — the animation only moves it — so a browser that never animates
   shows a finished headline rather than an empty space. */
@keyframes land {
  from { transform: translateY(16px) scale(.985); }
  to   { transform: none; }
}
/* `backwards`, never `both`. With `both` the final keyframe keeps applying
   for ever, and `transform: none` sitting there permanently would beat the
   button's own :active nudge — the button would stop answering the thumb.
   `backwards` holds the start state through the delay and then lets the
   element go back to its ordinary styles. */
.hero h1, .hero .eyebrow, .hero .lede, .hero .product-name, .hero .from-price {
  animation: land .55s cubic-bezier(.2, .75, .3, 1) backwards;
}
.hero .eyebrow   { animation-delay: .02s; }
.hero h1         { animation-delay: .06s; }
/* The lede carries the argument now, so it lands with the headline rather
   than trailing the product name. Pump/Wipe/Clean left the hero entirely. */
.hero .lede      { animation-delay: .11s; }
.hero .product-name { animation-delay: .17s; }
.hero .from-price{ animation-delay: .24s; }

/* The button lands and then breathes. Declared together, because a second
   `animation` on the same element replaces the first rather than joining it,
   and the glow from further up this file would otherwise be lost. */
.hero .cta {
  animation:
    land .55s cubic-bezier(.2, .75, .3, 1) .32s backwards,
    breathe 3.2s ease-in-out 1.2s infinite;
}

@media (prefers-reduced-motion: reduce) {
  .hero h1, .hero .eyebrow, .hero .lede, .hero .product-name,
  .hero .from-price, .hero .cta { animation: none !important; }
}

/* ---- the line under the headline ---------------------------------------
   It was grey body copy and the weakest thing in the hero, which is a waste:
   it is the only line that says what the product actually is. Brighter, a
   size up, and the name itself carries the weight. */

.hero .lede {
  font-size: 19px; line-height: 1.4;
  color: var(--ink);
  max-width: 26ch; margin-bottom: 14px;
}
.hero .lede strong {
  font-family: var(--display); font-weight: 800;
  font-size: 1.22em; letter-spacing: .01em;
  color: var(--lime);
}

/* Pump · Wipe · Clean, set as three beats rather than three words in a
   sentence. The dots between them are the brand's lime. */
.steps {
  display: flex; justify-content: center; align-items: center;
  flex-wrap: wrap; gap: 0;
  margin: 0 0 20px;
  font-family: var(--display); font-weight: 800;
  font-size: 16px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--ink);
}
.steps span { padding: 0 12px; position: relative; }
.steps span + span::before {
  content: ''; position: absolute; left: -3px; top: 50%;
  width: 6px; height: 6px; margin-top: -3px;
  background: var(--lime); border-radius: 50%;
}

/* A shorter phone — an SE, an older Android — has the same text stack in far
   less height. The type gives up a little and the image gives up more, so the
   button still lands above the fold rather than below it. */
@media (max-height: 720px) {
  .hero h1 { font-size: clamp(34px, 11vw, 92px); }
  .hero .lede { font-size: 17px; margin-bottom: 10px; }
  .steps { font-size: 14px; margin-bottom: 14px; }
  .hero .offer { margin-bottom: 12px; }
  .hero .from-price { margin-bottom: 14px; }
  .hero .shot { max-height: 26svh; margin-top: 12px; }
}

/* ---- the hero, with the middle cleared out ------------------------------
   The sentence and the offer pill are gone. That is about 115px of height
   handed back, and it goes to the two things that were always meant to carry
   this screen: the headline and the product.

   What the pill used to say now rides on the price line — one line rather
   than a box. On a phone the announcement bar is hidden, so without that
   there would be nothing on the first screen saying COD or free shipping. */

.hero h1 {
  font-size: clamp(48px, 15vw, 110px);
  margin-bottom: 18px;
}
.steps { font-size: 17px; margin-bottom: 22px; }
.from-price { font-size: 20px; margin-bottom: 24px; }
.hero .shot { max-height: 42svh; margin-top: 22px; }

/* The glow can be stronger with less sitting on top of it. */
.hero::after {
  top: 8%; height: 62%;
  background: radial-gradient(62% 100% at 50% 42%, rgba(195, 230, 0, .24), transparent 72%);
}

@media (max-height: 720px) {
  .hero h1 { font-size: clamp(38px, 13vw, 110px); margin-bottom: 12px; }
  .steps { font-size: 15px; margin-bottom: 14px; }
  .from-price { font-size: 17px; margin-bottom: 16px; }
  .hero .shot { max-height: 32svh; margin-top: 14px; }
}

/* ---- the product's name -------------------------------------------------
   The headline sells the idea. This is the word somebody has to remember
   when they go looking for it again, or say out loud to a friend, so it gets
   a line of its own rather than a clause inside a sentence. */

.product-name {
  margin: 0 0 12px;
  font-family: var(--display); font-weight: 800; font-style: italic;
  font-size: clamp(28px, 8vw, 44px);
  line-height: 1;
  letter-spacing: .01em;
  color: var(--lime);
}

@media (max-height: 720px) {
  .product-name { font-size: clamp(24px, 7vw, 44px); margin-bottom: 8px; }
}

/* Province and city are ordinary text boxes with a list attached, so they get
   a chevron of their own. Chrome draws one, Safari draws nothing, and a field
   that is quietly pickable is a field nobody picks from — so it is drawn here
   and the same in both. The extra right padding keeps a long town name from
   running underneath it. */
#checkout input[list] {
  padding-right: 34px;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--dim) 50%),
    linear-gradient(135deg, var(--dim) 50%, transparent 50%);
  background-position: right 20px center, right 14px center;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}
/* Safari puts its own clear button and list affordance inside the field. */
#checkout input[list]::-webkit-calendar-picker-indicator { display: none; }

/* Not an error — a note. Said in the quiet colour so a customer whose town is
   genuinely missing is reassured rather than alarmed. */
.hint {
  display: block; margin-top: 5px;
  color: var(--dim); font-size: 14px; font-weight: 400;
}

/* ---- what comes free ---------------------------------------------------- */

/* Gold rather than lime. Lime is the shop's "press this" colour and is spent
   on the buttons and the price; a second thing shouting in the same voice
   makes neither heard. Gold reads as a gift and stays out of the way of the
   call to action. */
.gift {
  margin: 0 0 14px;
  color: var(--gold); font-size: 13px; font-weight: 600; line-height: 1.45;
}

/* In the hero it is the line under the price, so it carries a little more
   weight and the same measure as the rest of the block. */
.hero .gift {
  max-width: 34ch; margin: -8px auto 20px;
  font-size: 15px;
}

/* In the totals it is reassurance after the fact, not a sales line: quieter,
   and set off from the amount above it. */
.totals .gift {
  margin: 10px 0 0; padding-top: 10px;
  border-top: 1px solid var(--line);
  font-size: 13px;
}

@media (max-height: 720px) {
  .hero .gift { font-size: 14px; margin-bottom: 16px; }
}

/* ---- the first screen leads with the problem ----------------------------
   The headline names something the reader has already felt and blamed on
   themselves, so the line under it has to carry two sentences of explanation
   rather than a product description. 26ch broke that across five short lines
   on a phone; this gives it room without letting it run the width of a
   laptop. */
.hero .lede { max-width: 31ch; }

/* Pump · Wipe · Clean moved out of the hero and into the section that answers
   "how much work is this?". The hero version centres itself; in a band it has
   to start where every other line in that band starts. */
.band .steps { justify-content: flex-start; margin: 0 0 16px; }

/* The self-check list is not a list of benefits, so it does not get the lime
   ticks. A hollow marker reads as a box to answer rather than a claim already
   made. */
.band .whys.check li::before {
  content: ''; top: 20px; left: 2px;
  width: 9px; height: 9px;
  border: 2px solid var(--dim); border-radius: 3px;
}

/* The hero is a grid (for the full-height centring), which blockifies every
   child — so the button stretches to the whole column. On a phone that is
   exactly right: the full width is the thumb target. On a laptop it made a
   770px-wide button, which reads as a mistake rather than a call to action.

   justify-self is the grid answer. Anything other than stretch sizes the item
   to its own content, so the button goes back to being as wide as its words. */
@media (min-width: 620px) {
  .hero .cta { justify-self: center; padding-left: 64px; padding-right: 64px; }
}

/* ---- reviews ------------------------------------------------------------
   Typed out from the shop's own marketplace screenshots, which are kept in
   docs/reviews/. Set as cards rather than a wall of quotes so the eye can
   land on one and move on: most people read two and scroll. */

.reviews { list-style: none; margin: 18px 0 0; padding: 0; display: grid; gap: 12px; }
@media (min-width: 620px) {
  .reviews { grid-template-columns: 1fr 1fr; }
}

.reviews li {
  padding: 14px 16px;
  background: var(--raised);
  border: 1px solid var(--line); border-radius: 12px;
}

.stars { margin: 0 0 6px; font-size: 15px; letter-spacing: .12em; color: var(--gold); }
/* The fourth review's missing star. Kept as a star rather than removed, so
   four out of five reads as four out of five and not as four. */
.stars .off { color: var(--line); }

.reviews blockquote { margin: 0; font-size: 15px; line-height: 1.5; }

.who {
  margin: 10px 0 0; font-size: 12px; color: var(--dim);
  display: flex; justify-content: space-between; gap: 10px;
}

/* The one studio photograph, in the section that explains the product rather
   than the one that has to stop somebody. Capped and cornered so a grey sweep
   reads as a product shot on a black page instead of a gap in it. */
.product-shot {
  max-width: 260px; margin: 18px 0 4px;
  border-radius: 12px; border: 1px solid var(--line);
}

/* The dock is the cart when the cart is not on screen, so it says so in the
   cart's own colour. Grey said "COD · Free shipping" and lime says "you have
   two of these and they come to ₱560" — the state a customer is trying to
   remember, in the colour everything else buyable on this page already uses. */
.dock.has-items .dock-txt { color: var(--lime); font-weight: 700; }

/* The ten-second demo. Same measure as the product photograph beside it, so
   the page keeps one column of visuals rather than two competing widths. */
.demo {
  display: block;
  width: 100%; max-width: 260px; height: auto;
  margin: 18px 0 4px;
  border-radius: 12px; border: 1px solid var(--line);
  background: var(--raised);
}

