/* ═══════════════════════════════════════════════════════════════════════════
   DESIGN DNA — CHASKA COFFEE ROASTERS · Koregaon Park, Pune · est. 2021
   Chaska Beverages Pvt. Ltd. · small-batch specialty roastery + espresso bar
   ---------------------------------------------------------------------------
   Direction   : PRODUCT DROP (invented for this build — nothing in the
                 24-direction library covered a render-led, e-commerce-energy
                 product brand). The site behaves like a sneaker drop: ONE
                 product enormous and floating, and everything else gets out of
                 its way. Loud on purpose, short sentences, never cutesy.
                 *chaska* = a craving you can't shake.

   Palette     : DEEP MULBERRY + BUTTER + TANGERINE. Purple was absent from the
                 entire portfolio; coffee's own tasting vocabulary — cocoa,
                 plum, dark berry — is what earns it here.
                 mulberry #3A1B33 is the ground · #2A1226 the deep ground
                 (footer, overlays) · #4A2742 raised panels · butter cream
                 #F6EEDC carries type on mulberry (11.9:1) · cream-dim #D8CDB8
                 is the muted voice (9.6:1) · tangerine #FF6B2C is the SPLASH —
                 4.8:1 on mulberry, so large type and graphics ONLY · ink
                 #241019 carries type on cream (15.4:1).
                 ⚠ HARD RULE, measured, do not re-decide: plain
                 --color-accent on a cream ground is 2.9:1 and is BARRED from
                 carrying type. On cream, only --color-accent-deep #C24A16
                 (4.6:1) may. Every cream-ground rule below obeys this through
                 the --ground-type-accent channel.

   Type        : FREDOKA (variable, 500/600/700 — fat, rounded terminals) for
                 display, set TIGHT: 0.85 leading at hero size and negative
                 tracking, so the word reads as one solid object the cup can
                 punch through. INSTRUMENT SANS (400/500/600) for body and UI.
                 Nothing on this site is heavier than 700.

   Hero        : THE DROP — the display word set edge-to-edge at --text-hero,
                 the 3D cup render overlapping and partially OCCLUDING the
                 middle letters (.drop-cup sits above .drop-word in z-index —
                 that overlap IS the hero), splash to the right, beans
                 scattered behind. Copy bottom-left, tagline bottom-right.

   Signature   : THE BEAN TICKER — one infinite horizontal marquee band reading
                 ARAKU VALLEY · 1100 M · MEDIUM · COCOA, ORANGE PEEL · for all
                 five origins, caps, bean glyph between entries. It is the band
                 under the hero AND every section divider on every page.
                 One device. There is no second one.

   Corners     : HEAVY 32px on cards, media and panels (--radius-md/--radius-lg)
                 · PILL on buttons and chips (--radius-full) · 20px on inputs
                 (--radius-sm). The ledger records 0px, 16px, the flat/pill
                 split and the arched language as spent and explicitly asks for
                 "heavy 24px+". This claims it.

   Elevation   : exactly ONE shadow token — --shadow-drop, a COLOURED drop in
                 the mulberry family — applied ONLY to the floating product
                 renders (hero cup, splash, origin bags, drink renders, merch,
                 turntable frames). It is applied as a drop-shadow() filter,
                 not box-shadow, because every render is a transparent cutout
                 and a box-shadow would draw its rectangle. NOTHING else on
                 this site casts a shadow — panels, cards, nav, mobile bar and
                 footer all separate with colour and 1–2px borders instead.

   First-timers: (1) LETTER WOBBLE — every character of a display heading
                 wrapped in a span and rotated on a fixed repeating pattern
                 (-4deg, 2deg, -1deg, 3deg, -2deg), with a matching vertical
                 nudge, so headings read hand-set rather than typed. Generated
                 by the PHP helper wobble(), not by JS, so it works with
                 scripting off. Never used on any previous site.
                 (2) DRAG-TO-SPIN TURNTABLE — the cup on /shop rotates through
                 8 rendered frames as you drag, with keyboard arrow support and
                 a visible frame counter, degrading to a static frame 1 with no
                 JS. Never used before.

   Motion      : one curve only — cubic-bezier(.34,1.56,.64,1), a slight
                 overshoot, correct for this brand and wrong for its sibling.
                 State changes carry var(--transition) here; every @keyframe,
                 reveal and the marquee itself belong to effects.css.
   ---------------------------------------------------------------------------
   FILE OWNERSHIP — this file is the base layer only.
     style.css      (this file) DNA, tokens, reset, base type, layout, ALL
                    components. MOBILE-FIRST 360px base.
                    NO media queries. NO @keyframes. NO !important.
     effects.css    keyframes, the ticker marquee, reveals, transitions,
                    prefers-reduced-motion.
     responsive.css the 480 / 768 / 1024 / 1440 min-width breakpoints ONLY.

   ⚠ NOTE FOR THE responsive.css AGENT — grid-template-areas
     This file declares NO grid-template-areas anywhere. Nothing needs
     resetting on that count. What it DOES declare, and what you will want to
     take over at 768px and up, is listed in §17 at the foot of this file.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ═══ 1. TOKENS ═══════════════════════════════════════════════════════════
   Copied verbatim from the build contract
   (docs/superpowers/specs/2026-08-11-cafe-demos-design.md → "Token block —
   chaskacoffee"). The contrast ratios behind these hexes are already measured
   and recorded there. Do not adjust a single value. ═══════════════════════ */

:root {
  /* Palette — Product Drop · mulberry / butter / tangerine */
  --color-bg:          #3A1B33;  /* deep mulberry — the ground */
  --color-bg-deep:     #2A1226;  /* footer, overlays */
  --color-surface:     #4A2742;  /* raised panels on the dark ground */
  --color-cream:       #F6EEDC;  /* butter cream — type on mulberry */
  --color-cream-dim:   #D8CDB8;  /* muted type on mulberry */
  --color-accent:      #FF6B2C;  /* electric tangerine — the splash colour */
  --color-accent-deep: #C24A16;  /* the ONLY tangerine that may carry type on cream (4.6:1) */
  --color-ink:         #241019;  /* type on cream sections */
  --color-border:      rgba(246,238,220,0.16);

  /* Typography */
  --font-display: 'Fredoka', system-ui, sans-serif;
  --font-body:    'Instrument Sans', system-ui, sans-serif;

  /* Fluid type scale */
  --text-xs: clamp(.75rem,1vw,.8125rem);    --text-sm: clamp(.8125rem,1.1vw,.9375rem);
  --text-base: clamp(1rem,1.4vw,1.0625rem); --text-lg: clamp(1.125rem,2vw,1.375rem);
  --text-xl: clamp(1.5rem,3vw,2rem);        --text-2xl: clamp(2rem,4.5vw,3rem);
  --text-3xl: clamp(2.5rem,6vw,4rem);       --text-hero: clamp(3.5rem,15vw,13rem);

  /* Spacing · Shape · Shadow · Motion */
  --space-xs:.5rem; --space-sm:1rem; --space-md:2rem; --space-lg:4rem; --space-xl:7rem;
  --radius-sm:20px; --radius-md:32px; --radius-lg:32px; --radius-full:9999px;
  --shadow-drop: 0 18px 40px rgba(58,27,51,.28);
  --transition:.35s cubic-bezier(.34,1.56,.64,1);

  --container:1280px;
  --gutter:1.25rem;  /* 2.5rem @768 · 3.5rem @1024 · 4.5rem @1440 */
}

/* ═══ 1a. MOTION — THE SPLIT CURVE ════════════════════════════════════════
   ⚠ --transition ABOVE KEEPS ITS OVERSHOOT. DO NOT SOFTEN IT.
   cubic-bezier(.34,1.56,.64,1) reads as "bounce easing" to a linter and it
   will be flagged again. It is deliberate, it is this brand's signature
   against the calm sibling site, it was raised with the user, and the user
   confirmed the split below. It is not an oversight and it is not a default.

     OVERSHOOT — cubic-bezier(.34,1.56,.64,1), the --transition token.
       Anything that ANSWERS A POINTER, TAP OR KEY: hover lift, :active press,
       the hero circles, button/chip/card/nav hovers, input focus, the skip
       link arriving on Tab. A thing you pushed is allowed to spring back.

     CALM — cubic-bezier(.22,1,.36,1), the --ease-in-view token.
       Anything that ARRIVES ON ITS OWN: scroll reveals, staggers, the ticker,
       anything IntersectionObserver starts. Nothing the reader did not ask
       for gets to overshoot.

   Every transition in THIS file is an interactive state change, so every one
   of them correctly uses --transition. --ease-in-view is declared here for
   effects.css, which owns the entrances; it is NOT part of the contract token
   block above, which is why it is in its own rule rather than edited into it —
   that block is copied verbatim from the spec and must stay diff-clean.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  --ease-in-view: cubic-bezier(.22,1,.36,1);
}


/* ═══ 2. THE GROUND CHANNEL ═══════════════════════════════════════════════
   The site runs on two grounds: mulberry (default) and butter cream (the
   .cream section modifier). Rather than writing every component twice, each
   ground publishes a small set of inherited channels and every component below
   reads from them. Drop .cream on any element and its whole subtree — cards,
   borders, muted copy, focus rings, the permitted tangerine — re-reads itself
   correctly with no per-component override.

   The one rule that survives in both directions:
     --ground-type-accent is the ONLY tangerine any component may put type in.
     On mulberry it resolves to --color-accent (4.8:1 — large type and
     graphics). On cream it resolves to --color-accent-deep (4.6:1). Plain
     --color-accent NEVER carries type on cream. Use --color-accent directly
     only for fills, rules, bars and glyphs.

   color-mix() keeps every derived tint tied to the two tokens above, so the
   palette cannot drift. ═════════════════════════════════════════════════ */

body {
  --ground:             var(--color-bg);
  --ground-deep:        var(--color-bg-deep);
  --ground-panel:       var(--color-surface);
  --ground-fg:          var(--color-cream);
  --ground-dim:         var(--color-cream-dim);
  --ground-line:        var(--color-border);
  --ground-line-strong: color-mix(in srgb, var(--color-cream) 34%, transparent);
  --ground-wash:        color-mix(in srgb, var(--color-cream) 7%, transparent);
  --ground-type-accent: var(--color-accent);       /* 4.8:1 — large type + graphics */
  --ground-focus:       var(--color-accent);
}

/* The cream section modifier. */
.cream {
  --ground:             var(--color-cream);
  --ground-deep:        var(--color-cream);
  --ground-panel:       color-mix(in srgb, var(--color-ink) 5%, var(--color-cream));
  --ground-fg:          var(--color-ink);
  --ground-dim:         color-mix(in srgb, var(--color-ink) 70%, var(--color-cream));
  --ground-line:        color-mix(in srgb, var(--color-ink) 16%, transparent);
  --ground-line-strong: color-mix(in srgb, var(--color-ink) 34%, transparent);
  --ground-wash:        color-mix(in srgb, var(--color-ink) 5%, transparent);
  --ground-type-accent: var(--color-accent-deep);  /* 4.6:1 — the ONLY tangerine allowed to carry type here */
  --ground-focus:       var(--color-accent-deep);
  background: var(--ground);
  color: var(--ground-fg);
}


/* ═══ 3. RESET ════════════════════════════════════════════════════════════ */

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

* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* The hero renders overhang their box by design; .drop clips them itself,
     this is the belt-and-braces guard against sideways scroll at 360px.
     `clip` (not `hidden`) so position:sticky on the header still works. */
  overflow-x: clip;
}

body {
  min-height: 100%;
  overflow-x: clip;
  background: var(--ground);
  color: var(--ground-fg);
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  /* Clearance for the sticky mobile call/WhatsApp bar.
     responsive.css retires the bar and zeroes this at 768px. */
  padding-bottom: 4.75rem;
}

/* main.js locks the page behind the open mobile nav panel. */
body.is-locked { overflow: hidden; }

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.02;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

p { text-wrap: pretty; }

a {
  color: inherit;
  text-decoration: none;
}

ul, ol { list-style: none; padding: 0; }

img, svg, video, iframe, canvas {
  display: block;
  max-width: 100%;
}

img { height: auto; }

button, input, select, textarea {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  border-radius: 0;
}

button { cursor: pointer; }

textarea { resize: vertical; }

table {
  border-collapse: collapse;
  width: 100%;
}

address { font-style: normal; }

figure, blockquote { margin: 0; }

fieldset { border: 0; padding: 0; }

legend { padding: 0; }

summary { cursor: pointer; list-style: none; }
summary::-webkit-details-marker { display: none; }

hr {
  border: 0;
  height: 1px;
  background: var(--ground-line);
}

::selection {
  background: var(--color-accent);
  color: var(--color-ink);
}

[hidden] { display: none; }

/* Anchor targets clear the sticky header. */
[id] { scroll-margin-top: 6rem; }


/* ═══ 4. FOCUS ════════════════════════════════════════════════════════════
   Every interactive element gets a visible ring. Tangerine on mulberry
   (4.8:1 against the ground — well past the 3:1 non-text requirement),
   tangerine-deep on cream. Filled tangerine controls flip their ring to the
   ground foreground so it is never orange-on-orange. ════════════════════ */

:focus-visible {
  outline: 3px solid var(--ground-focus);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

.btn-primary:focus-visible,
.chip.is-active:focus-visible,
.chip[aria-pressed="true"]:focus-visible,
.nav-link[aria-current="page"]:focus-visible,
.ticker a:focus-visible {
  outline-color: var(--ground-fg);
}


/* ═══ 5. UTILITIES ════════════════════════════════════════════════════════ */

/* Screen-reader-only text.
   ⚠ DEFINED ON PURPOSE AND DEFINED FIRST. A previous build in this repo
   emitted .sr-only in its markup and never wrote this rule, so every
   screen-reader string rendered as visible text on the page. It is written
   without !important because nothing in this stylesheet ever sets position,
   width or clip-path on .sr-only again. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Focusable skip target — visible the moment it takes focus. */
.sr-only:focus,
.sr-only:focus-visible {
  position: fixed;
  top: var(--space-xs);
  left: var(--gutter);
  z-index: 200;
  width: auto;
  height: auto;
  margin: 0;
  padding: .7rem 1.1rem;
  overflow: visible;
  clip: auto;
  clip-path: none;
  background: var(--color-accent);
  color: var(--color-ink);
  font-family: var(--font-display);
  font-weight: 600;
  border-radius: var(--radius-full);
}

/* Skip link — the first thing a keyboard user hits, before the nav.
   Held off-screen by a TRANSFORM rather than display:none or visibility, so it
   stays in the tab order and can be reached at all. position:fixed, so it lands
   in view even when the page is already scrolled. z-index clears the sticky
   header (60) and the mobile bar (80). It arrives in answer to a key press, so
   the overshoot curve is the correct one here — see §1a. */
.skip-link {
  position: fixed;
  top: 0;
  left: var(--gutter);
  z-index: 200;
  transform: translateY(-200%);
  padding: .7rem 1.15rem;
  background: var(--color-accent);
  color: var(--color-ink);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.1;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  transition: transform var(--transition);
}

.skip-link:focus,
.skip-link:focus-visible {
  transform: translateY(0);
  outline-color: var(--color-cream);
}

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Reveal hook. The RESTING state is fully visible on purpose — the page must
   read with JavaScript off and under prefers-reduced-motion. effects.css owns
   the hidden state, the transition and the is-* state class. */
.reveal { min-width: 0; }

/* ── 360px OVERFLOW GUARDS ───────────────────────────────────────────────
   Grid and flex children default to min-width:auto and refuse to shrink below
   their content — the single commonest cause of sideways scroll on a phone.
   Pinned to 0 here once instead of in twenty places. The two things that
   genuinely cannot fit at 360px — the origin rail and the ticker — scroll or
   clip inside their own boxes instead (§11, §10). */
.hero-inner,
.hero-copy,
.hero-actions,
.section-head,
.card,
.card-body,
.card-title,
.card-meta,
.field,
.field-input,
.footer-col,
.footer-base,
.mobile-bar-item,
.origin-card,
.merch-band > *,
.promise-tiles > *,
.grid > * { min-width: 0; }


/* ═══ 6. BASE TYPOGRAPHY ══════════════════════════════════════════════════
   Fredoka is a rounded, fat display face. It is set TIGHT everywhere — the
   whole point of the direction is that a display word reads as one solid
   object, not as a line of letters. Hierarchy comes from size and tracking,
   never from a weight above 700. ═══════════════════════════════════════ */

h1 { font-size: var(--text-3xl); letter-spacing: -0.035em; line-height: .94; }
h2 { font-size: var(--text-2xl); letter-spacing: -0.03em;  line-height: .96; }
h3 { font-size: var(--text-xl);  letter-spacing: -0.02em;  line-height: 1.05; }
h4 { font-size: var(--text-lg);  letter-spacing: -0.01em;  line-height: 1.15; }
h5, h6 {
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: .12em;
  text-transform: uppercase;
}

b, strong { font-weight: 600; }

small { font-size: var(--text-sm); }

/* Inline links inside prose. Tangerine underline is a GRAPHIC, the text stays
   in the ground foreground — which keeps it legal on the cream ground too. */
.card-body a:not(.btn):not(.chip),
.section-lead a:not(.btn):not(.chip),
.hero-copy a:not(.btn):not(.chip),
.footer-col a:not(.btn):not(.chip) {
  text-decoration: underline;
  text-decoration-color: var(--color-accent);
  text-decoration-thickness: 2px;
  text-underline-offset: .22em;
  transition: color var(--transition);
}

.card-body a:not(.btn):not(.chip):hover,
.section-lead a:not(.btn):not(.chip):hover,
.hero-copy a:not(.btn):not(.chip):hover,
.footer-col a:not(.btn):not(.chip):hover {
  color: var(--ground-type-accent);
}


/* ═══ 7. LAYOUT ═══════════════════════════════════════════════════════════ */

.section {
  position: relative;
  padding-block: clamp(3rem, 9vw, var(--space-xl));
  background: var(--ground);
  color: var(--ground-fg);
}

/* A section that sits directly against a ticker band needs no top air —
   the band already separates it. */
.ticker + .section { padding-block-start: clamp(2.25rem, 7vw, var(--space-lg)); }

.section-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  max-width: 46rem;
  margin-block-end: var(--space-md);
}

.section-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-2xl);
  line-height: .92;
  letter-spacing: -0.035em;
  text-transform: uppercase;
  color: var(--ground-fg);
}

.section-lead {
  font-size: var(--text-lg);
  line-height: 1.5;
  color: var(--ground-dim);
  max-width: 44ch;
}

/* The generic grid. Intrinsic, so it is already correct at 360px without a
   media query — one column on a phone, filling out as the viewport grows.
   responsive.css may pin exact column counts per section on top of this. */
.grid {
  display: grid;
  gap: var(--space-md);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}


/* ═══ 8. BUTTONS ══════════════════════════════════════════════════════════
   Pill, always. 48px minimum touch target. The trailing letter-space is
   subtracted from the end padding so the label sits optically centred inside
   the pill rather than a tracking-unit to the left. ══════════════════════ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .55em;
  min-height: 48px;
  padding-block: .8rem;
  padding-inline: 1.6rem calc(1.6rem - .02em);
  border: 2px solid transparent;
  border-radius: var(--radius-full);
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: .02em;
  text-align: center;
  /* A pill that wraps to two lines stops reading as one control — the header
     CTA was breaking to "Order / beans" at 390px. Labels here are short by
     design, so hold them on one line and let the pill set its own width. */
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--transition), color var(--transition),
              border-color var(--transition), transform var(--transition);
}

.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }

.btn svg { width: 1.15em; height: 1.15em; flex: none; }

/* The one action: Order beans. Tangerine fill, ink label — 6.3:1, and it reads
   identically on the mulberry ground and on a cream section. */
.btn-primary {
  background: var(--color-accent);
  color: var(--color-ink);
  border-color: var(--color-accent);
}

.btn-primary:hover {
  background: var(--color-accent-deep);
  border-color: var(--color-accent-deep);
  color: var(--color-cream);
}

.btn-ghost {
  background: transparent;
  color: var(--ground-fg);
  border-color: var(--ground-line-strong);
}

.btn-ghost:hover {
  background: var(--ground-fg);
  color: var(--ground);
  border-color: var(--ground-fg);
}

.btn[aria-disabled="true"],
.btn:disabled {
  opacity: .5;
  cursor: not-allowed;
  transform: none;
}


/* ═══ 9. CHIPS ════════════════════════════════════════════════════════════ */

.chip {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: .55em;
  min-height: 36px;
  padding-block: .35rem;
  padding-inline: 1.1rem;
  border: 1px solid var(--ground-line-strong);
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--ground-fg);
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1;
  letter-spacing: .1em;
  text-transform: uppercase;
  white-space: nowrap;
  width: fit-content;
  max-width: 100%;
  box-sizing: border-box;
  transition: background-color var(--transition), color var(--transition),
              border-color var(--transition);
}

a.chip:hover,
button.chip:hover { border-color: var(--ground-fg); }

.chip.is-active,
.chip[aria-pressed="true"] {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-ink);
}

/* ── Live open / closed status (contact.php, header, footer) ──────────
   This site has no rust token — that belongs to the sibling. The two states
   are told apart by SHAPE first (a solid disc versus a hollow ring) and by the
   word the PHP already prints inside the chip; the tangerine is a reinforcement
   on top of that, never the only carrier of the difference. Colour-blind and
   monochrome readers get the ring/disc and the word. */

.chip.is-open,
.chip.is-closed,
.chip.is-shut {
  gap: .55em;
  font-weight: 600;
}

.chip.is-open::before,
.chip.is-closed::before,
.chip.is-shut::before {
  content: "";
  flex: none;
  width: .6rem;
  height: .6rem;
  border-radius: 50%;
  box-sizing: border-box;
}

/* OPEN — a solid tangerine disc. Fill, not type. */
.chip.is-open {
  border-color: var(--color-accent);
  color: var(--ground-fg);
}

.chip.is-open::before { background: var(--color-accent); }

/* CLOSED — a hollow ring in the muted voice. */
.chip.is-closed,
.chip.is-shut {
  border-color: var(--ground-line-strong);
  color: var(--ground-dim);
}

.chip.is-closed::before,
.chip.is-shut::before {
  background: transparent;
  border: 2px solid currentColor;
}


/* ═══ 10. THE BEAN TICKER — the signature ═════════════════════════════════
   One infinite marquee band. It is the divider between every section on every
   page and it is the only divider on this site. Ink on tangerine measures
   6.3:1, so the band is legible as well as loud — and because it always
   carries the same two colours it reads as the same object whether the
   section above it is mulberry or cream.

   THE MARQUEE ITSELF LIVES IN effects.css. This file gives the band its box,
   its clip and its type; effects.css translates .ticker-track and pauses on
   hover / under prefers-reduced-motion. The track is emitted twice by the PHP
   helper bean_ticker() so the loop is seamless. ═════════════════════════ */

.ticker {
  position: relative;
  overflow: hidden;
  background: var(--color-accent);
  color: var(--color-ink);
  padding-block: .7rem;
  /* The band is a graphic device, not a section — it never inherits .cream. */
  user-select: none;
}

.ticker-track {
  display: flex;
  align-items: center;
  gap: 0;
  width: max-content;
  flex: none;
  will-change: transform;
}

.ticker-item {
  display: inline-flex;
  align-items: center;
  gap: .55em;
  padding-inline: .7rem;
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1;
  letter-spacing: .14em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--color-ink);
}

/* The bean glyph the helper drops between entries — whatever element carries
   it, keep it optically smaller than the caps beside it. */
.ticker-item svg,
.ticker-item img {
  width: 1.1em;
  height: 1.1em;
  flex: none;
}


/* ═══ 11. LETTER WOBBLE — first-timer #1 ══════════════════════════════════
   The PHP helper wobble() wraps every character of a display heading in
   <span class="wb wb-N">, cycling wb-1 … wb-5. CSS owns the rotations, so the
   effect is present with scripting off and costs nothing at runtime.

   Fixed repeating pattern: -4deg, 2deg, -1deg, 3deg, -2deg — with a matching
   vertical nudge in em, sized and signed to the rotation, so a letter that
   tilts anticlockwise also lifts and one that tilts clockwise also drops. The
   nudge is in em so it tracks the heading's size at every breakpoint.

   ⚠ .wb MUST be inline-block or the transform is discarded on an inline box.
   Consequence the markup agent should know about: inline-block characters can
   wrap between letters. Keep wobbled headings to short words, or let the word
   own its line. ══════════════════════════════════════════════════════════ */

/* One word of wobbled letters. Each letter below is an inline-block, and a
   browser is allowed to break a line BETWEEN two inline-blocks — which split
   "COFFEE" across two lines as "CO / FFEE". Holding each word together means
   breaks can only land on real spaces, exactly like ordinary text. */
.wb-word {
  white-space: nowrap;
}

.wb {
  display: inline-block;
  transform-origin: 50% 65%;
  will-change: transform;
}

.wb-1 { transform: rotate(-4deg) translateY(-0.030em); }
.wb-2 { transform: rotate( 2deg) translateY( 0.018em); }
.wb-3 { transform: rotate(-1deg) translateY(-0.010em); }
.wb-4 { transform: rotate( 3deg) translateY( 0.026em); }
.wb-5 { transform: rotate(-2deg) translateY(-0.018em); }


/* ═══ 12. HERO — THE DROP ═════════════════════════════════════════════════
   Mobile-first stack: the display word, then the cup pulled up over its
   middle, then the copy and the actions. The splash and the beans are
   absolutely positioned decoration behind everything and are clipped by the
   hero's own box, so they can hang off the edge without ever producing
   sideways scroll.

   Z-ORDER, and this is the whole hero:
     0  .drop-beans   scattered behind the type
     1  .drop-word    the giant word
     2  .drop-splash  reads as thrown from behind the cup
     3  .drop-cup     ABOVE the word — it occludes the middle letters
   .drop isolates itself so those four indices can never be re-ranked by a
   stacking context further up the page. ═════════════════════════════════ */

.hero {
  position: relative;
  isolation: isolate;
  background: var(--ground);
  color: var(--ground-fg);
  padding-block: clamp(2.5rem, 8vw, var(--space-lg)) clamp(3rem, 9vw, var(--space-xl));
}

.hero-inner {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
}

.hero-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-3xl);
  line-height: .92;
  letter-spacing: -0.035em;
  text-transform: uppercase;
  text-align: center;
  color: var(--ground-fg);
}

.hero-copy {
  /* POSITIONED ON PURPOSE. The hero's decorative renders are absolutely
     positioned inside .hero-inner, and CSS paints a positioned z-index:0 box
     ABOVE a non-positioned in-flow block — so unpositioned copy loses to the
     bean scatter and a bean lands on the sentence. Giving the copy its own
     stacking position is what keeps the words on top. */
  position: relative;
  z-index: 4;
  font-size: var(--text-lg);
  line-height: 1.45;
  color: var(--ground-dim);
  max-width: 34ch;
  text-align: center;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-sm);
  margin-block-start: var(--space-xs);
}

/* ── The Drop ─────────────────────────────────────────────────────────── */

.drop {
  overflow: hidden;
  padding-block: clamp(2rem, 7vw, var(--space-lg)) clamp(2.5rem, 8vw, var(--space-lg));
}

.drop-word {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  font-family: var(--font-display);
  font-weight: 700;
  /* TIGHT. 0.85 leading is the direction — the word has to read as one solid
     object for the cup to punch a hole in it. */
  font-size: var(--text-hero);
  line-height: .85;
  letter-spacing: -0.045em;
  text-transform: uppercase;
  text-align: center;
  color: var(--ground-fg);
  /* Optical centring: the NEGATIVE tracking above removes width from the end
     of the line, which pushes centred text a half-unit to the RIGHT. The
     matching end padding takes it back. Never text-indent. */
  padding-inline-end: .045em;
}

/* THE OVERLAP. The cup sits above the word and is pulled up by half the
   heroline's own size, which lands its top edge across the middle of the
   letters. Everything is expressed against --text-hero so the occlusion holds
   at every viewport width without a media query. */
.drop-cup {
  position: relative;
  z-index: 3;
  display: block;
  width: min(64vw, 25rem);
  height: auto;
  margin-inline: auto;
  margin-block-start: calc(var(--text-hero) * -0.48);
  /* The ONE shadow. drop-shadow(), not box-shadow — the render is a
     transparent cutout and a box-shadow would draw its rectangle. */
  filter: drop-shadow(var(--shadow-drop));
  pointer-events: none;
}

/* The splash reads as coffee thrown out from BEHIND the cup, so it has to
   overlap the cup's right edge — floating alone in the corner just looks like
   a stray asset. z-index 2 puts it under the cup (3) and over the beans (-1).
   Mobile keeps it high and tucked off the right edge so it never lands behind
   the copy block; responsive.css brings it in and enlarges it at 1024. */
.drop-splash {
  position: absolute;
  z-index: 2;
  top: 28%;
  width: min(54vw, 16rem);
  height: auto;
  filter: drop-shadow(var(--shadow-drop));
  pointer-events: none;
}

.drop-splash-r { inset-inline-end: -20%; }

/* Same artwork, mirrored, so the throw reads outward on BOTH sides instead of
   two arcs running the same way. transform does not move the layout box, so
   the inset below still positions it. */
.drop-splash-l {
  inset-inline-start: -20%;
  transform: scaleX(-1);
}

/* Behind EVERYTHING. z-index 0 put the scatter above the in-flow copy and a
   bean landed on the sentence; -1 keeps it under the text without needing a
   z-index on every sibling. The mask fades it out before it reaches the copy
   block, so the bottom third of the hero stays clean type on ground. */
.drop-beans {
  position: absolute;
  z-index: -1;
  top: 4%;
  left: 50%;
  translate: -50% 0;
  width: min(112vw, 54rem);
  height: auto;
  opacity: .62;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 52%, transparent 82%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 52%, transparent 82%);
}

/* ── Hero controls and tagline ─────────────────────────────────────────
   Reported by the pages agent as NEW CLASS in home.php. The prev/next
   circles sit under the copy block, the tagline opposite them.
   ⚠ responsive.css: at 1024 the copy + .drop-nav go bottom-LEFT and
   .drop-tagline bottom-RIGHT, per The Drop. Centred here for 360px. */

.drop-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .6rem;
  margin-block-start: var(--space-xs);
}

.drop-prev,
.drop-next {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  flex: none;
  padding: 0;
  border: 2px solid var(--ground-line-strong);
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--ground-fg);
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  line-height: 1;
  transition: background-color var(--transition), color var(--transition),
              border-color var(--transition), transform var(--transition);
}

.drop-prev:hover,
.drop-next:hover {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-ink);
  transform: translateY(-2px);
}

.drop-prev:focus-visible,
.drop-next:focus-visible { outline-color: var(--ground-focus); }

.drop-prev svg,
.drop-next svg { width: 1.1rem; height: 1.1rem; }

.drop-prev:disabled,
.drop-next:disabled,
.drop-prev[aria-disabled="true"],
.drop-next[aria-disabled="true"] {
  opacity: .45;
  cursor: not-allowed;
  transform: none;
}

/* Centred, tracked, uppercase — the drift case. inline-block plus a negative
   inline-end margin equal to the tracking. Never text-indent. */
.drop-tagline {
  /* Positioned for the same reason as .hero-copy — see the note there. */
  position: relative;
  z-index: 4;
  display: inline-block;
  margin-inline-end: -.12em;
  max-width: 28ch;
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: .12em;
  text-transform: uppercase;
  text-align: center;
  color: var(--ground-dim);
}


/* ═══ 13. HEADER & NAV ════════════════════════════════════════════════════
   A pill that floats on the mulberry ground. The header always sits on the
   dark ground — it is outside the sections, so it never inherits .cream.

   ⚠ MARKUP NOTE. The nav opens without JavaScript. .nav-panel is closed by
   default and is opened by ANY of: the .is-open state class (main.js), the
   :target pseudo-class (a plain <a href="#nav-panel"> fallback), a wrapping
   <details open>, or an adjacent control with aria-expanded="true". Whichever
   of those the markup agent chose, the panel works. ═════════════════════ */

.site-header {
  position: sticky;
  top: 0;
  z-index: 60;
  padding-block: var(--space-xs);
  background: var(--color-bg);
  border-block-end: 1px solid var(--color-border);
}

.nav {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding: .45rem .45rem .45rem 1.1rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  color: var(--color-cream);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--color-cream);
  white-space: nowrap;
}

.brand img,
.brand svg {
  height: 1.6rem;
  width: auto;
}

.nav-list {
  display: flex;
  align-items: center;
  gap: .25rem;
  list-style: none;
}

/* Mobile-first: the desktop link row (a direct child of .nav) is hidden and
   the links live in .nav-panel instead.
   ⚠ responsive.css: reveal `.nav > .nav-list { display: flex; }` at 1024px and
   hide .nav-toggle + .nav-panel there. */
.nav > .nav-list { display: none; }

.nav-panel .nav-list {
  flex-direction: column;
  align-items: stretch;
  gap: .25rem;
  width: 100%;
}

.nav-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-inline: .95rem;
  border-radius: var(--radius-full);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: .01em;
  color: var(--color-cream);
  transition: background-color var(--transition), color var(--transition);
}

.nav-link:hover { background: color-mix(in srgb, var(--color-cream) 12%, transparent); }

/* Current page: a tangerine pill with ink type — 6.3:1, and it never asks
   tangerine to carry small type against the ground. */
.nav-link[aria-current="page"] {
  background: var(--color-accent);
  color: var(--color-ink);
  font-weight: 600;
}

.nav-toggle {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 48px;
  height: 48px;
  flex: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-cream);
  transition: background-color var(--transition), border-color var(--transition);
}

.nav-toggle:hover { background: color-mix(in srgb, var(--color-cream) 12%, transparent); }

.nav-toggle span {
  display: block;
  width: 18px;
  height: 2px;
  background: currentColor;
  border-radius: var(--radius-full);
}

.nav-toggle svg { width: 20px; height: 20px; }

.nav-panel {
  display: none;
  position: absolute;
  top: calc(100% + .5rem);
  left: 0;
  right: 0;
  z-index: 60;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-sm);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-cream);
}

.nav-panel.is-open,
.nav-panel:target,
[open] > .nav-panel,
[aria-expanded="true"] + .nav-panel { display: flex; }

.nav-panel .btn { width: 100%; }

/* ── NO-JS NAV — HARD REQUIREMENT, DO NOT DELETE ───────────────────────
   The core PHP agent puts `no-js` on <html> and animations.js swaps it for
   `has-js`. Without the two rules below, a visitor with JavaScript off gets a
   hidden panel and a button that cannot open it — a menu that is a dead end.
   This repo has shipped that bug before.

   So with no JS the toggle is REMOVED (not disabled, not left dead) and the
   panel is simply rendered open, in flow, below the pill. Every link is
   reachable, nothing needs clicking, and the four open-state selectors above
   become irrelevant rather than wrong.

   `flex: 1 0 100%` covers the panel being a flex child of .nav; `width: 100%`
   covers it being a sibling of .nav inside .site-header. It lands either way,
   which matters because the core agent is still finishing that markup. */

.no-js .nav-toggle { display: none; }

.no-js .nav { flex-wrap: wrap; }

.no-js .nav-panel {
  display: flex;
  position: static;
  flex: 1 0 100%;
  width: 100%;
  margin-block-start: .5rem;
  background: transparent;
  border: 0;
  border-block-start: 1px solid var(--color-border);
  border-radius: 0;
  padding: var(--space-sm) .65rem 0;
}

/* ── Nav tools — the cart + search icons, right of the pill ────────────
   Written tolerantly on purpose: the core PHP agent is still running, so this
   lands whether the header uses .nav-tool on each control or just bare
   <button>/<a> inside a .nav-tools wrapper. A .btn or the .nav-toggle inside
   the wrapper keeps its own styling. */

.nav-tools {
  display: flex;
  align-items: center;
  gap: .25rem;
  flex: none;
}

.nav-tool,
.nav-tools > button:not(.btn):not(.nav-toggle),
.nav-tools > a:not(.btn) {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex: none;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-cream);
  transition: background-color var(--transition), border-color var(--transition);
}

.nav-tool:hover,
.nav-tools > button:not(.btn):not(.nav-toggle):hover,
.nav-tools > a:not(.btn):hover {
  background: color-mix(in srgb, var(--color-cream) 12%, transparent);
  border-color: var(--color-border);
}

.nav-tool svg,
.nav-tools > button:not(.btn):not(.nav-toggle) svg,
.nav-tools > a:not(.btn) svg {
  width: 20px;
  height: 20px;
}


/* ═══ 14. COMPONENTS ══════════════════════════════════════════════════════ */

/* ── Cards — the heavy 32px corner ────────────────────────────────────── */

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--ground-panel);
  border: 1px solid var(--ground-line);
  border-radius: var(--radius-md);
  color: var(--ground-fg);
  /* No shadow. Cards separate with colour and a hairline — the only shadow on
     this site belongs to the floating renders. */
  transition: transform var(--transition), border-color var(--transition),
              background-color var(--transition);
}

a.card:hover,
.card:hover {
  transform: translateY(-4px);
  border-color: var(--ground-line-strong);
}

.card-media {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background: var(--ground-wash);
  border-radius: var(--radius-md) var(--radius-md) 0 0;
}

.card-media > img,
.card-media > svg,
.card-media > video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* When the media element IS the image. */
img.card-media {
  width: 100%;
  height: auto;
  object-fit: cover;
}

.card-body {
  display: flex;
  flex-direction: column;
  gap: .55rem;
  padding: var(--space-md) var(--space-sm) var(--space-sm);
  flex: 1 1 auto;
}

.card-title {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--ground-fg);
}

.card-meta {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1.45;
  color: var(--ground-dim);
}

/* The price / hero fact inside a card meta block. Large enough that the
   tangerine channel is comfortable on either ground. */
.card-meta strong {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ground-type-accent);
}

.card .btn { align-self: flex-start; margin-block-start: auto; }

/* ── Promise tiles — Small batch · Indian estates · Roasted weekly · 48h ─
   The contract names the container only, so the tiles are styled through the
   child combinator rather than by inventing a class. */
.promise-tiles {
  display: grid;
  gap: var(--space-sm);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
  list-style: none;
}

.promise-tile,
.promise-tiles > * {
  display: flex;
  flex-direction: column;
  gap: .35rem;
  padding: var(--space-sm);
  background: var(--ground-panel);
  border: 1px solid var(--ground-line);
  border-radius: var(--radius-md);
  color: var(--ground-fg);
  font-size: var(--text-sm);
  line-height: 1.45;
}

.promise-tile > strong,
.promise-tile > h3,
.promise-tiles > * > strong,
.promise-tiles > * > h3 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.05;
  color: var(--ground-fg);
}

.promise-tile svg,
.promise-tile img,
.promise-tiles svg,
.promise-tiles img {
  width: 2rem;
  height: 2rem;
  color: var(--ground-type-accent);
}


/* ── The cream split — "An elevated coffee experience" ─────────────────
   The one cream-ground section on the home page: prose on the left, the
   cold-brew render floating on the right. .cream republishes the ground
   channel, so everything below reads itself correctly on butter cream with no
   bespoke overrides — including the price, which switches to the only
   tangerine permitted to carry type there.
   ⚠ responsive.css: two columns at 768/1024, media column second. */

.cream-split {
  display: grid;
  gap: var(--space-md);
  grid-template-columns: 1fr;
  align-items: center;
}

.cream-copy {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  align-items: flex-start;
  min-width: 0;
}

.cream-media {
  position: relative;
  display: grid;
  place-items: center;
  min-width: 0;
  padding: var(--space-md) var(--space-sm);
  background: color-mix(in srgb, var(--color-accent) 14%, var(--ground-panel));
  border: 1px solid var(--ground-line);
  border-radius: var(--radius-lg);
}

/* A floating product render — so it, and only it, carries the one shadow. */
.cream-media img {
  width: auto;
  max-height: 26rem;
  object-fit: contain;
  filter: drop-shadow(var(--shadow-drop));
}

.cream-list {
  display: flex;
  flex-direction: column;
  gap: .7rem;
  list-style: none;
}

.cream-list li {
  position: relative;
  padding-inline-start: 1.6rem;
  font-size: var(--text-base);
  line-height: 1.5;
  color: var(--ground-dim);
}

.cream-list li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: .55em;
  width: .55rem;
  height: .55rem;
  border-radius: var(--radius-full);
  background: var(--color-accent);
}

.cream-list strong {
  font-weight: 600;
  color: var(--ground-fg);
}


/* ── About split (about.php) ───────────────────────────────────────────
   .about-prose does double duty: the prose column inside the split, and a
   full-width run of copy in the estates section. The measure is capped at 68ch
   so it stays readable in the second case without needing a second class.
   ⚠ responsive.css: two columns at 768/1024. */

.about-split {
  display: grid;
  gap: var(--space-md);
  grid-template-columns: 1fr;
  align-items: center;
}

.about-split > img,
.about-split .card-media {
  border-radius: var(--radius-lg);
  min-width: 0;
}

.about-prose {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  max-width: 68ch;
  min-width: 0;
  font-size: var(--text-base);
  line-height: 1.65;
  color: var(--ground-dim);
}

.about-prose h2,
.about-prose h3 {
  color: var(--ground-fg);
  text-transform: uppercase;
  letter-spacing: -0.03em;
}

.about-prose strong { color: var(--ground-fg); }

/* ── The origin rail — five estates, scroll-snapped ─────────────────────
   A rail, not a grid: at 360px it must be swipeable rather than stacked, so it
   overflows inside its own box and bleeds to the gutter edges. */
.origin-rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: min(76%, 19rem);
  gap: var(--space-sm);
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--gutter);
  padding-inline: var(--gutter);
  padding-block: var(--space-xs) var(--space-sm);
  margin-inline: calc(var(--gutter) * -1);
  scrollbar-width: none;
  list-style: none;
}

.origin-rail::-webkit-scrollbar { display: none; }

.origin-card {
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--ground-panel);
  border: 1px solid var(--ground-line);
  border-radius: var(--radius-md);
  color: var(--ground-fg);
  transition: transform var(--transition), border-color var(--transition);
}

.origin-card:hover {
  transform: translateY(-4px);
  border-color: var(--ground-line-strong);
}

/* The bag render floats on a warm tangerine wash, contained not cropped, and
   carries the one shadow. */
.origin-card .card-media {
  aspect-ratio: 1 / 1.15;
  display: grid;
  place-items: center;
  padding: var(--space-sm);
  background: color-mix(in srgb, var(--color-accent) 15%, var(--ground-panel));
}

.origin-card .card-media > img {
  width: auto;
  max-height: 100%;
  object-fit: contain;
  filter: drop-shadow(var(--shadow-drop));
}

/* ── Prices — one look, two blocks ─────────────────────────────────────
   .origin-price is the 250g figure on an .origin-card (home + shop);
   .card-price is the rupee figure in a .card-body on the menu. Different
   blocks, same job, so they are styled together and a price is identical
   wherever it appears. --ground-type-accent means the figure is tangerine on
   mulberry and tangerine-DEEP on a cream section — the only tangerine allowed
   to carry type there. Tabular figures so a column of prices lines up. */

.origin-price,
.card-price {
  display: flex;
  align-items: baseline;
  gap: .4rem;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--ground-type-accent);
  font-variant-numeric: tabular-nums;
}

/* The unit that rides alongside it — "250 g", "per cup" — steps back into the
   body face and the muted voice. */
.origin-price small,
.origin-price span,
.card-price small,
.card-price span {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ground-dim);
}

/* On a menu row the price is pushed to the end of the line. */
.card-body .card-price { margin-block-start: auto; }


/* ── Origin spec list (shop.php) ───────────────────────────────────────
   A <dl> of altitude / roast / notes. The rows inside are bare <div>s, as
   reported, so they are addressed through the child combinator. Hairline
   rules BETWEEN rows — a table rule, deliberately not a coloured tab down
   one edge. */

.origin-spec {
  display: flex;
  flex-direction: column;
  margin: 0;
}

.origin-spec > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  min-width: 0;
  padding-block: .6rem;
  border-block-end: 1px solid var(--ground-line);
}

.origin-spec > div:last-child { border-block-end: 0; }

.origin-spec dt {
  flex: none;
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ground-dim);
}

.origin-spec dd {
  margin: 0;
  min-width: 0;
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1.4;
  text-align: end;
  color: var(--ground-fg);
}

/* ── Merch band — tote + cup ────────────────────────────────────────────
   Deliberately NOT tangerine: a ticker band sits directly above it in the home
   rhythm and two orange blocks would fuse into one. A raised mulberry panel
   instead, with the renders floating on it. */
.merch-band {
  display: grid;
  gap: var(--space-md);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  align-items: center;
  padding: var(--space-md) var(--space-sm);
  background: var(--ground-panel);
  border: 1px solid var(--ground-line);
  border-radius: var(--radius-lg);
  color: var(--ground-fg);
  text-align: center;
}

.merch-band img { filter: drop-shadow(var(--shadow-drop)); margin-inline: auto; }

.merch-band h2,
.merch-band h3 {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: -0.03em;
}

/* ── Turntable — first-timer #2 ─────────────────────────────────────────
   Eight rendered frames stacked in one box. Frame 1 is shown by default so the
   control degrades to a static render with JavaScript off; the moment any
   frame carries .is-active, frame 1 steps back. main.js owns the dragging,
   the keyboard arrows and the counter text. */
/* The cup showcase — replaced the turntable (see shop.php). Layout only;
   effects.css owns the hover tilt, per this file's no-@media / no-transition
   split. The drop-shadow is a filter, not a box-shadow, because the render is
   a transparent cutout and a box-shadow would draw its rectangle. */
.cup-show {
  display: flex;
  justify-content: center;
  margin-block-start: var(--space-lg);
  perspective: 1200px;
}

.cup-show img {
  width: min(100%, 20rem);
  height: auto;
  filter: drop-shadow(var(--shadow-drop));
}

.turntable {
  display: grid;
  justify-items: center;
  gap: var(--space-sm);
}

.turntable-stage {
  position: relative;
  width: min(100%, 24rem);
  aspect-ratio: 1000 / 1400;
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--color-accent) 12%, var(--ground-panel));
  /* Let the page scroll vertically while a horizontal drag spins the cup. */
  touch-action: pan-y;
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
}

.turntable-stage:active { cursor: grabbing; }

.turntable-frame {
  position: absolute;
  inset: var(--space-sm);
  width: auto;
  height: auto;
  max-width: calc(100% - var(--space-md));
  max-height: calc(100% - var(--space-md));
  margin: auto;
  object-fit: contain;
  opacity: 0;
  filter: drop-shadow(var(--shadow-drop));
  pointer-events: none;
}

.turntable-frame:first-child,
.turntable-frame.is-active { opacity: 1; }

.turntable-frame.is-active { z-index: 1; }

/* Once JS has promoted a frame, the no-JS default stands down. */
.turntable-stage:has(.is-active) .turntable-frame:first-child:not(.is-active) { opacity: 0; }

/* A centred, tracked, uppercase label — the exact case that drifts left,
   because letter-spacing appends a trailing space after the last character.
   Fixed with inline-block + a negative inline-end margin equal to the
   tracking, which is the repo's golden rule. NOT text-indent. */
.turntable-count {
  display: inline-block;
  margin-inline-end: -.14em;
  padding: .4rem 1rem;
  border: 1px solid var(--ground-line-strong);
  border-radius: var(--radius-full);
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ground-dim);
  font-variant-numeric: tabular-nums;
}

/* The drag instruction. HIDDEN BY DEFAULT AND ONLY REVEALED BY .is-ready,
   which main.js adds once the turntable can actually be dragged. With
   scripting off the hint never appears, so the page never tells anyone to
   drag a control that cannot move. Same reason the counter and the stage
   degrade to a static frame 1 rather than a dead control.
   Positive tracking pushes centred text left by half a tracking unit; the
   matching inline-START padding puts it back. Never text-indent. */
.turntable-hint {
  display: none;
  padding-inline-start: .1em;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: .1em;
  text-transform: uppercase;
  text-align: center;
  color: var(--ground-dim);
}

.turntable.is-ready .turntable-hint { display: block; }


/* ── Newsletter (home) — the closing band before the footer ────────────
   A raised mulberry ground rather than another tangerine block: a ticker band
   sits directly above it in the home rhythm, and two orange blocks would fuse
   into one. Only --ground is re-pointed; the rest of the channel still
   resolves against the dark ground, which is correct here. */

.newsletter {
  --ground: var(--color-surface);
  background: var(--ground);
  text-align: center;
}

.newsletter .section-head {
  align-items: center;
  margin-inline: auto;
}

/* Negative tracking pushes centred text RIGHT; the matching inline-END padding
   takes it back. The opposite sign to .turntable-hint above, on purpose. */
.newsletter .section-title { padding-inline-end: .035em; }

.newsletter .section-lead { margin-inline: auto; }

.newsletter .form {
  margin-inline: auto;
  max-width: 34rem;
  text-align: start;
}

.newsletter .form-note { text-align: center; }


/* ═══ 15. FORMS ═══════════════════════════════════════════════════════════
   20px corners on inputs — the third corner value in the language, between the
   32px panels and the pill controls. ══════════════════════════════════════ */

.form {
  display: grid;
  gap: var(--space-sm);
  max-width: 40rem;
}

.field {
  display: grid;
  gap: .4rem;
}

.field-label {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ground-dim);
}

.field-input {
  width: 100%;
  min-height: 48px;
  padding: .8rem 1.05rem;
  background: var(--ground-wash);
  border: 2px solid var(--ground-line);
  border-radius: var(--radius-sm);
  color: var(--ground-fg);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: 1.45;
  transition: border-color var(--transition), background-color var(--transition);
}

textarea.field-input { min-height: 8.5rem; }

select.field-input { cursor: pointer; }

.field-input::placeholder {
  color: var(--ground-dim);
  opacity: .75;
}

.field-input:hover { border-color: var(--ground-line-strong); }

.field-input:focus { border-color: var(--ground-type-accent); }

.field-input[aria-invalid="true"] { border-color: var(--color-accent); }

/* The error text stays in the ground foreground and a tangerine GLYPH carries
   the signal, so nothing ever depends on orange type against butter cream
   (plain --color-accent measures 2.9:1 there and is barred from type — as a
   glyph fill it is unrestricted). A filled triangle rather than a disc: it
   suits Fredoka's rounded weight by contrast, it is not the same mark as the
   status disc on .chip, and it reads as a warning on its own.
   :empty matters — every .field-error is emitted into the DOM permanently and
   sits blank until there is something to say. Without it, every form would
   render a column of stray glyphs. */
.field-error {
  display: flex;
  align-items: baseline;
  gap: .4rem;
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.4;
  color: var(--ground-fg);
}

.field-error::before {
  content: "\25B2";
  flex: none;
  font-size: .7em;
  line-height: 1;
  color: var(--color-accent);
}

.field-error:empty { display: none; }

.form-note {
  font-size: var(--text-xs);
  line-height: 1.5;
  color: var(--ground-dim);
}


/* ═══ 16. ALERTS ══════════════════════════════════════════════════════════ */

.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md) var(--space-sm) var(--space-sm);
  border: 2px solid var(--ground-line-strong);
  border-radius: var(--radius-md);
  background: var(--ground-wash);
  color: var(--ground-fg);
  font-size: var(--text-base);
  line-height: 1.5;
}

.alert::before {
  content: "";
  flex: none;
  width: .85rem;
  height: .85rem;
  margin-block-start: .42em;
  border-radius: var(--radius-full);
  background: var(--ground-fg);
}

.alert-ok { border-color: var(--ground-fg); }

.alert-err {
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 16%, transparent);
}

.alert-err::before { background: var(--color-accent); }


/* ═══ 17. FOOTER & MOBILE BAR ═════════════════════════════════════════════ */

.site-footer {
  /* The footer republishes the ground channel on the deep mulberry, so any
     component dropped inside it reads correctly without a bespoke rule. */
  --ground:             var(--color-bg-deep);
  --ground-panel:       var(--color-surface);
  --ground-fg:          var(--color-cream);
  --ground-dim:         var(--color-cream-dim);
  --ground-line:        var(--color-border);
  --ground-line-strong: color-mix(in srgb, var(--color-cream) 34%, transparent);
  --ground-wash:        color-mix(in srgb, var(--color-cream) 7%, transparent);
  --ground-type-accent: var(--color-accent);
  --ground-focus:       var(--color-accent);
  background: var(--ground);
  color: var(--ground-fg);
  padding-block: var(--space-lg) var(--space-md);
  border-block-start: 1px solid var(--color-border);
}

.footer-col {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .6rem;
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--ground-dim);
}

.footer-title {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ground-fg);
}

.footer-col a { transition: color var(--transition); }
.footer-col a:hover { color: var(--ground-fg); }

.site-footer .brand { color: var(--color-cream); }

/* The closing line of the footer — copyright, the registered entity, the demo
   flags. A hairline separating two regions, not a tab down one side.
   ⚠ responsive.css: row with space-between at 768. */
.footer-base {
  display: flex;
  flex-direction: column;
  gap: .5rem;
  min-width: 0;
  margin-block-start: var(--space-md);
  padding-block-start: var(--space-sm);
  border-block-start: 1px solid var(--ground-line);
  font-size: var(--text-xs);
  line-height: 1.6;
  color: var(--ground-dim);
}

.footer-base a { transition: color var(--transition); }
.footer-base a:hover { color: var(--ground-fg); }

/* ── Sticky mobile call / WhatsApp bar ─────────────────────────────────
   Designed at 360px first: two equal pills, 48px tall, safe-area aware.
   No shadow — the deep ground plus a hairline does the separating.
   ⚠ responsive.css hides this at 768px and zeroes body padding-bottom. */
.mobile-bar {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 80;
  display: flex;
  gap: .5rem;
  padding: .5rem var(--gutter);
  padding-block-end: calc(.5rem + env(safe-area-inset-bottom, 0px));
  background: var(--color-bg-deep);
  border-block-start: 1px solid var(--color-border);
}

.mobile-bar .btn {
  flex: 1 1 0;
  min-width: 0;
  padding-inline: .75rem;
  font-size: var(--text-sm);
}

.mobile-bar .btn:hover { transform: none; }

/* Two-line bar items: a tracked label over the actual number/action. Declared
   AFTER .btn on purpose — if the markup ships `mobile-bar-item btn btn-primary`
   the column direction here wins on source order while .btn-primary keeps
   supplying the tangerine fill, so both spellings land correctly. */
.mobile-bar-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .1rem;
  flex: 1 1 0;
  min-width: 0;
  min-height: 48px;
  padding: .35rem .6rem;
  border: 2px solid var(--color-border);
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-cream);
  text-align: center;
  transition: background-color var(--transition), border-color var(--transition),
              color var(--transition);
}

.mobile-bar-item:hover {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-ink);
}

/* Positive tracking pulls centred text left by half a unit; the matching
   inline-START padding puts it back. Never text-indent. */
/* Every inline icon in this site is drawn as an outline: the paths ship
   fill="none" and a stroke-width, but no stroke — and SVG's default stroke is
   none, so without this rule they render completely invisible. One rule covers
   the nav, the bar and the footer. */
.site-header svg,
.mobile-bar svg,
.site-footer svg {
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.mobile-bar-label {
  /* CENTRED TRACKED LABEL. letter-spacing appends its space AFTER the last
     letter, which drags centred text to the LEFT. The repo rule is to pull
     that trailing space back off the END — never padding on the start (which
     was here and made it worse), never text-indent. */
  display: inline-block;
  margin-inline-end: -.14em;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: .14em;
  text-transform: uppercase;
  opacity: .72;
}

/* The number itself. Truncates rather than wrapping — at 360px two of these
   share the width and a wrapped phone number would push the bar taller than
   the clearance body reserves for it. */
.mobile-bar-value {
  max-width: 100%;
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}


/* ═══ 18. HAND-OFF NOTES ══════════════════════════════════════════════════

   grid-template-areas: NOT USED ANYWHERE IN THIS FILE. Nothing to reset.

   NO COLOURED SIDE-TABS. There is not one border-left / border-inline-start of
   any width or colour in this stylesheet. Where a block needs a signal — the
   field error, the alerts, the open/closed chip, the cream list — it carries a
   GLYPH or a disc, never a bar down one edge. Keep it that way: the pattern
   had to be stripped out of Studio Chaukhat once already, and the sibling site
   solved the same problems the same way, so the pair stays coherent.

   What responsive.css will want to take over at 768 / 1024 / 1440:
     · --gutter                 grow to 2.5 / 3.5 / 4.5rem (token comment)
     · body                     padding-bottom: 0 at 768 (mobile bar retires)
     · .mobile-bar              display: none at 768
     · .nav > .nav-list         display: flex at 1024
     · .nav-toggle, .nav-panel  display: none at 1024
     · .hero-inner              flex-direction: row / align to the edges at
                                1024 so .hero-copy + .drop-nav sit bottom-LEFT
                                and .drop-tagline bottom-RIGHT, per The Drop
     · .hero-title, .hero-copy  text-align: start at 1024
     · .drop-tagline            text-align: end at 1024
     · .drop-cup                widen and re-tune margin-block-start if the
                                hero word wraps to two lines
     · .cream-split             2 columns at 768/1024
     · .about-split             2 columns at 768/1024
     · .origin-rail             may become a 5-across grid at 1440, or stay a
                                rail — it snaps correctly either way
     · .merch-band              padding up to var(--space-lg)
     · .newsletter .form        may become input + button on one row at 768
     · .grid / .promise-tiles   already intrinsic (auto-fit); pin exact column
                                counts only where a section needs them

     · .footer-base             row with space-between at 768
     · .mobile-bar-*            retires with the bar at 768

   Do NOT restyle from responsive.css:
     · .turntable-hint          it is display:none until .turntable.is-ready.
                                Any breakpoint that sets display on it would
                                surface a drag hint on a control that cannot
                                be dragged with JS off.
     · .field-error             it is display:none while :empty.
     · .no-js .nav-toggle       and .no-js .nav-panel — the no-JS menu. A
                                breakpoint that hides .nav-panel at 1024 must
                                be written as `.has-js .nav-panel`, or a
                                desktop visitor with JS off loses every nav
                                link on the site. Same for .nav-toggle.
     · .skip-link               it is held off-screen by transform, never by
                                display or visibility, so it stays tabbable.
     · --transition             the overshoot is user-confirmed. See §1a.

   Owned elsewhere, deliberately absent here:
     · the ticker marquee translation and its pause states  → effects.css
     · every @keyframes, the .reveal hidden state, staggers  → effects.css
     · prefers-reduced-motion                                → effects.css
     · every min-width media query                           → responsive.css
   ═══════════════════════════════════════════════════════════════════════ */
