/* ==========================================================================
   Coffeenomics — Shared design system
   Editorial, documentary, anti-template. See project handoff for the rules
   this file encodes: no gradients, no glassmorphism, no floating cards,
   square-ish corners, thin rules instead of shadows, yellow as a signal only.
   ========================================================================== */

/* ---- Reset ---- */
/* The [hidden] attribute only hides an element via the browser's default
   stylesheet ([hidden] { display: none }) — any later, more specific rule
   that sets `display` on the same element (a lot of our components do,
   e.g. .field { display: flex }) silently overrides it, leaving the
   element invisible-looking-but-actually-still-laid-out or, worse, fully
   visible depending on the rest of the box model. Found via the Playwright
   test suite catching a conditional form field that stayed visible. This
   rule guarantees [hidden] always wins, everywhere, permanently. */
[hidden] { display: none !important; }

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body, h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img, picture, svg { display: block; max-width: 100%; }
input, button, textarea, select { font: inherit; color: inherit; }
button { cursor: pointer; }
a { color: inherit; text-decoration: none; }

/* ---- Design tokens ---- */
:root {
  /* Brand colour tokens (locked — see handoff §2) */
  --ink: #101820;
  --cream: #F5F1E8;
  --yellow: #F4C542;
  --slate: #53606A;
  --white: #FFFFFF;

  /* Derived */
  --ink-70: rgba(16, 24, 32, 0.7);
  --ink-15: rgba(16, 24, 32, 0.15);
  --ink-08: rgba(16, 24, 32, 0.08);
  --cream-on-ink: rgba(245, 241, 232, 0.85);
  --rule: rgba(16, 24, 32, 0.18);
  --rule-on-ink: rgba(245, 241, 232, 0.25);

  /* Type. Avenir Next Condensed is licensed (Apple/Linotype), not a
     freely embeddable webfont — no substitute file is provided in the
     project yet, so this falls back to the handoff's own documented
     fallback chain. Swap --font-display's first entry when a licensed
     webfont file is supplied. */
  --font-display: "Avenir Next Condensed", "Arial Narrow", "Helvetica Neue Condensed", Arial, sans-serif;
  --font-body: "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Courier New", monospace;

  /* Spacing base: 8px, deliberately not applied with mechanical evenness */
  --sp-1: 8px;
  --sp-2: 16px;
  --sp-3: 24px;
  --sp-4: 32px;
  --sp-5: 48px;
  --sp-6: 64px;
  --sp-7: 96px;
  --sp-8: 128px;

  --radius-control: 6px;
  --radius-form: 10px;

  --content-max: 1440px;
  --measure: 62ch; /* ~58-68 characters */

  --ease: cubic-bezier(0.2, 0.6, 0.2, 1);
}

/* ---- Base ---- */
html, body { background: var(--cream); }
html { overflow-x: hidden; }
body {
  font-family: var(--font-body);
  color: var(--ink);
  line-height: 1.5;
  font-size: 17px;
  background: var(--cream);
}
/* overflow-x: hidden is only declared on <html> above, not repeated
   here. Setting it on both broke every position:sticky element site-
   wide (including .site-header): overflow-x: hidden forces overflow-y
   to compute as auto (a documented CSS interdependency), which turned
   body into an extra intermediate scroll container that never
   actually scrolls internally — so every sticky descendant computed
   its offset against a scrollport that never moves, instead of the
   real page scroll on <html>. html's own overflow-x: hidden already
   fully blocks horizontal scroll for the whole document. */

/* Web Interface Guidelines audit (vercel-labs/web-interface-guidelines):
   anchor-linked sections (buying-request-form, upcoming-events, etc.) sit
   under a sticky header — without this, jumping to #id or focusing a field
   inside it lands partly hidden behind the header. */
[id] { scroll-margin-top: 96px; }

/* Same audit: touch targets shouldn't get the double-tap-zoom delay, and
   the tap highlight should be an intentional colour, not the browser
   default grey flash. */
a, button, input, select, textarea, label {
  touch-action: manipulation;
  -webkit-tap-highlight-color: rgba(244, 197, 66, 0.35);
}

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

h1 { font-size: clamp(2.6rem, 5.2vw, 5.4rem); text-transform: uppercase; }
/* Baseline heading-to-body gap. Section intros were relying on an
   inline margin-bottom per instance, which drifted between 0 and
   var(--sp-4) for the same "heading directly above a paragraph"
   relationship. This default covers every heading with no explicit
   override; the larger, deliberate inline overrides (numbered-
   sequence intros, form headings) and the deliberate 0 on solo
   aside-media headings still win, since an inline style always beats
   this rule. */
h2 { font-size: clamp(1.9rem, 3.4vw, 3.4rem); text-transform: uppercase; margin-bottom: var(--sp-2); }
h3 { font-size: clamp(1.3rem, 1.8vw, 1.7rem); text-transform: uppercase; }

p { max-width: var(--measure); }
p + p { margin-top: var(--sp-2); }

/* Centers a plain .wrap text section (eyebrow + h2 + body copy) as a single
   column on the page instead of letting it hug the left edge with empty
   space to the right. Every line (heading, paragraphs) is centered, and
   the eyebrow's leading dash is dropped since it reads as a left-alignment
   marker once the label itself is centered. */
.wrap.centered-copy { max-width: 640px; margin-inline: auto; text-align: center; }
.centered-copy h2, .centered-copy p { margin-inline: auto; }
.centered-copy .eyebrow::before { display: none; }
/* Opt back into the eyebrow's dash on a centered section where it should stay. */
.centered-copy.centered-copy--keep-dash .eyebrow::before { display: inline-block; }

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--slate);
  margin-bottom: var(--sp-2);
}
.eyebrow::before { content: ""; display: inline-block; width: 18px; height: 3px; background: var(--yellow); }

.on-ink .eyebrow { color: var(--cream-on-ink); }

/* ---- Accessibility ---- */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: var(--cream);
  padding: var(--sp-2) var(--sp-3);
  z-index: 1000;
}
.skip-link:focus {
  left: var(--sp-2);
  top: var(--sp-2);
}

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

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

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---- Layout / grid ---- */
.wrap {
  max-width: var(--content-max);
  margin-inline: auto;
  /* content-max (1440px) sits close to a lot of real desktop
     viewports, so the old var(--sp-5) (48px) read as nearly
     edge-to-edge. A little more room on wide screens; tablet and
     mobile keep their existing, already-appropriate padding. */
  padding-inline: var(--sp-6);
}
@media (max-width: 1080px) { .wrap { padding-inline: var(--sp-3); } }
@media (max-width: 640px) { .wrap { padding-inline: 20px; } }

.grid-12 {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--sp-3);
}
@media (max-width: 1080px) {
  .grid-12 { grid-template-columns: repeat(8, 1fr); }
}
@media (max-width: 640px) {
  .grid-12 { grid-template-columns: repeat(4, 1fr); }
}

section { padding-block: var(--sp-6); }
section.tight { padding-block: var(--sp-4); }
section.roomy { padding-block: var(--sp-8); }
section.on-ink { background: var(--ink); color: var(--cream); }
section.on-white { background: var(--white); }

.rule-top { border-top: 1px solid var(--rule); }
.rule-bottom { border-bottom: 1px solid var(--rule); }
.on-ink .rule-top { border-top-color: var(--rule-on-ink); }
.on-ink .rule-bottom { border-bottom-color: var(--rule-on-ink); }

/* ---- Image slots (real photography per handoff §2; placeholder until supplied) ---- */
.image-slot {
  position: relative;
  width: 100%;
  aspect-ratio: var(--slot-ratio, 4 / 5);
  background:
    repeating-linear-gradient(135deg, var(--ink-08) 0 1px, transparent 1px 14px),
    var(--white);
  border: 1px solid var(--ink-15);
  display: flex;
  align-items: flex-end;
  overflow: hidden;
}
.image-slot img { width: 100%; height: 100%; object-fit: cover; }
.image-slot__label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--slate);
  background: var(--cream);
  border: 1px solid var(--ink-15);
  padding: 6px 10px;
  margin: var(--sp-2);
}
.image-slot--wide { --slot-ratio: 16 / 9; }
.image-slot--square { --slot-ratio: 1 / 1; }
.image-slot--tall { --slot-ratio: 3 / 4; }

.evidence-label {
  position: absolute;
  left: var(--sp-2);
  bottom: var(--sp-2);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.03em;
  background: var(--ink);
  color: var(--cream);
  padding: 5px 9px;
}

/* ---- Buttons ---- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.98rem;
  padding: 14px 22px;
  border-radius: var(--radius-control);
  border: 2px solid var(--ink);
  transition: background-color 160ms var(--ease), color 160ms var(--ease), border-color 160ms var(--ease);
  white-space: nowrap;
}
.btn-primary {
  background: var(--yellow);
  border-color: var(--yellow);
  color: var(--ink);
}
.btn-primary:hover { background: #e0b334; border-color: #e0b334; }
.btn-primary:active { background: #cfa02e; border-color: #cfa02e; }
.btn-secondary {
  background: transparent;
  border-color: var(--ink);
  color: var(--ink);
}
.btn-secondary:hover { background: var(--ink); color: var(--cream); }
.on-ink .btn-secondary { border-color: var(--cream); color: var(--cream); }
.on-ink .btn-secondary:hover { background: var(--cream); color: var(--ink); }

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
  border-bottom: 2px solid var(--ink);
  padding-bottom: 2px;
}
.on-ink .link-arrow { border-bottom-color: var(--cream); }
.link-arrow::after { content: "→"; transition: transform 160ms var(--ease); }
.link-arrow:hover::after { transform: translateX(3px); }

.actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }

/* ---- Cookie consent banner ---- */
.consent-banner {
  position: fixed;
  left: var(--sp-2);
  right: var(--sp-2);
  bottom: var(--sp-2);
  z-index: 550;
  max-width: 760px;
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  background: var(--ink);
  color: var(--cream);
  /* Square and flat, like every other surface in the system. This block
     used to carry border-radius + box-shadow, the only two breaches of
     the Square Surface Rule and the Nothing Floats Rule in the whole
     stylesheet, on the first component most visitors ever see. A 2px
     cream border is how every other on-ink element comes forward. */
  border: 2px solid var(--cream);
  padding: var(--sp-3);
}
.consent-banner__text { flex: 1 1 280px; font-size: 0.92rem; }
.consent-banner__actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-left: auto; }
.consent-banner .btn-secondary { border-color: var(--cream); color: var(--cream); }
.consent-banner .btn-secondary:hover { background: var(--cream); color: var(--ink); }
@media (max-width: 480px) {
  .consent-banner { flex-direction: column; align-items: stretch; }
  /* flex: 1 1 280px on .consent-banner__text sets a 280px *width* basis
     for the row layout above - but flex-basis applies to the main axis,
     which switches to height once the banner goes to flex-direction:
     column here, forcing the text block to a 280px-tall minimum (plus
     flex-grow stretching it further) for what's actually 2-3 lines of
     text. Reset to its natural content height. */
  .consent-banner__text { flex: 0 1 auto; }
  .consent-banner__actions { margin-left: 0; justify-content: center; }
}

/* ---- Crosslink ---- */
/* The "looking to buy?" / "already have a quotation?" bands that hand a
   visitor off to another page's form (buy-coffee, partner-with-us,
   review-my-deal). Centered to read as a single closing prompt, matching
   .final-cta's treatment rather than the left-pinned body copy above it. */
.crosslink { text-align: center; }
.crosslink h2, .crosslink p, .crosslink .actions { margin-inline: auto; }
.crosslink .actions { justify-content: center; }
/* Baseline text-to-CTA gap. Every page hero's button row sat flush
   against its last paragraph (no margin at all) while in-body
   section CTAs mostly carried an inline margin-top: var(--sp-3) —
   the same relationship, two different results. var(--sp-4) reads
   right against the large display headings sitting above this copy;
   scoped to "actions right after content" so it never touches
   .final-cta__grid, where the actions row sits beside its heading in
   a flex row rather than below body copy. */
p + .actions, .ledger + .actions, .checklist + .actions, figure + .actions {
  margin-top: var(--sp-4);
}

/* ---- SiteHeader ---- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 500;
  background: var(--cream);
  border-bottom: 1px solid var(--rule);
}
.site-header__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: var(--sp-3);
  transition: padding-block 160ms var(--ease);
}
.site-header.is-compact .site-header__bar { padding-block: var(--sp-1); }

.wordmark { display: flex; align-items: center; }
.wordmark img { height: 32px; width: auto; display: block; }

.site-nav { display: flex; align-items: center; gap: var(--sp-4); }
.site-nav__links { display: flex; gap: var(--sp-3); }
.site-nav__links a {
  font-weight: 700;
  font-size: 0.92rem;
  padding-bottom: 2px;
  border-bottom: 2px solid transparent;
}
/* Adding Coffee Market made this a six-item bar. Without nowrap the
   labels break mid-phrase ("Buy / Coffee") before the bar ever runs out
   of room, and the tighter gaps between 901px and 1400px stop the first
   item colliding with the wordmark on a laptop. */
.site-nav__links a { white-space: nowrap; }
@media (min-width: 1001px) and (max-width: 1400px) {
  .site-nav { gap: var(--sp-3); }
  .site-nav__links { gap: var(--sp-2); }
  .site-header__bar { gap: var(--sp-3); }
}
.site-nav__links a:hover, .site-nav__links a[aria-current="page"] {
  border-bottom-color: var(--yellow);
}

.menu-toggle {
  display: none;
  background: none;
  border: 2px solid var(--ink);
  border-radius: var(--radius-control);
  width: 44px; height: 44px;
  align-items: center;
  justify-content: center;
}

/* Raised from 900px when Coffee Market made this a six-item bar: at 920px
   the six links, language switch and CTA needed 986px and pushed the header
   past the viewport. The hamburger now takes over before that happens. */
@media (max-width: 1000px) {
  .site-nav__links { display: none; }
  .site-header__cta.desktop-only { display: none; }
  .site-nav .lang-switch { display: none; }
  .menu-toggle { display: flex; }
}

.mobile-menu {
  position: fixed;
  inset: 0;
  background: var(--ink);
  color: var(--cream);
  z-index: 600;
  display: flex;
  flex-direction: column;
  padding: max(var(--sp-3), env(safe-area-inset-top)) max(var(--sp-3), env(safe-area-inset-right)) max(var(--sp-5), env(safe-area-inset-bottom)) max(var(--sp-3), env(safe-area-inset-left));
  transform: translateY(-100%);
  transition: transform 220ms var(--ease);
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* (Real hidden-when-[hidden] behaviour for this element is now guaranteed
   by the global [hidden] rule near the top of this file.) */
.mobile-menu.is-open { transform: translateY(0); }
.mobile-menu__top { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--sp-6); }
.mobile-menu__close {
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 2px solid var(--cream);
  border-radius: var(--radius-control);
  width: 44px; height: 44px;
  color: var(--cream);
}
.mobile-menu__links { display: flex; flex-direction: column; gap: var(--sp-3); margin-bottom: auto; }
.mobile-menu__links a { font-family: var(--font-display); font-size: 2rem; text-transform: uppercase; font-weight: 800; }
.mobile-menu__links a:active { color: var(--yellow); }
/* The mobile menu carried no current-page state at all: no aria-current in
   the markup and no style for it here, so at phone width neither a sighted
   nor an assistive visitor could tell where they were. Matches the desktop
   nav's yellow underline, at the menu's own scale. */
.mobile-menu__links a[aria-current="page"] {
  border-bottom: 3px solid var(--yellow);
  padding-bottom: 4px;
  align-self: flex-start;
}
.mobile-menu .btn-primary { align-self: flex-start; margin-top: var(--sp-5); }

/* ---- Language switcher ---- */
.lang-switch {
  font-weight: 700;
  font-size: 0.85rem;
  border: 2px solid var(--ink);
  border-radius: var(--radius-control);
  padding: 5px 10px;
  line-height: 1;
}
.lang-switch:hover { border-color: var(--yellow); }
.mobile-menu .lang-switch {
  align-self: flex-start;
  font-family: inherit;
  text-transform: none;
  font-size: 0.95rem;
  font-weight: 700;
  border: 2px solid var(--cream);
  border-radius: var(--radius-control);
  color: var(--cream);
  padding: 6px 14px;
  margin-top: var(--sp-3);
}

/* ---- PageHero ---- */
.page-hero { padding-block: var(--sp-6) var(--sp-6); }
.page-hero__grid {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: var(--sp-5);
  align-items: end;
}
.page-hero__copy h1 { margin-block: var(--sp-2) var(--sp-3); }
.page-hero__copy p { color: var(--slate); font-size: 1.15rem; }
@media (max-width: 900px) {
  .page-hero__grid { grid-template-columns: 1fr; }
}

/* Every other page-hero uses a compact 3D object image, roughly as
   tall as the text column, so align-items:end barely shows. Meet Us
   is the only hero using a tall photo (media-frame--tall, 3:4), much
   taller than its text — once top-aligned instead of bottom-aligned,
   the photo still runs well past where the text column ends, with
   real vertical room to spare. Rather than leave that as dead space,
   .page-hero__copy sticks in place while the photo scrolls past it:
   .page-hero__copy-slot stretches to the row's full height (the
   photo's height) so the sticky element nested inside has somewhere
   to travel — same reason as the homepage's .sticky-visual__slot. */
.page-hero__grid.page-hero__grid--sticky { align-items: stretch; }
.page-hero__copy--sticky {
  position: sticky;
  top: 96px;
  align-self: start;
}
@media (max-width: 900px) {
  .page-hero__copy--sticky { position: static; }
}

/* ---- ProofRail ---- */
.proof-rail {
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.proof-rail__list {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}
.proof-rail__item {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-3) var(--sp-2);
  border-left: 1px solid var(--rule);
  font-family: var(--font-display);
  font-weight: 800;
  text-transform: uppercase;
  font-size: 1rem;
  letter-spacing: 0.01em;
  text-align: center;
}
.proof-rail__item:first-child { border-left: none; }
.on-ink .proof-rail__item { border-left-color: var(--rule-on-ink); }
@media (max-width: 640px) {
  .proof-rail__list { grid-template-columns: repeat(2, 1fr); }
  .proof-rail__item:nth-child(3) { border-left: none; }
}

/* ---- Copy paired with a vertical stat list ----
   A paragraph/CTA block with a short list of trust points positioned
   beside it rather than below it (HomeDealReviewFeature: the "free
   comparison / private / response time" points belong to this specific
   CTA, not the whole merged network+deal-review section they used to
   sit underneath as a single unrelated-looking strip). */
.copy-with-stats { display: grid; grid-template-columns: 2fr 1fr; gap: var(--sp-4); align-items: start; }
.stat-list { display: flex; flex-direction: column; gap: var(--sp-4); }
.stat-list__item {
  font-family: var(--font-display);
  font-weight: 800;
  text-transform: uppercase;
  font-size: clamp(1.3rem, 1.8vw, 1.7rem);
  letter-spacing: -0.01em;
  line-height: 1.02;
}
@media (max-width: 900px) {
  .copy-with-stats { grid-template-columns: 1fr; }
  /* At full column width, .stat-list's heading-scale items (up to 1.7rem,
     32px gaps) stacked directly under the CTA button read as more giant
     headlines rather than supporting trust points. Rework at proof-rail's
     type scale instead, one item per line, with a divider so it reads as
     a distinct, secondary group under the button. */
  .stat-list {
    gap: var(--sp-2);
    margin-top: var(--sp-4);
    padding-top: var(--sp-3);
    border-top: 1px solid var(--rule);
  }
  .stat-list__item { font-size: 0.85rem; letter-spacing: 0.01em; }
}

/* ---- EditorialIntro ---- */
.editorial-intro__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-5);
  align-items: center;
}
@media (max-width: 900px) { .editorial-intro__grid { grid-template-columns: 1fr; } }

/* Same sticky technique as .page-hero__grid--sticky / .sticky-visual:
   the shorter text column stays pinned in view while the taller
   image+ledger column scrolls past beside it. .editorial-intro__copy-slot
   stretches to the row's full height so the sticky element nested
   inside has room to travel — align-items:start on a plain grid item
   would otherwise shrink it to its own content size, leaving no room
   to stick at all. */
.editorial-intro__grid.editorial-intro__grid--sticky { align-items: stretch; }
.editorial-intro__copy--sticky {
  position: sticky;
  top: 96px;
  align-self: start;
}
@media (max-width: 900px) {
  .editorial-intro__copy--sticky { position: static; }
}

/* ---- StickyVisual ----
   One figure tracks scroll across two stacked text blocks: it starts
   level with the first block and, via position:sticky, stays pinned
   at `top` while the text column scrolls past underneath it, then
   settles at the bottom of the row once the second block's end
   catches up — which is what makes it look like it "arrives" there.
   Sticky is inherently bidirectional, so scrolling back up reverses
   it with no extra code.

   The figure sits in a wrapper (.sticky-visual__slot), not directly
   in the grid, on purpose: a sticky element needs a containing block
   taller than itself to have room to travel in. With align-items:start
   a grid item shrinks to fit its own content, leaving zero such room —
   sticky computes fine but never visibly sticks. Default grid stretch
   makes the slot match the row's full height (the taller text column);
   the shorter figure inside it then has real space to move within. */
.sticky-visual {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-4) var(--sp-5);
}
.sticky-visual__text {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
}
/* .media-frame.sticky-visual__figure (not just .sticky-visual__figure):
   .media-frame itself sets position:relative, and as an equal-specificity
   single-class selector defined later in this file it would otherwise win
   and silently cancel the sticky behaviour. The compound selector's higher
   specificity guarantees this wins regardless of source order. */
.media-frame.sticky-visual__figure {
  position: sticky;
  top: 96px;
}
@media (max-width: 900px) {
  .sticky-visual { grid-template-columns: 1fr; }
  .media-frame.sticky-visual__figure { position: static; }
}

/* ---- NumberedSequence ---- */
.numbered-sequence { border-top: 1px solid var(--rule); }
.numbered-sequence__row {
  display: grid;
  grid-template-columns: 100px 1fr;
  gap: var(--sp-3);
  padding-block: var(--sp-4);
  border-bottom: 1px solid var(--rule);
}
.on-ink .numbered-sequence__row { border-bottom-color: var(--rule-on-ink); }
/* Some rows wrap their heading + paragraph in one div (one grid item,
   auto-placed into the 1fr column); others — every step inside
   --horizontal, since that variant collapses each row to a single
   column above 901px and doesn't need the wrapper — list the heading
   and paragraph as separate flat children. Below 901px, without this,
   those flat children fall through the base [number][text] grid one
   at a time: heading lands in column 2 as expected, but the paragraph
   then auto-places into a new implicit row starting back at column 1
   (100px), wrapping to one word per line. Pinning both to column 2
   makes the flat markup behave like the wrapped markup at every width. */
.numbered-sequence__row > h3, .numbered-sequence__row > p { grid-column: 2; }
.numbered-sequence__num {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 2.6rem;
  /* Was color: var(--yellow), which measures 1.44:1 on cream and 1.63:1
     on white against a 3:1 floor for large text. The numeral is now ink
     and the yellow moves to a rule beneath it, which is how the system
     already marks things (the eyebrow's leading dash). Yellow stays a
     signal, the sequence stays readable. */
  color: var(--ink);
  line-height: 1;
  display: inline-block;
  border-bottom: 3px solid var(--yellow);
  padding-bottom: 4px;
}
.on-ink .numbered-sequence__num { color: var(--cream); }
.numbered-sequence__row h3 { margin-bottom: var(--sp-1); }
@media (min-width: 901px) {
  .numbered-sequence--horizontal { display: grid; grid-template-columns: repeat(3, 1fr); border-top: none; }
  /* A four-step sequence (Review My Deal's "second opinion" steps) used to
     split into a 3-across row plus a lone 4th item dropped below it, and
     that orphaned item's copy was mis-nested into the row's default
     [number][text] grid, wrapping to a ~100px sliver. Four items read
     better as one row than as an awkward 3-plus-1. */
  .numbered-sequence--horizontal.numbered-sequence--four { grid-template-columns: repeat(4, 1fr); }
  .numbered-sequence--horizontal .numbered-sequence__row {
    grid-template-columns: 1fr;
    border-bottom: none;
    border-left: 1px solid var(--rule);
    padding-inline: var(--sp-3);
  }
  /* This variant's row only has one column, so the column-2 pin above
     (needed for the flat-markup fix below 901px) would otherwise create
     a phantom second column here. */
  .numbered-sequence--horizontal .numbered-sequence__row > h3,
  .numbered-sequence--horizontal .numbered-sequence__row > p {
    grid-column: auto;
  }
  .numbered-sequence--horizontal .numbered-sequence__row:first-child { border-left: none; padding-left: 0; }
}
@media (min-width: 901px) {
  .numbered-sequence--horizontal.on-ink .numbered-sequence__row { border-left-color: var(--rule-on-ink); }
}

/* ---- DisclosureBlock ---- */
.disclosure-block {
  border: 1px solid var(--rule);
  padding: var(--sp-4);
  background: var(--white);
}
.disclosure-block p { color: var(--slate); font-size: 0.98rem; }
.disclosure-block h2, .disclosure-block h3 { color: var(--ink); }

/* ---- FormShell ---- */
.form-shell {
  background: var(--white);
  border: 1px solid var(--rule);
  padding: var(--sp-5);
}
@media (max-width: 640px) { .form-shell { padding: var(--sp-3); } }

.form-stages {
  display: flex;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.form-stages__step { padding-bottom: var(--sp-1); border-bottom: 3px solid var(--rule); color: var(--slate); flex: 1; }
.form-stages__step.is-active { border-bottom-color: var(--yellow); color: var(--ink); font-weight: 700; }
.form-stages__step.is-done { border-bottom-color: var(--ink); color: var(--ink); }

.form-stage { display: none; }
.form-stage.is-active { display: block; }

.field-group { display: grid; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.field-group--split { grid-template-columns: 1fr 1fr; }
@media (max-width: 640px) { .field-group--split { grid-template-columns: 1fr; } }

.field { display: flex; flex-direction: column; gap: 6px; }
.field label {
  font-size: 0.9rem;
  font-weight: 700;
}
.field .hint { font-size: 0.82rem; color: var(--slate); }
.field .optional-tag {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--slate);
  text-transform: uppercase;
  font-weight: 400;
}
.field input[type="text"],
.field input[type="email"],
.field input[type="tel"],
.field input[type="number"],
.field select,
.field textarea {
  border: 1.5px solid var(--ink);
  border-radius: 4px;
  padding: 12px 14px;
  background: var(--white);
  font-size: 1rem;
}
.field textarea { resize: vertical; min-height: 96px; }
/* Selects: the platform control draws its own chevron and sits at its own
   height, which left a row of "price / currency / unit" looking like three
   different kinds of box. One chevron, one height, the same box as a text
   input. padding-right clears the chevron so long options never run under
   it; background-image goes after the shared rule's background shorthand
   on purpose, or the shorthand would wipe it. */
.field select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 30px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%23101820' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 12px 8px;
}
/* One height for every control in a row, whatever it is. */
.field input[type="text"],
.field input[type="email"],
.field input[type="tel"],
.field input[type="number"],
.field select {
  min-height: 52px;
}
.field input:focus, .field select:focus, .field textarea:focus {
  outline: 3px solid var(--yellow);
  outline-offset: 1px;
}
.field.has-error input, .field.has-error select, .field.has-error textarea {
  border-color: #B3261E;
}
.field-error {
  font-size: 0.85rem;
  color: #B3261E;
  display: none;
}
.field.has-error .field-error { display: block; }

.radio-group, .checkbox-group { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.radio-option, .checkbox-option {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1.5px solid var(--ink-15);
  border-radius: var(--radius-control);
  padding: 10px 14px;
  font-size: 0.92rem;
}
.radio-option:has(input:checked), .checkbox-option:has(input:checked) {
  border-color: var(--ink);
  background: var(--cream);
}

/* flex-wrap:wrap — without it, two full-length labels like "Back to
   Requirement" / "Continue to Contact" don't fit side by side on a
   narrow phone and neither button shrinks (same flex min-width:auto
   issue as .acknowledgment label), so the second button silently
   overflows past the edge of the screen, partly or fully untappable.
   Wrapping lets it drop to its own full-width row instead. */
.form-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2);
  margin-top: var(--sp-4);
}
@media (max-width: 480px) {
  .form-nav .btn { flex: 1 1 100%; }
}

.acknowledgment {
  font-size: 0.88rem;
  color: var(--slate);
  border-top: 1px solid var(--rule);
  padding-top: var(--sp-3);
  margin-top: var(--sp-3);
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: flex-start;
}
.acknowledgment input { margin-top: 4px; }
/* flex-basis defaults to the label's full unwrapped text width, which
   is far wider than the row (checkbox + label + long sentence), so
   the label was pushed to its own flex line entirely instead of
   wrapping its own text within the remaining space next to the
   checkbox. flex:1 1 0% + min-width:0 is the standard fix: it ignores
   content size for sizing and lets the label wrap normally instead. */
.acknowledgment label { flex: 1 1 0%; min-width: 0; }
input[type="checkbox"], input[type="radio"] { accent-color: var(--yellow); }
.acknowledgment .required-tag {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--ink);
  text-transform: uppercase;
  font-weight: 700;
  margin-left: 4px;
}
/* Checkboxes fail native validation the same as text fields, but had no
   equivalent .has-error treatment — someone could fail to submit with no
   visible sign of which checkbox was the problem. Mirrors .field.has-error
   (red border, only-on-error message) but as a full box, since a checkbox
   row has no input border of its own to redden. */
.acknowledgment.has-error {
  border: 1.5px solid #B3261E;
  border-radius: 4px;
  padding: var(--sp-2) var(--sp-3);
  background: rgba(179, 38, 30, 0.05);
}
.acknowledgment.has-error label { color: #B3261E; }
.acknowledgment .field-error {
  font-size: 0.85rem;
  color: #B3261E;
  display: none;
  flex-basis: 100%;
}
.acknowledgment.has-error .field-error { display: block; }

.form-status {
  font-size: 0.9rem;
  padding: var(--sp-2);
  border: 1px solid var(--rule);
  margin-bottom: var(--sp-3);
  display: none;
}
.form-status.is-visible { display: block; }
.form-status.is-error { border-color: #B3261E; color: #B3261E; }

.form-success { text-align: left; padding-block: var(--sp-4); }
.form-success h2 { margin-bottom: var(--sp-2); }

/* ---- FinalCTA ---- */
.final-cta {
  background: var(--ink);
  color: var(--cream);
  text-align: center;
}
/* .final-cta.roomy (not just .final-cta) for the specificity to beat
   section.roomy's own padding-block: this content is just a heading
   and a button (~166px), and .roomy's normal 128px top/bottom (meant
   for sections with substantially more content) left it looking like
   mostly empty space rather than a deliberately spacious close. */
.final-cta.roomy { padding-block: var(--sp-6); }
.final-cta__grid {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-4);
}
.final-cta h2 { margin-bottom: var(--sp-2); max-width: 16ch; margin-inline: auto; }
.final-cta p { color: var(--cream-on-ink); }
.final-cta .actions { justify-content: center; }

/* Modifier for a final-cta heading written as several short lines (explicit
   <br> per line) rather than one wrapped sentence. Needs its own width (no
   16ch cap) and, on narrow phones, a smaller size so each line still fits
   without an unwanted extra wrap. */
.final-cta h2.final-cta__heading--lines { max-width: none; }
@media (max-width: 640px) {
  .final-cta h2.final-cta__heading--lines { font-size: clamp(22px, 7vw, 30px); }
}

/* ---- SiteFooter ---- */
.site-footer {
  background: var(--ink);
  color: var(--cream);
  padding-block: var(--sp-6) var(--sp-4);
}
.site-footer__grid {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  gap: var(--sp-5);
}
@media (max-width: 900px) { .site-footer__grid { grid-template-columns: 1fr; gap: var(--sp-4); } }
.site-footer p { color: var(--cream-on-ink); }
.site-footer__links { display: flex; flex-direction: column; gap: 10px; }
.site-footer__links a:hover { color: var(--yellow); }
.site-footer__bottom {
  border-top: 1px solid var(--rule-on-ink);
  margin-top: var(--sp-5);
  padding-top: var(--sp-3);
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-2);
  font-size: 0.82rem;
  /* --slate on the ink footer ground measures 2.75:1, below the 4.5:1
     floor, and it was the one on-ink element not using the system's own
     on-ink text token. Every other one (eyebrow, ledger dd, figcaption)
     switches to --cream-on-ink; this now does too, at 11.66:1. */
  color: var(--cream-on-ink);
}
.site-footer__wordmark { color: var(--cream); }

/* ---- Ledger / split / editorial utilities ---- */
.ledger { border-top: 1px solid var(--rule); }
.ledger__row {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: var(--sp-3);
  padding-block: var(--sp-3);
  border-bottom: 1px solid var(--rule);
}
.on-ink .ledger__row { border-bottom-color: var(--rule-on-ink); }
.ledger__row dt { font-weight: 700; }
.ledger__row dd { color: var(--slate); margin: 0; }
.on-ink .ledger__row dd { color: var(--cream-on-ink); }
/* A link inside a ledger row (e.g. UpcomingEvents' "Event website")
   otherwise inherits the site's plain a { color: inherit; text-decoration:
   none; } and reads as identical, non-clickable body text against the
   already-muted dd color. */
.ledger__row dd a { text-decoration: underline; text-underline-offset: 3px; font-weight: 700; }
.ledger__row dd a:hover { color: var(--yellow); }
@media (max-width: 640px) {
  .ledger__row { grid-template-columns: 1fr; gap: 6px; }
}

/* ---- Ledger: stacked variant ----
   The default side-by-side term/description row reads fine at full
   width, but crowds a bold heading when the whole ledger already sits
   in a half-width column next to an image (aside-media). Stacks the
   term above the description instead, styled like an h3, matching the
   card heading treatment used everywhere a list has the full width to
   itself (.two-col-list). */
.ledger--stacked .ledger__row { grid-template-columns: 1fr; gap: var(--sp-1); padding-block: var(--sp-4); }
.ledger--stacked .ledger__row dt {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.3rem, 1.8vw, 1.7rem);
  text-transform: uppercase;
  letter-spacing: -0.01em;
  line-height: 1.02;
}

.split { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-5); }
@media (max-width: 900px) { .split { grid-template-columns: 1fr; } }

.two-col-list {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-4) var(--sp-5);
}
@media (max-width: 900px) {
  .two-col-list { grid-template-columns: 1fr; }
}

.stamp {
  border: 2px solid var(--ink);
  padding: var(--sp-3);
}
.stamp.is-active { border-color: var(--yellow); background: var(--ink); color: var(--cream); }
.stamp__label {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.3rem;
  letter-spacing: 0.02em;
}

.checklist { display: grid; gap: 10px; }
.checklist li { display: flex; gap: 10px; align-items: baseline; }
.checklist li::before { content: ""; display: inline-block; width: 10px; height: 10px; border: 2px solid var(--yellow); flex: none; transform: translateY(1px); }

.location-line {
  font-family: var(--font-mono);
  letter-spacing: 0.03em;
  color: var(--slate);
}
.on-ink .location-line { color: var(--cream-on-ink); }

.container-note {
  font-size: 0.85rem;
  color: var(--slate);
}

/* ---- Full-bleed statement (per handoff §3: "full-width statements" as
   one of the preferred alternatives to a repeated split layout) ---- */
.full-bleed {
  position: relative;
  min-height: 520px;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
}
.full-bleed .image-slot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: none;
  aspect-ratio: auto;
}
.full-bleed__scrim {
  position: absolute;
  inset: 0;
  /* The panel's upper lines (eyebrow, h2) sat where the old scrim was
     only 5-11% opaque, over bright sky in the Poznan photo: measured
     1.56:1, 1.66:1 and 2.71:1. Raising the floor so the scrim never
     drops below 38% keeps the photograph readable as a photograph while
     the text clears its thresholds. */
  background: linear-gradient(0deg, rgba(16,24,32,0.88) 0%, rgba(16,24,32,0.74) 40%, rgba(16,24,32,0.55) 70%, rgba(16,24,32,0.38) 100%);
}
.full-bleed__panel {
  position: relative;
  z-index: 1;
  color: var(--cream);
  padding: var(--sp-5);
  max-width: 720px;
}
.full-bleed__panel .eyebrow { color: var(--cream-on-ink); }
.full-bleed__panel p { color: var(--cream-on-ink); }
@media (max-width: 640px) {
  .full-bleed { min-height: 440px; }
  .full-bleed__panel { padding: var(--sp-3); }
}
.full-bleed__media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---- Media system (real photography + approved 3D objects) ----
   .media-frame is the shared wrapper. Two variants:
   --photo crops to a fixed ratio with object-fit: cover, for real
   documentary photography. --object never crops (object-fit: contain,
   transparent background) for the approved 3D renders, so the full
   silhouette always survives regardless of the surrounding column
   width. Ratio modifiers (--wide/--tall/--square) only apply to
   --photo; --object ignores them and sizes from its own intrinsic
   ratio via height:auto, per the "never crop a 3D object" rule. */
.media-frame {
  position: relative;
  width: 100%;
  display: block;
}
.media-frame figcaption {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.03em;
  color: var(--slate);
  padding-top: var(--sp-1);
}
.on-ink .media-frame figcaption { color: var(--cream-on-ink); }

.media-frame--photo {
  aspect-ratio: var(--frame-ratio, 4 / 5);
  overflow: hidden;
  border: 1px solid var(--ink-15);
  background: var(--white);
}
.media-frame--photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--frame-position, center);
  display: block;
}
.media-frame--wide { --frame-ratio: 16 / 9; }
.media-frame--tall { --frame-ratio: 3 / 4; }
.media-frame--square { --frame-ratio: 1 / 1; }

.media-frame--object {
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
}
.media-frame--object img {
  width: 100%;
  height: auto;
  object-fit: contain;
  display: block;
  filter: drop-shadow(0 20px 16px rgba(16, 24, 32, 0.16));
}
/* A small number of object placements sit on a light card so a
   transparent-background PNG reads clearly against a busy section
   (e.g. next to a ledger). Used sparingly, never as the default. */
.media-frame--object.media-frame--grounded {
  background: var(--white);
  border: 1px solid var(--ink-15);
  padding: var(--sp-4);
}
.on-ink .media-frame--object.media-frame--grounded {
  background: rgba(245, 241, 232, 0.06);
  border-color: var(--rule-on-ink);
}

/* A plain --object figure placed straight into a 50/50 (or near-50/50)
   two-column grid — HomeBuyingIntro, HomeDealReviewFeature, the three
   HomeNetwork/BuyNetwork/PartnerNetwork placements, BuySharedContainer,
   BuyPromptBoard — has no size cap, so width:100% fills the whole
   column: at content-max widths that let a 3D render outgrow a short
   ledger or paragraph sitting next to it (the "coffee sack" report).
   The aside-media system already solved this for its own placements
   with a max-width; this brings the remaining plain placements to the
   same scale. --hero stays excluded: it is deliberately allowed to
   run larger. */
.editorial-intro__grid .media-frame--object:not(.media-frame--hero),
.wrap.split .media-frame--object:not(.media-frame--hero),
.buying-brief__grid .media-frame--object:not(.media-frame--hero) {
  max-width: 360px;
  margin-inline: auto;
}

/* HomeFounders / AboutHero: two aligned portraits standing in for one
   "Kevin and Dougie together" image, per the handoff's own fallback
   (§ founder imagery: "two aligned documentary portraits"). */
.founder-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-2);
}
.founder-pair .media-frame--photo { --frame-ratio: 4 / 5; }

/* AboutOriginStory: a small real sequence (archival + current) rather
   than one image standing in for the whole story. */
.archive-sequence {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-2);
}
.archive-sequence .media-frame--photo { --frame-ratio: 3 / 4; }

/* DealOutcomes: each verdict plate sits directly above its matching
   .stamp panel, grouped as one .verdict-item so the pairing holds at
   every width. object-fit: contain keeps each plate's baked-in
   lettering legible at every width; this is the one place more than
   one 3D asset shares a viewport, which the asset manifest allows
   for a deliberate side-by-side comparison. */
.verdict-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-3);
}
.verdict-item .media-frame--object { max-width: 100%; margin-bottom: var(--sp-3); }
.verdict-item .media-frame--object img { max-height: 220px; margin-inline: auto; }
@media (max-width: 640px) {
  .verdict-grid { grid-template-columns: 1fr; gap: var(--sp-6); }
}

/* Form success screens: a small confirmation object, never load-bearing
   for the copy above it. */
.form-success__visual {
  max-width: 200px;
  margin-bottom: var(--sp-3);
}

/* Hero-scale 3D placements (HomePartnerFeature-style tall columns,
   HomeHero) are allowed to run a little larger than a supporting
   in-page object. */
.media-frame--hero img { max-height: 560px; margin-inline: auto; }
@media (max-width: 640px) {
  .media-frame--hero img { max-height: 360px; }
}

/* ---- Aside-media: copy + one 3D object, sharing one mobile rule ----
   Every new placement below needs the same thing: an intro (eyebrow +
   heading, sometimes a lead paragraph), the object, then the rest of
   the section's real content (a ledger, a checklist, a list). Rather
   than one-off markup per section, grid-template-areas puts the
   object on the left or right on desktop and always between the
   intro and the rest on mobile, matching "place after the
   introduction, before the detailed content" for every one of these
   sections without extra markup or JS reordering. */
.aside-media {
  display: grid;
  gap: var(--sp-4) var(--sp-5);
  align-items: center;
}
.aside-media--right {
  grid-template-columns: 1fr 1fr;
  grid-template-areas: "intro visual" "body visual" "extra visual";
}
.aside-media--left {
  grid-template-columns: 1fr 1fr;
  grid-template-areas: "visual intro" "visual body" "visual extra";
}
.aside-media__intro { grid-area: intro; }
.aside-media__body { grid-area: body; }
.aside-media__extra { grid-area: extra; }
.aside-media__visual {
  grid-area: visual;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Recommended sizing: ~440-560px standard, up to 580px for a wider
   composition (three trays side by side needs the room), smaller when
   an asset should stay visually subordinate to the section's main
   point rather than compete with it. */
/* These target the figure itself: every actual usage puts .aside-media__visual
   and .media-frame on the same element (never nested), so the selector must
   be compound (no space) to actually match. */
.aside-media__visual.media-frame { width: 100%; max-width: 520px; }
.aside-media__visual--wide.media-frame { max-width: 580px; }
.aside-media__visual--subordinate.media-frame { max-width: 400px; }

/* ---- AsideMedia sticky visual ----
   These sections usually carry a multi-row ledger, making the text
   column (intro+body+extra) taller than the image, which just sits
   centered in its spanned grid area and scrolls away as soon as the
   shorter visual ends. Same nested-slot technique as .sticky-visual:
   .aside-media__visual-slot takes over the "visual" grid-area and
   stretches to the row's full spanned height (align-items:stretch),
   giving the sticky figure nested inside real room to travel. */
.aside-media.aside-media--sticky { align-items: stretch; }
.aside-media__visual-slot { grid-area: visual; }
.aside-media__visual--sticky {
  position: sticky;
  top: 96px;
  align-self: start;
}
@media (max-width: 900px) {
  .aside-media__visual--sticky { position: static; }
}

@media (max-width: 1024px) and (min-width: 901px) {
  .aside-media__visual.media-frame { max-width: 440px; }
  .aside-media__visual--wide.media-frame { max-width: 460px; }
}
@media (max-width: 900px) {
  .aside-media--right, .aside-media--left {
    grid-template-columns: 1fr;
    grid-template-areas: "intro" "visual" "body" "extra";
    gap: var(--sp-3);
  }
  .aside-media__visual.media-frame,
  .aside-media__visual--wide.media-frame,
  .aside-media__visual--subordinate.media-frame {
    max-width: 360px;
  }
  .aside-media__visual.media-frame img { width: 84vw; max-width: 360px; margin-inline: auto; }
}

/* ---- BuyPromptBoard: ledger + buying-brief object ---- */
.buying-brief__grid {
  display: grid;
  grid-template-columns: 1fr 0.8fr;
  gap: var(--sp-5);
  align-items: center;
}
@media (max-width: 900px) { .buying-brief__grid { grid-template-columns: 1fr; } }

/* The ledger here runs longer than the figure, so the image can stick
   in view while the ledger scrolls past — same nested-slot technique
   as .sticky-visual/.aside-media--sticky. */
.buying-brief__grid.buying-brief__grid--sticky { align-items: stretch; }
.buying-brief__grid__visual--sticky {
  position: sticky;
  top: 96px;
  align-self: start;
}
@media (max-width: 900px) {
  .buying-brief__grid__visual--sticky { position: static; }
}

/* JS-only motion reveal (see js/main.js). Never a static default: an
   element only gets this class added by script, and the class itself
   only sets the *starting* state — a no-JS visitor, or a page where
   the observer never fires for some reason, keeps full opacity via
   the [data-reveal] default below. prefers-reduced-motion is already
   handled globally (transition-duration clamped near the top of this
   file), so no separate override is needed here. */
[data-reveal] {
  opacity: 1;
  transform: none;
}
[data-reveal].is-pending {
  opacity: 0;
  transform: translateY(8px);
}
[data-reveal].is-visible {
  opacity: 1;
  transform: none;
  transition: opacity 420ms var(--ease), transform 420ms var(--ease);
}

/* ---- ComparePrices calculator (compare-my-prices.html) ----
   Inputs sit in the shared white .form-shell; the live result is an
   ink panel reusing .stamp and .ledger (their .on-ink variants), so the
   verdict reads like the site's existing three-stamp scale. The panel
   sticks beside the inputs on desktop and stacks below them on mobile. */
.calc-hero { padding-block: var(--sp-5) var(--sp-6); }
.calc-intro { max-width: 780px; margin-bottom: var(--sp-4); }
.calc-intro h1 { margin-block: var(--sp-2) var(--sp-3); }
.calc-intro p { color: var(--slate); font-size: 1.15rem; }
.calc {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--sp-4);
  align-items: start;
}
@media (max-width: 900px) { .calc { grid-template-columns: minmax(0, 1fr); } }
/* minmax(0, 1fr), not the implicit auto column: otherwise the widest
   child (the grade-comparison table) sets the column width and the form
   overflows a 360px screen instead of the table scrolling in its box. */
.calc__inputs { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-3); }
.calc__row { display: grid; gap: var(--sp-2); }
/* A two-line label ("Your current price") would otherwise push its own
   control below its neighbours'. Bottom-aligning keeps every control in a
   row on one line, whatever its label does. */
.calc__row > .field { min-width: 0; justify-content: flex-end; }
/* Both rows run on the same three tracks, so "Your current price" lines
   up with "Quantity" and "Currency" with "Unit". The quantity's unit
   spans the last two tracks: one field, but the same right edge. */
/* The floors are what the longest option needs ("per 60 kg bag", "GBP")
   plus the padding and the chevron, so no value is ever clipped; the fr
   parts share whatever is left over. */
.calc__row--price,
.calc__row--qty { grid-template-columns: minmax(0, 1fr) minmax(5rem, 0.7fr) minmax(9rem, 1.25fr); }
.calc__row--qty > .field:nth-child(2) { grid-column: 2 / -1; }
.calc__row .calc__min-order { grid-column: 1 / -1; margin: 0; }
@media (max-width: 640px) {
  /* Price on its own line, then the two selects. Not an even split: the
     unit carries "per 60 kg bag" and the currency only three letters. */
  .calc__row--price { grid-template-columns: 0.8fr 1.2fr; }
  .calc__row--price > .field:first-child { grid-column: 1 / -1; }
}
.calc__min-order { font-size: 0.82rem; color: var(--slate); }
.calc__fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
.calc__fieldset legend { font-size: 0.9rem; font-weight: 700; padding: 0; margin-bottom: 6px; }

.calc__result {
  background: var(--ink);
  color: var(--cream);
  padding: var(--sp-5);
  position: sticky;
  top: 96px;
}
@media (max-width: 900px) { .calc__result { position: static; } }
@media (max-width: 640px) { .calc__result { padding: var(--sp-3); } }
.calc__notice {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border: 2px solid var(--yellow);
  padding: var(--sp-1) var(--sp-2);
  margin-bottom: var(--sp-3);
  max-width: none;
}
.calc__headline {
  font-family: var(--font-display);
  font-weight: 800;
  text-transform: uppercase;
  /* 7vw keeps the longest Polish word ("ZAOSZCZĘDZIĆ") on one line at
     360px; the cap keeps desktop at the original size. */
  font-size: clamp(1.6rem, 7vw, 2.8rem);
  line-height: 1.02;
  letter-spacing: -0.01em;
  text-wrap: balance;
  overflow-wrap: normal;
  hyphens: manual;
  max-width: none;
}
.calc__figure { color: var(--yellow); white-space: nowrap; }
.calc__sub { margin-top: var(--sp-1); font-size: 1.1rem; color: var(--cream-on-ink); }
/* Notes use the site's checklist marker (yellow-outlined square), not a
   coloured side border. */
.calc__note {
  margin-top: var(--sp-2);
  font-size: 0.92rem;
  color: var(--cream-on-ink);
  display: flex;
  gap: 10px;
  align-items: baseline;
}
.calc__note::before,
.calc-prefill-note::before {
  content: "";
  flex: none;
  width: 10px;
  height: 10px;
  border: 2px solid var(--yellow);
  transform: translateY(1px);
}
/* The three stamps read as one left-to-right scale, not as buttons: a
   single track along the top with a tick per step, labels beneath, and
   only the active step framed (its stretch of track turns yellow). Every
   step stays legible; none look "disabled". */
.calc__stamps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 2px;
  margin-block: var(--sp-3);
  border-top: 2px solid var(--rule-on-ink);
}
.calc__stamps .stamp {
  position: relative;
  display: flex;
  align-items: flex-start;
  min-height: 44px;
  padding: 14px 10px 10px;
  border: 2px solid transparent;
  border-top: 0;
  color: var(--cream-on-ink);
  transition: border-color 160ms var(--ease), color 160ms var(--ease);
}
.calc__stamps .stamp::before {
  content: "";
  position: absolute;
  top: 0;
  left: 10px;
  width: 2px;
  height: 8px;
  background: var(--rule-on-ink);
}
.calc__stamps .stamp.is-active::after {
  content: "";
  position: absolute;
  left: -2px;
  right: -2px;
  top: -2px;
  height: 2px;
  background: var(--yellow);
}
.calc__stamps .stamp__label { font-size: 0.86rem; font-weight: 700; line-height: 1.1; }
.calc__stamps .stamp.is-active { border-color: var(--yellow); color: var(--cream); }
.calc__stamps .stamp.is-active::before { background: var(--yellow); }
.calc__stamps .stamp.is-active .stamp__label { font-size: 1rem; font-weight: 800; }
.calc__ledger { border-top-color: var(--rule-on-ink); }
.calc__ledger .ledger__row { grid-template-columns: 170px minmax(0, 1fr); gap: var(--sp-2); padding-block: 12px; }
.calc__ledger .ledger__row dt { font-size: 0.9rem; }
.calc__ledger .ledger__row dd { font-size: 0.95rem; }
@media (max-width: 640px) { .calc__ledger .ledger__row { grid-template-columns: 1fr; gap: 2px; } }
.calc__actions { margin-top: var(--sp-4); }
.calc__disclaimer { margin-top: var(--sp-3); font-size: 0.85rem; color: var(--cream-on-ink); }
.calc-prefill-note {
  display: flex;
  gap: 10px;
  align-items: baseline;
  margin-bottom: var(--sp-3);
  font-weight: 700;
}

.hint a { text-decoration: underline; text-underline-offset: 3px; font-weight: 700; }

/* ---- ComparePrices: option groups, breakdown, phone pass ----
   Price basis stacks as full-width options (its labels are long);
   destination is a three-up segmented control. Both keep the shared
   .radio-option look and 48px touch targets. */
.calc__options { display: grid; gap: var(--sp-1); }
.calc__options .radio-option { min-height: 48px; }
.calc__options--stack { grid-template-columns: minmax(0, 1fr); }
.calc__options--segmented { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.calc__options--segmented .radio-option { justify-content: center; text-align: center; padding: 8px 10px; }
.radio-option input { flex: none; }
/* Key numbers stay visible above the fold-away working. Hidden while
   there is nothing to show, so no stray rule appears. */
.calc__ledger--key .ledger__row dd { color: var(--cream); font-weight: 700; }
.calc__ledger--key:not(:has(.ledger__row:not([hidden]))) { display: none; }
.calc__ledger dd { font-variant-numeric: tabular-nums; }
/* list-item (not flex) keeps the native open/closed marker; the padding
   makes a 44px tap target. */
.calc__breakdown > summary {
  cursor: pointer;
  padding-block: 11px;
  font-weight: 700;
  font-size: 0.92rem;
  color: var(--cream);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.calc__breakdown[open] > summary { margin-bottom: var(--sp-1); }

@media (max-width: 640px) {
  .calc-hero { padding-block: var(--sp-4) var(--sp-5); }
  .calc-intro { margin-bottom: var(--sp-3); }
  .calc-intro p { font-size: 1.02rem; }
  .calc { gap: var(--sp-3); }
  .calc__result { padding: var(--sp-4) 20px; }
  .calc__row--qty { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  /* Segments: radio above the label, so the label gets the full width. */
  .calc__options--segmented .radio-option { flex-direction: column; gap: 4px; padding: 8px 6px; line-height: 1.2; }
  .calc__stamps .stamp { padding: 14px 6px 8px; }
  .calc__stamps .stamp::before { left: 6px; }
  .calc__stamps .stamp.is-active .stamp__label { font-size: 0.92rem; }
  .calc__actions { flex-direction: column; align-items: stretch; }
  .calc__actions .btn { justify-content: center; white-space: normal; }
  .calc__actions .link-arrow { align-self: flex-start; }
}

/* Phone summary bar: the key numbers and the next step, pinned to the
   bottom while the full result is off-screen (js/compare-prices.js). */
.calc-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 540;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 10px max(16px, env(safe-area-inset-right)) max(10px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
  background: var(--ink);
  color: var(--cream);
  border-top: 2px solid var(--yellow);
}
.calc-bar:not([hidden]) { animation: calc-bar-in 220ms var(--ease); }
@keyframes calc-bar-in { from { transform: translateY(100%); } }
.calc-bar__summary {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  padding: 2px 0;
  background: none;
  border: 0;
  color: inherit;
  text-align: left;
}
.calc-bar__main { font-family: var(--font-display); font-weight: 800; text-transform: uppercase; font-size: clamp(0.92rem, 4.2vw, 1.05rem); line-height: 1.05; white-space: nowrap; }
.calc-bar__main em { font-style: normal; color: var(--yellow); }
.calc-bar__sub {
  max-width: 100%;
  font-size: 0.8rem;
  line-height: 1.3;
  color: var(--cream-on-ink);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.calc-bar .btn { flex: none; padding: 12px 16px; }
@media (min-width: 901px) { .calc-bar { display: none !important; } }

/* ---- BuyAvailableNow: one card per grade (buy-coffee.html) ----
   Flat white panels with a 1px rule, like .form-shell and
   .disclosure-block. Optional stock details reuse .ledger and only
   render when filled in (js/available-coffee.js). */
.coffee-grid {
  display: grid;
  /* One track per grade, three across at most; js/available-coffee.js
     sets --coffee-cols. auto-fill reserved a track for a grade that is no
     longer offered, leaving dead space to the right of the cards. */
  grid-template-columns: repeat(var(--coffee-cols, 2), minmax(0, 1fr));
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}
.coffee-card {
  background: var(--white);
  border: 1px solid var(--rule);
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
/* Phones: one card per row. The explicit column count above has no
   content floor to collapse on, unlike the auto-fill it replaced. */
@media (max-width: 640px) {
  .coffee-grid { grid-template-columns: minmax(0, 1fr); }
  .coffee-card { padding: var(--sp-3); }
}
.coffee-card__price { border-block: 1px solid var(--rule); padding-block: var(--sp-2); }
.coffee-card__kg strong { font-size: 1.6rem; font-weight: 700; }
.coffee-card__kg--none { color: var(--slate); font-weight: 700; }
.coffee-card__bag { font-size: 0.88rem; color: var(--slate); margin-top: 4px; }
.coffee-card__desc { font-size: 0.92rem; color: var(--slate); }
.coffee-card__desc + .coffee-card__desc { margin-top: 0; }
.coffee-card__ledger .ledger__row { grid-template-columns: 130px minmax(0, 1fr); gap: var(--sp-2); padding-block: 8px; font-size: 0.9rem; }
.coffee-card__actions { margin-top: auto; padding-top: var(--sp-2); display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.coffee-card__actions .btn { flex: 1 1 auto; justify-content: center; }

/* ---- GradePicker: the calculator's coffee choice ----
   A radio group styled as tiles. The native radio stays in the DOM
   (focusable, announced) but is visually hidden; the whole tile is its
   label. Selected: ink border, cream ground, a yellow bar along the
   bottom, and a small "pop" of the illustration (the global
   prefers-reduced-motion rule clamps it to nothing). */
.grade-picker { border: 0; padding: 0; margin: 0; min-width: 0; }
.grade-picker legend { font-size: 0.9rem; font-weight: 700; padding: 0; margin-bottom: 6px; }
.grade-picker .hint { display: block; margin-top: var(--sp-1); font-size: 0.82rem; color: var(--slate); }
.grade-picker__grid {
  display: grid;
  /* One track per grade, three across at most, so the tiles always fill
     the row exactly like the full-width "Other" tile below them.
     js/compare-prices.js sets --grade-cols from the number of grades in
     pricing-config.js. Neither auto-fill nor auto-fit can do this here:
     "Other" spans every track, so no track is ever empty enough for
     auto-fit to collapse, and the tiles stop short of the right edge. */
  grid-template-columns: repeat(var(--grade-cols, 2), minmax(0, 1fr));
  gap: var(--sp-1);
}
.grade-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 12px 14px;
  border: 1.5px solid var(--ink-15);
  border-radius: var(--radius-control);
  background: var(--white);
  cursor: pointer;
  overflow: hidden;
  transition: border-color 160ms var(--ease), background-color 160ms var(--ease);
}
.grade-tile::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 4px;
  background: transparent;
  transition: background-color 160ms var(--ease);
}
.grade-tile:hover { border-color: var(--ink-70); }
.grade-tile input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; pointer-events: none; }
.grade-tile:has(input:checked) { border-color: var(--ink); background: var(--cream); }
.grade-tile:has(input:checked)::after { background: var(--yellow); }
/* Ink, not the site's yellow: yellow is ~1.6:1 on white/cream and would
   also read as the selected tile's yellow bar. */
.grade-tile:has(input:focus-visible) { outline: 2px solid var(--ink); outline-offset: 2px; }
.grade-tile[data-grade="other"] { grid-column: 1 / -1; flex-direction: row; align-items: center; gap: 12px; }
.grade-tile__art { display: block; margin-bottom: 2px; }
.grade-tile:has(input:checked) .grade-art { animation: grade-pop 280ms var(--ease); }
@keyframes grade-pop {
  0% { transform: scale(0.92); }
  60% { transform: scale(1.05); }
  100% { transform: scale(1); }
}
.grade-tile__code { font-family: var(--font-mono); font-size: 0.82rem; font-weight: 700; letter-spacing: 0.01em; }
.grade-tile__name { font-size: 0.84rem; line-height: 1.3; color: var(--slate); }
.grade-tile__price { display: flex; flex-wrap: wrap; gap: 0 10px; font-family: var(--font-mono); font-size: 0.74rem; margin-top: 4px; }
.grade-tile__price span { white-space: nowrap; }
.grade-tile__price:empty { display: none; }

/* Bean + cup-grade illustration (js/market.js gradeArt). */
.grade-art { display: block; overflow: visible; transform-origin: 30px 32px; }
.grade-art__body { fill: var(--slate); }
.grade-art__crease { fill: none; stroke: var(--cream); stroke-width: 1.6; stroke-linecap: round; }
/* Reference outline and empty cup bar at >= 3:1 (non-text contrast), so
   "smaller than 17/18" and "one bar = Good Cup" actually read. Solid,
   not dashed: a dashed line reads as a projection. */
.grade-art__ghost .grade-art__body { fill: none; stroke: var(--slate); stroke-width: 1; }
.grade-art__ghost .grade-art__crease { display: none; }
.grade-art__bean--open .grade-art__body { fill: none; stroke: var(--slate); stroke-width: 1.5; }
.grade-art__bean--open .grade-art__crease { stroke: var(--slate); stroke-width: 1.2; }
.grade-tile:has(input:checked) .grade-art__bean:not(.grade-art__bean--open) .grade-art__body { fill: var(--ink); }
.grade-art__bar { fill: none; stroke: var(--ink-70); stroke-width: 1; }
.grade-art__bar.is-on { fill: var(--yellow); stroke: none; }

/* Phones: the picker becomes a list, illustration left, words right.
   (After the base .grade-tile rule on purpose, so it wins.) */
@media (max-width: 640px) {
  .grade-picker__grid { grid-template-columns: minmax(0, 1fr); }
  .grade-tile {
    display: grid;
    grid-template-columns: 48px minmax(0, 1fr);
    column-gap: 12px;
    align-items: center;
    justify-items: start;
    text-align: left;
    padding: 8px 12px 10px;
  }
  .grade-tile__art { grid-row: 1 / span 3; margin: 0; }
  .grade-tile__art .grade-art { width: 48px; height: 40px; }
  .grade-compare .grade-compare__bag { display: none; }
}
.grade-compare__scroll:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* "What do 17/18, 15 up and FC mean?" and "Compare all grades today". */
.grade-explainer, .grade-compare { margin-top: var(--sp-2); border-top: 1px solid var(--rule); padding-top: var(--sp-2); }
.grade-explainer summary, .grade-compare summary {
  cursor: pointer;
  padding-block: 11px;
  font-weight: 700;
  font-size: 0.92rem;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.grade-explainer__body { display: grid; gap: var(--sp-2); margin-top: var(--sp-2); font-size: 0.92rem; }
.grade-explainer__body p { color: var(--slate); }
.grade-explainer__body p + p { margin-top: 0; }
.sieve-art { width: 100%; max-width: 340px; height: auto; display: block; }
.sieve-art__plate { fill: var(--white); stroke: var(--rule); stroke-width: 1; }
.sieve-art__hole { fill: var(--cream); stroke: var(--ink); stroke-width: 1.5; }
.sieve-art__label { font-family: var(--font-mono); font-size: 11px; fill: var(--ink); }
.sieve-art__mm { font-family: var(--font-mono); font-size: 11px; fill: var(--slate); }
.grade-compare__scroll { overflow-x: auto; margin-top: var(--sp-2); }
.grade-compare table { border-collapse: collapse; width: 100%; font-size: 0.86rem; font-variant-numeric: tabular-nums; }
.grade-compare th, .grade-compare td { text-align: left; padding: 8px 12px 8px 0; border-bottom: 1px solid var(--rule); vertical-align: top; white-space: nowrap; }
.grade-compare thead th { font-family: var(--font-mono); font-size: 0.72rem; font-weight: 400; color: var(--slate); text-transform: uppercase; letter-spacing: 0.04em; }
.grade-compare tbody th { font-family: var(--font-mono); font-weight: 700; }
.grade-compare tbody th span { display: block; font-family: var(--font-body); font-weight: 400; color: var(--slate); white-space: normal; }
.grade-compare tr.is-selected th, .grade-compare tr.is-selected td { background: var(--cream); }
.coffee-card__art .grade-art { width: 76px; height: 64px; }
.coffee-card__code { font-family: var(--font-mono); font-size: 0.82rem; font-weight: 700; margin-top: -4px; }
.coffee-explainer { max-width: 720px; }

/* ---- HomeCoffeePrice strip ----
   A quiet one-line band between the homepage hero and proof rail. It
   stays [hidden] until js/market.js has real data, and stays hidden on
   any failure, so it never shows a blank or broken number. */
.price-strip { padding-block: var(--sp-2); background: var(--white); }
.price-strip__bar {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 4px var(--sp-4);
  align-items: center;
}
.price-strip__label {
  font-family: var(--font-display);
  font-weight: 800;
  text-transform: uppercase;
  font-size: 1rem;
  letter-spacing: 0.01em;
}
.price-strip__figures {
  display: flex;
  flex-wrap: wrap;
  gap: 2px var(--sp-3);
  font-family: var(--font-mono);
  font-size: 0.88rem;
  max-width: none;
}
.price-strip__main { font-weight: 700; }
.price-strip__product { grid-column: 2; font-family: var(--font-mono); font-size: 0.82rem; color: var(--slate); max-width: none; text-decoration: underline; text-underline-offset: 3px; }
.price-strip__product:hover { color: var(--ink); }
.price-strip__links { grid-column: 3; grid-row: 1 / span 2; display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); }
@media (max-width: 900px) {
  .price-strip__bar { grid-template-columns: minmax(0, 1fr); }
  .price-strip__product, .price-strip__links { grid-column: auto; grid-row: auto; }
  .price-strip__links { margin-top: var(--sp-1); }
}

/* ---- HomeCoffeePrice card: figures left, mini chart right ----
   The strip grew into a small market preview. Stacks on phones. The old
   single-row .price-strip__bar grid positions are neutralised inside the
   new head column. */
.price-strip { padding-block: var(--sp-3); }
.price-strip__card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  gap: var(--sp-3) var(--sp-5);
  align-items: center;
}
@media (max-width: 900px) { .price-strip__card { grid-template-columns: minmax(0, 1fr); } }
.price-strip__head { display: grid; gap: var(--sp-1); align-content: start; }
.price-strip__head .price-strip__product,
.price-strip__head .price-strip__links { grid-column: auto; grid-row: auto; }
.price-strip__head .price-strip__links { margin-top: var(--sp-1); }
.price-strip__chart { min-width: 0; }

/* Mini chart (homepage card, calculator breakdown). Same marks as the
   Coffee Market chart: ink line, hairline guides at the low and high,
   one yellow dot for today, text in slate/ink, never the line colour. */
.mini-ranges { display: flex; justify-content: flex-end; gap: 4px; margin-bottom: 4px; }
.mini-ranges__btn {
  min-height: 36px;
  min-width: 44px;
  padding: 4px 10px;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: var(--radius-control);
  cursor: pointer;
}
@media (pointer: coarse) { .mini-ranges__btn { min-height: 44px; } }
.mini-ranges__btn:hover { border-color: var(--ink-70); }
.mini-ranges__btn[aria-pressed="true"] { border-color: var(--ink); background: var(--cream); font-weight: 700; }
.mini-chart { position: relative; min-width: 0; }
.mini-chart__svg { display: block; overflow: visible; }
.mini-chart__guide { stroke: var(--rule); stroke-width: 1; }
.mini-chart__line { fill: none; stroke: var(--ink); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.mini-chart__end { fill: var(--yellow); stroke: var(--white); stroke-width: 2; }
.mini-chart__label { font-family: var(--font-mono); font-size: 11px; fill: var(--slate); font-variant-numeric: tabular-nums; }
.mini-chart__label--end { fill: var(--ink); font-weight: 700; }
.mini-chart__caption { font-family: var(--font-mono); font-size: 0.74rem; color: var(--slate); margin-top: 4px; max-width: none; }
.mini-chart__svg.is-on-ink .mini-chart__guide { stroke: var(--rule-on-ink); }
.mini-chart__svg.is-on-ink .mini-chart__line { stroke: var(--cream); }
.mini-chart__svg.is-on-ink .mini-chart__end { stroke: var(--ink); }
.mini-chart__svg.is-on-ink .mini-chart__label { fill: var(--cream-on-ink); }
.mini-chart__svg.is-on-ink .mini-chart__label--end { fill: var(--cream); }
.calc-mini { margin-top: var(--sp-2); }
.calc-mini__label { font-size: 0.85rem; font-weight: 700; color: var(--cream); margin-bottom: 4px; }
.calc-mini .mini-chart__caption { color: var(--cream-on-ink); }

/* ---- CoffeeMarket page (coffee-market.html) ----
   Summary figure + ledger, then a hand-drawn SVG line chart. Chart
   marks follow the site palette only: ink line, yellow end dot, rule
   gridlines, slate axis text. */
.market-status { font-size: 1.05rem; color: var(--slate); }
.market-summary {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  gap: var(--sp-4) var(--sp-5);
  align-items: start;
}
@media (max-width: 900px) { .market-summary { grid-template-columns: minmax(0, 1fr); } }
.market-figure {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: clamp(3rem, 7vw, 5rem);
  line-height: 1;
  letter-spacing: -0.02em;
  margin-block: var(--sp-1);
  max-width: none;
}
.market-figure__unit { font-size: 0.3em; letter-spacing: 0; color: var(--slate); }
.market-summary__kg { font-weight: 700; margin-top: var(--sp-2); }
.market-summary__sentence { margin-top: var(--sp-3); font-size: 1.15rem; }
.market-summary__lead .container-note { margin-top: var(--sp-2); }
.market-summary__updated { grid-column: 1 / -1; }
.market-chart { position: relative; margin-top: var(--sp-4); }
.market-chart__svg { display: block; overflow: visible; touch-action: pan-y; }
.market-chart__svg:focus-visible { outline: 3px solid var(--yellow); outline-offset: 4px; }
.market-chart__grid { stroke: var(--rule); stroke-width: 1; }
.market-chart__tick { fill: var(--slate); font-family: var(--font-mono); font-size: 12px; font-variant-numeric: tabular-nums; }
.market-chart__tick--strong { fill: var(--ink); font-weight: 700; }
.market-chart__line { fill: none; stroke: var(--ink); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.market-chart__end { fill: var(--yellow); stroke: var(--white); stroke-width: 2; }
.market-chart__cross { stroke: var(--ink-70); stroke-width: 1; }
.market-chart__dot { fill: var(--ink); stroke: var(--white); stroke-width: 2; }
.market-chart__tip {
  position: absolute;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  background: var(--ink);
  color: var(--cream);
  padding: 6px 10px;
  border-radius: 4px;
  font-size: 0.85rem;
  white-space: nowrap;
}
.market-chart__tip.is-flipped { transform: translateX(-100%); }
.market-chart__tip span { color: var(--cream-on-ink); font-size: 0.78rem; }
.market-chart__table { margin-top: var(--sp-3); }
.market-chart__table summary { cursor: pointer; font-weight: 700; }
.market-chart__table-scroll { overflow: auto; max-height: 360px; margin-top: var(--sp-2); }
.market-chart__table table { border-collapse: collapse; font-size: 0.9rem; font-variant-numeric: tabular-nums; }
.market-chart__table th, .market-chart__table td { text-align: left; padding: 6px 24px 6px 0; border-bottom: 1px solid var(--rule); }
.market-notes { display: grid; gap: var(--sp-4); margin-top: var(--sp-4); }
.market-note { border-top: 1px solid var(--rule); padding-top: var(--sp-3); }
.market-note h3 { margin-block: var(--sp-1); }

/* ---- Phone overflow guard (site-wide) ----
   Found by the 360px no-sideways-scroll test, and already present on
   main before this work: a stacked 1fr grid column grows to its widest
   child (a long no-wrap button, a long uppercase word), pushing the page
   past a 360px screen. minmax(0, 1fr) lets the column shrink; buttons may
   wrap on narrow phones; h1 scales with the screen below 480px so words
   like "CONVERSATIONS" or "ŚWIADOMOŚCIĄ" fit. Desktop is unchanged. */
@media (max-width: 900px) {
  .page-hero__grid,
  .aside-media--right,
  .aside-media--left,
  .split,
  .editorial-intro__grid,
  .buying-brief__grid,
  .copy-with-stats,
  .sticky-visual,
  .two-col-list { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 640px) {
  .proof-rail__list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .proof-rail__item { overflow-wrap: anywhere; hyphens: auto; }
}
@media (max-width: 480px) {
  /* Four stage labels ("4. Kontakt") no longer push a form wider than
     the screen: tighter gaps, and labels may shrink and wrap. */
  .form-stages { gap: var(--sp-1); font-size: 0.72rem; }
  .form-stages__step { min-width: 0; overflow-wrap: anywhere; }
  .btn { white-space: normal; text-align: center; justify-content: center; }
  h1 { font-size: clamp(2rem, 9.4vw, 2.6rem); overflow-wrap: break-word; }
  h2 { overflow-wrap: break-word; }
}

/* ---- Company contact + identification (from data/company.json via
   scripts/apply-company.mjs) ---- */
.site-footer__label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-bottom: 10px;
}
.site-footer__office { font-size: 0.88rem; margin-top: var(--sp-2); }
.site-footer__office strong { display: block; color: var(--cream); }
.site-footer .site-footer__legal { display: flex; flex-wrap: wrap; gap: 2px var(--sp-2); max-width: none; font-size: 0.82rem; }
.talk-line { margin-top: var(--sp-2); }
.talk-line a { text-decoration: underline; text-underline-offset: 3px; font-weight: 700; }
/* Preview-only marker for company details still to be supplied. The
   test suite fails if one survives once data/company.json says the
   details are final. */
.placeholder-marker {
  font-family: var(--font-mono);
  font-size: 0.85em;
  padding: 0 6px;
  border: 1px solid var(--yellow);
  border-radius: 4px;
}
/* Long contact values (an email address, a marker) must wrap inside
   their column instead of widening the footer grid past a phone screen. */
.site-footer__grid > * { min-width: 0; }
.site-footer__contact a, .site-footer__legal { overflow-wrap: anywhere; }
