/* ==========================================================================
   SMOOSHERY - Halloween butter squishies
   Art direction taken from the product box: purple-to-magenta night sky,
   orange headline type outlined in cream, purple drips, graveyard silhouette.
   Single-theme by design: a storefront should look identical to every
   customer, so every colour is painted explicitly and nothing is inherited
   from the host or browser theme.
   ========================================================================== */

:root {
  /* Moonlit graveyard: green-teal night, the reference palette.
     Darkest at the edges, cold light blooming around the moon. */
  --night:    #08120F;
  --deep:     #0D1F1A;
  --moss-sky: #17342B;
  --teal:     #235044;
  --glow:     #3E8A63;
  --moonlit:  #9FD8B4;

  /* Legacy names kept so existing rules keep resolving */
  --purple:   #0D1F1A;
  --purple-2: #17342B;
  --magenta:  #235044;
  --orchid:   #3E8A63;
  --surface:  #12241E;

  /* Warm - pumpkins and the brand, the only warmth in the scene */
  --orange:   #E8761B;
  --amber:    #F5A032;
  --ember:    #B0500D;

  /* Weathered stone */
  --stone:    #7C8768;
  --stone-lo: #4A5340;

  /* Paper */
  --cream:    #F5EFE0;

  --lime:     #A8C63F;
  --moss:     #5E7A2E;

  /* Text */
  --fg:       #F5EFE0;
  --fg-soft:  #C3D4C4;
  --fg-faint: #8CA391;

  /* Type */
  --display: "Luckiest Guy", Impact, "Arial Black", sans-serif;
  --bouncy:  "Chewy", "Comic Sans MS", cursive;
  --body:    "Nunito", "Segoe UI", Helvetica, Arial, sans-serif;
  --stamp:   "Nunito", "Segoe UI", Helvetica, Arial, sans-serif;

  /* Scale */
  --step--1: clamp(0.8rem, 0.78rem + 0.1vw, 0.86rem);
  --step-0:  clamp(1rem, 0.97rem + 0.15vw, 1.08rem);
  --step-1:  clamp(1.2rem, 1.1rem + 0.5vw, 1.45rem);
  --step-2:  clamp(1.5rem, 1.3rem + 1vw, 2.1rem);
  --step-3:  clamp(2.1rem, 1.6rem + 2.2vw, 3.4rem);
  --step-4:  clamp(2.8rem, 2rem + 3.8vw, 5.4rem);

  --wrap: 68rem;
  /* Height of the mobile sticky buy bar, and the iPhone home-indicator inset.
     js/main.js measures the bar and overwrites --cta-h with its real height, so
     the clearance below the footer is never an arbitrary guess. The value here
     is only the pre-measurement fallback. --cta-h is 0 on desktop because the
     bar is display:none there. */
  --cta-h: 0px;
  --safe-b: env(safe-area-inset-bottom, 0px);
  --radius: 14px;
}

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

html { background: #08120F; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

body {
  margin: 0;
  background: var(--night);
  color: var(--fg);
  font-family: var(--body);
  font-size: var(--step-0);
  line-height: 1.65;
  /* ONE gradient spanning the whole document, not stacked coloured blocks.
     `fixed` is deliberately absent: it would pin this to the viewport, so the
     page would never actually travel from night sky down into the earth. */
  background-image:
    radial-gradient(ellipse 70% 22% at 68% 4%, rgba(62,138,99,0.28), transparent 66%),
    linear-gradient(180deg,
      #08120F  0%,    /* deep night, above the clouds   */
      #0D1F1A 10%,
      #17342B 20%,    /* moonlit teal                   */
      #2C5540 30%,
      #6B5426 39%,    /* warmth bleeding in             */
      #B86A1A 45%,    /* harvest core - the orange zone */
      #C9741C 50%,
      #9A5416 54%,
      /* The graveyard sits at about 57% of the page, and it is the moment the
         sunset gives out. The cool-down used to start at 66%, well below it,
         so the scene's near-black ground met daylight and left a hard edge
         across the page. Night now arrives exactly where the scene does. */
      #4A3418 57%,
      #0F0D08 60%,
      #101E1A 68%,    /* back up into moonlight          */
      #0D1F1A 84%,
      #0A1109 92%,    /* ground level                   */
      #140D07 100%    /* turned earth                   */
    );
  background-repeat: no-repeat;
}

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

a { color: var(--amber); text-decoration-thickness: 1px; text-underline-offset: 3px; }

:where(a, button, summary, input):focus-visible {
  outline: 3px solid var(--amber);
  outline-offset: 3px;
  border-radius: 4px;
}

h1, h2, h3 { text-wrap: balance; margin: 0; line-height: 1.05; }

.wrap { width: min(100% - 2.5rem, var(--wrap)); margin-inline: auto; }

/* --- Outlined headline type, as on the box ----------------------------- */
/* Cream outline + a dark shadow under it. text-shadow rather than
   -webkit-text-stroke so the outline sits OUTSIDE the glyph everywhere. */
.outlined {
  color: var(--orange);
  text-shadow:
    -2px -2px 0 var(--cream),  2px -2px 0 var(--cream),
    -2px  2px 0 var(--cream),  2px  2px 0 var(--cream),
    -3px  0   0 var(--cream),  3px  0   0 var(--cream),
     0   -3px 0 var(--cream),  0    3px 0 var(--cream),
     0    7px 0 rgba(6,16,12,0.55);
}

/* --- Small caps label -------------------------------------------------- */
.stamp {
  font-family: var(--stamp);
  font-size: var(--step--1);
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin: 0;
}
.stamp--warm { color: var(--amber); }

/* --- Drip divider: the purple run-off under "BUTTER" on the box -------- */
.tear {
  height: 26px;
  border: 0;
  margin: 0;
  background-repeat: repeat-x;
  background-size: 120px 26px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 26'%3E%3Cg fill='%23F2871E'%3E%3Crect width='120' height='9'/%3E%3Ccircle cx='18' cy='11' r='7'/%3E%3Ccircle cx='48' cy='9' r='5'/%3E%3Ccircle cx='80' cy='13' r='8.5'/%3E%3Ccircle cx='105' cy='9' r='4.5'/%3E%3C/g%3E%3C/svg%3E");
}

/* --- Wrapper marquee --------------------------------------------------- */
.marquee {
  position: relative;
  overflow: hidden;
  background: var(--orange);
  color: var(--night);
  padding-block: 0.5rem;
}

/* --- Pumpkins floating in the ticker ----------------------------------- */
.marquee__pumpkins {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
}
.marquee__pumpkins img {
  position: absolute;
  left: var(--x);
  top: 50%;
  height: 132%;
  width: auto;
  opacity: 0.5;
  /* each one carries its own scale and a negative delay, so five copies of one
     photograph never bob in step */
  transform: translate(-50%, -50%) scale(var(--s));
  animation: tickerbob 5.2s ease-in-out infinite;
  animation-delay: var(--d);
}
@keyframes tickerbob {
  0%, 100% { transform: translate(-50%, -58%) scale(var(--s)) rotate(-5deg); }
  50%      { transform: translate(-50%, -44%) scale(var(--s)) rotate(6deg); }
}
/* the words have to stay on top of them */
.marquee__track { position: relative; z-index: 1; }
@media (prefers-reduced-motion: reduce) {
  .marquee__pumpkins img { animation: none; }
}
.marquee__track {
  display: flex;
  gap: 2.5rem;
  width: max-content;
  animation: slide 38s linear infinite;
  font-family: var(--body);
  font-weight: 800;
  font-size: 0.78rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}
@keyframes slide { to { transform: translateX(-50%); } }

/* --- Header ------------------------------------------------------------ */
.site-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-block: 1.2rem;
}
.wordmark {
  font-family: var(--display);
  font-size: var(--step-2);
  letter-spacing: 0.03em;
  color: var(--cream);
  text-decoration: none;
  line-height: 1;
}
.wordmark span {
  /* ERY cut out of a lit jack-o-lantern. background-clip paints the photo
     through the glyphs; the colour underneath is the fallback for anything
     that cannot do it, so the letters are never invisible. */
  color: var(--orange);
  background-image: url("../img/scene/ery-fill.webp");
  background-size: 100% auto;
  background-position: 50% 46%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  filter: drop-shadow(0 0 5px rgba(255,150,40,0.45));
}
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .wordmark span { -webkit-text-fill-color: var(--orange); }
}

/* --- Buttons ----------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-family: var(--body);
  font-weight: 800;
  font-size: var(--step-0);
  line-height: 1;
  padding: 1rem 1.6rem;
  border: 3px solid var(--cream);
  border-radius: 999px;
  background: var(--orange);
  color: var(--night);
  text-decoration: none;
  cursor: pointer;
  box-shadow: 0 5px 0 var(--ember), 0 9px 16px rgba(6,16,12,0.5);
  transition: transform 110ms ease, box-shadow 110ms ease, background 110ms ease;
}
.btn:hover { background: var(--amber); transform: translateY(-2px); box-shadow: 0 7px 0 var(--ember), 0 12px 20px rgba(6,16,12,0.55); }
.btn:active { transform: translateY(3px); box-shadow: 0 2px 0 var(--ember), 0 4px 8px rgba(6,16,12,0.5); }
.btn--ghost {
  background: transparent;
  color: var(--cream);
  border-color: rgba(245,239,224,0.45);
  box-shadow: none;
}
.btn--ghost:hover { background: rgba(245,239,224,0.1); border-color: var(--cream); transform: none; box-shadow: none; }
.btn--wide { width: 100%; }

/* --- Hero -------------------------------------------------------------- */
.hero { padding-block: clamp(2rem, 5vw, 4rem) 0; position: relative; }
.hero__grid {
  display: grid;
  gap: clamp(2rem, 5vw, 3.5rem);
  align-items: center;
}
@media (min-width: 60rem) {
  .hero__grid { grid-template-columns: 1.05fr 0.95fr; }
}
.hero__eyebrow {
  text-shadow: 0 2px 10px rgba(4,12,9,0.7);
  font-family: var(--bouncy);
  font-size: var(--step-2);
  color: var(--amber);
  line-height: 1;
  margin-bottom: 0.2rem;
  transform: rotate(-2deg);
  display: inline-block;
}
.hero__title {
  font-family: var(--display);
  font-size: var(--step-4);
  letter-spacing: 0.02em;
  margin-block: 0.4rem 0;
}
/* The eyebrow sits inside the h1 now, so the heading reads "Happy Halloween
   BUTTER" rather than "BUTTER". width:max-content keeps the rotation turning
   a tight box - as a full-width block it would pivot about the column's
   centre and swing the words out of place. */
.hero__title .hero__eyebrow { display: block; width: max-content; }
.hero__word { display: block; }
.hero__lede {
  font-size: var(--step-1);
  color: var(--fg-soft);
  max-width: 34ch;
  margin-block: 1.4rem 0;
  text-shadow: 0 2px 10px rgba(4,12,9,0.75), 0 1px 2px rgba(4,12,9,0.9);
}
.hero__cta { display: flex; flex-wrap: wrap; gap: 0.9rem; margin-top: 1.9rem; }

/* Box callouts: "Super Soft! Slow Rising!" */
.callouts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1.4rem;
  padding: 0;
  list-style: none;
}
.callouts li {
  font-family: var(--body);
  font-weight: 800;
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--night);
  background: var(--amber);
  border-radius: 999px;
  padding: 0.35rem 0.85rem;
}
.callouts li:nth-child(2) { background: var(--lime); }
.callouts li:nth-child(3) { background: var(--cream); }

/* Scarcity stamp */
.scarcity {
  display: inline-block;
  font-family: var(--body);
  font-weight: 800;
  font-size: 0.82rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--night);
  background: var(--lime);
  border-radius: 6px;
  padding: 0.45rem 0.8rem;
  transform: rotate(-2deg);
  margin-top: 1.5rem;
}

/* --- Scroll journey ----------------------------------------------------
   Top of the page is night sky with clouds; the middle warms to the orange
   band off the box; the graveyard rises out of the bottom of that band and
   the page falls back into darkness. Each band carries its own text colours
   so nothing is ever cream-on-orange. */

.clouds {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: auto;
  z-index: 0;
  pointer-events: none;
  fill: rgba(6,16,12,0.55);
}
/* Only the content wrapper is lifted above the decorative layers. Using
   `.hero > *` here would also match .clouds and friends and cancel their
   absolute positioning, dropping them back into flow. */
.hero > .wrap { position: relative; z-index: 3; }

/* The warm middle band */
/* No background of its own any more - the page gradient is already warm here,
   so this only carries the darker text colours that warmth demands. A block
   with its own gradient is exactly what produced the hard cut-off. */
.band--orange {
  color: #1A0F04;
  position: relative;
  /* .fog inside here is inset-inline:-10%, i.e. 120% wide on purpose so its
     drift animation never exposes an edge. Nothing clipped it, so it - and it
     alone - pushed the document to 422px at a 375px viewport. Clipping at this
     component keeps the bleed and contains it. Verified no sticky descendants,
     which overflow would have broken. */
  overflow: hidden;
  /* The graveyard's ground is near-black, but the band itself had no
     background - the orange came from the page gradient - so the ground ended
     in a hard bar with sunset still showing underneath it. Carrying the night
     down through the last stretch of the band means the ground meets darkness
     instead of daylight. Anchored in pixels, not a percentage, so it always
     covers the scene and never creeps up into the reel above it. */
  /* Warm, not black. Fading the band to near-black under the hay put a dark
     bar between the straw and the section below it; ending on the straw's own
     shadow colour lets the ground carry on instead of stopping. */
  background: linear-gradient(to bottom,
              /* The night above this band ends on #120C06, and the page
                 gradient at this height is a mid green - so the two met in a
                 hard line straight across the page under the clifftop.
                 Carrying the dark down into the top of the band and fading it
                 out means the cliff's base runs out into shadow instead of
                 stopping at an edge. */
              #120C06 0,
              rgba(18,12,6,0.72) 90px,
              rgba(18,12,6,0) 210px,
              transparent calc(100% - 300px),
              rgba(38,25,12,0.55) calc(100% - 150px),
              #241708 100%);
}
.band--orange .section__title { color: #1A0F04; }
.band--orange .section__lede  { color: rgba(26,15,4,0.85); }
.band--orange .stamp          { color: rgba(26,15,4,0.65); }
.band--orange .design {
  background: rgba(6,16,12,0.22);
  border-color: rgba(6,16,12,0.35);
}
.band--orange .ph        { color: rgba(6,16,12,0.62); }
.band--orange .ph__icon  { color: rgba(6,16,12,0.35); }

/* Graveyard silhouette: rises out of the orange band into the dark below */
.scene {
  display: block;
  width: 100%;
  /* Fixed height + "slice" in the SVG: the scene crops at the sides on narrow
     screens rather than scaling the tombstones down to nothing. */
  /* taller than before: the scene now has a far, mid and near layer, and
     they need vertical room to separate */
  height: clamp(190px, 26vw, 340px);
  margin-bottom: -1px;   /* kill the sub-pixel gap above the next section */
}

/* The .bat rules that were here are gone: there have been no .bat elements
   in the markup for a while, so they were three animations that could never
   run and a keyframe set nothing referenced. */

/* --- Product frame (the wrapper) --------------------------------------- */
.wrapper-frame {
  position: relative;
  /* This used to read `linear-gradient(160deg, var(--magenta), var(--purple-2))`,
     written when those variables really were magenta and purple. The palette
     moved to green and the names stayed, so the gradient quietly became dark
     green into slightly darker green - on a dark green page. That is where the
     flatness came from: it was not an absent design, it was a stale one.
     Literal values here, so a future palette change cannot do it again. */
  background:
    linear-gradient(158deg, #2A5647 0%, #16352C 48%, #0B1F19 100%);
  border: 3px solid var(--cream);
  border-radius: var(--radius);
  padding: 0.85rem;
  isolation: isolate;
}

/* A warm edge along the top-left, as if the same low sun lighting the
   graveyard were catching the frame. Cheap, and it stops the border reading
   as a flat sticker. */
.wrapper-frame::before {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: var(--radius);
  padding: 3px;
  background: linear-gradient(150deg,
              rgba(255,196,110,0.75), rgba(255,196,110,0) 42%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  z-index: 2;
}
.wrapper-frame__inner {
  position: relative;
  border-radius: 8px;
  overflow: hidden;
  aspect-ratio: 4 / 5;
  display: grid;
  place-items: center;
  /* A lit case rather than a flat panel: a warm pool behind where the sticks
     stand, darkness gathering in the corners, and an inset shadow so the
     opening has a lip. The product is the brightest thing in it, which is the
     only job this box has. */
  background:
    radial-gradient(ellipse 58% 44% at 50% 46%, rgba(255,170,72,0.30), transparent 72%),
    radial-gradient(ellipse 140% 70% at 50% 128%, rgba(0,0,0,0.58), transparent 62%),
    linear-gradient(180deg, #16342C 0%, #0A1C17 62%, #060F0C 100%);
  box-shadow:
    inset 0 2px 16px rgba(0,0,0,0.55),
    inset 0 -26px 46px rgba(0,0,0,0.42);
}

/* the pool of light the cluster is standing in */
.wrapper-frame__inner::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 9%;
  width: 62%;
  height: 8%;
  transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(ellipse at center,
              rgba(0,0,0,0.55), rgba(0,0,0,0) 70%);
  pointer-events: none;
}
.wrapper-frame img { width: 100%; height: 100%; object-fit: cover; }
.wrapper-frame__tag {
  position: absolute;
  z-index: 3;
  top: -0.85rem;
  left: 1.2rem;
  background: var(--lime);
  color: var(--night);
  font-family: var(--body);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0.25rem 0.7rem;
  border-radius: 999px;
}

/* Placeholder shown until real photography lands */
.ph {
  display: grid;
  place-items: center;
  gap: 0.5rem;
  text-align: center;
  padding: 2rem;
  color: var(--fg-faint);
  font-family: var(--body);
  font-weight: 700;
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.ph__icon { font-family: var(--display); font-size: 2.2rem; color: rgba(245,239,224,0.25); }

/* --- Generic section --------------------------------------------------- */
.section { padding-block: clamp(3rem, 7vw, 5rem); }
.section__head { max-width: 46ch; margin-bottom: clamp(1.8rem, 4vw, 2.8rem); }
.section__title {
  font-family: var(--display);
  font-size: var(--step-3);
  color: var(--cream);
  margin-top: 0.6rem;
  letter-spacing: 0.02em;
}
.section__lede { color: var(--fg-soft); margin-top: 1rem; max-width: 60ch; }

/* --- Steps (the mystery mechanic) -------------------------------------- */
.steps {
  display: grid;
  gap: 1.5rem;
  counter-reset: step;
  padding: 0;
  margin: 0;
  list-style: none;
}
@media (min-width: 48rem) { .steps { grid-template-columns: repeat(3, 1fr); } }
.step {
  background: rgba(35,80,68,0.18);
  border: 2px solid rgba(245,239,224,0.14);
  border-radius: var(--radius);
  padding: 1.4rem 1.3rem;
}
.step::before {
  counter-increment: step;
  content: counter(step);
  display: grid;
  place-items: center;
  width: 2.2rem;
  height: 2.2rem;
  border-radius: 999px;
  background: var(--orange);
  color: var(--night);
  font-family: var(--display);
  font-size: 1.1rem;
  margin-bottom: 0.8rem;
}
.step h3 { font-size: var(--step-1); font-family: var(--body); font-weight: 800; color: var(--cream); }
.step p { color: var(--fg-soft); margin-block: 0.5rem 0; }

/* --- Design grid ------------------------------------------------------- */
.designs {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.9rem;
}
@media (min-width: 40rem) { .designs { grid-template-columns: repeat(4, 1fr); } }
.design {
  aspect-ratio: 3 / 4;
  background: linear-gradient(160deg, rgba(35,80,68,0.35), rgba(13,31,26,0.5));
  border: 2px solid rgba(245,239,224,0.16);
  border-radius: 10px;
  display: grid;
  place-items: center;
  overflow: hidden;
}
.design img { width: 100%; height: 100%; object-fit: cover; }
.design .ph { padding: 0.75rem; font-size: 0.66rem; }
.design .ph__icon { font-size: 1.3rem; }

/* --- Pricing ----------------------------------------------------------- */
.prices {
  display: grid;
  gap: 1.2rem;
  grid-template-columns: 1fr;
}
@media (min-width: 46rem) { .prices { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 68rem) { .prices { grid-template-columns: repeat(4, 1fr); } }

.price {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  background: linear-gradient(170deg, rgba(35,80,68,0.3), rgba(13,31,26,0.55));
  border: 2px solid rgba(245,239,224,0.18);
  border-radius: var(--radius);
  padding: 1.5rem 1.3rem 1.3rem;
  position: relative;
}
.price--hero {
  border-color: var(--amber);
  background: linear-gradient(170deg, rgba(245,160,50,0.20), rgba(13,31,26,0.6));
  box-shadow: 0 10px 28px rgba(6,16,12,0.5);
}
.price__flag {
  position: absolute;
  top: -0.8rem;
  left: 1.1rem;
  background: var(--lime);
  color: var(--night);
  font-family: var(--body);
  font-weight: 800;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.25rem 0.7rem;
  border-radius: 999px;
}
.price__name {
  font-family: var(--body);
  font-weight: 800;
  font-size: var(--step-1);
  color: var(--cream);
}
.price__fig {
  font-family: var(--display);
  font-size: var(--step-3);
  color: var(--amber);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  margin-top: 0.35rem;
}
.price__each {
  font-family: var(--body);
  font-weight: 700;
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-faint);
  font-variant-numeric: tabular-nums;
}
.price__ship { font-size: var(--step--1); color: var(--fg-soft); margin-block: 0.55rem 0; }
.price__ship strong { color: var(--lime); }
.price .btn { margin-top: 1.2rem; }

/* --- Trust row --------------------------------------------------------- */
.trust {
  display: grid;
  gap: 1.3rem;
  grid-template-columns: repeat(2, 1fr);
  padding: 0;
  margin: 0;
  list-style: none;
}
@media (min-width: 52rem) { .trust { grid-template-columns: repeat(4, 1fr); } }
.trust li { border-left: 3px solid var(--orange); padding-left: 0.9rem; }
.trust b { display: block; color: var(--cream); font-size: var(--step-0); font-weight: 800; }
.trust span { color: var(--fg-soft); font-size: var(--step--1); }

/* --- Pre-order notice -------------------------------------------------- */
.preorder-note {
  display: none;               /* switched on by main.js when preOrder is true */
  align-items: flex-start;
  gap: 0.75rem;
  border: 2px solid var(--moss);
  background: rgba(168,198,63,0.12);
  border-radius: var(--radius);
  padding: 1rem 1.15rem;
  margin-top: 1.6rem;
  max-width: 60ch;
}
.preorder-note.is-on { display: flex; }
.preorder-note p { margin: 0; color: var(--fg-soft); font-size: var(--step--1); line-height: 1.7; }
.preorder-note strong {
  color: var(--lime);
  display: block;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-bottom: 0.3rem;
}

/* --- FAQ --------------------------------------------------------------- */
.faq { display: grid; gap: 0; max-width: 54rem; }
.qa { border-bottom: 2px solid rgba(245,239,224,0.14); }
.qa summary {
  cursor: pointer;
  list-style: none;
  padding-block: 1.2rem;
  font-weight: 800;
  color: var(--cream);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
}
.qa summary::-webkit-details-marker { display: none; }
.qa summary::after {
  content: "+";
  font-family: var(--display);
  color: var(--orange);
  font-size: 1.4rem;
  line-height: 1;
  flex: none;
}
.qa[open] summary::after { content: "\2013"; }
.qa p { color: var(--fg-soft); margin-block: 0 1.2rem; max-width: 62ch; }

/* --- Sticky buy bar (mobile) ------------------------------------------ */
/* the bar exists below 60rem, so that is where the page owes it clearance */
@media (max-width: 59.999rem) {
  :root { --cta-h: 4.25rem; }
}

.buybar {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.7rem 1rem calc(0.7rem + var(--safe-b));
  background: var(--purple-2);
  border-top: 3px solid var(--orange);
  transform: translateY(110%);
  transition: transform 220ms ease;
}
.buybar.is-visible { transform: translateY(0); }
.buybar__txt { font-size: 0.74rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg-soft); }
.buybar__txt b { display: block; font-size: 0.98rem; font-weight: 800; letter-spacing: 0; text-transform: none; color: var(--cream); }
.buybar .btn { padding-inline: 1.2rem; }
@media (min-width: 60rem) { .buybar { display: none; } }

/* --- Footer ------------------------------------------------------------ */
.site-foot {
  /* Bottom clearance is the bar's measured height plus the home-indicator
     inset plus a little breathing room - not a fixed 6rem, which was both
     arbitrary and blind to the safe area. */
  padding-block: clamp(2.5rem, 5vw, 4rem)
                 calc(clamp(1.5rem, 4vw, 2.5rem) + var(--cta-h) + var(--safe-b));
  color: var(--fg-faint);
  font-size: var(--step--1);
}
@media (min-width: 60rem) { .site-foot { padding-bottom: 3rem; } }
.site-foot a { color: var(--fg-soft); }
.foot__grid { display: flex; flex-wrap: wrap; gap: 1.5rem 2.5rem; justify-content: space-between; }
.foot__links { display: flex; flex-wrap: wrap; gap: 1.1rem; }
.warning {
  border: 2px solid rgba(245,239,224,0.16);
  border-left: 4px solid var(--orange);
  border-radius: 8px;
  padding: 0.9rem 1.1rem;
  margin-top: 2rem;
  max-width: 62ch;
  font-size: 0.78rem;
  line-height: 1.75;
  color: var(--fg-soft);
}
.warning strong { color: var(--amber); }

/* --- Simple prose pages (policies, success) ---------------------------- */
.prose { max-width: 62ch; padding-block: clamp(2.5rem, 6vw, 4.5rem); }
.prose h1 { font-family: var(--display); font-size: var(--step-3); color: var(--cream); letter-spacing: 0.02em; }
.prose h2 { font-family: var(--body); font-weight: 800; font-size: var(--step-1); color: var(--amber); margin-top: 2.4rem; }
.prose p, .prose li { color: var(--fg-soft); }
.prose ul { padding-left: 1.1rem; }
.prose li { margin-block: 0.45rem; }
.prose .stamp { margin-bottom: 0.8rem; }
.prose strong { color: var(--cream); }

/* Bare-tree branches in the graveyard scene are strokes, not fills */
/* The tree is a photograph now, so there are no strokes left to style. */

/* ==========================================================================
   ATMOSPHERE
   Storm clouds, lightning, wind, ghosts, cobwebs, crows, carved pumpkins.
   Every animation here is disabled by the prefers-reduced-motion block
   near the top of this file.
   ========================================================================== */

/* --- Storm clouds ------------------------------------------------------ */
/* Gone from here: .clouds / .cloud--back / --mid / --front and their `gust`
   drift, .bolt and `strike`, and .sky-flash with `skyflash`.

   The first three sets had no elements left in the markup at all - the sky is
   .cloudrow now - so they were dead rules with live keyframes attached.

   .sky-flash did have an element, and it was worse than dead: a
   full-hero-sized radial gradient pulsing to full opacity and back every
   seven seconds, forever. It was written as the sky wash for a lightning
   strike, and .bolt - the strike itself - no longer exists. So it was a flash
   with nothing flashing, compositing a layer the size of the viewport every
   seven seconds on a page whose whole brief is that the scenery holds still.

/* The three wind streaks that used to live here are gone. They were abstract
   swooshes rather than anything in the scene, and they were three more things
   moving in a hero that already has clouds, mist, leaves, ghosts, crows and a
   spider in it. Cutting them is the cheapest way to make what is left read as
   deliberate rather than as a page where everything wobbles. */

/* --- Cobwebs in the corners -------------------------------------------
   The .web__spokes / .web__ring / .web__loose rules that used to be here
   were dead: they styled the strokes of a DRAWN web, and the web became a
   sourced image a while back. An <img> has no children for them to match.

   The artwork is a TOP-RIGHT corner web - hub in its own upper right, sheet
   fanning down and left. So --r is the unflipped one and --l is the one that
   needs mirroring. That was the wrong way round, which is why the left web
   sat in open space with its fan cut off by the viewport edge.

   It also never flipped at all: `[data-parallax]` set `transform` at the same
   specificity later in this file, so it won outright and --r rendered
   unflipped - correct, by accident, for the wrong reason.

   Both are pulled up by the hero's own top padding so they meet the corner
   instead of hanging below it, and the left one is smaller and fainter
   because the headline runs underneath it. */
.webs {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}
.cobweb {
  position: absolute;
  top: clamp(-4rem, -5vw, -2rem);
  width: clamp(110px, 17vw, 230px);
  height: auto;
  opacity: 0.72;
  /* The silk is pale cream on a pale cloud deck, so this shadow is doing real
     work rather than decoration - without it the web vanishes into whatever
     happens to be drifting behind it. */
  filter: drop-shadow(0 1px 3px rgba(4,12,9,0.75));
}
/* :not(.spider) is load-bearing. The spider is also a direct img child of
   .cobweb--r, and this rule outranks `.spider { width: 30% }` on specificity
   - so without it the spider was laid out at 100% of the web, 230px wide,
   starting 24% in. That put its right edge 55px past the viewport and put a
   horizontal scrollbar on the whole document. */
.cobweb > img:not(.spider) { display: block; width: 100%; height: auto; }
.cobweb--l {
  left: 0;
  transform: scaleX(-1);
  /* Smaller than its twin, because BUTTER runs underneath it - but not by as
     much as it was, since it carries the spider now and the spider is a
     percentage of it. */
  width: clamp(96px, 14vw, 196px);
  opacity: 0.58;
}
.cobweb--r { right: 0; }

/* --- The spider -------------------------------------------------------
   It used to be painted into web-corner.webp, where it could be neither
   mirrored without mirroring the spider nor animated at all. It is its own
   element now, parked at the end of the dragline that is still in the web.

   The transform-origin is up at the TOP of that dragline, well outside the
   spider itself, so the swing is a long pendulum: a couple of degrees at the
   pivot is a few pixels at the spider, which is all a hanging spider does. */
.spider {
  position: absolute;
  left: 24%;
  top: 47%;
  width: 30%;
  height: auto;
  transform-origin: 43% -95%;
  animation: dangle 11s ease-in-out infinite;
  filter: drop-shadow(0 3px 5px rgba(4,10,8,0.6));
}
@keyframes dangle {
  0%, 100% { transform: rotate(-2.2deg); }
  50%      { transform: rotate(1.8deg); }
}
@media (max-width: 40rem) {
  /* at phone width the web is 110px across, so the spider is a 33px speck
     with an animation attached - not worth the paint */
  .spider { display: none; }
}

/* --- Ghost ------------------------------------------------------------- */
.ghost {
  position: absolute;
  z-index: 1;
  /* wider than the old cartoon: this one is billowing cloth, so it needs room
     to read as a shape rather than a smudge */
  width: clamp(92px, 13vw, 168px);
  height: auto;
  pointer-events: none;
  opacity: 0.82;
  mix-blend-mode: screen;   /* apparitions add light, they do not block it */
  animation: haunt 11s ease-in-out infinite;
}
.ghost--1 { top: 46%; right: 6%; }
.ghost--2 { top: 14%; right: 38%; width: clamp(62px, 8.5vw, 108px); animation-delay: 3.5s; opacity: 0.55; transform: scaleX(-1); }
@keyframes haunt {
  0%, 100% { transform: translate(0, 0) rotate(-2deg); opacity: 0.85; }
  50%      { transform: translate(-16px, -22px) rotate(3deg); opacity: 0.45; }
}
@media (max-width: 60rem) {
  .ghost--2 { display: none; }
  .ghost--1 { top: auto; bottom: 4%; right: 4%; opacity: 0.5; }
}
@media (max-width: 40rem) {
  /* `screen` forces the hero into an extra compositing pass to blend one
     small decorative image. Faking it with plain alpha costs one element's
     opacity and nothing else; at this size nobody can tell which it was. */
  .ghost { mix-blend-mode: normal; opacity: 0.34; }
}

/* --- Crows ------------------------------------------------------------- */
.crow-fly {
  position: absolute;
  z-index: 1;
  width: clamp(44px, 5.5vw, 76px);
  fill: #180C2C;
  pointer-events: none;
  animation: haunt 13s ease-in-out infinite;
}
.crow-fly--1 { top: 9%;  right: 27%; }
.crow-fly--2 { top: 23%; right: 11%; width: clamp(30px, 3.8vw, 52px); animation-delay: 2.2s; }
@media (max-width: 60rem) { .crow-fly { display: none; } }

/* --- Carved pumpkins in the graveyard ---------------------------------- */
.jack-glow { animation: emberpulse 4.5s ease-in-out infinite; transform-origin: center; }
.jack-glow--b { animation-delay: 1.6s; }
@keyframes emberpulse {
  0%, 100% { opacity: 0.45; }
  50%      { opacity: 0.85; }
}

/* The tree is STATIC now.

   It used to run `lean` about `transform-origin: 980px 160px`. The tree is
   drawn at x=150..382, so that origin was six hundred pixels to its right:
   rotating two and a half degrees about a point that far away does not sway
   a tree, it swings the whole thing bodily sideways across the fence. That
   is what looked wrong about it, and no amount of tuning the angle would
   have fixed it - the pivot was in the wrong place.

   `lean` itself is gone with it. The scarecrow was its only other user, and
   a scarecrow on a post does not sway either. */

/* ==========================================================================
   DEPTH
   Parallax layers, atmospheric haze, a 3D-tilting product frame and
   scroll reveals. All of it degrades to a flat, static page when the
   viewer asks for reduced motion (js/depth.js checks the same query).
   ========================================================================== */

/* The @property --py declaration, the [data-parallax] rule and the
   will-change hints that went with them are gone: js/depth.js no longer
   writes --py, because it no longer listens to scroll at all. See the header
   of that file for what the two listeners were costing.

   The [data-parallax] rule is also what was silently overriding
   `.cobweb--r { transform: scaleX(-1) }` - same specificity, later in the
   file - so removing it fixes the right-hand web as a side effect. */

@keyframes haunt {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(-2deg); opacity: 0.85; }
  50%      { transform: translate3d(-16px, -22px, 0) rotate(3deg); opacity: 0.45; }
}

/* --- The moon: furthest layer, moves least -------------------------- */
.moon {
  position: absolute;
  /* Right-hand sky: the hero copy runs down the left, so the moon would
     otherwise sit behind the lede and wash it out. */
  top: 3%;
  right: 8%;
  width: clamp(90px, 13vw, 210px);
  aspect-ratio: 1;
  border-radius: 50%;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(circle at 36% 32%, #FFF6DC 0%, #F6E4B4 46%, #D9BE86 100%);
  box-shadow:
    0 0 60px 18px rgba(255, 236, 186, 0.22),
    0 0 160px 60px rgba(255, 220, 150, 0.12);
}
/* craters */
.moon::before,
.moon::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  background: rgba(180, 152, 106, 0.35);
}
.moon::before { width: 22%; height: 22%; top: 24%; left: 52%; }
.moon::after  { width: 14%; height: 14%; top: 58%; left: 30%; }

/* --- Atmospheric haze: sells distance between the layers ------------- */
.haze {
  position: absolute;
  inset-inline: 0;
  height: 42%;
  bottom: 0;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(180deg, transparent, rgba(13, 31, 26, 0.55) 70%, rgba(6, 16, 12, 0.85));
}

/* Far cloud layer sits slightly out of focus - cheap, convincing depth */
.cloud--back { filter: blur(2.5px); }
.cloud--mid  { filter: blur(0.8px); }

/* --- Fog rolling through the graveyard ------------------------------- */
.fog {
  position: absolute;
  inset-inline: -10%;
  bottom: 0;
  height: 120px;
  z-index: 2;
  pointer-events: none;
  background: radial-gradient(ellipse 60% 100% at 30% 100%, rgba(190, 226, 200, 0.16), transparent 70%),
              radial-gradient(ellipse 50% 100% at 75% 100%, rgba(190, 226, 200, 0.12), transparent 70%);
  /* Static. This lies across the bottom of the graveyard, directly over the
     straw, and a full-width layer sliding back and forth there is read as the
     GROUND moving - which is the one thing scenery must not do. The same goes
     for .mist at the foot of the hero. `roll` has no users left. */
}

/* --- Product frame: real 3D tilt ------------------------------------- */
.frame-stage { perspective: 1100px; }
.wrapper-frame {
  transform-style: preserve-3d;
  transition: transform 260ms cubic-bezier(.2,.7,.3,1), box-shadow 260ms ease;
  box-shadow:
    0 2px 6px rgba(4, 12, 9, 0.4),
    0 12px 28px rgba(4, 12, 9, 0.45),
    0 34px 70px rgba(4, 12, 9, 0.5);
}
.frame-stage:hover .wrapper-frame {
  box-shadow:
    0 4px 10px rgba(4, 12, 9, 0.45),
    0 20px 42px rgba(4, 12, 9, 0.5),
    0 52px 96px rgba(4, 12, 9, 0.55);
}
/* the label and the photo lift off the card face */
.wrapper-frame__tag  { transform: translateZ(46px); }
.wrapper-frame__inner { transform: translateZ(18px); }

/* --- Scroll reveal ----------------------------------------------------
   Gated behind .js (set by an inline script in <head>). If JavaScript is
   blocked or depth.js throws, none of this applies and every block renders
   normally - content must never be able to get stuck invisible. */
.js .reveal {
  opacity: 0;
  transform: translate3d(0, 26px, 0);
  transition: opacity 700ms cubic-bezier(.2,.7,.3,1),
              transform 700ms cubic-bezier(.2,.7,.3,1);
}
.js .reveal.is-in { opacity: 1; transform: none; }
.js .reveal:nth-child(2) { transition-delay: 80ms; }
.js .reveal:nth-child(3) { transition-delay: 160ms; }

/* Cards gain depth rather than flat borders */
.price, .step {
  box-shadow: 0 2px 4px rgba(4,12,9,0.3), 0 10px 26px rgba(4,12,9,0.34);
  transition: transform 220ms cubic-bezier(.2,.7,.3,1), box-shadow 220ms ease;
}
.price:hover, .step:hover {
  transform: translateY(-4px);
  box-shadow: 0 4px 8px rgba(4,12,9,0.34), 0 18px 44px rgba(4,12,9,0.42);
}
.price--hero:hover { transform: translateY(-6px); }

/* No motion requested: everything sits still and fully visible.

   The global rule near the top of this file already crushes every animation
   to 0.01ms, which stops things moving but leaves them wherever frame one
   happens to put them. These rules say where frame one should BE, and switch
   off the decoration that only exists to move. A leaf frozen mid-air at the
   top of the hero is not a design; it is an animation someone turned off. */
@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1; transform: none; transition: none; }
  .price:hover, .step:hover, .frame-stage:hover .wrapper-frame { transform: none; }

  /* Things whose whole purpose is movement: gone rather than frozen. */
  .leaves,
  .spider,
  .ghost,
  .crow-fly { display: none; }

  /* Things that should simply stop where they are and still look right. */
  .cloudrow { animation: none; }
  .worm     { animation: none; transform: none; }
  .jack-glow { animation: none; opacity: 0.62; }
  .raven__wing { animation: none; }
  .showcase__item { animation: none; opacity: 1; }
  .marquee__track { animation: none; }
  .marquee__pumpkins img { animation: none; }
}

/* On narrow screens the hero is single-column and the lede runs full width,
   so the moon has to move up and shrink to stay out of the text. */
@media (max-width: 48rem) {
  .moon {
    top: 0.5%;
    right: 5%;
    width: clamp(64px, 19vw, 92px);
    opacity: 0.85;
  }
}

/* ==========================================================================
   REALISTIC SKY
   Clouds are fractal-noise turbulence rather than drawn circles - that is
   what buys the soft, wispy edges a shape-based cloud can never have.
   The filter rasterises once; only cheap transforms are animated over it.

   TO USE A REAL PHOTO INSTEAD: drop a file at img/bg-graveyard.jpg and set
       .sky-stage { background-image: url("../img/bg-graveyard.jpg"); }
       .sky { display: none; }
   Everything else keeps working.
   ========================================================================== */

.sky-stage {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  background-size: cover;
  background-position: center bottom;
}
.sky { width: 100%; height: 100%; display: block; }

/* Cloud decks: two baked tiles, two speeds.

   Each row is 200% wide and holds two copies of one seamless tile, so sliding
   it by exactly -50% lands copy two where copy one started and the loop
   closes on itself. That is why this can be `linear infinite` instead of the
   out-and-back alternate the turbulence version needed - live fractal noise
   has no period, so a loop that reset to zero snapped every time round.

   One animation per element, and it is the only thing either element does.
   No will-change: translate3d in the keyframes already gets these onto the
   compositor, and two permanently-promoted full-width layers is exactly the
   kind of standing cost will-change is not supposed to be spent on. */
.cloudrow {
  position: absolute;
  left: 0;
  width: 200%;
  display: flex;
  pointer-events: none;
  animation: cloudDrift linear infinite;
}
.cloudrow img {
  width: 50%;
  height: 100%;
  object-fit: fill;   /* soft noise, deliberately stretched to the band */
}
.cloudrow--far  { top: 2%;  height: 44%; opacity: 0.34; animation-duration: 155s; }
.cloudrow--near { top: 13%; height: 36%; opacity: 0.26; animation-duration: 98s; }

@keyframes cloudDrift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

/* Ground mist sitting at the base of the sky stage */
.mist {
  position: absolute;
  inset-inline: -5%;
  bottom: -2%;
  height: 34%;
  z-index: 1;
  pointer-events: none;
  background:
    radial-gradient(ellipse 55% 100% at 22% 100%, rgba(159,216,180,0.13), transparent 72%),
    radial-gradient(ellipse 45% 100% at 62% 100%, rgba(159,216,180,0.10), transparent 72%),
    radial-gradient(ellipse 60% 100% at 88% 100%, rgba(159,216,180,0.09), transparent 72%);
}

/* --- Falling leaves ----------------------------------------------------
   Six leaves, three shapes, and no two behaving alike: each carries its own
   --x column, --dx drift, --dur fall time, --delay phase, --w size, --o
   opacity and --spin total rotation, so nothing is shared and nothing lines
   up. The old set was five copies of one teardrop, all drifting the same
   70px and all spinning 520 degrees, which is what made it read as a
   particle effect rather than as weather.

   The fall is on the <span> and the spin is on the <svg> inside it. Both
   animate `transform`, and two transform animations on ONE element do not
   compose - the later one wins and the other is silently dropped. That is
   the same trap that had the cloud drift cancelled for months.

   The spin keyframes also pinch scaleX at the quarter points, which is a
   leaf turning edge-on to the wind. One animation doing both, because a
   second one here would hit that same rule.

   Nothing in here can be clicked (the container is pointer-events:none and
   sits at z-index 2, under .hero > .wrap at 3), and nothing can widen the
   page: .leaves is inset:0 with overflow:hidden. */
.leaves {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  overflow: hidden;
}
.leaf {
  position: absolute;
  top: -9%;
  left: var(--x);
  width: var(--w);
  opacity: 0;
  animation: fall var(--dur) linear infinite;
  animation-delay: var(--delay);
}
.leaf > svg {
  display: block;
  width: 100%;
  height: auto;
  animation: leafspin var(--dur) linear infinite;
  animation-delay: var(--delay);
}
.leaf path { fill: currentColor; }
.leaf .leaf__vein {
  fill: none;
  stroke: #2B1405;
  stroke-width: 1.1;
  stroke-linecap: round;
  opacity: 0.32;
}
.leaf--maple { color: #C4581A; }
.leaf--oak   { color: #9A4413; }
.leaf--ovate { color: #D98426; }
.leaf:nth-child(2n)   { color: #B14E15; }
.leaf:nth-child(3n+1) { color: #E08A22; }

@keyframes fall {
  0%   { transform: translate3d(0, 0, 0); opacity: 0; }
  7%   { opacity: var(--o); }
  86%  { opacity: var(--o); }
  100% { transform: translate3d(var(--dx), 106vh, 0); opacity: 0; }
}
@keyframes leafspin {
  0%   { transform: rotate(0deg)                       scaleX(1); }
  23%  { transform: rotate(calc(var(--spin) * 0.23))   scaleX(0.48); }
  50%  { transform: rotate(calc(var(--spin) * 0.5))    scaleX(1); }
  74%  { transform: rotate(calc(var(--spin) * 0.74))   scaleX(0.66); }
  100% { transform: rotate(var(--spin))                scaleX(1); }
}
/* Four on a phone, not six. They are decoration, and the smallest two were
   never legible at 375px anyway. */
@media (max-width: 48rem) { .leaf:nth-child(n+5) { display: none; } }

/* ==========================================================================
   THE REEL - swipeable mystery-bundle browser
   Native CSS scroll-snap does the swiping, so touch drag works with no
   library and no JS. The buttons and dots are progressive enhancement.
   ========================================================================== */

.reel { position: relative; margin-top: clamp(1.5rem, 4vw, 2.5rem); }

.reel__track {
  display: flex;
  gap: clamp(1rem, 3vw, 2rem);
  margin: 0;
  padding: 0 0 1.25rem;
  list-style: none;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}
.reel__track::-webkit-scrollbar { display: none; }

.reel__item {
  flex: 0 0 min(78%, 320px);
  scroll-snap-align: center;
  scroll-snap-stop: always;
}
@media (min-width: 60rem) { .reel__item { flex-basis: 300px; } }

/* --- The plinth: a weathered tombstone the squishy rests on ------------ */
.plinth { display: flex; flex-direction: column; align-items: center; }

/* Shared stage: the stone fills it, the artwork sits in front, near the base */
.plinth__scene {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 5;
  display: grid;
  place-items: end center;
}
.plinth__stone {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  /* contain, anchored to the bottom: these are real stones with real
     proportions, so letting each one keep its own width is what stops the
     row looking stamped out. They stand on the card's baseline. */
  object-fit: contain;
  object-position: 50% 100%;
  filter: drop-shadow(0 14px 20px rgba(4,12,9,0.62));
}
/* every other card mirrored, so neighbours never read as the same stone */
.plinth__stone[data-flip] { transform: scaleX(-1); }
/* The artwork used to be a framed photo card, because the images were photo
   cutouts on a background. They are now transparent renders, so the squishy
   can just stand on the stone - which is what the tombstone was drawn for.
   No frame, no crop: the stage is the stick's own proportions and the image
   is contained inside it. */
.plinth__art {
  position: relative;
  z-index: 1;
  width: 42%;
  aspect-ratio: 46 / 100;      /* the stick's real proportions */
  margin-bottom: 7%;
  display: grid;
  place-items: end center;
  transition: transform 300ms cubic-bezier(.2,.7,.3,1);
}
.plinth__art img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* grounds it against the stone instead of floating over it */
  filter: drop-shadow(0 12px 14px rgba(4,12,9,0.62));
}

/* the placeholder still wants the old card treatment, since it has no art */
.plinth__art .ph {
  width: 100%;
  height: 100%;
  border-radius: 10px;
  background: linear-gradient(165deg, #1B3A30, #0C1C17);
  border: 2px solid rgba(245,239,224,0.3);
  box-shadow: 0 14px 30px rgba(4,12,9,0.7);
  display: grid;
  place-items: center;
}
.reel__item:hover .plinth__art { transform: translateY(-8px) scale(1.03); }

.plinth__label { margin-top: 0.9rem; text-align: center; }
.plinth__label b {
  display: block;
  font-weight: 800;
  color: var(--cream);
  font-size: var(--step-0);
}
.plinth__label span { font-size: var(--step--1); color: var(--fg-faint); }

/* --- Arrows ------------------------------------------------------------ */
.reel__nav {
  position: absolute;
  top: 38%;
  z-index: 3;
  width: 46px;
  height: 46px;
  border-radius: 999px;
  border: 2px solid rgba(245,239,224,0.35);
  background: rgba(8,18,15,0.78);
  color: var(--cream);
  font-size: 1.4rem;
  line-height: 1;
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: background 150ms ease, border-color 150ms ease, opacity 150ms ease;
}
.reel__nav:hover { background: var(--orange); border-color: var(--cream); color: var(--night); }
.reel__nav[disabled] { opacity: 0.3; cursor: default; }
.reel__nav--prev { left: -6px; }
.reel__nav--next { right: -6px; }
@media (min-width: 60rem) {
  .reel__nav--prev { left: -22px; }
  .reel__nav--next { right: -22px; }
}
/* Touch devices swipe; the arrows would only get in the way */
@media (hover: none) { .reel__nav { display: none; } }

/* --- Dots -------------------------------------------------------------- */
.reel__dots {
  display: flex;
  justify-content: center;
  gap: 0;                 /* the gap lives INSIDE each button now - see below */
  margin: 0;
  padding: 0;
  list-style: none;
}
/* The dot you see is 9px. The thing you TAP is not.

   These were 9x9 buttons with an 8px gap, which is a 9px-wide target on a
   phone - well under the 24x24 minimum, and about a fifth of the width of a
   fingertip. Twelve of them in a row meant the odds of hitting the one you
   aimed at were poor, on the control that browses the entire product range.

   The button is now a transparent 26x40 hit area and the dot is drawn by
   ::before, so the target grew without the dots visibly changing size. 26px
   rather than 44: twelve 44px targets is 528px and would not fit a 375px
   phone at all. Twelve of these is 312px, which does, and 26 clears the
   minimum with room. */
.reel__dots button {
  width: 26px;
  height: 40px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  display: grid;
  place-items: center;
  -webkit-tap-highlight-color: transparent;
}
.reel__dots button::before {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 999px;
  background: rgba(245,239,224,0.3);
  transition: background 180ms ease, transform 180ms ease;
}
/* the scale goes on the DOT, not the button - scaling the button would scale
   the hit area and overlap its neighbours */
.reel__dots button[aria-current="true"]::before {
  background: var(--orange);
  transform: scale(1.45);
}
.reel__dots button:focus-visible { outline: 2px solid var(--amber); outline-offset: -4px; border-radius: 6px; }

.reel__hint {
  text-align: center;
  margin-top: 0.9rem;
  font-size: var(--step--1);
  color: var(--fg-faint);
}
@media (hover: hover) { .reel__hint--swipe { display: none; } }
@media (hover: none)  { .reel__hint--drag  { display: none; } }

/* Placeholder inside the plinth needs more contrast than the page default */
.plinth__art .ph { color: rgba(245,239,224,0.55); font-size: 0.6rem; padding: 0.5rem; }
.plinth__art .ph__icon { font-size: 1.1rem; color: rgba(245,239,224,0.3); }

/* ==========================================================================
   CART
   Slide-over drawer, styled from the same tokens as the rest of the site.
   ========================================================================== */

/* Header cart button + count badge */
.cartbtn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.5rem 0.9rem;
  border-radius: 999px;
  border: 2px solid rgba(245,239,224,0.3);
  background: rgba(8,18,15,0.55);
  color: var(--cream);
  font-family: var(--body);
  font-weight: 800;
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color 150ms ease, background 150ms ease;
}
.cartbtn:hover { border-color: var(--orange); background: rgba(232,118,27,0.14); }
.cartbtn__count {
  display: grid;
  place-items: center;
  min-width: 1.35rem;
  height: 1.35rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: var(--orange);
  color: var(--night);
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
}

/* Backdrop */
.cart__backdrop {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(4,12,9,0.66);
  opacity: 0;
  visibility: hidden;
  transition: opacity 220ms ease, visibility 220ms ease;
}
.cart__backdrop.is-open { opacity: 1; visibility: visible; }

/* Drawer */
.cart {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 100;
  width: min(100%, 26rem);
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, #12241E, #08120F);
  border-left: 2px solid rgba(245,239,224,0.16);
  box-shadow: -18px 0 50px rgba(4,12,9,0.6);
  transform: translateX(100%);
  transition: transform 260ms cubic-bezier(.2,.7,.3,1);
}
.cart.is-open { transform: translateX(0); }

.cart__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.15rem 1.25rem;
  border-bottom: 2px solid rgba(245,239,224,0.14);
}
.cart__head h2 {
  font-family: var(--display);
  font-size: var(--step-1);
  color: var(--cream);
  letter-spacing: 0.02em;
}
.cart__close {
  background: none;
  border: 0;
  color: var(--fg-soft);
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  padding: 0.2rem 0.4rem;
  border-radius: 6px;
}
.cart__close:hover { color: var(--cream); }

.cart__body { flex: 1 1 auto; overflow-y: auto; padding: 0.5rem 1.25rem; }
.cart__empty { color: var(--fg-faint); padding: 2rem 0; text-align: center; }

.cart__line {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "main  total"
    "qty   remove";
  gap: 0.5rem 1rem;
  align-items: center;
  padding: 1rem 0;
  border-bottom: 1px solid rgba(245,239,224,0.12);
}
.cart__lineMain { grid-area: main; }
.cart__lineMain b { display: block; color: var(--cream); font-weight: 800; }
.cart__lineMain span { font-size: var(--step--1); color: var(--fg-faint); }
.cart__lineTotal {
  grid-area: total;
  color: var(--amber);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.cart__qty {
  grid-area: qty;
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  border: 2px solid rgba(245,239,224,0.22);
  border-radius: 999px;
  padding: 0.1rem;
}
.cart__qty button {
  width: 1.9rem;
  height: 1.9rem;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--cream);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}
.cart__qty button:hover { background: rgba(232,118,27,0.25); }
.cart__qty span {
  min-width: 1.8rem;
  text-align: center;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--cream);
}

.cart__remove {
  grid-area: remove;
  justify-self: end;
  background: none;
  border: 0;
  color: var(--fg-faint);
  font-size: var(--step--1);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.cart__remove:hover { color: var(--orange); }

.cart__foot {
  flex: 0 0 auto;
  padding: 1.15rem 1.25rem calc(1.15rem + env(safe-area-inset-bottom));
  border-top: 2px solid rgba(245,239,224,0.14);
  background: rgba(4,12,9,0.5);
}
.cart__row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-size: var(--step--1);
  color: var(--fg-soft);
  margin-bottom: 0.4rem;
}
.cart__row span:last-child { font-variant-numeric: tabular-nums; }
.cart__row--total {
  font-size: var(--step-0);
  font-weight: 800;
  color: var(--cream);
  margin-top: 0.7rem;
  padding-top: 0.7rem;
  border-top: 1px solid rgba(245,239,224,0.16);
}
.cart__note { font-size: 0.74rem; color: var(--fg-faint); margin: 0.7rem 0 0.9rem; }

.cart__error {
  margin: 0 0 0.8rem;
  padding: 0.7rem 0.85rem;
  border-radius: 8px;
  border: 1px solid rgba(232,118,27,0.5);
  background: rgba(232,118,27,0.12);
  color: #FFD3A8;
  font-size: var(--step--1);
}

/* Checkout button fills the drawer foot */
.cart__foot .btn { width: 100%; }
.cart__foot .btn[disabled] { opacity: 0.6; cursor: default; }

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

/* Header right-hand cluster: stock stamp + cart button */
.site-head__right { display: flex; align-items: center; gap: 0.9rem; }
@media (max-width: 30rem) {
  .site-head__right .stamp { display: none; }   /* cart wins the space on small phones */
}

/* ==========================================================================
   MID-PAGE: THE SCARECROW
   Sits in the warm zone, between the mystery section and the reel. Bleeds
   off the right edge so it reads as part of the landscape rather than an
   illustration dropped into a box.
   ========================================================================== */

/* THE GAP.

   There was a wide flat band of dark green between the bottom of the video
   and the top of the cliff - about 170px of nothing at desktop width. Two
   things made it, and only fixing both closes it:

     The video's own mask fades it out from 84% to 100% of its height, which
     at a 706px-tall film is 113px of deliberate dissolve. That is correct -
     it is how the film joins the page - but the dissolve was landing on
     empty background instead of on anything.

     The cliff then started BELOW that, because it was `height:auto` on a
     2400x610 image: 362px of picture in a 420px panel, leaving another 58px
     of nothing above it.

   So the panel is pulled up into the film's dissolve, and the cliff now
   fills the panel instead of sitting at the bottom of it. The film fades
   down onto the clifftop, which is what the fade was for. */
.scarecrow {
  position: relative;
  /* On a phone the 31vw term was only ~116px, so this always pinned to its
     floor while the ground image rendered just 95px tall. The floor tracks
     the viewport now instead of being a desktop constant. */
  /* THE OVERLAP IS MEASURED FROM THE ARTWORK, not from the video.

     The previous version matched the overlap to the film's fade, which was
     the wrong thing to match. The cliff photograph is not solid at its top
     edge: its upper quarter is a ragged outcrop against transparency, and it
     does not reach 70% coverage - the ridge line - until 25% of its own
     height. So aligning the PANEL top with the video's fade put the RIDGE a
     quarter of a panel lower, and that quarter was the dark band: at 1440 the
     video was gone by 243px and the ridge did not arrive until 257, with
     nothing but sparse outcrop between 200 and 321.

     Overlapping by the sky fraction instead puts the ridge itself where the
     panel used to start, so the cliff's own silhouette meets the fading
     video rather than trailing a quarter-panel behind it. The extra 0.06
     pushes the ridge a little further up INTO the fade so the two interlock
     instead of merely touching.

     Both terms are fractions of the panel's own height, so this holds at
     every width with no breakpoint - which is what the last two attempts at
     this were missing. */
  /* The 420px cap was covering a third of the film.

     Overlap is forced by geometry, not taste. The panel's top must sit far
     enough above the video's bottom that the cliff's SOLID edge still clears
     the film's bottom dissolve, and the solid edge is 42.9% of the panel down
     (measured, see --sky). That gives:

         overlap = 0.429 * panel-h + clearance + bottom-fade

     so the only two terms that can shrink the coverage are the panel and the
     fade. --seal cannot: dropping it moves the whole panel down, the ridge
     with it, and the dissolve reappears above the rock - which is the exact
     failure the --seal note below was written after.

     420 -> 340 takes 36px off the overlap, and the shorter .gravefilm::after
     takes another 7. At 1440 that is 227px of covered film down to 184 - the
     bottom row of butter is clear of the dirt instead of buried in it. The
     clifftop is correspondingly lower, which is the trade. */
  --panel-h: clamp(168px, 46vw, 340px);
  /* How much of the cliff is sky, measured off the asset - and measured at
     the point the ridge is SOLID, not at the point it first appears.

     This was 0.31, the row where coverage first passes 50%. But the edge is
     irregular by design, so at 50% coverage half the width is still holes:
     the dips between the high points had nothing in them, and on the right,
     where the deepest dip happens to fall, that left an unsealed notch
     between the video and the dirt. Coverage does not reach 97% until 39%
     of the asset's height, so that is where it actually seals.

     Re-measure if the asset is rebuilt - scratchpad/ridge.py prints the
     coverage profile, and the number to take is the 97% row. */
  --sky: 0.40;
  /* How far the cliff's SOLID edge is pushed up past the film's bottom, as a
     fraction of the panel. It has to clear .gravefilm::after, the film's
     bottom dissolve - anything less and the top of that dissolve shows above
     the rock as a green-black wedge, because the dissolve runs to #0A1310.
     0.06 was not enough: it cleared the film's edge but not its fade.

     It stays 0.14 through the panel-h change on purpose. Clearance scales
     with the panel, so a shorter panel needs a LARGER fraction to hold the
     same pixel gap - 340px at 0.14 leaves 14.7px of fully-solid rock above
     the dissolve, against 14.9px at 420px. Same seal, less coverage; the
     shorter .gravefilm::after is what paid for it. Measure, don't nudge:
     the margin here is pixels, not percentages. */
  --seal: 0.14;
  height: var(--panel-h);
  margin-top: calc(var(--panel-h) * (var(--sky) + var(--seal)) * -1);
  pointer-events: none;
  overflow: hidden;
  /* Above the film's dissolves.

     .gravefilm::after carries z-index:1 and .scarecrow__ground carries
     z-index:0, and neither .gravefilm nor .nightband establishes a stacking
     context - position:relative with z-index:auto does not - so the two were
     competing in the SAME context and the dissolve won. The film's bottom
     fade was painting over the cliff and cutting it off in a dead-straight
     line exactly at the video's bottom edge, which is where the green line
     came from. The irregular ridge was there the whole time, underneath it.

     A z-index here also makes this element a stacking context, so __ground
     and __fig sort against each other locally and cannot drift out again. */
  z-index: 2;
  /* Dark sky behind the cliff's ridge, and dark earth under its base.

     The cliff photograph is only solid from 44% of its own height down - the
     top of it is a ragged ridge line against transparency, which is what
     makes it read as a clifftop rather than a rectangle. Behind that ridge
     the panel has to be night, or the page gradient shows through the gaps
     as teal.

     It stays transparent down to 29%, though, because the film overlaps this
     panel by about 130px and anything opaque up there would cut the bottom
     off the video. */
  /* Only the earth under the cliff's base, and only as an overlay - the
     night behind the ridge comes from .nightband. Transparent at the top,
     which it must be: this element overlaps the video by design. */
  background: linear-gradient(to bottom,
              rgba(16,13,8,0) 0%,
              rgba(16,13,8,0) 58%,
              rgba(16,13,8,0.55) 86%,
              rgba(18,12,6,0.85) 100%);
}
.scarecrow__fig {
  position: absolute;
  right: 4%;
  /* Lifted so the feet disappear into the clifftop instead of ending in
     mid-air at the panel edge. 62% rather than the 80% the SVG used: that
     viewBox carried empty margin around the drawing and the photograph is
     cropped tight to the figure, so the same number renders a much bigger
     scarecrow. */
  bottom: 21%;
  height: 62%;
  width: auto;

  /* STATIC. A scarecrow is a coat nailed to a post driven into the ground -
     the one thing in this scene that certainly does not move - and when it
     ran the same `lean` the tree did it was the largest moving object on the
     page.

     Full opacity, and a shadow under it. At 0.94 over a dark cliff it was a
     dark shape on a dark shape with nothing separating the two. The
     drop-shadow is what grounds it: a figure with no shadow on a surface it
     is supposed to be standing on always looks pasted. Static filter on a
     static element - rasterised once, never animated. */
  opacity: 1;
  filter: drop-shadow(0 8px 12px rgba(0,0,0,0.78))
          drop-shadow(0 1px 0 rgba(255,214,150,0.10));
  z-index: 1;
}

/* The ground he stands on. It bleeds past both edges so the land keeps going
   rather than stopping at the viewport.

   `cover` at every width, not just below 40rem. The image is 2400x610 - 3.9:1
   - so laid out by width it is always far shorter than the panel, at every
   breakpoint, and the leftover was the dead space. Cropping it instead of
   letterboxing it means the panel is full of cliff whatever the viewport is
   doing.

   Feathered at BOTH ends now. The base still dissolves into the dark below,
   as it did; the top is new, and it is what lets the clifftop rise out of
   the video's own fade-out rather than starting on a cut line. */
.scarecrow__ground {
  position: absolute;
  left: -2%;
  bottom: 0;
  width: 104%;
  /* max-width: none is load-bearing.

     The global `img { max-width: 100% }` near the top of this file was
     clamping this to 100% of the panel while `left: -2%` still shifted it
     left - so the right edge finished 19px short of the panel and there was a
     transparent strip down that side for the whole life of this element. That
     is the "not sealed on the side" gap: not the artwork, a base rule quietly
     overriding the width. */
  max-width: none;
  height: 100%;
  object-fit: cover;
  object-position: 50% 32%;
  z-index: 0;
  pointer-events: none;
  /* NO top fade. It used to run `transparent 0%, #000 12%`, which did two
     bad things: it rubbed out the outcrop peak that is the most interesting
     part of the silhouette, and it replaced a photographed, irregular ridge
     with a dead-straight horizontal gradient edge. The asset already has an
     alpha channel and that alpha IS the irregular upper edge - it just has to
     be left alone.

     The bottom feather stays, but it starts at 86% rather than 68%: a third
     of the rock was dissolving away exactly where the scarecrow stands. */
  /* A LONG, EASED tail rather than a short linear one.

     86% -> 100% is a 59px fade at desktop, and a straight ramp over 59px
     still reads as an edge: the texture contrast collapses too fast and the
     eye finds the line where it started. Holding solid to 64% and then
     easing out over the remaining third lets the dirt decay into the dark
     instead of stopping in it. Compared side by side offline against the
     short version and a longer one; this keeps the most rock while losing
     the line. */
  -webkit-mask-image: linear-gradient(to bottom,
      #000 0%, #000 64%, rgba(0,0,0,0.72) 78%,
      rgba(0,0,0,0.34) 89%, rgba(0,0,0,0.10) 96%, transparent 100%);
  mask-image: linear-gradient(to bottom,
      #000 0%, #000 64%, rgba(0,0,0,0.72) 78%,
      rgba(0,0,0,0.34) 89%, rgba(0,0,0,0.10) 96%, transparent 100%);
}
@media (max-width: 640px) {
  /* cliff-run-sm.webp is already cropped back to its ridge, so there is far
     less sky above it and the panel needs correspondingly less overlap. */
  .scarecrow { --sky: 0.30; }   /* cliff-run-sm.webp is 97% solid at 30% */
}
/* Past about 1590px the panel is wider, proportionally, than cliff-run.webp
   is (3.93:1), so object-fit:cover stops cropping the sides and starts
   scaling the whole asset up and cropping it vertically instead. That makes
   the sky band TALLER in panel pixels even though 32% of the excess comes off
   the top - 40% of the asset becomes an effective 42% at 1920 and 45% at
   2560 - and every one of those pixels eats into the seal. Give it back. */
@media (min-width: 100rem) {
  /* 0.19 -> 0.23 alongside the shorter panel.

     Clearance is seal * panel-h in PIXELS, so shrinking the panel shrinks
     the gap this breakpoint exists to protect: measured at 2560, 340px at
     0.19 put the solid rock 0.4px BELOW the top of the film's dissolve -
     unsealed, where 420px at 0.19 had 11px to spare. 0.23 restores it to
     13px, and the panel is still short enough that 2560 covers 214px of
     film rather than 248.

     Measured with the asset's own alpha profile, not by eye: the row where
     coverage reaches 99.9% is what has to clear the fade, and at these
     widths object-fit:cover is scaling the asset up, which moves that row
     further down the panel than --sky alone predicts. */
  .scarecrow { --seal: 0.23; }
}
@media (max-width: 40rem) {
  /* Stays at full opacity here too. It was 0.8, which on a 179px panel made
     an already-dark figure disappear into an already-dark cliff - the exact
     "can't actually see it" complaint. The drop-shadow does the separating. */
  .scarecrow__fig { right: -6%; }
}


/* ==========================================================================
   FOOT OF THE PAGE: TURNED EARTH
   A single soil photograph, masked so it rises out of the dark instead of
   beginning at a hard rectangle edge. Two soil sources at different scales
   is what produced the visible seam before.
   ========================================================================== */

.earthzone {
  position: relative;
  isolation: isolate;
  /* Was clamp(150px, 20vw, 280px) - 280px of empty dirt above the footer at
     desktop, which is what made the bottom of the page read as a big black
     slab with the footer parked at the very end of it. The soil still needs
     room to fade up out of the dark, but it needs about half that: the fade
     itself is 250px and most of it is doing its work behind the footer's own
     top padding. */
  padding-top: clamp(96px, 11vw, 168px);
}

.earthzone::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  /* The wash over the soil used to reach rgba(5,3,2,0.99) - effectively
     opaque black - by the bottom, so the last third of the page was a flat
     black rectangle with the texture buried under it. It stops at 0.90 now
     and gets there later, which keeps real dirt visible all the way down
     while still being dark enough to read footer text against. */
  background:
    linear-gradient(180deg, rgba(42,25,10,0.30) 0%, rgba(15,9,4,0.78) 48%, rgba(7,4,2,0.90) 100%),
    url("../img/earth-soil.jpg") center center / cover no-repeat;
  /* Fades up out of the dark at the top - the fade IS the transition, no
     drawn mound and nothing to misalign - and now also eases off over the
     last 90px so the soil runs out naturally instead of stopping dead on the
     bottom edge of the document. */
  -webkit-mask-image: linear-gradient(180deg,
      transparent 0, rgba(0,0,0,0.18) 60px, rgba(0,0,0,0.62) 140px,
      #000 250px, #000 calc(100% - 90px), rgba(0,0,0,0.55) 100%);
  mask-image: linear-gradient(180deg,
      transparent 0, rgba(0,0,0,0.18) 60px, rgba(0,0,0,0.62) 140px,
      #000 250px, #000 calc(100% - 90px), rgba(0,0,0,0.55) 100%);
}

/* Worms sit in the soil, above the texture, below the footer text */
.worms-layer {
  position: absolute;
  inset-inline: 0;
  /* follows the reduced padding above it, so the worms stay in the band of
     soil between the fade-in and the footer text rather than drifting down
     behind the links */
  top: clamp(72px, 8.5vw, 132px);
  height: clamp(130px, 17vw, 230px);
  width: 100%;
  z-index: 1;
  pointer-events: none;
}

/* The stir.

   Only three of the five move, and each one on its own clock: --dur is 13s,
   17s and 21s, which share no common multiple worth noticing, and each
   starts on a different negative delay so they are already out of phase on
   the first frame. Nothing here ever ticks together, which is the whole
   difference between worms in soil and a row of windscreen wipers.

   --sweep is under two and a half degrees, and it pivots near the head, so
   the tail travels a few pixels over ten or twenty seconds. That is the
   amount a worm actually moves while it is lying in the dirt.

   The angle each worm LIES at is on the <g> in the markup; this is on the
   <image> inside it. Two transform animations on one element do not compose.

   transform-box: fill-box is what makes transform-origin mean "inside this
   image" rather than "somewhere in the 1200x260 SVG user space" - the same
   mistake that had the bare tree swinging sideways across the fence. */
.worm {
  filter: drop-shadow(0 2px 3px rgba(0,0,0,0.55));
  transform-box: fill-box;
  transform-origin: 50% 12%;
}
.worm--stir {
  animation: wormstir var(--dur, 15s) ease-in-out infinite alternate;
  animation-delay: var(--delay, 0s);
}
@keyframes wormstir {
  from { transform: rotate(calc(var(--sweep, 1.4deg) * -0.5)); }
  to   { transform: rotate(var(--sweep, 1.4deg)); }
}





.site-foot { position: relative; z-index: 2; }

/* ==========================================================================
   PAGE-LEVEL GUARDS
   These were lost once to an over-greedy regex cleanup. They are load-bearing:
   without the first, the bleeding scene layers put a sideways scrollbar on
   phones; without the second, the cart badge shows "0" on an empty cart.
   ========================================================================== */

/* The document-wide `overflow-x: clip` that used to live here is gone. It was
   hiding exactly one real bug - .fog, 120% wide inside an unclipped
   .band--orange, which pushed the document to 422px at a 375px viewport - and
   every scene layer that bleeds on purpose is clipped at its own component
   instead: .sky-stage already contained .mist, .scarecrow, .gravefilm and
   .marquee already clipped themselves, and .band--orange now contains .fog.
   Clipping the whole page made that bug invisible rather than absent, and any
   future one would have been invisible too. */

/* [hidden] is only display:none in the UA stylesheet, so any author rule that
   sets display (e.g. .cartbtn__count's grid) silently beats it. */
[hidden] { display: none !important; }

/* ==========================================================================
   THE WRAPPER SPINNER
   A horizontal reel rather than a radial wheel: it fills a phone screen
   properly, and it is the format people already know from unboxing videos -
   which is the whole point, since this is meant to be filmed.
   ========================================================================== */

.spin {
  position: relative;
  margin: clamp(1.5rem, 4vw, 2.5rem) 0;
  padding: clamp(1rem, 3vw, 1.6rem) 0 clamp(1.1rem, 3vw, 1.5rem);
  border: 2px solid rgba(245,239,224,0.16);
  border-radius: var(--radius);
  background: linear-gradient(180deg, rgba(35,80,68,0.30), rgba(8,18,15,0.62));
  overflow: hidden;
}

.spin__head {
  text-align: center;
  padding-inline: 1rem;
  margin-bottom: 1rem;
}
.spin__head h3 {
  font-family: var(--display);
  font-size: var(--step-2);
  color: var(--cream);
  letter-spacing: 0.02em;
}
.spin__head p {
  margin: 0.4rem 0 0;
  font-size: var(--step--1);
  color: var(--fg-faint);
}

/* ==========================================================================
   THE WHEEL
   Twelve segments of 30deg. The conic-gradient starts at -15deg so that
   segment 0 is CENTRED on twelve o'clock, where the pointer is - which is
   what lets js/spinner.js land segment k simply by turning to -k*30.
   ========================================================================== */
.wheel {
  position: relative;
  width: clamp(250px, 40vw, 400px);
  aspect-ratio: 1;
  margin: 0.4rem auto 0;
}
.wheel__dial {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  /* six pairs, alternating, from -15deg */
  background: conic-gradient(from -15deg,
    #1D4136 0deg 30deg,  #112A22 30deg 60deg,
    #1D4136 60deg 90deg, #112A22 90deg 120deg,
    #1D4136 120deg 150deg, #112A22 150deg 180deg,
    #1D4136 180deg 210deg, #112A22 210deg 240deg,
    #1D4136 240deg 270deg, #112A22 270deg 300deg,
    #1D4136 300deg 330deg, #112A22 330deg 360deg);
  border: 4px solid var(--cream);
  box-shadow:
    0 0 0 5px rgba(232,118,27,0.55),
    inset 0 0 40px rgba(0,0,0,0.55),
    0 18px 38px rgba(4,12,9,0.5);
  /* No will-change: a standing compositor layer for the whole visit, for the
     sake of one 4.4s transition that may never happen. Browsers promote a
     running transform transition anyway, which is when the layer is worth
     having. */
}
.wheel__seg {
  position: absolute;
  inset: 0;
  transform: rotate(calc(var(--i) * 30deg));
  pointer-events: none;
}
/* Each segment's art sits at the top of an unrotated circle and is carried
   round by the segment's own rotation, so "up" is always towards the rim. */
.wheel__seg img {
  position: absolute;
  left: 50%;
  top: 5.5%;
  width: 15%;
  height: auto;
  transform: translateX(-50%);
  filter: drop-shadow(0 3px 5px rgba(0,0,0,0.55));
}
.wheel__no {
  position: absolute;
  left: 50%;
  top: 9%;
  transform: translateX(-50%);
  font-family: var(--display);
  font-size: 1.1rem;
  color: var(--fg-soft);
}
.wheel__hub {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 26%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(circle at 40% 34%, #244E40, #0B1F19 78%);
  border: 3px solid var(--cream);
  box-shadow: 0 6px 16px rgba(4,12,9,0.6);
  display: grid;
  place-items: center;
  overflow: hidden;
  z-index: 2;
}
.wheel__hub img { width: 72%; height: auto; }
/* the marker the dial lands under */
.wheel__pointer {
  position: absolute;
  left: 50%;
  top: -14px;
  transform: translateX(-50%);
  border: 15px solid transparent;
  border-top-color: var(--amber);
  border-top-width: 26px;
  z-index: 3;
  pointer-events: none;
  filter: drop-shadow(0 2px 3px rgba(4,12,9,0.7));
}
.spin.is-landed .wheel__pointer { filter: drop-shadow(0 0 8px var(--amber)); }

.spin__note {
  margin: 0.7rem 0 0;
  font-size: var(--step--1);
  color: var(--fg-faint);
}

@media (prefers-reduced-motion: reduce) {
  .wheel__dial { transition: none !important; }
}

/* ==========================================================================
   THE PACK REVEAL STRIP  (success.html only)

   The shop page's wheel replaced a slot strip - but success.html still runs
   one, and it is a different job: it reveals the wrappers /api/pack has
   already reserved against a paid order, one at a time. Deleting these rules
   with the shop-page strip took the confirmation page's artwork with them.

   Scoped under .spin--reveal so the two can never be confused again.
   ========================================================================== */
.spin--reveal .spin__window {
  position: relative;
  overflow: hidden;
  padding-block: 0.6rem;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.spin--reveal .spin__reel {
  display: flex;
  gap: 0.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
  width: max-content;
}
.spin--reveal .spin__tile {
  flex: 0 0 auto;
  width: clamp(88px, 22vw, 124px);
  aspect-ratio: 5 / 8;
  border-radius: 10px;
  background: var(--tile);
  color: var(--ink);
  border: 2px solid rgba(0,0,0,0.28);
  box-shadow: inset 0 -14px 24px rgba(0,0,0,0.22), 0 6px 14px rgba(4,12,9,0.45);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.2rem;
  text-align: center;
  padding: 0.4rem;
  font-weight: 800;
  font-size: 0.68rem;
  line-height: 1.2;
  overflow: hidden;
}
/* The renders are transparent cutouts of a tall stick, so `cover` would crop
   the print away entirely - contain it and let the wrapper colour behind show
   through as the tile. */
.spin--reveal .spin__tile img {
  width: 100%;
  flex: 1 1 auto;     /* height:100% in a flex column overflows the tile */
  min-height: 0;
  object-fit: contain;
  filter: drop-shadow(0 4px 6px rgba(0,0,0,0.45));
}
.spin--reveal .spin__tile > span {
  flex: 0 0 auto;
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.spin--reveal .spin__no {
  font-family: var(--display);
  font-size: 1.5rem;
  line-height: 1;
  opacity: 0.85;
}
.spin--reveal .spin__pin {
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 3.6rem;
  width: 3px;
  margin-left: -1.5px;
  background: linear-gradient(180deg, var(--amber), rgba(245,160,50,0));
  z-index: 3;
  pointer-events: none;
}
.spin--reveal .spin__pin::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  border: 9px solid transparent;
  border-top-color: var(--amber);
}
.spin--reveal.is-landed .spin__pin::before { filter: drop-shadow(0 0 6px var(--amber)); }
@media (prefers-reduced-motion: reduce) {
  .spin--reveal .spin__reel { transition: none !important; }
}

.spin__foot { text-align: center; margin-top: 1rem; padding-inline: 1rem; }
.spin__result {
  margin: 0.9rem 0 0;
  color: var(--fg-soft);
  font-size: var(--step-0);
}
.spin__result b { color: var(--amber); }




/* --- THE SHOWCASE -----------------------------------------------------
   This replaces img/hero.webp, which was one baked render of five sticks and
   therefore looked identical on every visit. Five elements can be dealt.

   The SLOTS are fixed - their lefts, widths and baselines are written into
   the markup - and js/main.js only ever permutes them and adds a bounded
   nudge. So every arrangement it can produce is one of a small number of
   arrangements that were designed, not generated.

   Three separate guards keep this from ever reaching the page edge, which is
   the failure mode that would matter: the slots are absolute inside a
   position:relative box, the nudges are applied to the <img> rather than to
   the slot, and .wrapper-frame__inner is overflow:hidden. A stick can lean a
   little further than intended. It cannot escape the case, and it cannot put
   a scrollbar on the document.

   Byte cost: five sealed sticks are about 160KB against the single render's
   76KB, so roughly +84KB here - but the reel below uses these same five
   files, so they are already in cache by the time it builds and the page
   total comes out lower than it was. */
.showcase {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  z-index: 1;
}
.showcase__item {
  position: absolute;
  left: var(--l);
  width: var(--w);
  bottom: var(--b);
  z-index: var(--z, 1);
}
/* Beats `.wrapper-frame img { object-fit: cover }` on specificity, which
   would otherwise crop every stick to the slot box. */
.showcase__item img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: contain;
  transform: translate(var(--dx, 0), var(--dy, 0))
             rotate(var(--rot, 0deg))
             scale(var(--sc, 1));
  transform-origin: 50% 100%;   /* they lean from where they stand */
  /* two shadows: a tight one that seats it, and a warm wide one that reads as
     the light in the case wrapping round the sticks */
  filter:
    drop-shadow(0 10px 14px rgba(2,10,7,0.62))
    drop-shadow(0 0 26px rgba(255,164,64,0.18));
}

/* The entrance. One run, then done - `both` holds the end state and nothing
   is left animating. It is on the <li> while the slot transform is on the
   <img>, so the two never touch the same property on the same element.

   Gated on .is-dealt, which only js/main.js adds, so with scripting off the
   sticks are simply there. */
.js .showcase.is-dealt .showcase__item {
  animation: dealin 620ms cubic-bezier(.2,.8,.3,1) both;
  animation-delay: var(--in, 0ms);
}
@keyframes dealin {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}


/* The .mist-drift rules that were here are gone with the elements they moved.
   See the note in index.html: two animated rects, each carrying a live
   four-octave feTurbulence and a 7px Gaussian blur, sitting over the straw.
   They are two static gradient bands now. */


/* The lit eye breathes rather than blinks - a blink reads as a glitch at this
   size, a slow pulse reads as something watching. */
.sc-eye { animation: sceye 5.5s ease-in-out infinite; transform-origin: center; }
@keyframes sceye {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .sc-eye { animation: none; } }


/* The raven's wing is its own layer over the body, pivoting at the shoulder.
   The beat is deliberately small: the wing has to stay over where it started,
   because the body underneath still has the original wing painted into it. */
.raven__wing {
  transform-box: fill-box;
  transform-origin: 64.5% 31.5%;
  animation: ravenwing 2.6s ease-in-out infinite;
}
@keyframes ravenwing {
  0%, 62%, 100% { transform: rotate(0deg); }
  70%           { transform: rotate(-7deg); }
  78%           { transform: rotate(13deg); }
  86%           { transform: rotate(-4deg); }
  92%           { transform: rotate(6deg); }
}
@media (prefers-reduced-motion: reduce) { .raven__wing { animation: none; } }


/* The worms are photographs now, so they only need grounding in the soil. */
/* .worm now lives with the rest of the worm rules, further up. */


/* ==========================================================================
   THE POST-PURCHASE REVEAL
   Same furniture as the shop wheel, but this one is showing a buyer what they
   actually bought, so the landed wrappers stay on screen as a list.
   ========================================================================== */
.spin--reveal .spin__tile { opacity: 0.92; }

.reveal__pack {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.6rem;
  margin: 1.1rem 0 0;
  padding: 0;
  list-style: none;
}
.reveal__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  width: clamp(64px, 15vw, 92px);
  padding: 0.4rem 0.3rem 0.5rem;
  border-radius: 10px;
  background: var(--tile, rgba(255,255,255,0.06));
  border: 2px solid rgba(0,0,0,0.28);
  font-size: 0.62rem;
  font-weight: 800;
  line-height: 1.2;
  text-align: center;
  color: #1A0F04;
  /* each one lands as it is revealed rather than all appearing at once */
  animation: revealpop 420ms cubic-bezier(.2,.9,.3,1.3) both;
}
.reveal__item img {
  width: 100%;
  height: clamp(54px, 12vw, 78px);
  object-fit: contain;
  filter: drop-shadow(0 3px 5px rgba(0,0,0,0.45));
}
@keyframes revealpop {
  from { opacity: 0; transform: translateY(10px) scale(0.9); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .reveal__item { animation: none; }
}


/* ==========================================================================
   THE GRAVE FILM
   A full-bleed band between the hero and the scarecrow. Feathered top and
   bottom so it joins the sections either side rather than sitting in a box.
   ========================================================================== */
.gravefilm {
  position: relative;
  width: 100%;
  overflow: hidden;
  line-height: 0;
  /* the clip keeps it from ever widening the page on a narrow screen */
  max-width: 100%;
  /* No background: .nightband, the wrapper this and .scarecrow now share,
     carries one continuous one for both of them. See the note in index.html
     for why they cannot each have their own. */
}

/* One backdrop for the film and the clifftop together. */
.nightband {
  position: relative;
  background: linear-gradient(to bottom,
              #0A1310 0%, #0A1310 56%, #150E07 84%, #120C06 100%);
  /* NO negative margin.

     It used to pull the section below up over this one by 48-80px. That was
     harmless when the next section's background started transparent - but
     .band--orange now begins on an opaque #120C06 (added so the cliff's base
     would run out into shadow instead of stopping on the page gradient), and
     an opaque rectangle overlapping by 48px lands squarely on the bottom of
     the cliff. That is the hard horizontal line under the rock: not the
     cliff ending, but the NEXT SECTION painting over it.

     With no overlap the two meet exactly at the panel's bottom edge, and
     they meet on the same colour - this gradient ends on #120C06 and
     .band--orange starts on #120C06 - so the join is invisible and the
     cliff's own mask is free to fade out into it. */
}
.gravefilm__vid {
  display: block;
  width: 100%;
  height: auto;
  /* No filter and no mask.

     Both were being applied to a PLAYING video, which means per frame, for
     every frame, for as long as the band is on screen. The dusk grade is
     baked into the encodes instead, and the two dissolves are painted as
     ordinary gradients by the ::before and ::after below - which is the same
     picture for a fraction of the work, because .nightband behind this is a
     known flat colour here and a gradient to that colour is
     indistinguishable from masking to transparent. */
}

/* The dissolves. Top: joins the hero. Bottom: hands over to the clifftop -
   12% rather than the old 16%, since the cliff rises into it now and a
   shorter fade wastes less of the film on nothing. */
.gravefilm::before,
.gravefilm::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  z-index: 1;
  pointer-events: none;
}
.gravefilm::before {
  top: 0;
  height: 15%;
  background: linear-gradient(to bottom, #0A1310, rgba(10,19,16,0));
}
.gravefilm::after {
  bottom: 0;
  /* PIXELS, not a percentage.

     13% of the film is 24px on a phone and 93px at 1440 - but the cliff that
     has to cover it overlaps by a fraction of --panel-h, which is capped at
     420px. So the fade outgrew its cover as the viewport widened and there
     was no single --seal that could win at every width. A bounded height
     stays inside the overlap from 320px to 2560px.

     And it fades to warm earth rather than #0A1310. The cliff covers this
     completely now, so the colour should never be visible at all - but if it
     ever is again, a sliver of earth shadow is a seam and a sliver of night
     green is a line. */
     Trimmed from clamp(10px, 2.2vw, 34px). Every pixel of this fade is a
     pixel the cliff has to cover to stay sealed, so an over-long dissolve
     that is never seen is paid for twice: once in film hidden under rock,
     and once in a taller panel needed to hide it. It only has to be long
     enough to stop the video's last row reading as a cut if the seal ever
     fails. */
  height: clamp(8px, 1.6vw, 24px);
  background: linear-gradient(to top, #140D06, rgba(20,13,6,0));
}
@media (max-width: 640px) {
  /* on a phone a 2:1 strip is a letterbox sliver, so crop to the middle where
     the hands actually are rather than shrinking the whole frame away */
  .gravefilm { height: 46vw; }
  .gravefilm__vid { height: 100%; object-fit: cover; object-position: 50% 58%; }
}
