/* Self-hosted webfonts (latin subset, SIL OFL). Display: Playfair Display,
   working baseline pending owner review 2026-08. Body: Inter; the four static
   weights cover every sans weight requested (650/750/850/900 snap to the
   nearest declared face). */
@font-face {
  font-family: "Playfair Display";
  src: url("../fonts/playfair-display-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Playfair Display";
  src: url("../fonts/playfair-display-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: Inter;
  src: url("../fonts/inter-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: Inter;
  src: url("../fonts/inter-600.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: Inter;
  src: url("../fonts/inter-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: Inter;
  src: url("../fonts/inter-800.woff2") format("woff2");
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: dark;
  --ink-950: #030405;
  --scrim-rgb: 3, 4, 5;
  --ink-900: #07090c;
  --ink-850: #0b0e12;
  --ink-800: #10141a;
  --ink-700: #171c23;
  --paper: #f5f4ef;
  --white: #fff;
  --text: #f6f7f8;
  --text-soft: #c4c9d0;
  --text-dim: #8c949f;
  --line: rgba(255, 255, 255, 0.12);
  --line-strong: rgba(255, 255, 255, 0.22);
  --ember: #ef913c;
  --ember-light: #ffad62;
  --ember-dark: #c96c1f;
  --blue: #2e9ff5;
  --blue-light: #70c8ff;
  --green: #72c84a;
  --purple: #9654df;
  --danger: #ff756d;
  --success: #82d7a4;
  --container: 1420px;
  --container-narrow: 1040px;
  --gutter: clamp(20px, 4vw, 64px);
  --section-space: clamp(80px, 8vw, 132px);
  --radius-sm: 10px;
  --radius-md: 18px;
  --radius-lg: 30px;
  --radius-pill: 999px;
  --shadow-deep: 0 34px 90px rgba(0, 0, 0, 0.54);
  --font-sans:
    Inter, "Helvetica Neue", Helvetica, Arial, ui-sans-serif, system-ui,
    -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-display: "Playfair Display", Georgia, "Times New Roman", serif;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --header-height: 84px;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Anchor jumps are deliberately instant: a long CSS smooth scroll is canceled
   by any concurrent scroll write (smooth-scroll extensions, mouse-driver
   smoothing), stranding the viewport partway. Instant jumps are atomic and
   still honor the [id] scroll-margin-top offset below. */
html {
  min-width: 320px;
  background: var(--ink-950);
  color: var(--text);
  font-family: var(--font-sans);
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

/* Anchor jumps must land below the fixed header (id selectors may override with larger offsets) */
[id] {
  scroll-margin-top: calc(var(--header-height) + 12px);
}

body {
  min-width: 320px;
  margin: 0;
  overflow-x: hidden;
  background:
    radial-gradient(circle at 90% 18%, rgba(28, 103, 158, 0.08), transparent 31rem),
    var(--ink-950);
  color: var(--text);
  font-family: inherit;
  font-size: 16px;
  line-height: 1.55;
}

body.nav-open,
body.age-gate-open {
  overflow: hidden;
}

.js[data-age-gate-state] .site-shell {
  transition: filter 320ms ease;
}

.js[data-age-gate-state="pending"] .site-shell,
.no-js .site-shell {
  visibility: hidden;
}

.js[data-age-gate-state="required"] .site-shell {
  visibility: visible;
  filter: blur(10px);
}

.js[data-age-gate-state="verified"] .site-shell {
  visibility: visible;
  filter: none;
}

img,
svg,
picture,
video {
  display: block;
  max-width: 100%;
  height: auto;
}

/* [hidden] must always beat component display rules (grid/flex), or hidden
   elements like the inquiry form's spam honeypot render visibly
   (handling plans, Section 1). */
[hidden] {
  display: none !important;
}

button,
input,
select,
textarea {
  color: inherit;
  font: inherit;
}

button,
a {
  -webkit-tap-highlight-color: transparent;
}

a {
  color: inherit;
}

p,
h1,
h2,
h3,
h4,
ul,
ol,
dl,
dd,
figure {
  margin-top: 0;
}

h1,
h2,
h3,
h4 {
  text-wrap: balance;
}

/* Headings balance; running text gets `pretty`, which only reflows the last few
   lines to stop a single short word being stranded alone. An audit of every
   text block on every page found 46 such orphans across the site. */
p,
li,
dd,
figcaption {
  text-wrap: pretty;
}

p {
  text-wrap: pretty;
}

::selection {
  background: rgba(239, 145, 60, 0.95);
  color: var(--ink-950);
}

:focus-visible {
  outline: 2px solid var(--ember-light);
  outline-offset: 4px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: fixed;
  z-index: 10000;
  top: 14px;
  left: 14px;
  padding: 11px 16px;
  border-radius: var(--radius-pill);
  background: var(--paper);
  color: var(--ink-950);
  font-weight: 800;
  text-decoration: none;
  transform: translateY(-180%);
  transition: transform 160ms ease;
}

.skip-link:focus {
  transform: translateY(0);
}

.container {
  width: min(calc(100% - (2 * var(--gutter))), var(--container));
  margin-inline: auto;
}

.section {
  position: relative;
  padding-block: var(--section-space);
}

/* The header is fixed, so a page whose first block is a plain .section has to
   clear it on its own. The section budget alone cannot be trusted for that:
   the short-viewport block trims --section-space to as little as 48px, which
   is under every --header-height step (84/76/66), and that slid the Learn and
   About page titles up behind the header bar on short windows (laptops at
   display scaling). max() keeps the roomier budget wherever it already wins,
   so tall viewports are unchanged. Bespoke heroes (product, legal, home,
   carry) are not .section and already add their own clearance. */
main > .section:first-child {
  padding-top: max(var(--section-space), calc(var(--header-height) + clamp(24px, 2.5vw, 44px)));
}

.section--tight {
  padding-block: clamp(56px, 6vw, 88px);
}

.section--line {
  border-top: 1px solid var(--line);
}

.section--paper {
  background: var(--paper);
  color: #15171a;
}

.section--slate {
  background:
    linear-gradient(135deg, rgba(255, 255, 255, 0.024), transparent 38%),
    var(--ink-850);
}

.section--blue {
  background:
    radial-gradient(circle at 77% 38%, rgba(46, 159, 245, 0.18), transparent 31rem),
    linear-gradient(180deg, #07111a, var(--ink-950));
}

.section-heading {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(280px, 0.55fr);
  gap: clamp(32px, 7vw, 110px);
  align-items: end;
  margin-bottom: clamp(40px, 5vw, 72px);
}

.section-heading--stack {
  display: block;
  max-width: 850px;
}

.section-heading--stack .section-copy {
  margin-top: 24px;
}

.section-heading--lineup {
  grid-template-columns: minmax(0, 1.05fr) minmax(300px, 0.55fr);
}

/* One-line section titles (lineup "Meet the ZMOKE Lineup", HYH "Highlight
   Your High."): hold a single line on desktop, release to natural wrapping
   below 780px where the measure cannot carry them. */
.section-heading--lineup .section-title,
.section-heading--one-line .section-title {
  max-width: none;
  white-space: nowrap;
  font-size: clamp(2.3rem, 4.14vw, 4.68rem);
}

/* Family-tinted title words (the Highlight Your High heading, 2026-08-24):
   each word takes its family's legibility-tuned tint - the softened prose
   blue rather than the raw --blue token, and purple lightened for contrast
   on the dark ground. These supersede the retired .lineup-family-word rules
   that were reserved for this section. */
.section-title__family-word[data-family="blue"] {
  color: #5a9be8;
}

.section-title__family-word[data-family="green"] {
  color: var(--green);
}

.section-title__family-word[data-family="purple"] {
  color: color-mix(in srgb, var(--purple) 78%, white);
}

/* A TM inside a family-tinted word stays in the plain heading white (Mark,
   2026-08-28): the claim mark is brand voice, not family voice. */
.section-title__family-word .tm-mark {
  color: var(--text);
}

/* HYH is the one centered section heading on the site (Mark, 2026-08-28,
   adopting the 08-27 preview): the symmetric three-color title over three
   symmetric cards reads as a title plate. Scoped here on purpose - every
   other section heading stays left-anchored; do not generalize. */
#highlight-your-high .section-heading {
  margin-inline: auto;
  text-align: center;
}

#highlight-your-high .section-copy {
  max-width: 52ch;
  margin-inline: auto;
}

.lineup-family-selector {
  display: inline-grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  width: min(100%, 390px);
  margin-top: 18px;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--ink-850) 88%, transparent);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035);
}

.lineup-family-option {
  --selector-color: var(--text-soft);
  display: inline-flex;
  min-width: 0;
  min-height: 44px;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 6px 14px;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-soft);
  font: inherit;
  font-size: 0.86rem;
  font-weight: 750;
  letter-spacing: 0.01em;
  cursor: pointer;
  transition:
    background-color 180ms ease,
    border-color 180ms ease,
    color 180ms ease;
}

.lineup-family-option[data-family="blue"] {
  --selector-color: #5a9be8;
}

.lineup-family-option[data-family="green"] {
  --selector-color: var(--green);
}

.lineup-family-option[data-family="purple"] {
  --selector-color: color-mix(in srgb, var(--purple) 78%, white);
}

.lineup-family-option:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.035);
}

.lineup-family-option:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--text-soft) 68%, transparent);
  outline-offset: 2px;
}

.lineup-family-option[aria-pressed="true"] {
  border-color: color-mix(in srgb, var(--selector-color) 44%, var(--line));
  background: color-mix(in srgb, var(--selector-color) 12%, var(--ink-800));
  color: var(--text);
}

.lineup-family-swatch {
  width: 7px;
  height: 7px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--selector-color);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
}

.family-status {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Eyebrow labels were removed site-wide 2026-08-24 (owner's call). The only
   survivor of the pattern is .legal-nav__label below: a functional list label,
   restyled without the decorative rule line. */

.display {
  max-width: 13ch;
  margin-bottom: 0;
  font-family: var(--font-display);
  font-size: clamp(2.7rem, 5.85vw, 6.66rem);
  font-weight: 500;
  letter-spacing: normal;
  line-height: 1.05;
}

/* Trademark symbol inside display/section headings (emitted by e_tm() in
   helpers.php). Playfair's ™ sits near cap height, which shouts at display
   scale; this demotes it to a small raised mark. line-height 0 keeps the sup
   from opening the heading's line box. */
/* As small as legibility allows (Mark, 2026-08-24): proportional 0.26em with
   a 9px floor so the glyph stays readable at the phone heading floors. The
   mark sits with its INK BOTTOM on the text baseline everywhere (Mark, same
   day): Playfair draws ™ ink ending ~0.26-0.28em above its own baseline
   (canvas actualBoundingBoxDescent, roman and italic), so the sup drops by
   that much in its own em and the seat scales with every clamp. */
.tm-mark {
  font-size: max(0.26em, 9px);
  line-height: 0;
  vertical-align: -0.27em;
}

.section-title {
  max-width: 15ch;
  margin-bottom: 0;
  font-family: var(--font-display);
  font-size: clamp(2.3rem, 4.5vw, 5.22rem);
  font-weight: 500;
  letter-spacing: normal;
  line-height: 1.05;
  text-wrap: balance;
}

/* Titles that author their own line breaks.

   An authored break is a promise: "One Drop. / Endless Possibilities." must be
   two lines at every width, never three. Three separate mechanisms defeat that
   and all three are neutralised here.

   1. max-width. The shared 15ch cap re-wraps any authored line longer than 15
      characters; 24ch clears the longest in use ("Endless Possibilities.", 22).
   2. text-wrap: balance. It exists to pick a break point automatically, which
      is precisely what the data already did.
   3. The clamp floor. Below ~640px the shared 2.3rem floor renders the long
      line wider than a phone's content box, so it wrapped no matter how much
      max-width it was given. Only the floor is lowered; from ~640px up the
      4.5vw term is identical to the shared ramp, so nothing changes on
      tablet or desktop.

   Verified by width sweep, 360-1920px in 20px steps: exactly two lines
   everywhere. Re-run that sweep before editing any authored title string. */
.section-title--authored {
  max-width: 24ch;
  font-size: clamp(1.8rem, 4.5vw, 5.22rem);
  text-wrap: normal;
}

/* Tagline voice (currently the second One Drop line). NOTE: only roman
   Playfair files are self-hosted, so this renders as a browser-synthesized
   oblique; self-host the true Playfair Display italic face if the style
   sticks. --italic takes a whole heading, __italic takes one authored line. */
.section-title--italic,
.section-title__italic {
  font-style: italic;
}

/* One-line rule for the italic tagline phrase (Mark, 2026-08-24). A
   staircase indent under "Drop" was tried and reverted the same day; the
   line sits flush left. At the 1.8rem authored floor the phrase needs ~7em
   (~200px), which fits a 320px viewport, so the nowrap cannot overflow on
   any supported width. */
#where-it-goes-title .section-title__italic {
  white-space: nowrap;
}

/* The two-column heading squeezed the title track to ~340px around 800px wide,
   which forced a third line even at 24ch. Authored headings take a larger
   share and let the supporting copy column shrink to its 240px floor. */
.section-heading--authored {
  grid-template-columns: minmax(0, 1.25fr) minmax(240px, 0.5fr);
}

/* A heading with no supporting copy must not reserve the empty copy track:
   the One Drop heading lost ~35% of its measure to a blank column after its
   sentence was removed 2026-08-24, which broke the authored two-line promise
   at mid widths. Placed after --authored so it wins at equal specificity. */
.section-heading--solo {
  grid-template-columns: 1fr;
}

/* 680px rather than 650px: at 650 the Find Your Fire copy fell 7px short of
   holding two lines and broke to three. 680 is ~73 characters, still inside the
   comfortable measure. */
.section-copy,
.lede {
  max-width: 680px;
  margin-bottom: 0;
  color: var(--text-soft);
  font-size: clamp(1.05rem, 1.35vw, 1.28rem);
  line-height: 1.65;
}

.section--paper .section-copy,
.section--paper .lede {
  color: #555b63;
}

.microcopy {
  max-width: 44em;
  color: var(--text-dim);
  font-size: 0.86rem;
  letter-spacing: 0.02em;
  line-height: 1.55;
}

.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: 13px;
  align-items: center;
}

.button {
  display: inline-flex;
  min-height: 52px;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 0 28px;
  border: 1px solid rgba(0, 0, 0, 0.28);
  border-radius: var(--radius-sm);
  background: var(--ember);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.28),
    0 1px 2px rgba(0, 0, 0, 0.35);
  color: #111214;
  cursor: pointer;
  font-size: 0.94rem;
  font-weight: 650;
  letter-spacing: 0.012em;
  line-height: 1;
  text-decoration: none;
  transition:
    color 180ms ease,
    background 180ms ease,
    border-color 180ms ease,
    box-shadow 180ms ease;
}

.button:hover,
.button:focus-visible {
  border-color: rgba(0, 0, 0, 0.32);
  background: var(--ember-light);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.3),
    0 6px 20px rgba(239, 145, 60, 0.18);
}

.button--small {
  min-height: 43px;
  padding-inline: 21px;
  font-size: 0.86rem;
}

.button--ghost {
  border-color: rgba(255, 255, 255, 0.28);
  background: rgba(var(--scrim-rgb), 0.22);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.07),
    0 1px 2px rgba(0, 0, 0, 0.3);
  color: var(--text);
  backdrop-filter: blur(8px);
}

.button--ghost:hover,
.button--ghost:focus-visible {
  border-color: rgba(255, 255, 255, 0.62);
  background: rgba(255, 255, 255, 0.09);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.12),
    0 5px 18px rgba(0, 0, 0, 0.22);
}

.button--blue {
  border-color: rgba(0, 0, 0, 0.28);
  background: var(--blue);
  color: #03111a;
}

.button--blue:hover,
.button--blue:focus-visible {
  border-color: rgba(0, 0, 0, 0.32);
  background: var(--blue-light);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.3),
    0 6px 20px rgba(46, 159, 245, 0.18);
}

.button--text {
  min-height: 34px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  color: var(--text);
  /* Underline removed 2026-08-31 (Mark). The affordance is now position in
     the hero's .button-row beside the filled primary, plus the ember hover;
     keyboard focus is unaffected because the global :focus-visible outline
     does that work, not the underline. */
  text-decoration: none;
}

.button--text:hover,
.button--text:focus-visible {
  background: none;
  box-shadow: none;
  color: var(--ember-light);
}

.button:active {
  border-color: rgba(0, 0, 0, 0.34);
  background: var(--ember-dark);
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.32);
}

.button--ghost:active {
  border-color: rgba(255, 255, 255, 0.42);
  background: rgba(255, 255, 255, 0.05);
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.28);
}

.button--blue:active {
  border-color: rgba(0, 0, 0, 0.34);
  background: color-mix(in srgb, var(--blue) 76%, black);
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.32);
}

.button--text:active {
  border-color: transparent;
  background: none;
  box-shadow: none;
  color: var(--ember-dark);
}

.button[aria-disabled="true"],
.button:disabled {
  cursor: not-allowed;
  opacity: 0.52;
  box-shadow: none;
}
/* Header */

.site-header {
  position: fixed;
  z-index: 700;
  top: 0;
  right: 0;
  left: 0;
  color: var(--white);
  transition:
    background 220ms var(--ease),
    box-shadow 220ms var(--ease);
}

.site-header.is-scrolled,
body:not(.page--home) .site-header {
  background: rgba(var(--scrim-rgb), 0.86);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(18px);
}

.site-header__inner {
  display: grid;
  grid-template-columns: 205px minmax(0, 1fr) auto;
  gap: 28px;
  align-items: center;
  width: min(calc(100% - (2 * var(--gutter))), var(--container));
  min-height: var(--header-height);
  margin-inline: auto;
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}

.is-scrolled .site-header__inner,
body:not(.page--home) .site-header__inner {
  border-bottom-color: transparent;
}

.site-header__brand {
  display: inline-flex;
  width: min(100%, 184px);
  align-items: center;
  text-decoration: none;
}

.site-header__brand img {
  width: 100%;
  height: auto;
}

.site-header__nav,
.site-header__links {
  display: flex;
  justify-content: center;
  gap: clamp(16px, 2vw, 32px);
  align-items: center;
}

.site-header__link {
  position: relative;
  display: inline-flex;
  min-height: var(--header-height);
  align-items: center;
  color: rgba(255, 255, 255, 0.82);
  font-size: 0.86rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}

.site-header__link::after {
  position: absolute;
  right: 0;
  bottom: -1px;
  left: 0;
  height: 2px;
  background: var(--ember);
  content: "";
  opacity: 0;
  transform: scaleX(0.3);
  transition:
    opacity 180ms ease,
    transform 180ms var(--ease);
}

.site-header__link:hover,
.site-header__link:focus-visible,
.site-header__link.is-current {
  color: var(--white);
}

.site-header__link:hover::after,
.site-header__link:focus-visible::after,
.site-header__link.is-current::after {
  opacity: 1;
  transform: scaleX(1);
}

.site-header__actions {
  display: flex;
  gap: 10px;
  align-items: center;
  justify-content: flex-end;
}

.site-header__nav {
  grid-column: 2 / 4;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(18px, 2vw, 32px);
  align-items: center;
  min-width: 0;
}

.site-header__links {
  flex: 1 1 auto;
  justify-self: center;
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-header__links li {
  display: flex;
}

.site-header__cta {
  flex: 0 0 auto;
}

.site-header__actions {
  justify-self: end;
}

.site-header__link--carry {
  min-height: var(--header-height);
}

/* Products dropdown (Mark, 2026-08-25): native details/summary disclosure,
   adopted from the owner-preview prototype he liked. site.js only closes it
   on outside click and Escape; with JS off it still opens and closes. */
.site-header__menu-item,
.site-header__menu {
  position: relative;
}

.site-header__menu summary {
  gap: 8px;
  cursor: pointer;
  list-style: none;
}

/* Open-state chip on the dropdown trigger, adapted from dutchie.com/business
   (Mark, 2026-08-29). Theirs is `.dropdown-nav-2.w--open { background:
   rgba(241,232,214,.12); border-radius: 10px }` - keyed on OPEN, not :hover,
   and tinted with their own text colour at low alpha rather than a new one.
   Both ideas carry over; the geometry cannot. Their trigger is a compact
   inline-block with its own padding, ours is a full-header-height flex link
   with none, so a background on the element would paint an 84px slab and any
   padding would shove the whole nav sideways.

   So the chip is a pseudo-element instead: absolutely positioned, it bleeds
   10px past the text horizontally and sits 34px tall on the vertical centre,
   which costs no layout at all. 10px of bleed stays clear of the neighbouring
   label even at the 16px minimum nav gap. Colour reuses the same white-at-low
   alpha the secondary button hover already uses, so this adds no new value to
   the palette. Desktop only: below 981px the panel is an inline drawer
   accordion and a floating chip would make no sense there. */
@media (min-width: 981px) {
  /* The chip has to paint UNDER the label, which needs z-index: -1 (a
     positioned pseudo-element otherwise paints above its parent's text).
     Isolating the summary keeps that negative index inside the summary's own
     stacking context, so it resolves against the summary's transparent
     background instead of reaching up to .site-header, which paints a real
     background and a backdrop-filter once scrolled. Without this the chip
     works only by luck of the painting order. */
  .site-header__menu summary {
    isolation: isolate;
  }

  .site-header__menu summary::before {
    position: absolute;
    z-index: -1;
    top: 50%;
    right: -10px;
    left: -10px;
    height: 34px;
    border-radius: var(--radius-sm);
    background: rgba(255, 255, 255, 0.09);
    content: "";
    opacity: 0;
    transform: translateY(-50%);
    transition: opacity 160ms var(--ease);
  }

  .site-header__menu[open] summary::before {
    opacity: 1;
  }
}

.site-header__menu summary::-webkit-details-marker {
  display: none;
}

.site-header__menu-chevron {
  width: 7px;
  height: 7px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 180ms var(--ease);
}

.site-header__menu[open] .site-header__menu-chevron {
  transform: translateY(2px) rotate(225deg);
}

.site-header__menu-panel {
  position: absolute;
  top: calc(100% - 4px);
  left: 50%;
  display: grid;
  width: min(88vw, 390px);
  padding: 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: rgba(var(--scrim-rgb), 0.97);
  box-shadow: var(--shadow-deep);
  transform: translateX(-50%);
}

.site-header__menu-panel a {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 4px 18px;
  align-items: center;
  padding: 15px 16px;
  border-radius: var(--radius-sm);
  color: var(--text);
  text-decoration: none;
}

.site-header__menu-panel a + a {
  border-top: 1px solid var(--line);
}

.site-header__menu-panel a:hover,
.site-header__menu-panel a:focus-visible {
  background: rgba(255, 255, 255, 0.06);
}

.site-header__menu-panel span {
  font-size: 0.94rem;
  font-weight: 700;
}

.site-header__menu-panel small {
  color: var(--text-dim);
  font-size: 0.75rem;
}

.button--secondary {
  border-color: rgba(255, 255, 255, 0.28);
  background: rgba(var(--scrim-rgb), 0.22);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.07),
    0 1px 2px rgba(0, 0, 0, 0.3);
  color: var(--text);
}

.button--secondary:hover,
.button--secondary:focus-visible {
  border-color: rgba(255, 255, 255, 0.62);
  background: rgba(255, 255, 255, 0.09);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.12),
    0 5px 18px rgba(0, 0, 0, 0.22);
}

.button--secondary:active {
  border-color: rgba(255, 255, 255, 0.42);
  background: rgba(255, 255, 255, 0.05);
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.28);
}

/* Burger / close toggle. The two metrics below drive both the bar spacing and
   the distance the outer bars travel to form the X, because those are the same
   number and MUST stay equal: each outer bar has to land exactly on the middle
   bar's centre or the diagonals cross off-centre and the glyph reads as a
   lopsided cross rather than an X. It shipped with a hardcoded 3px against a
   6px spacing, which is precisely that bug (Mark spotted it 2026-08-29).

   Flex centring rather than margins on the spans: three 1px bars plus collapsed
   5px margins measured 23px inside a 20px content box, so the glyph overflowed
   and sat 1.5px below the button's centre. The bar spread is unchanged at 12px
   outer-centre to outer-centre; it is just centred now. */
.site-header__toggle {
  --burger-bar: 1px;
  --burger-gap: 5px;
  /* Centre-to-centre, so it is also exactly how far an outer bar must move. */
  --burger-step: calc(var(--burger-bar) + var(--burger-gap));

  display: none;
  flex-direction: column;
  justify-content: center;
  gap: var(--burger-gap);
  width: 44px;
  height: 44px;
  padding: 11px;
  border: 1px solid rgba(255, 255, 255, 0.24);
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.2);
  cursor: pointer;
}

.site-header__toggle span {
  display: block;
  width: 100%;
  height: var(--burger-bar);
  background: var(--white);
  transition:
    transform 180ms ease,
    opacity 180ms ease;
}

.nav-open .site-header__toggle span:first-child {
  transform: translateY(var(--burger-step)) rotate(45deg);
}

.nav-open .site-header__toggle span:nth-child(2) {
  opacity: 0;
  transform: scaleX(0);
}

.nav-open .site-header__toggle span:last-child {
  transform: translateY(calc(var(--burger-step) * -1)) rotate(-45deg);
}

/* Home hero */

/* Hero height follows the VIEWPORT HEIGHT, not the viewport width (2026-08-14).
   The previous `clamp(740px, 54vw, 920px)` meant widening the window made the
   hero taller, which is backwards, and it left the hero 92px taller than the
   screen on a 1366x768 laptop with no hint that anything existed below it.

   `100svh - 140px` reserves a fixed scroll cue at every size, and svh rather
   than vh so a phone's collapsing URL bar cannot swallow it. The 560px floor
   is the shortest box the lockup, intro and buttons fit in; below that the
   content would decide the height instead, which is the same class of bug.

   Deliberately NO ceiling: the height is defined as the viewport minus a
   constant, so it can never exceed the viewport, and a ceiling only converts
   large monitors into letterbox slots. The old 1280px cap is what made a
   3440px display render a 2.69:1 slot. */
.home-hero {
  position: relative;
  isolation: isolate;
  --hero-h: max(560px, calc(100svh - 140px));
  /* The cluster art is 1400x933, so 1.5:1. Capping its WIDTH at 1.17x the frame
     height holds its height near 78% of the frame and leaves the top quarter
     clear for the wordmark. Both values have to derive from one variable: while
     the frame was height-driven and the cluster stayed width-driven, a 1920x937
     screen rendered an 819px cluster inside a 797px hero, so it overflowed the
     top edge and sat on the lockup. */
  --cluster-cap: calc(1.17 * var(--hero-h));
  min-height: var(--hero-h);
  overflow: hidden;
  background: var(--ink-950);
}

.home-hero__art,
.home-hero__art img,
.home-hero__group,
.home-hero__group img,
.home-hero__scrim,
.home-hero__wash {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* Ground-up layered hero (2026-08-13): an opaque smoke stage breathes as one
   object behind a static transparent product cluster, so every wisp moves
   together and a frozen-vs-moving split cannot occur. The cluster sits between
   stage and wash so the bottom dissolve carries it into the next band. */
.home-hero__art {
  z-index: -6;
  background: var(--ink-950);
}

/* Stage-integration scrim: the generated stage plate lights its floor evenly,
   which reads as a lit tabletop under a pasted object. Sinking the floor and
   corners toward ink-950 rebuilds the pooled-light falloff a single
   photographed scene has, and gives the cluster's rock somewhere to dissolve
   into. Sits above the stage, below the cluster. */
.home-hero__scrim {
  z-index: -5;
  background:
    radial-gradient(120% 78% at 50% 118%, rgba(var(--scrim-rgb), 0.92) 0%, rgba(var(--scrim-rgb), 0.62) 34%, transparent 70%),
    radial-gradient(78% 62% at 50% 46%, transparent 46%, rgba(var(--scrim-rgb), 0.55) 100%);
}

/* The generated plate carries a violet-magenta band and a broad orange wash
   that between them own most of its saturated pixels, which is the neon
   smoke-shop register the brand hard-rejects, and it sits far too bright for a
   near-black stage. Desaturating before crushing collapses that magenta (it
   lives in the midtones at low saturation) to neutral near-black instead of
   rotating it somewhere equally wrong; brand hue is then re-added deliberately
   by __tint. Hue-rotate cannot do this: the magenta and the amber sit on
   opposite sides of the wheel, so any rotation that fixes one breaks the
   other. */
.home-hero__art img {
  object-fit: cover;
  object-position: 50% 62%;
  filter: saturate(0.36) brightness(0.66) contrast(1.24);
  animation: hero-breathe 58s ease-in-out infinite alternate;
  will-change: transform;
}

/* Re-lights the crushed plate in brand hues only: amber confined to a rim at
   the right edge rather than washed across the lower frame, cool blue behind
   the products. Screen blend and z-index sit on the same element on purpose;
   putting the blend on a child of a z-indexed wrapper isolates it against an
   empty backdrop and silently does nothing. */
.home-hero__tint {
  position: absolute;
  z-index: -5;
  inset: 0;
  background:
    radial-gradient(46% 58% at 99% 62%, rgba(239, 145, 60, 0.34), transparent 64%),
    radial-gradient(54% 56% at 44% 42%, rgba(46, 159, 245, 0.15), transparent 70%);
  mix-blend-mode: screen;
  pointer-events: none;
}

/* Scale floor 1.05 keeps the 1.1% travel from ever exposing a plate edge. */
@keyframes hero-breathe {
  from {
    transform: translate3d(-1.1%, 0.4%, 0) scale(1.05);
  }

  to {
    transform: translate3d(1.1%, -0.5%, 0) scale(1.085);
  }
}

.home-hero__cluster {
  position: absolute;
  z-index: -4;
  right: clamp(24px, 4vw, 96px);
  bottom: clamp(24px, 5vh, 64px);
  width: min(46vw, 980px, var(--cluster-cap));
  pointer-events: none;
}

/* Products scale up where the copy column leaves room; the 781-1100px band
   keeps 46vw because the landscape-phone intro measure meets the cluster
   there at anything larger. Above 1400 the group breaks the right edge rather
   than sitting fully inside the frame with bare stone beside it: containment
   is what made it read as a tabletop vignette instead of a hero subject. */
@media (min-width: 1101px) {
  .home-hero__cluster {
    width: min(58vw, 1080px, var(--cluster-cap));
  }
}

@media (min-width: 1401px) {
  .home-hero__cluster {
    right: -2.5vw;
    width: min(64vw, 1320px, var(--cluster-cap));
  }
}

.home-hero__cluster img {
  position: relative;
  display: block;
  width: 100%;
  height: auto;
  /* Light wrap, then grounding. drop-shadow() derives from the alpha channel,
     so a near-zero-offset COLOURED shadow paints a soft rim that follows the
     cutout's exact silhouette. That is the cheap approximation of light wrap:
     in a real photograph the environment spills a little light around a
     subject's edge, and its total absence is most of why a cutout reads as
     pasted. Warm from the upper right where the stage's key is, cool from the
     lower left where the blue wash sits, matching __tint's two sources.

     Order is deliberate: the rims go on first so the black grounding shadow is
     thrown from the lit silhouette rather than the rims being thrown from the
     shadow. This is a mitigation, not a fix. The fix is a product cut from the
     same render as its background, which is what the new scene delivers. */
  filter:
    drop-shadow(5px -4px 9px rgba(239, 145, 60, 0.3))
    drop-shadow(-4px 3px 8px rgba(46, 159, 245, 0.18))
    drop-shadow(-9px 14px 17px rgba(0, 0, 0, 0.78));
  /* The slab's lower lip is the one edge with nothing behind it to justify a
     hard termination, so it dissolves rather than ends. */
  -webkit-mask-image: linear-gradient(180deg, #000 93%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 93%, transparent 100%);
}

/* Contact shadow: the cutout carries no cast shadow, so the rock grounds
   itself on the stage floor. Offset left and wider than the slab because the
   stage's key light comes from the upper right. */
.home-hero__cluster::before {
  content: "";
  position: absolute;
  right: 2%;
  bottom: -1%;
  left: -4%;
  height: 20%;
  background: radial-gradient(48% 50% at 46% 52%, rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0.34) 46%, transparent 76%);
  filter: blur(22px);
}

/* Light falloff down the slab. In a photographed scene the stone loses the key
   as it descends into the floor's shadow; without this the slab reads as an
   evenly lit object pasted onto the stage. Stops above the products so only
   the stone sinks. */
.home-hero__cluster::after {
  content: "";
  position: absolute;
  z-index: 1;
  inset: 0;
  background: linear-gradient(
    180deg,
    transparent 72%,
    rgba(var(--scrim-rgb), 0.2) 87%,
    rgba(var(--scrim-rgb), 0.62) 100%
  );
}

/* Foreground atmosphere: a screen-blended plate drifting between the viewer
   and the products, which is what a single generated scene gives for free and
   a composite otherwise never has. Low opacity keeps the standing rule that
   smoke frames the product and never obscures it; because this plate and the
   stage both move, there is no still-versus-moving contradiction. */
/* The wrapper deliberately carries NO z-index. Giving it one makes it a
   stacking context, which isolates the child's screen blend against an empty
   backdrop and silently reduces the veil to nothing; the blend and the
   stacking order both have to live on the image itself. */
.home-hero__veil {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.home-hero__veil img {
  position: absolute;
  z-index: -3;
  right: -8%;
  bottom: -10%;
  width: 92%;
  height: auto;
  opacity: 0.5;
  mix-blend-mode: screen;
  animation: hero-veil 44s ease-in-out infinite alternate;
  will-change: transform, opacity;
  /* Screen blend still paints the plate's own rectangle, so its edges have to
     dissolve or the veil reads as a lit panel rather than as air. The comment
     was right and the number was wrong: at a 72% horizontal radius against a
     gradient centred at 50%, the mask was still ~52% opaque at the plate's own
     left edge, so it dissolved everywhere except the one edge that lands inside
     the hero. 50% is the largest value that actually reaches zero there. */
  -webkit-mask-image: radial-gradient(50% 68% at 50% 44%, #000 40%, transparent 100%);
  mask-image: radial-gradient(50% 68% at 50% 44%, #000 40%, transparent 100%);
}

@keyframes hero-veil {
  from {
    opacity: 0.26;
    transform: translate3d(1.4%, 0.8%, 0) scale(1.02);
  }

  to {
    opacity: 0.4;
    transform: translate3d(-1.4%, -1%, 0) scale(1.07);
  }
}

/* Motion veil: the same foreground-atmosphere slot as __veil, but driven by a
   looping clip instead of a drifting plate. Occupies identical geometry so the
   two are interchangeable and the still remains the fallback.

   The element carries z-index AND mix-blend-mode together, per the same rule
   __veil and __tint document: a blend on a child of a z-indexed wrapper
   isolates against an empty backdrop and silently does nothing. Because
   mix-blend-mode already creates a stacking context, the ::after colour pass
   below composites against the clip alone rather than the hero behind it;
   `isolation` is written out only to make that guarantee legible. */
/* Sits at -5, so ABOVE the stage (-6), the scrim and the tint (both -5, both
   earlier in the DOM, so source order puts this on top of them), and BELOW the
   product cluster (-4). The clip is the air the products stand in, not a sheet
   drawn over the front of them.

   The still plate above stays in the -3 slot at low opacity, which is what
   keeps this from flattening: real smoke passes on both sides of a subject, and
   one layer can only ever be on one side. Two slots, one decoder. */
.home-hero__motion-veil {
  position: absolute;
  z-index: -5;
  right: -8%;
  bottom: -10%;
  width: 92%;
  isolation: isolate;
  mix-blend-mode: screen;
  opacity: 0;
  transition: opacity 900ms linear;
  pointer-events: none;
  /* The horizontal radius MUST NOT exceed 50%. The gradient is centred at 50%,
     so 50% is exactly the distance to the element's own left and right edges,
     and anything larger leaves the mask partly opaque at the point the element
     stops existing. At 72% it was still ~52% opaque there, and because this box
     is wider than the hero, its right edge got clipped by overflow:hidden while
     its LEFT edge landed inside the frame at x=290 and drew a hard vertical
     seam through the backdrop. Vertical can stay generous: top and bottom both
     fall outside the hero and are clipped. */
  -webkit-mask-image: radial-gradient(50% 68% at 50% 44%, #000 40%, transparent 100%);
  mask-image: radial-gradient(50% 68% at 50% 44%, #000 40%, transparent 100%);
}

.home-hero__motion-veil.is-live {
  opacity: 0.5;
}

/* saturate(0) is the same crush-then-re-tint discipline __art applies to the
   stage plate, and it is what makes the pipeline indifferent to whatever hue
   the source clip happens to carry: a neutral clip and a violently coloured
   one land in the same place, so colour is a CSS decision that can be retuned
   against the tokens without re-encoding a single frame. */
.home-hero__motion-veil video {
  display: block;
  width: 100%;
  height: auto;
  filter: saturate(0) contrast(1.08) brightness(0.96);
}

/* Colour pass. `color` blend takes hue and saturation from this gradient and
   keeps luminance from the clip, so the smoke is lit by the same two sources
   __tint uses on the stage (amber rim at the right, cool wash behind the
   products) instead of floating over the scene as uncoloured grey. Uncovered
   regions stay neutral, which is what keeps it from reading as flat wash. */
.home-hero__motion-veil::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(58% 66% at 88% 56%, rgba(239, 145, 60, 0.85), transparent 66%),
    radial-gradient(66% 68% at 26% 44%, rgba(46, 159, 245, 0.5), transparent 72%);
  mix-blend-mode: color;
  pointer-events: none;
}

/* With the clip carrying the atmosphere from behind, the still plate stops
   being a duplicate and becomes the foreground pass: the thin veil of smoke
   that drifts between the viewer and the products. It keeps drifting, at a
   third of its solo strength.

   Swapping animation-name rather than setting opacity is the only way in: the
   base rule animates opacity, and a running animation beats a plain
   declaration. Putting the opacity on the .home-hero__veil wrapper instead
   would create a stacking context and silently kill the child's screen blend,
   which is the trap its own comment documents. */
.home-hero.has-motion-veil .home-hero__veil img {
  animation-name: hero-veil-front;
}

@keyframes hero-veil-front {
  from {
    opacity: 0.1;
    transform: translate3d(1.4%, 0.8%, 0) scale(1.02);
  }

  to {
    opacity: 0.18;
    transform: translate3d(-1.4%, -1%, 0) scale(1.07);
  }
}

/* ---- Single coherent scene (2026-08-15) ----------------------------------
   The plate is now one generated exposure with the products, the stone, the
   smoke and the lighting all in it, so every correction that existed to make a
   pasted cutout sit on a separate backdrop is not just unnecessary here, it is
   actively harmful. The colour crush in particular: `saturate(0.36)` existed to
   kill the old plate's neon magenta, and applied to this one it would drain the
   brand amber and blue that the scene was lit with. */
.home-hero--scene .home-hero__art img,
.home-hero--scene .home-hero__group img {
  filter: none;
  animation-name: hero-breathe-scene;
}

/* The product group sits above the background AND above the motion veil, so
   the smoke clip drifts between the backdrop and the products rather than over
   the front of them. Stacking: art -6 < motion veil -5 < group -4 < wash -2.

   Every geometry property here must stay identical to __art. The two images
   are the same canvas, so matching object-fit, object-position and animation is
   what holds them in register; diverge on any one and the products slide off
   the slab. */
.home-hero__group {
  z-index: -4;
  pointer-events: none;
}

.home-hero__group img {
  object-fit: cover;
}

/* Landscape plate. Scoped to the breakpoint because these rules outrank the
   portrait block on specificity and would otherwise silently re-aim the mobile
   plate too. 62% rather than 50% because the source is 2.35:1 and narrower
   frames crop WIDTH from the centre, which bites into the product group.

   Two approaches to the copy/product overlap were tried here and rejected:

   1. Padding the ASSET with black on the left cannot work. Under
      object-fit:cover with a right anchor the product group lands at
      1 - 1.456/frameAR regardless of how much padding is added, because cover
      crops away exactly what you pad. That evaluates to 39.6% at a 2.41 frame,
      which is the collision measured on 2026-08-15.

   2. Containing the plate at 68% width, right-pinned, letting --ink-950 run
      across the left. The geometry works (the group starts at 1 - 0.62w, so
      w = 0.677 puts it at the measured 0.58 the copy needs) and the overlap
      does clear. But the plate then reads as a rectangular panel dropped into
      the frame: its top and left edges show as tonal steps against the hero
      ground. That is the pasted-panel problem the coherent scene existed to
      remove, so it is worse than what it fixes.

   The composition gets fixed in the plate instead. */
@media (min-width: 781px) {
  .home-hero--scene .home-hero__art img {
    object-position: 62% 50%;
  }

  .home-hero--scene .home-hero__group img {
    object-position: 62% 50%;
  }
}

/* The old breathe sat at a 1.05 scale floor, which permanently cropped 5% on
   top of whatever the aspect already took. This plate is framed tight to the
   products and cannot spare it, so the drift runs shallower. */
@keyframes hero-breathe-scene {
  from {
    transform: translate3d(-0.5%, 0.25%, 0) scale(1.02);
  }

  to {
    transform: translate3d(0.5%, -0.3%, 0) scale(1.045);
  }
}

/* Text-safe left darkener plus a bottom dissolve into --ink-950 so the hero
   hands off to the next band without a hard photographic seam. */
.home-hero__wash {
  z-index: -2;
  background:
    linear-gradient(180deg, transparent calc(100% - 140px), var(--ink-950) 100%),
    linear-gradient(
      90deg,
      rgba(var(--scrim-rgb), 0.42) 0%,
      rgba(var(--scrim-rgb), 0.18) 38%,
      rgba(var(--scrim-rgb), 0.02) 60%,
      transparent 76%
    ),
    linear-gradient(180deg, rgba(0, 0, 0, 0.2), transparent 30%, rgba(0, 0, 0, 0.08));
}

.home-hero__content {
  position: relative;
  z-index: 2;
  width: min(calc(100% - (2 * var(--gutter))), var(--container));
  margin-inline: auto;
  padding-top: calc(var(--header-height) + clamp(68px, 6vh, 92px));
  /* Bottom floor (Mark, 2026-08-24): on short viewports the hero's height is
     content-driven, and without this the pillars band rode up flush against
     the Find Your Fire button. Quartered from the first 56-92px cut the same
     day; a thin guarantee is all it needs. On tall viewports the svh term
     already leaves more room than this, so nothing changes there. */
  padding-bottom: clamp(14px, 1.8vh, 24px);
}

/* Staged entrance: lockup, intro, actions rise in sequence on load; the art
   settles from a slight zoom. One-shot animations (not .reveal, which waits
   on the observer) because the hero is always the first viewport. */
@keyframes hero-rise {
  from {
    opacity: 0;
    transform: translateY(14px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes hero-settle {
  from {
    transform: scale(1.03);
  }

  to {
    transform: scale(1);
  }
}

.js .home-hero__lockup {
  animation: hero-rise 700ms var(--ease) 80ms backwards;
}

.js .home-hero__intro {
  animation: hero-rise 700ms var(--ease) 220ms backwards;
}

.js .home-hero__actions {
  animation: hero-rise 700ms var(--ease) 360ms backwards;
}

.js .home-hero__cluster img {
  transform-origin: center bottom;
  animation: hero-settle 1600ms ease-out both;
}

/* Ceiling raised from 610px so the brand mark keeps its share of the frame
   past 1920 (39vw slope unchanged through the common range). */
.home-hero__lockup {
  width: clamp(430px, 39vw, 840px);
  margin: 0;
}

.home-hero__lockup img {
  width: 100%;
  height: auto;
}

/* 34ch capped the intro at 439px, 28px short of the 467px that would hold it to
   three lines, so it always broke to four. Measured in em, not ch, so the
   font-display: swap fallback cannot change the wrap. */
.home-hero__intro {
  max-width: 24em;
  margin: 26px 0 0;
  color: rgba(255, 255, 255, 0.78);
  font-size: clamp(1.02rem, 1.35vw, 1.28rem);
  line-height: 1.6;
}

.home-hero__actions {
  margin-top: 23px;
}

/* The `(min-width: 1200px) and (min-height: 700px)` override that used to sit
   here is gone. Its intent was right, a 160px scroll cue, but it was defeated
   twice: the `min-height: 700px` guard switched the whole rule off on short
   laptops (1366x768 among them, so they fell back to the width-driven rule and
   overflowed), and its own 760px clamp floor overrode the svh maths on any
   viewport under ~920px tall, quietly shrinking a 160px cue to 20px. The base
   rule now does the job unconditionally, so there is nothing left to override. */

/* Homepage Liquid Dabz drop */

.liquid-drop {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  min-height: clamp(720px, 54vw, 900px);
  background: #020305;
  box-shadow: inset 0 1px rgba(255, 255, 255, 0.08);
}

.liquid-drop__backdrop,
.liquid-drop__backdrop img,
.liquid-drop__veil {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.liquid-drop__backdrop {
  z-index: -4;
}

.liquid-drop__backdrop img {
  object-fit: cover;
  object-position: center;
}

.liquid-drop__veil {
  z-index: -3;
  background:
    linear-gradient(90deg, rgba(var(--scrim-rgb), 0.91) 0%, rgba(var(--scrim-rgb), 0.78) 35%, rgba(var(--scrim-rgb), 0.18) 62%, rgba(var(--scrim-rgb), 0.06) 100%),
    linear-gradient(180deg, rgba(var(--scrim-rgb), 0.12), transparent 58%, rgba(var(--scrim-rgb), 0.46));
}

.liquid-drop__inner {
  width: min(calc(100% - (2 * clamp(42px, 6vw, 94px))), 1470px);
  min-height: inherit;
  display: grid;
  grid-template-columns: minmax(470px, 1.05fr) minmax(480px, 0.95fr);
  gap: clamp(20px, 2.5vw, 46px);
}

.liquid-drop__copy {
  display: flex;
  z-index: 3;
  min-width: 0;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  padding-block: clamp(84px, 8vw, 126px) clamp(72px, 7vw, 112px);
}

/* Authored <br> carries the two-line break; nowrap makes it the only break
   point (the per-line spans it replaced read as generic nameless nodes in
   the inspector; the h2 is one block now). */
.liquid-drop__title {
  max-width: none;
  margin: 0;
  color: var(--paper);
  font-family: var(--font-display);
  font-size: clamp(3.78rem, 5.63vw, 5.4rem);
  font-weight: 700;
  letter-spacing: normal;
  line-height: 1.05;
  white-space: nowrap;
  text-shadow: 0 12px 34px rgba(0, 0, 0, 0.46);
}

.liquid-drop__summary {
  max-width: 55ch;
  margin: clamp(28px, 2.6vw, 40px) 0 0;
  color: rgba(244, 248, 252, 0.78);
  font-size: clamp(1rem, 1.25vw, 1.16rem);
  line-height: 1.62;
}

.liquid-drop__families {
  display: flex;
  align-items: center;
  gap: clamp(16px, 2vw, 28px);
  margin: clamp(20px, 2vw, 28px) 0 0;
  padding: 0;
  list-style: none;
}

/* Stepped up from the 0.75rem label tier to the 0.86rem small-body step
   2026-08-24 (Mark: "a bit bigger"); the dot scales with it. */
.liquid-drop__family {
  display: grid;
  grid-template-columns: 10px auto;
  gap: 9px;
  align-items: center;
  color: rgba(244, 248, 252, 0.88);
  font-size: 0.86rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  line-height: 1.1;
  text-transform: uppercase;
}

.liquid-drop__family + .liquid-drop__family {
  padding-left: clamp(16px, 2vw, 28px);
  border-left: 1px solid rgba(255, 255, 255, 0.16);
}

.liquid-drop__family small {
  display: block;
  margin-top: 4px;
  color: rgba(244, 248, 252, 0.72);
  font-size: 0.86rem;
  font-weight: 650;
  letter-spacing: 0.1em;
}

/* No fixed height: the bar stretches to the two-line label stack so the
   two always match, at every breakpoint (Mark flagged the mobile
   mismatch, 2026-08-25). */
.liquid-drop__family-dot {
  width: 10px;
  align-self: stretch;
  background: var(--blue);
  box-shadow: 0 4px 12px color-mix(in srgb, var(--blue) 28%, transparent);
}

.liquid-drop__family--green .liquid-drop__family-dot {
  background: var(--green);
  box-shadow: 0 4px 12px color-mix(in srgb, var(--green) 24%, transparent);
}

.liquid-drop__family--purple .liquid-drop__family-dot {
  background: var(--purple);
  box-shadow: 0 4px 12px color-mix(in srgb, var(--purple) 24%, transparent);
}

.liquid-drop__cta {
  min-height: 58px;
  margin-top: clamp(30px, 3vw, 44px);
  padding-inline: 30px;
}

.liquid-drop__stage {
  position: relative;
  z-index: 1;
  align-self: stretch;
  min-width: 0;
  min-height: inherit;
  margin: 0;
  isolation: isolate;
  pointer-events: none;
}

/* Product lineup */

/* Four equal tiles since 2026-08-24 (owner): the wide Liquid Dabz lead card
   is retired and every card carries portrait art. */
.lineup-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 18px;
}

/* Restructured 2026-08-31 to Mark's mockup: the card was a full-bleed image
   with the title and copy overlaid on a scrim; it is now a square image with
   a solid body panel beneath it. The DOM order (media, title, body) already
   matched, so this is a layout change only -- no markup was reordered.

   Consequences of the square media box, deliberate:
   - the ::after scrim is gone, because nothing sits on the image any more;
   - every master here is portrait (0.667 or 0.75), so cover now crops
     VERTICALLY and object-position's X term does nothing. The two horizontal
     framing fixes made earlier today are neutralised below rather than left
     to misfire. */
.lineup-card {
  position: relative;
  display: flex;
  isolation: isolate;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--ink-850);
  text-decoration: none;
}

/* min-height: 0 and the absolutely-placed img are both load-bearing. As a
   flex item the media takes min-height: auto, so without these the box sizes
   to the IMAGE's intrinsic ratio and the aspect-ratio is ignored: the 2:3
   Liquid Dabz master made its card 562px tall against 499px for the 3:4
   masters beside it, which is what pushed that one title out of line. */
.lineup-card__media {
  position: relative;
  z-index: 0;
  width: 100%;
  min-height: 0;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  transition: transform 700ms var(--ease);
}

.lineup-card__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition:
    opacity 180ms ease,
    filter 260ms ease;
}

.lineup-grid[data-swapping="true"] .lineup-card__media img {
  opacity: 0.24;
  filter: saturate(0.68);
}

/* DEAD as of 2026-08-24: no markup carries --lead or --closing since the
   lineup flattened to four equal tiles. Every --lead/--closing rule below and
   in the media blocks is unreferenced; delete the set once Roy approves the
   flattened grid (kept for a cheap revert until then). */
.lineup-card--lead {
  grid-column: 1 / -1;
  min-height: clamp(300px, 26vw, 390px);
}

.lineup-card--lead .lineup-card__media {
  background: linear-gradient(120deg, rgba(46, 159, 245, 0.12), transparent 55%);
}

.lineup-card--lead .lineup-card__media img {
  object-fit: contain;
  object-position: 58% 50%;
  padding: 22px 34px;
  transform: translateY(16px) scale(2.7);
}

.lineup-card--lead .lineup-card__title {
  font-size: clamp(1.92rem, 3.2vw, 3.36rem);
}

.lineup-card--resin .lineup-card__media,
.lineup-card--float .lineup-card__media,
.lineup-card--photo .lineup-card__media {
  background: radial-gradient(ellipse 58% 52% at 50% 38%, color-mix(in srgb, var(--blue) 17%, transparent), transparent 72%);
}

.lineup-card--photo .lineup-card__media {
  background: radial-gradient(ellipse 58% 52% at 50% 38%, color-mix(in srgb, var(--purple) 17%, transparent), transparent 72%);
}

.lineup-card--resin .lineup-card__media img,
.lineup-card--float .lineup-card__media img,
.lineup-card--photo .lineup-card__media img {
  object-fit: contain;
  object-position: center 36%;
  padding: 52px 32px 150px;
  filter: drop-shadow(0 24px 28px rgba(0, 0, 0, 0.34));
}

.lineup-card--resin .lineup-card__media img {
  transform: scale(1.55);
  transform-origin: center 36%;
}

.lineup-card--photo .lineup-card__media img {
  transform: scale(1.15);
  transform-origin: center 36%;
}

.lineup-card--float .lineup-card__media img {
  padding: 0;
  object-fit: cover;
  object-position: center;
  filter: none;
}

.lineup-card--vignette .lineup-card__media {
  background: none;
}

.lineup-card--vignette .lineup-card__media img {
  padding: 0;
  object-fit: cover;
  object-position: center;
  filter: none;
  transform: none;
}

/* The Liquid Dabz master (2:3) is wider than the card box, so cover crops its
   sides, and at 50% the crop fell left of the composition: it kept dead slate
   on the left and cut the applicator's nib on the right (Mark, 2026-08-31).

   Derived, not eyeballed. In the master the product spans x 0.165 to 0.955.
   The tightest band is ~1200px, where the card box is 0.513 against the
   master's 0.667, so cover keeps w = 0.513/0.667 = 0.77 of the width -- very
   slightly less than the product's own 0.79 span, meaning no value fits both
   ends there. 76% centers the product in that window (visible 0.176-0.945),
   trading a ~1% shave at each end for the 7% of nib that 50% was cutting.
   Wider viewports keep the whole product; below ~1080px the box is wider than
   the master, so cover crops vertically and this value stops applying.

   Scoped to the liquid slot. If the dormant family selector is ever revived,
   the hybrid and indica masters are the older portrait art with different
   framing and will each want their own value -- or, better, regenerating them
   at 2:3 to this composition would let one value serve all three. */
/* SUPERSEDED by the square media box (2026-08-31). The 76% was solving a
   horizontal crop that no longer happens: against a 1:1 box every portrait
   master is cropped vertically instead, so the X term is inert. Kept at 50%
   rather than deleted so the derivation above stays with the code if the
   card ever returns to a tall box. */
.lineup-card__media img[data-lineup-image="liquid"] {
  object-position: 50% 50%;
}

/* The approved disposable vignette was authored with a deliberately small,
   right-weighted subject for the previous copy layout. Reframe those pixels
   so the device is centered and carries slightly more visual weight than the
   narrower 510 cartridge. */
/* -16% -> -30% (Mark, 2026-08-31): the device sat right of centre in the
   card. Derived from the master, where it spans x 0.57-0.76 (centre 0.66).
   At the desktop band cover keeps w = 0.68 of the width, so with
   object-position centred the device lands at 0.735 of the element before
   the transform; scale(1.32) about the centre carries it to 0.810, and the
   old -16% left it at 0.650. -30% lands it at 0.510 here and 0.474 at
   1920px, i.e. centred across the band. The scale is untouched: it is doing
   the separate job of giving the device more visual weight than the
   narrower 510 cartridge beside it. */
/* SUPERSEDED by the square media box (2026-08-31). Unlike the object-position
   above this one is NOT inert: a -30% translate would drag the device out of
   a square frame entirely, so it has to go rather than merely stop helping.
   The device reads at a good size in the taller box on its own. */
.lineup-card--float.lineup-card--vignette .lineup-card__media img {
  transform: none;
}

/* The 981px block below sets object-position: left center for --photo, which
   was written for that class's contain + scale(1.15) treatment. --photo has
   exactly one consumer and it is also --vignette, so that rule only ever
   lands here, where it wins on source order at equal specificity and pins a
   cover crop to the master's left edge -- which is what pushed the cartridge
   right of centre. The cart is already centred in its own master (x
   0.42-0.58), so restoring a centred crop is the whole fix, and it holds at
   every width: for a centred subject the solution to
   p(1-w) + w/2 = 0.5 is p = 0.5 regardless of w. Mirrors the
   --float.--vignette pairing above. */
.lineup-card--photo.lineup-card--vignette .lineup-card__media img {
  object-position: center;
}

/* The portrait Liquid Dabz scene is reserved for the true one-column phone
   layout. The right-shifted wide composition supports every wider grid. */
@media (min-width: 1081px) {
  .lineup-card--lead.lineup-card--vignette .lineup-card__media img {
    transform: none;
  }

  .lineup-card--lead .lineup-card__body p {
    max-width: 34ch;
  }
}

@media (min-width: 981px) {
  .lineup-card--float .lineup-card__media img {
    object-position: center;
  }

  .lineup-card--photo .lineup-card__media img {
    object-position: left center;
  }
}

.lineup-card:hover .lineup-card__media,
.lineup-card:focus-visible .lineup-card__media {
  transform: scale(1.025);
}

/* Title and body share the panel's inset rather than each carrying its own
   absolute offsets. */
.lineup-card__title {
  margin: 0;
  padding: 22px 26px 12px;
  font-family: var(--font-display);
  font-size: clamp(1.45rem, 1.85vw, 1.95rem);
  font-weight: 500;
  letter-spacing: normal;
  line-height: 1.1;
  text-wrap: balance;
}

/* flex: 1 + margin-top: auto on the link pins every CTA to a shared baseline,
   so uneven copy lengths no longer stagger the row the way they did while the
   body was bottom-anchored. */
.lineup-card__body {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: 0 26px 26px;
}

.lineup-card__body p {
  margin-bottom: 20px;
  color: rgba(255, 255, 255, 0.76);
  font-size: clamp(0.92rem, 0.95vw, 0.98rem);
  line-height: 1.55;
}

/* Ember tagline between the title and the sentence (Mark's 2026-08-31
   mockup). Tighter margin than a body paragraph so it reads as a subtitle
   attached to the name above it rather than a first line of copy.

   Scoped through .lineup-card__body deliberately: as a bare class (0,1,0) it
   loses to `.lineup-card__body p` (0,1,1) and the tagline renders in body
   white with a 20px gap. */
.lineup-card__body .lineup-card__tagline {
  margin-bottom: 9px;
  color: var(--ember-light);
  font-size: clamp(0.9rem, 0.92vw, 0.95rem);
  font-weight: 700;
}

/* Ember pill on the artwork, replacing the inline "NEW." run-in that opened
   the Liquid Dabz sentence. */
.lineup-card__badge {
  position: absolute;
  z-index: 2;
  top: 18px;
  left: 18px;
  padding: 6px 14px;
  border-radius: 999px;
  background: var(--ember);
  color: #1a1206;
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.lineup-card__link {
  display: inline-flex;
  align-items: center;
  margin-top: auto;
  gap: 9px;
  color: var(--ember-light);
  font-size: 0.82rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}


/* No arrow: the lineup cards go to our own product pages. Site-wide rule
   (Mark, 2026-08-28): the arrow marks a link that LEAVES zmoke.com, so it
   belongs only to outbound links (currently the retailer store links). Do
   not add one to an internal link to suggest forward motion. */

/* Use-surface proof panels (Liquid Dabz).

   Real daylight photography inside the house frame: hairline border, radius-md,
   ink-850 ground. The frame is what keeps a warm, bright shoot reading as
   contained evidence against the site's near-black key rather than competing
   with it. auto-fit sizing carries every breakpoint on its own, so this block
   needs no media query and cannot lose a cascade-order race. */

/* Two across is the phone floor, three the desktop rhythm: six surfaces divide
   evenly either way, so no row is ever left with a single orphaned card.
   Explicit column counts rather than auto-fit, because auto-fit derives the
   count from the track floor and silently produced a 4+2 split at some widths.
   The wider rule follows the base rule deliberately: at equal specificity a
   media block placed first loses on source order. */

.proof-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(16px, 2vw, 26px);
  margin: 0;
  padding: 0;
  list-style: none;
}

.proof-card {
  overflow: hidden;
  height: 100%;
  margin: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--ink-850);
}

.proof-card__media {
  position: relative;
  aspect-ratio: 4 / 5;
  background: var(--ink-800);
}

.proof-card__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Captions stepped down to an understated label (Mark, 2026-08-28,
   superseding the 08-24 body-ramp caption call): the photography carries
   the section, the caption bar slims, and the label sits secondary at the
   documented 0.94rem step. */
.proof-card__label {
  padding: 13px 18px 15px;
  color: var(--text-soft);
  font-size: 0.94rem;
  font-weight: 600;
  line-height: 1.4;
}

@media (max-width: 480px) {
  .proof-card__label {
    padding: 11px 14px 13px;
  }
}

/* Pop-out exploration (Mark, 2026-09-03, ruling P2 of the controlling-docs
   review): three trigger models behind ?pop=a|b|c on /liquid-dabz/, for a
   live comparison. Shared: each card's caption holds a native <details> whose
   summary is the label and whose body is the short procedure, so keyboard and
   assistive tech get a real disclosure in every model. The chosen model
   replaces this block and the others go. */
.proof-card__caption {
  position: relative;
  margin: 0;
}

.proof-card__how summary.proof-card__label {
  color: var(--text);
  cursor: pointer;
  list-style: none;
}

.proof-card__how summary::-webkit-details-marker {
  display: none;
}

/* No glyph on the label (Mark, 2026-09-03): on a phone the photo is the
   invitation, on a desktop the hover is the discovery. */

.proof-card__how summary:focus-visible {
  outline-offset: -3px;
}

.proof-card__text {
  margin: 0;
  padding: 0 18px 18px;
  color: var(--text-soft);
  font-size: 0.86rem;
  line-height: 1.55;
}

.proof-card__text strong {
  color: var(--text);
  font-weight: 700;
}

/* A and C open in place: the row grows, the other cards keep their height. */
.proof-grid--pop-a,
.proof-grid--pop-c {
  align-items: start;
}

.proof-grid--pop-a .proof-card,
.proof-grid--pop-c .proof-card {
  height: auto;
}

/* A: the whole card is the trigger (site.js forwards a click on the photo to
   the disclosure). The frame and the photo answer hover so the card reads as
   pressable, without moving: the motion doctrine keeps hover lifts out. */
.proof-grid--pop-a .proof-card {
  cursor: pointer;
  transition: border-color 0.2s ease;
}

.proof-grid--pop-a .proof-card__media img {
  transition: filter 0.2s ease;
}

@media (hover: hover) {
  .proof-grid--pop-a .proof-card:hover {
    border-color: var(--text-dim);
  }

  .proof-grid--pop-a .proof-card:hover .proof-card__media img {
    filter: brightness(1.06);
  }
}

/* B: the text is a panel over the lower part of the photo. Hover shows it on
   pointer devices (site.js opens the disclosure), tap toggles it elsewhere;
   the card never changes height. */
.proof-grid--pop-b .proof-card {
  position: relative;
  cursor: pointer;
  transition: border-color 0.2s ease;
}

@media (hover: hover) {
  .proof-grid--pop-b .proof-card:hover {
    border-color: var(--text-dim);
  }
}

.proof-grid--pop-b .proof-card__text {
  position: absolute;
  right: 0;
  bottom: 100%;
  left: 0;
  padding: 16px 18px;
  background: rgba(var(--scrim-rgb), 0.84);
  color: var(--text);
  backdrop-filter: blur(4px);
}

/* B on phones and tablets: a card (native <dialog>) with the photo across
   the top, then the label and the text. On a phone it is a sheet whose top
   edge meets the bottom of the fixed header (Mark, 2026-09-03), the photo
   filling whatever sits between the header and the text; from 600px it is
   the same card centred with rounded corners all round. site.js opens it
   from the tapped card. */
.proof-sheet {
  position: fixed;
  inset: var(--header-height) 0 0;
  width: 100%;
  max-width: none;
  height: auto;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  overflow: hidden;
  background: var(--ink-900);
  color: var(--text);
  transform: translateY(100%);
  transition: transform 0.32s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.proof-sheet.is-open {
  transform: translateY(0);
}

.proof-sheet[open] {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) auto;
}

.proof-sheet__navigation {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 16px calc(8px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  background: var(--ink-900);
}

.proof-sheet__navigation button {
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  color: var(--text);
  background: var(--ink-800);
  font: inherit;
  font-size: 0.875rem;
  cursor: pointer;
}

.proof-sheet__position {
  margin: 0;
  color: var(--text-dim);
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.proof-sheet.is-dragging {
  transition: none;
}

/* Three full-width panels side by side; the middle one is current. The
   base transform shows it; a sideways drag moves the track under the
   thumb, the release animates to the neighbour, and site.js recycles only
   the offscreen panel before returning to the base without a transition. */
.proof-sheet__track {
  display: flex;
  align-items: stretch;
  height: 100%;
  min-height: 0;
  transform: translateX(-100%);
  transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
  touch-action: pan-y;
  will-change: transform;
}

.proof-sheet__track.is-dragging {
  transition: none;
}

.proof-sheet__panel {
  display: flex;
  flex: 0 0 100%;
  flex-direction: column;
  height: 100%;
  overflow-x: hidden;
  overflow-y: auto;
}

/* The photo and the handle always pull; the whole sheet pulls once its
   current panel fits the screen (site.js sets is-fixed), otherwise the body
   keeps scrolling. */
.proof-sheet.is-fixed .proof-sheet__track,
.proof-sheet__media {
  touch-action: none;
}

.proof-sheet__grab {
  position: absolute;
  z-index: 2;
  top: calc(10px + env(safe-area-inset-top));
  left: 50%;
  width: 42px;
  height: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.72);
  transform: translateX(-50%);
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .proof-sheet,
  .proof-sheet__track {
    transition: none;
  }
}

.proof-sheet::backdrop {
  background: rgba(var(--scrim-rgb), 0.64);
}

@media (min-width: 600px) {
  .proof-sheet {
    inset: 0;
    width: min(560px, calc(100% - 48px));
    /* Pinned top and bottom for centring, so the height must be told to fit
       its content or the card stretches to the screen. */
    height: fit-content;
    max-height: min(88svh, 920px);
    margin: auto;
    border-radius: var(--radius-md);
  }

  .proof-sheet__track {
    height: auto;
  }

  .proof-sheet__panel {
    display: block;
    height: auto;
    max-height: calc(88svh - 61px - env(safe-area-inset-bottom));
  }

  .proof-sheet__media {
    flex: none;
    max-height: 46svh;
    aspect-ratio: 4 / 5;
  }

  .proof-sheet__body {
    min-height: 15rem;
  }
}

body:has(.proof-sheet[open]) {
  overflow: hidden;
}

/* On a phone the photo takes whatever height the text leaves; on a tablet
   it keeps the tiles' 4:5 frame, capped so the text always shows beneath it. */
.proof-sheet__media {
  flex: 1 1 auto;
  width: 100%;
  min-height: 0;
  background: var(--ink-800);
}

.proof-sheet__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.proof-sheet__close {
  position: absolute;
  z-index: 2;
  top: calc(12px + env(safe-area-inset-top));
  right: 12px;
  width: 44px;
  height: 44px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: rgba(var(--scrim-rgb), 0.6);
  color: var(--text);
  cursor: pointer;
  font-size: 1.5rem;
  line-height: 1;
}

/* Dialog autofocus can match :focus-visible after a tap. Keep this control's
   focus cue within its existing grey circle instead of the global ember ring. */
.proof-sheet__close:focus-visible {
  outline: none;
  border-color: rgba(255, 255, 255, 0.85);
  background: rgba(var(--scrim-rgb), 0.9);
}

/* One text-area height for all six tiles, sized for the longest text at each
   width, so the photo's bottom edge stays put while browsing. */
.proof-sheet__body {
  flex: none;
  min-height: 17.5rem;
  padding: 22px 20px calc(28px + env(safe-area-inset-bottom));
}

.proof-sheet__title {
  margin: 0 0 10px;
  font-family: var(--font-display);
  font-size: 1.7rem;
  font-weight: 500;
  line-height: 1.1;
}

.proof-sheet__text {
  margin: 0;
  color: var(--text-soft);
  font-size: 1rem;
  line-height: 1.6;
}

.proof-sheet__text strong {
  color: var(--text);
  font-weight: 700;
}

/* C: only the label row is the trigger; the word underlines on hover so the
   affordance is the label, not the frame. */
@media (hover: hover) {
  .proof-grid--pop-c summary.proof-card__label:hover .proof-card__label-text {
    text-decoration: underline;
    text-underline-offset: 3px;
  }
}

@media (max-width: 480px) {
  .proof-card__text {
    padding: 0 14px 15px;
  }

  .proof-grid--pop-b .proof-card__text {
    padding: 12px 14px;
  }
}

/* One Drop and how-to section frames (Mark, 2026-08-28): the headings hug
   their grids. Scoped; every other section keeps the shared rhythm. */
#where-it-goes .section-heading,
#how-to-use .section-heading {
  margin-bottom: clamp(26px, 3vw, 44px);
}

.proof-grid__footnote {
  margin-top: clamp(22px, 3vw, 34px);
  color: var(--text-dim);
  font-size: 0.86rem;
}

@media (min-width: 781px) {
  .proof-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* Find and Carry */

/* The homepage Find Your Fire section (.find-fire) and all its rules were
   removed 2026-09-01 (Mark; the scrim-only trim earlier the same day is
   superseded). Pass 2 had already reduced the section to a teaser for
   /where-to-buy/, and the header's Find Your Fire action carries the job.
   The retailer grid below stays: it is the canonical store list on
   /where-to-buy/. */

.retailer-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(215px, 1fr));
  gap: 14px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* 4 columns keeps 8 retailer cards forming two complete rows instead of
   the 5-column auto-fit leaving 2 orphans on the final row. Must follow the
   base rule: equal specificity, source order decides.

   The cap sizes the cards to their content rather than to the container:
   across the full 1420px shell each card ran ~317px against a widest name
   of 224px, so a third of every card was trailing air. 1150px lands cards
   near 277px, which still holds "The Flower Shop Dispensary" on one line.
   Re-measure this if the retailer names get longer. */
@media (min-width: 981px) {
  .retailer-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    max-width: 1150px;
  }
}

/* Cards stretch to their row so a 4-store card and a 1-store card still
   align, but the row is sized by content: no forced equal height across the
   whole grid, and no padded-out blank space in the short cards. */
.retailer-grid__item {
  display: grid;
}

.retailer-card {
  display: grid;
  align-content: start;
  gap: 6px;
  padding: 20px 22px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: rgba(12, 15, 19, 0.55);
  color: inherit;
  text-decoration: none;
  transition: border-color 0.3s ease, transform 0.3s ease;
}

/* Both card shapes share one internal rhythm (2026-08-28): the single-store
   card used to reserve a two-line name row to line meta rows up across the
   grid, which left a gap under its one-line names and made the two variants
   read as different components. Card heights already vary by store count, so
   that reservation bought nothing and is gone. */

/* Ember means "this retailer": it answers hover anywhere in the card and,
   via :focus-within, a keyboard tab into any location inside it. */
.retailer-card:hover,
.retailer-card:focus-visible,
.retailer-card:focus-within {
  border-color: var(--ember-light);
  transform: translateY(-3px);
}

.retailer-card__name {
  margin: 0;
  font-size: 1rem;
  font-weight: 700;
}

.retailer-card__meta {
  margin: 0;
  color: var(--ember-light);
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.retailer-card__cities {
  color: var(--text-dim);
  font-size: 0.94rem;
}

/* Store list: understated text links, never buttons or boxes. The row gap
   carries the 24px AA touch floor on the link's own box (padding-block
   below), so taps do not collide on a phone. */
/* The list is the card's substance, so it reads at the same step as a
   single card's city line and sits tight: a loose, small list made the
   multi-store cards look like a different component next to the compact
   single ones. Row separation still clears the 24px AA target floor from
   the link's own padding (0.94rem x 1.4 + 8px = ~29px). */
.retailer-card__locations {
  display: grid;
  gap: 1px;
  margin: 2px 0 0;
  padding: 0;
  list-style: none;
}

.retailer-location {
  display: inline-flex;
  gap: 6px;
  align-items: baseline;
  padding-block: 4px;
  color: var(--text-dim);
  font-size: 0.94rem;
  text-decoration: none;
  transition: color 0.25s ease;
}

/* White means "this exact store". Only the pointed-at location brightens;
   its siblings stay muted, and neither ever turns ember. */
.retailer-location:hover,
.retailer-location:focus-visible {
  color: var(--text);
}

/* Keyboard focus uses the site-wide ember ring (:focus-visible, line ~227);
   no local override, so the store list matches every other focusable thing
   on the site. The ring is the focus indicator; the ember CARD border and
   the white location text still carry the two hover meanings. */
.retailer-location:focus-visible {
  border-radius: 4px;
  outline-offset: 2px;
}

/* The arrow rides its label's color, including the brighten. */
.retailer-location__icon {
  font-size: 0.78em;
  opacity: 0.7;
  transition: opacity 0.25s ease;
}

.retailer-location:hover .retailer-location__icon,
.retailer-location:focus-visible .retailer-location__icon,
.retailer-card--single:hover .retailer-location__icon,
.retailer-card--single:focus-visible .retailer-location__icon {
  opacity: 1;
}

/* A single-store card is one link, so its city brightens with the card. */
.retailer-card--single:hover .retailer-card__cities,
.retailer-card--single:focus-visible .retailer-card__cities {
  color: var(--text);
}

.retailer-card--single .retailer-card__cities {
  transition: color 0.25s ease;
}

/* Secondary line under a Where to Buy block (now just the SD program link):
   supporting information, not a CTA band. */
.where__aside {
  max-width: 62ch;
  margin: 18px 0 0;
}

/* Where to Buy hero: lead left, at-a-glance market card right. Single
   column until the 981px step, where the retailer grid also goes four-up. */
.where-hero__grid {
  display: grid;
  gap: clamp(30px, 4vw, 56px);
  align-items: center;
}

@media (min-width: 981px) {
  .where-hero__grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.82fr);
  }

  /* The intro was already dead-centre in its track, but its widest line (the
     lead, capped at the shared 680px measure) filled the track edge to edge
     from 1280px down, so its right edge sat against the column gap and read
     as crowding the availability card. Capping the block below the track
     width restores air on BOTH sides and lets the auto margins centre it
     (Mark, 2026-08-29).

     Neither term is arbitrary. The inset repeats the grid's own gap clamp,
     so the intro is held half a gap clear of each track edge and the air
     scales with the same value the gap does instead of needing its own
     breakpoints. The 36rem cap then takes over on wide screens, where a
     percentage inset alone would leave the lead too long to read. Scoped to
     the two-column layout on purpose: the stacked layout never had the
     problem and is left exactly as it was. */
  .where-hero__copy {
    width: min(100% - clamp(30px, 4vw, 56px), 36rem);
    margin-inline: auto;
  }
}

/* The title, rule, and lead share one centre axis and one measure (Mark,
   2026-08-28), so the hero copy reads as a single plate instead of three
   left-anchored fragments at three different widths. This is scoped to the
   Where to Buy hero; section headings elsewhere stay left-anchored. */
.where-hero__copy {
  text-align: center;
}

.where-hero__copy .section-title {
  max-width: 14ch;
  margin-inline: auto;
}

.where-hero__copy .section-copy {
  margin-inline: auto;
  text-wrap: balance;
}

/* Rule with the ZMOKE flame set into it. The flame is the SAME asset the
   marquee and logo use, pulled in as a mask so it can take ember from the
   palette instead of shipping a recolored copy of the mark. The 680px cap
   is the .section-copy measure on purpose: the rule ends where the lead
   ends, at every width. */
.where-hero__rule {
  display: flex;
  gap: 18px;
  align-items: center;
  max-width: 680px;
  margin: clamp(18px, 2vw, 26px) auto;
}

.where-hero__rule::before,
.where-hero__rule::after {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--line-strong), transparent);
  content: "";
}

.where-hero__flame {
  flex: none;
  width: 15px;
  height: 32px;
  background: var(--ember);
  -webkit-mask: url("/assets/img/brand/zmoke-flame-white.svg") center / contain no-repeat;
  mask: url("/assets/img/brand/zmoke-flame-white.svg") center / contain no-repeat;
}

.market-card {
  display: grid;
  gap: clamp(18px, 2.2vw, 26px);
  padding: clamp(24px, 3vw, 38px);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: rgba(12, 15, 19, 0.55);
}

/* The divider sits between the two markets only, so a future third market
   inherits it without a modifier class. */
.market-card__row + .market-card__row {
  padding-top: clamp(18px, 2.2vw, 26px);
  border-top: 1px solid var(--line);
}

.market-card__row {
  display: flex;
  gap: 20px;
  align-items: center;
  justify-content: space-between;
}

.market-card__label {
  margin: 0 0 6px;
  color: var(--text-dim);
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.market-card__label--live {
  color: var(--ember-light);
}

.market-card__state {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 2.2vw, 2.3rem);
  font-weight: 500;
  line-height: 1.1;
}

.market-card__meta {
  display: flex;
  gap: 8px;
  align-items: center;
  margin: 8px 0 0;
  color: var(--text-soft);
  font-size: 0.94rem;
}

.market-card__pin {
  display: inline-flex;
  flex: none;
  width: 17px;
  color: var(--ember-light);
}

.market-card__pin svg {
  width: 100%;
  height: auto;
}

/* The state sits in a hairline disc: live market in ember, the one that is
   not open yet in the muted line color, so the card reads at a glance. */
.market-card__shape {
  display: grid;
  flex: none;
  place-items: center;
  width: clamp(74px, 8vw, 104px);
  aspect-ratio: 1;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--text-dim);
}

.market-card__shape--live {
  color: var(--ember);
}

/* The outline is a mask, so it inherits the disc's color (see
   components/state-outline.php). Sized per shape so both states carry the
   same optical weight in the disc: South Dakota is wide and short,
   Minnesota tall and narrow. Scale the two together (both went up 25% on
   2026-08-28) or one state starts outweighing the other. */
.state-outline {
  display: block;
  background: currentColor;
  -webkit-mask: var(--state-mask) center / contain no-repeat;
  mask: var(--state-mask) center / contain no-repeat;
}

.state-outline--south-dakota {
  width: 67.5%;
  aspect-ratio: 240 / 144;
}

/* Width is derived, not chosen: Minnesota is matched to South Dakota on the
   axis each state actually spans. SD reads across the disc at 67.5% wide;
   MN reads down it, so 67.5% TALL, which is 60.75% wide at 216/240. Setting
   both by width is what made MN look under-filled next to SD. */
.state-outline--minnesota {
  width: 60.75%;
  aspect-ratio: 216 / 240;
}

/* Where to Buy is a locator, not a product page: same rhythm language, one
   step denser. Scoped to this page so no other section changes. */
.page-where-to-buy .section {
  padding-block: clamp(44px, 4.5vw, 68px);
}

/* About page (editorial redesign, Mark's spec 2026-09-01). Page-scoped
   additions only; nothing here reaches other pages.
   - .about-eyebrow: the ONE eyebrow on the site. Eyebrows were removed
     site-wide 2026-08-24 (owner's call); this page reintroduces one under
     Mark's spec, scoped so stripping it is a two-line change.
   - .about-statement: the full-width "One extract. Four formats." line.
   - .about-family-word: the color-system paragraph's family words, same
     legibility-tuned tints as .section-title__family-word (softened blue,
     raw green, purple lightened for contrast on dark).
   - .about-state: sizes and colors the South Dakota silhouette; ember is
     the live-market color from Where to Buy, dialed down to stay an
     accent. */
.page-about .about-eyebrow {
  margin-bottom: 18px;
  color: var(--text-dim);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* These two headings carry deliberate <br> breaks and must remain two lines.
   The shared authored-title ramp outgrows their fixed story columns above
   1600px, while its 1.8rem floor narrowly re-wraps "Purpose-built around" at
   320px. Cap and lower only these two headings; nowrap still honors <br>. */
.page-about #about-formats-title,
.page-about #about-colors-title {
  font-size: clamp(1.65rem, 4.5vw, 4.5rem);
  white-space: nowrap;
}

.page-about .about-statement {
  margin: clamp(48px, 6vw, 84px) 0 0;
  max-width: 17em;
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 3.4vw, 3.4rem);
  font-weight: 500;
  line-height: 1.2;
  text-wrap: balance;
}

.page-about .about-family-word[data-family="blue"] {
  color: #5a9be8;
}

.page-about .about-family-word[data-family="green"] {
  color: var(--green);
}

.page-about .about-family-word[data-family="purple"] {
  color: color-mix(in srgb, var(--purple) 78%, white);
}

.page-about .about-state {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ember);
}

.page-about .about-state .state-outline--south-dakota {
  width: min(340px, 74%);
  opacity: 0.85;
}

/* ZC-09: editorial draft; authentic people/place placeholders are local only. */
.page-about .about-editorial {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: clamp(32px, 7vw, 100px);
  align-items: start;
}

.page-about .about-editorial .section-copy:last-child { margin-bottom: 0; }
.page-about .about-editorial .section-copy + .section-copy { margin-top: 20px; }
.page-about #about-title { max-width: 12em; }
.page-about #about-foundation-title {
  max-width: 13em;
  font-size: clamp(1.5rem, 3.2vw, 3.5rem);
}

.page-about .about-review__label {
  color: var(--ember);
  font-weight: 700;
  margin-bottom: 24px;
}

.page-about .about-review__photos {
  display: grid;
  grid-template-columns: minmax(0, 0.7fr) minmax(0, 1fr);
  gap: clamp(24px, 4vw, 56px);
  margin-top: 40px;
  align-items: end;
}

.page-about .about-review__figure { margin: 0; }
.page-about .about-review__photo {
  display: grid;
  place-items: center;
  padding: 24px;
  border: 1px dashed var(--line-strong);
  background: var(--ink-850);
  color: var(--text-soft);
  text-align: center;
}
.page-about .about-review__photo--portrait { aspect-ratio: 4 / 5; }
.page-about .about-review__photo--landscape { aspect-ratio: 3 / 2; }
.page-about .about-review__figure figcaption {
  margin-top: 16px;
  color: var(--text-dim);
  font-size: 0.95rem;
}

@media (max-width: 780px) {
  .page-about .about-editorial,
  .page-about .about-review__photos { grid-template-columns: minmax(0, 1fr); }
  .page-about .about-origin .about-state { padding-block: 16px; }
}

/* No bespoke header clearance here: the shared main > .section:first-child
   rule near the top of this file now guarantees it for every page. */

/* South Dakota is the page's only .section-heading now that the Minnesota
   and closing blocks collapsed into the shared band (2026-08-28), and the
   store grid follows it, so it takes a tightened margin rather than the
   shared heading's full reserve. */
#south-dakota .section-heading {
  margin-bottom: clamp(24px, 2.6vw, 34px);
}

/* South Dakota was the one h2 still at full .section-title scale, which put
   it level with the "Where to Buy" h1 (Mark, 2026-08-28). Stepped down so
   the page title stays the largest thing on the page, while South Dakota
   stays clearly ahead of the closing band's heading below it. */
#south-dakota .section-title {
  font-size: clamp(2.1rem, 3.8vw, 4.4rem);
}

/* Touch: hover states never gate meaning, and the lift is pointer-only so a
   tap does not leave a card stuck raised. */
@media (hover: none) {
  .retailer-card:hover {
    transform: none;
  }

  .retailer-location {
    padding-block: 7px;
  }

  /* Touch needs the separation as much as the target: adjacent stores must
     not sit a mis-tap apart, so the desktop's tight 1px row gap opens up
     here even though the type size stays the same. */
  .retailer-card__locations {
    gap: 6px;
  }
}

.carry-band {
  overflow: hidden;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background:
    linear-gradient(90deg, rgba(46, 159, 245, 0.08), transparent 40%),
    var(--ink-850);
}

/* min-height sets the band's resting height; the padding is the FLOOR that
   takes over once copy outgrows it. Without it (the state before 2026-08-28)
   the 36-52px breathing room at every current width was a coincidence of
   short headings, and one longer title would have put text against the
   border. box-sizing is border-box globally, so 32px changes nothing at any
   width today - the tallest band content measures 140px against 144px of
   room - and only engages when a future heading needs it. */
.carry-band__inner {
  display: grid;
  grid-template-columns: 70px minmax(0, 1fr) auto;
  gap: clamp(20px, 4vw, 48px);
  align-items: center;
  min-height: 210px;
  padding-block: 32px;
}

.carry-band__mark {
  width: 56px;
  opacity: 0.88;
}

.carry-band h2 {
  margin-bottom: 6px;
  font-family: var(--font-display);
  font-size: clamp(1.98rem, 3.6vw, 3.96rem);
  font-weight: 500;
  letter-spacing: normal;
  line-height: 1.05;
}

.carry-band p {
  margin-bottom: 0;
  color: var(--text-dim);
}

/* Product page */

.product-hero {
  position: relative;
  isolation: isolate;
  min-height: min(900px, 92svh);
  overflow: hidden;
  padding-top: var(--header-height);
  background:
    radial-gradient(circle at 78% 48%, var(--page-glow, rgba(46, 159, 245, 0.17)), transparent 31rem),
    linear-gradient(120deg, #040507, #0a0e13);
}

.product-hero::before {
  position: absolute;
  z-index: -2;
  inset: 0;
  background:
    linear-gradient(90deg, rgba(var(--scrim-rgb), 0.95), rgba(var(--scrim-rgb), 0.62) 48%, transparent 72%),
    repeating-linear-gradient(
      115deg,
      transparent 0,
      transparent 56px,
      rgba(255, 255, 255, 0.012) 57px
    );
  content: "";
}

/* Ember, not blue. The Liquid Dabz hero art is a warm back-bar world, and the
   section's radial plus the ring glow sit directly behind it; the blue value
   used elsewhere fought the plate. Same literal already used by --cart, so
   this introduces no new colour. Blue remains the page's accent everywhere
   below the hero, where it reads as the Sativa family colour. */
.product-hero--liquid {
  --page-glow: rgba(239, 145, 60, 0.14);
}

.product-hero--resin {
  --page-glow: rgba(150, 84, 223, 0.19);
}

.product-hero--disposable {
  --page-glow: rgba(46, 159, 245, 0.14);
}

.product-hero--cart {
  --page-glow: rgba(239, 145, 60, 0.14);
}

.product-hero--vapes {
  --page-glow: rgba(46, 159, 245, 0.16);
}

.product-hero__inner {
  display: grid;
  grid-template-columns: minmax(350px, 0.82fr) minmax(480px, 1.18fr);
  gap: clamp(38px, 7vw, 110px);
  align-items: center;
  min-height: calc(min(900px, 92svh) - var(--header-height));
  padding-block: clamp(70px, 8vw, 124px);
}

.product-hero__copy {
  position: relative;
  z-index: 2;
}

.product-hero__copy .display {
  margin-bottom: 30px;
}

/* "Disposable vapes and 510 carts." needs 7.7em for its narrowest two-line
   split, but the hero copy column is only 0.82fr of the band (502px at 1440),
   so at the base 5.85vw this H1 wrapped to three lines from 981px up. Same
   shape as .find-fire__copy .section-title: give it an em measure (the 13ch
   base is font-metric dependent, so the face painted during font-display: swap
   can wrap differently), then size the type to the column. 10.5em clears the
   7.7em split and stays under the 14.1em one-line width. */
body.page--vapes .product-hero__copy .display {
  max-width: 10.5em;
}

/* Exact complement of the 980px hero collapse, so a fractional viewport cannot
   land between the two and get the two-column hero at the full-size headline. */
@media not all and (max-width: 980px) {
  body.page--vapes .product-hero__copy .display {
    font-size: clamp(2.7rem, 4.2vw, 4.05rem);
  }
}

/* Below ~410px the single column is narrower than the 2.7rem base floor needs
   for two lines. Inert above that: 10.5vw only drops under 2.7rem at 411px. */
@media (max-width: 480px) {
  body.page--vapes .product-hero__copy .display {
    font-size: clamp(1.9rem, 10.5vw, 2.7rem);
  }
}

/* "The concentrate category, in full color." is the longest H1 on the site. It
   needs a 9.8em measure for a two-line split and was rendering FOUR lines above
   980px and three below. Same treatment as the vapes H1. */
body.page--concentrates .product-hero__copy .display {
  max-width: 10.5em;
}

@media not all and (max-width: 980px) {
  body.page--concentrates .product-hero__copy .display {
    font-size: clamp(2rem, 3.3vw, 3.2rem);
  }
}

@media (max-width: 480px) {
  body.page--concentrates .product-hero__copy .display {
    font-size: clamp(1.5rem, 8.5vw, 2.7rem);
  }
}

/* "Bring ZMOKE to your market." needs 7.0em and was running to four lines at
   1920px, where the type keeps growing on vw after .container caps at 1420. */
/* "Liquid Dabz™" must hold ONE line in the side-by-side hero. The copy
   column tops out near 594px while the string needs ~6.53em of run, so the
   shared 6.66rem display cap breaks it ("Liquid" / orphaned "Dabz™") above
   ~1556px viewport. 5.6rem keeps 6.53em inside the column at every
   side-by-side width; below 980px the stacked column is wide enough at the
   shared ramp. */
@media not all and (max-width: 980px) {
  body.page--liquid-dabz .product-hero__copy .display {
    font-size: clamp(2.7rem, 5.85vw, 5.6rem);
  }
}

body.page--retailers .display {
  max-width: 10.5em;
}

@media not all and (max-width: 980px) {
  body.page--retailers .display {
    font-size: clamp(2.7rem, 5vw, 4.6rem);
  }
}

@media (max-width: 480px) {
  body.page--retailers .display {
    font-size: clamp(1.8rem, 11.5vw, 2.7rem);
  }
}

.product-hero__copy .lede {
  max-width: 570px;
  margin-bottom: 34px;
}

/* .meta-pill outlived the hero pill row it was built for (removed 2026-08-13,
   Mark). It still styles the vapes catalog band's status pill and the home
   page's retailer-count pill, so the class stays even though
   .product-hero__meta is gone. */

.meta-pill {
  display: inline-flex;
  min-height: 31px;
  align-items: center;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  color: var(--text-soft);
  font-size: 0.75rem;
  font-weight: 750;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

/* margin-inline: 0 is load-bearing. This element is a <figure>, and the reset
   above only clears figure's margin-top, so the UA default 40px side margins
   survived and inset the hero art by 40px on both edges at every width. That
   was invisible while the art was a contained packshot; it stops a bleeding
   hero from ever reaching the section edge. */
.product-hero__visual {
  position: relative;
  margin-inline: 0;
  min-height: clamp(520px, 49vw, 710px);
}

.product-hero__visual::before {
  position: absolute;
  top: 50%;
  left: 50%;
  width: min(46vw, 640px);
  aspect-ratio: 1;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 50%;
  box-shadow:
    0 0 0 72px rgba(255, 255, 255, 0.012),
    0 0 90px var(--page-glow, rgba(46, 159, 245, 0.12));
  content: "";
  transform: translate(-50%, -50%);
}

/* The halo and its ring were staging for a floating cut-out packshot: they gave
   a transparent PNG something to sit against. A full-bleed photographic plate
   carries its own lighting, so the ring just draws a hard circular edge across
   the artwork and the glow tints it. Heroes using a bleed plate drop both and
   let the mask gradient do the fade instead. */
.product-hero:has(.product-hero__image--bleed) {
  --page-glow: transparent;
}

.product-hero:has(.product-hero__image--bleed) .product-hero__visual::before {
  content: none;
}

.product-hero__image {
  position: absolute;
  z-index: 2;
  top: 50%;
  left: 50%;
  width: min(100%, 760px);
  max-height: 690px;
  object-fit: contain;
  filter: drop-shadow(0 34px 30px rgba(0, 0, 0, 0.68));
  transform: translate(-50%, -50%);
}

.product-hero__image--liquid {
  width: min(115%, 900px);
  transform: translate(-48%, -50%) rotate(-17deg);
}

.product-hero__image--resin {
  width: min(90%, 660px);
}

.product-hero__image--scene {
  width: 100%;
  height: 100%;
  max-height: 710px;
  border-radius: var(--radius-md);
  object-fit: contain;
  filter: drop-shadow(0 34px 50px rgba(0, 0, 0, 0.48));
}

/* Bleeding hero art.

   The trio render carries its own near-black ground, blue smoke and wet slate,
   which is already the site's key. Boxing it in a rounded, drop-shadowed panel
   fought that: the art read as a picture sitting ON the page rather than being
   the page. This fit lets it run past the container gutter to the section edge
   and dissolves its inner edges, so the render's own smoke resolves into the
   background.

   object-fit: cover is load-bearing. With contain, the painted image floats
   inside the element box while the mask anchors to the box, which is exactly
   the hard vertical seam the home hero hit at ~2000px. cover makes painted
   area and element box the same rectangle, so the mask cannot drift off the
   art.

   Stacked layout (<=980px) puts copy above the art, so the base mask softens
   the top edge. The side-by-side rule fades the inner (left) edge instead and
   follows the base rule deliberately: equal specificity, source order wins. */

/* Art-directed heroes wrap the image in <picture>. That wrapper is an inline
   box in normal flow, and it collapsed the visual column from 350px to 270px on
   a phone once it existed. display: contents removes the wrapper box entirely,
   so the absolutely-positioned image keeps resolving against
   .product-hero__visual exactly as it did before the <picture> was added. */
.product-hero__visual picture {
  display: contents;
}

.product-hero__image--bleed {
  width: calc(100% + var(--gutter));
  height: 100%;
  max-height: none;
  border-radius: 0;
  filter: none;
  object-fit: cover;
  /* Stacked layout: the portrait plate is taller than its box, so cover trims
     vertically. Anchored below centre to keep the nozzles, drops and the slate
     reflection, trading away empty smoke at the top that the mask fades anyway. */
  object-position: 50% 58%;
  transform: translate(calc(-50% + var(--gutter) / 2), -50%);
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 16%, #000 88%, transparent 100%);
  mask-image: linear-gradient(180deg, transparent 0, #000 16%, #000 88%, transparent 100%);
}

/* Concentrates, phone band only (the 4:5 mobile plate is served <=600px). The
   900x1125 plate's subject (tin pyramid + slate) starts ~39% down, so a
   centered cover crop in a fixed-height box banks ~150px of empty smoke
   between the hero buttons and the tins. Size the box to the subject instead:
   subject height is ~61% of the plate = ~76% of the box width once cover is
   width-bound, plus headroom for the top fade. Anchor the crop to the plate's
   bottom so all trimming happens in the smoke, none in the slate. Numbers are
   this plate's geometry; rederive if the art is regenerated. */
@media (max-width: 600px) {
  body.page--concentrates .product-hero__inner {
    /* The copy-to-art seam and the section's tail carry the phone rhythm
       here; the stock 38px gap + 70px bottom pad read as dead ink around a
       plate that already fades its own edges. */
    gap: 14px;
    padding-bottom: 26px;
  }

  body.page--concentrates .product-hero__visual {
    /* Subject-height sizing: the tins display at ~76vw tall once cover is
       width-bound, plus ~25px of smoke headroom for the entry fade and ~19px
       of slate margin. Bigger constants reintroduce the dead band above the
       tins that this override exists to remove. */
    min-height: calc(76vw + 44px);
    margin-bottom: 0;
  }

  body.page--concentrates .product-hero__image--bleed {
    object-position: 50% 100%;
    /* Short fixed fades: 24px in at the top (the visible plate starts just
       above the lids now, so a proportional fade would eat them), 4% out at
       the bottom where the dark slate blends on its own. */
    -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 24px, #000 96%, transparent 100%);
    mask-image: linear-gradient(180deg, transparent 0, #000 24px, #000 96%, transparent 100%);
  }
}

@media (min-width: 981px) {
  /* The square plate matches the side-by-side box almost exactly (~1.02), so
     it needs no anchoring bias here. */
  .product-hero__image--bleed {
    object-position: 50% 50%;
    -webkit-mask-image:
      linear-gradient(90deg, transparent 0, #000 30%),
      linear-gradient(180deg, transparent 0, #000 13%, #000 87%, transparent 100%);
    mask-image:
      linear-gradient(90deg, transparent 0, #000 30%),
      linear-gradient(180deg, transparent 0, #000 13%, #000 87%, transparent 100%);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
  }
}

.product-hero__caption {
  position: absolute;
  z-index: 3;
  right: 0;
  bottom: 8px;
  max-width: 260px;
  padding: 8px 10px;
  border-radius: 7px;
  background: rgba(var(--scrim-rgb), 0.7);
  color: var(--text-soft);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  backdrop-filter: blur(8px);
}

.vape-hero-pair {
  position: absolute;
  z-index: 2;
  inset: clamp(40px, 7vw, 80px) 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 15px;
}

.vape-hero-pair__panel {
  /* Flex column, not grid rows: iOS Safari fails to resolve a percentage
     image height against a minmax(0, 1fr) grid track and falls back to the
     aspect-ratio height from the width/height attributes, overflowing the
     packshot over the caption. Flex sizing sets the box height directly. */
  display: flex;
  overflow: hidden;
  min-width: 0;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  /* Cream cards with floating packshots read as a spec sheet next to the rest
     of the site, which stages everything on dark slate. The plates now carry
     their own vignette, so the panel only has to get out of their way: an
     ink ground behind them and no padding pushing them off it. */
  background: var(--ink-850);
  color: var(--text);
  text-decoration: none;
}

.vape-hero-pair__panel:hover,
.vape-hero-pair__panel:focus-visible {
  border-color: rgba(46, 159, 245, 0.62);
}

.vape-hero-pair__panel img {
  width: 100%;
  height: 0;
  min-height: 0;
  flex: 1 1 0;
  /* cover, not contain: these are staged scenes that must bleed to the panel
     edges. contain would letterbox them and reintroduce the card look. */
  object-fit: cover;
  object-position: 50% 42%;
}

.vape-hero-pair__panel span {
  padding: 16px 18px;
  border-top: 1px solid var(--line);
  background: var(--ink-900);
  font-size: 0.75rem;
  font-weight: 850;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

/* The pillars band: the brand proof points loop as a slow one-line ticker,
   flame glyph between phrases. The marquee became the baseline treatment
   2026-08-24 (Mark's call after the static-vs-marquee bake-off; the static
   grid's rules were removed with it and live in git history). Two identical
   track copies each slide one full copy width, so the loop is seamless;
   transform and opacity only. ~28px/s reads ambient rather than urgent.
   Under reduced motion the track holds still and the edge mask still fades
   the overflow, which is a dignified static band, not a broken one. */
.feature-strip {
  position: relative;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: var(--ink-850);
}

/* Fluid band (Mark, 2026-08-24): every internal dimension scales with the
   viewport so the band breathes with the rest of the page instead of sitting
   at one fixed size. The ticker's type clamp drives it: the glyphs are
   em-based, so one value scales the lot. */
.feature-strip__viewport {
  display: flex;
  overflow: hidden;
  padding-block: clamp(16px, 1.5vw, 26px);
  mask-image: linear-gradient(90deg, transparent 0, #000 clamp(44px, 6vw, 96px), #000 calc(100% - clamp(44px, 6vw, 96px)), transparent 100%);
}

.feature-strip__ticker {
  display: flex;
  flex: none;
  gap: clamp(34px, 4vw, 64px);
  align-items: center;
  padding-right: clamp(34px, 4vw, 64px);
  /* Fluid phrase size; the rem term keeps browser zoom working. Roughly
     0.93rem at 390px, the old 1.08rem near 780px, capped at 1.26rem from
     ~1240px up. */
  font-size: clamp(0.92rem, 0.78rem + 0.62vw, 1.26rem);
  /* One copy holds 3 repeats of the 7-phrase sequence; its pixel width now
     scales with the font clamp (~4.9k px at phone sizes, ~6.6k px once the
     clamp caps), so the duration steps below hold the crawl near ~28px/s at
     every size instead of letting narrow screens feel faster. Rescale all
     three durations whenever the phrase list or repeat count changes (the
     repeat count is also color-cycle math, see feature-list.php). */
  animation: none;
}

.feature-strip.is-ready .feature-strip__ticker {
  animation: strip-marquee 205s linear infinite;
}

.feature-strip.is-paused .feature-strip__ticker {
  animation-play-state: paused;
}

.feature-strip__pause {
  position: absolute;
  top: 50%;
  right: 8px;
  min-width: 76px;
  min-height: 44px;
  padding: 8px 12px;
  transform: translateY(-50%);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--ink-850);
  color: var(--text);
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
}

@media (max-width: 780px) {
  .feature-strip.is-ready .feature-strip__ticker {
    animation-duration: 175s;
  }
}

@media (min-width: 1500px) {
  .feature-strip.is-ready .feature-strip__ticker {
    animation-duration: 235s;
  }
}

@keyframes strip-marquee {
  to {
    transform: translateX(-100%);
  }
}

.feature-strip__phrase {
  white-space: nowrap;
  font-weight: 700;
}

.feature-strip__glyph {
  width: auto;
  /* Rides the ticker's font clamp: ~22px at phone sizes up to ~33px at the
     cap (was a fixed 28px). */
  height: 1.62em;
  opacity: 0.5;
}

/* Family-colored flames (?flames=color) need more presence than the white
   ones for blue/green/purple to actually read as the classification colors,
   while staying a step behind the phrase text. */
.feature-strip__glyph--family {
  opacity: 0.85;
}

@media (prefers-reduced-motion: reduce) {
  .feature-strip.is-ready .feature-strip__ticker {
    animation: none;
  }

  .feature-strip__pause {
    display: none;
  }
}

.format-story {
  display: grid;
  grid-template-columns: minmax(410px, 1.1fr) minmax(340px, 0.9fr);
  gap: clamp(48px, 8vw, 120px);
  align-items: center;
}

.format-story--reverse .format-story__media {
  order: 2;
}

.format-story__media {
  position: relative;
  min-height: 650px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background:
    radial-gradient(circle at 50% 48%, rgba(46, 159, 245, 0.13), transparent 20rem),
    var(--ink-850);
}

.format-story__media img {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 90%;
  height: 88%;
  object-fit: contain;
  filter: drop-shadow(0 30px 30px rgba(0, 0, 0, 0.6));
  transform: translate(-50%, -50%);
}

.format-story__media--cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Panels that declare the ratio their art was shot at.

   The base rule pins a min-height while the panel's width follows the grid, so
   the box ratio swings ~0.56-1.15 across breakpoints and cover crops the
   difference: measured at 37% of the frame at 360px and 34% at 981px, which is
   enough to cut the product out of the shot. Supplying an aspect via the
   component's media.aspect leaves cover nothing to crop.

   Double-class for specificity, not source order: the min-height is re-declared
   inside two max-width blocks further down the file, and an equal-specificity
   rule here would lose to both. */
.format-story__media.format-story__media--ratio {
  aspect-ratio: var(--story-aspect);
  min-height: 0;
}

.format-story__media--paper {
  background: var(--paper);
}

.format-story__copy .section-title {
  margin-bottom: 28px;
}

.format-story__small {
  margin-top: 14px;
}

.format-story__copy .section-copy {
  margin-bottom: 34px;
}

.detail-list {
  display: grid;
  margin: 0;
  border-top: 1px solid var(--line);
}

.detail-list > div {
  display: grid;
  grid-template-columns: 130px 1fr;
  gap: 20px;
  padding-block: 21px;
  border-bottom: 1px solid var(--line);
}

.detail-list dt {
  color: var(--text-dim);
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.detail-list dd {
  color: var(--text-soft);
}

/* Numbered step band. Unlike .feature-strip (visually hidden h2, sits directly
   under a hero, full-bleed) this pairs a visible section-heading with an
   ordered list, which is the right semantics for a sequence. Type values are
   deliberately identical to .feature-strip so the two read as one family. */
.process-steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}

.process-steps__item {
  padding: 30px 26px 0;
  border-top: 1px solid var(--line);
}

.process-steps__item + .process-steps__item {
  border-left: 1px solid var(--line);
}

.process-steps__item:first-child {
  padding-left: 0;
}

.process-steps__item:last-child {
  padding-right: 0;
}

/* The 01-04 __index numbers were removed 2026-08-24 (owner), matching the
   feature strip. */

.process-steps__item h3 {
  margin-bottom: 6px;
  font-size: 1.08rem;
  font-weight: 700;
}

.process-steps__item p {
  margin-bottom: 0;
  color: var(--text-dim);
  font-size: 0.86rem;
}

.variant-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 15px;
}

/* Qualifying line under a family grid whose cards name an effect (Mark,
   2026-08-31). Centred to match the HYH heading, and held to a readable
   measure so it stays one or two lines rather than spanning the full
   1420px container. Quiet, but not so quiet it reads as decorative: this
   is the sentence that qualifies the descriptors above it. */
.variant-grid__footnote {
  /* 92ch (~781px) clears the 749px the sentence needs on one line, so it
     stays a single quiet line wherever the container allows it -- roughly
     981px up. Below that the container is narrower than the sentence and it
     wraps on its own, which is the right behaviour on a phone. */
  max-width: 92ch;
  margin: clamp(18px, 2vw, 26px) auto 0;
  color: var(--text-dim);
  font-size: 0.84rem;
  text-align: center;
  text-wrap: balance;
}

.variant-card {
  position: relative;
  overflow: hidden;
  min-height: 590px;
  border-radius: var(--radius-md);
  background: var(--paper);
  color: #17191c;
}

.variant-card__badge {
  position: absolute;
  z-index: 2;
  top: 22px;
  left: 22px;
  display: inline-flex;
  min-height: 31px;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  border: 1px solid rgba(20, 22, 25, 0.13);
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.78);
  font-size: 0.75rem;
  font-weight: 850;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  backdrop-filter: blur(8px);
}

.variant-card__badge::before {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--variant-color);
  box-shadow: 0 0 10px var(--variant-color);
  content: "";
}

.variant-card--blue {
  --variant-color: var(--blue);
}

.variant-card--green {
  --variant-color: var(--green);
}

.variant-card--purple {
  --variant-color: var(--purple);
}

.variant-card__media {
  height: 470px;
  padding: 38px 22px 0;
}

.variant-card__media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.variant-card__media--pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0;
  padding-inline: 10px;
}

.variant-card__media--pair img {
  min-width: 0;
}

/* Photographic variant card: the artwork is a full staged scene with its own
   dark ground, not a cut-out on the card's paper. It fills the media box edge
   to edge; the card body keeps the paper strip beneath it. Must stay after the
   base .variant-card__media img rule, which it overrides at equal specificity. */
.variant-card__media--photo {
  padding: 0;
}

.variant-card__media--photo img {
  object-fit: cover;
}

/* Night variant: the Highlight Your High cards (homepage, 2026-08-24, built
   to Mark's reference). Photographic vignette on top, and the family color
   rising as a glow through the name band at the card's base (it lived behind
   the transparent packshots first; Mark moved it under the photos when the
   vignettes replaced them). Badge and code are omitted by their templates,
   so the card reads media + name + color caption only. */
.variant-card--night {
  background:
    radial-gradient(120% 74% at 50% 112%, color-mix(in srgb, var(--variant-color) 36%, transparent), transparent 72%)
    var(--ink-800);
  color: var(--text);
}

.variant-card--night .variant-card__body {
  padding-top: 0;
  border-top: 0;
}

.variant-card--night .variant-card__body h3 {
  margin-bottom: 2px;
  font-family: var(--font-display);
  /* Floor shares the documented 1.8rem authored-title phone step; the cap
     matches the section-title clamp base. */
  font-size: clamp(1.8rem, 2.2vw, 2.3rem);
  font-weight: 500;
}

.variant-card--night .variant-card__body p {
  color: var(--text-soft);
}

.variant-card__body {
  position: absolute;
  right: 24px;
  bottom: 22px;
  left: 24px;
  display: flex;
  justify-content: space-between;
  gap: 20px;
  align-items: end;
  padding-top: 18px;
  border-top: 1px solid rgba(20, 22, 25, 0.13);
}

.variant-card__body h3 {
  margin-bottom: 3px;
  font-size: 1.08rem;
}

.variant-card__body p {
  margin-bottom: 0;
  /* Darkened from #777b81 (3.74:1, AA fail at 12px on the cream card) to hold
     comfortably above 4.5:1, matching the __code treatment below. */
  color: #565f6b;
  font-size: 0.86rem;
}

.variant-card__code {
  /* Family hue darkened toward card ink so 12px text holds AA on the cream card */
  color: color-mix(in srgb, var(--variant-color) 50%, #17191c);
  font-size: 0.75rem;
  font-weight: 900;
  letter-spacing: 0.1em;
}

/* More from ZMOKE (image-led rework, owner brief 2026-09-03). Three equal
   image-first tiles on the section ground: a bordered 16:9 frame, the serif
   title, a two-line description. No shared outline, no dividers, no card
   body, no arrow (site-wide rule: the arrow marks a link that LEAVES
   zmoke.com). The band ran at utility padding (36-56px) while it was a
   thumbnail strip; image-led tiles carry enough weight for a section rhythm
   again, held a shade under the format-story bands above so the page still
   tapers toward the CTA band and footer.

   :not(:first-child) is load-bearing (2026-08-28). /learn/ once opened with
   a section that also held a .cross-sell, and the unscoped rule gave the
   Learn PAGE TITLE utility padding, which parked "Learn." behind the fixed
   header. Keep this to bands that follow something. */
.section:not(:first-child):has(> .container > .cross-sell) {
  padding-block: clamp(64px, 7vw, 108px);
}

.section:not(:first-child):has(> .container > .cross-sell) .section-heading {
  margin-bottom: clamp(24px, 2.4vw, 32px);
}

/* Mobile-first: one column, two across from 781px (the third tile starts a
   second row at its own width), three from 1081px. The thumbnail strip's
   --two and --four modifiers retired with it; nothing called them. */
.cross-sell {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 36px 24px;
}

@media (min-width: 781px) {
  .cross-sell {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1081px) {
  .cross-sell {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* The 404 page includes all four categories. Keep balanced pairs until
   there is enough room for four readable image cards in one row. */
@media (min-width: 781px) {
  .cross-sell--all-products {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1440px) {
  .cross-sell--all-products {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.cross-sell__tile {
  display: block;
  min-width: 0;
  color: var(--text);
  text-decoration: none;
  cursor: pointer;
}

/* The frame is the only bordered element. --cross-sell-position comes from
   product-card.php, which sets it inline from the catalog's per-image crop. */
.cross-sell__frame {
  display: block;
  overflow: hidden;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--ink-900);
  transition: border-color 180ms ease;
}

.cross-sell__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--cross-sell-position, 50% 50%);
  transition: transform 280ms ease;
}

/* Interim for a portrait asset (510 Carts, until a landscape shot exists):
   the whole product stays visible, and ink-900 is the closest flat match to
   the vignette's studio black, so the letterbox reads as frame ground. */
.cross-sell__frame--contain .cross-sell__image {
  object-fit: contain;
}

.cross-sell__tile:hover .cross-sell__frame,
.cross-sell__tile:focus-visible .cross-sell__frame {
  border-color: color-mix(in srgb, var(--ember-light) 68%, var(--line));
}

.cross-sell__tile:hover .cross-sell__image,
.cross-sell__tile:focus-visible .cross-sell__image {
  transform: scale(1.02);
}

/* 28-32px across the desktop band (1rem + 1.1vw: 30px at 1280, 32px from
   ~1455), 24px on phones. */
.cross-sell__title {
  margin: clamp(20px, 1.6vw, 24px) 0 0;
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 1rem + 1.1vw, 2rem);
  font-weight: 500;
  letter-spacing: normal;
  line-height: 1.12;
}

.cross-sell__text {
  margin: 10px 0 0;
  color: var(--text-soft);
  font-size: 0.96rem;
  line-height: 1.5;
}

@media (prefers-reduced-motion: reduce) {
  .cross-sell__frame,
  .cross-sell__image {
    transition: none;
  }

  .cross-sell__tile:hover .cross-sell__image,
  .cross-sell__tile:focus-visible .cross-sell__image {
    transform: none;
  }
}

/* Carry form */

.carry-hero {
  position: relative;
  overflow: hidden;
  padding: calc(var(--header-height) + clamp(84px, 9vw, 144px)) 0
    clamp(78px, 8vw, 124px);
  background:
    radial-gradient(circle at 82% 22%, rgba(239, 145, 60, 0.2), transparent 28rem),
    radial-gradient(circle at 63% 54%, rgba(46, 159, 245, 0.16), transparent 38rem),
    var(--ink-950);
}

.carry-hero::after {
  position: absolute;
  right: -4vw;
  bottom: -24vw;
  width: 62vw;
  height: 62vw;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 50%;
  box-shadow:
    0 0 0 90px rgba(255, 255, 255, 0.012),
    0 0 0 180px rgba(255, 255, 255, 0.008);
  content: "";
}

.carry-hero__inner {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(50px, 9vw, 140px);
  align-items: end;
}

.carry-hero__copy {
  max-width: 760px;
}

.carry-hero__copy .display {
  margin-bottom: 30px;
}

.carry-hero__copy .lede {
  margin-bottom: 34px;
}

.carry-hero__facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.carry-fact {
  min-height: 170px;
  padding: 22px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: rgba(14, 18, 23, 0.65);
  backdrop-filter: blur(10px);
}

.carry-fact span {
  display: block;
  margin-bottom: 58px;
  color: var(--ember-light);
  font-size: 0.75rem;
  font-weight: 850;
  letter-spacing: 0.11em;
}

.carry-fact p {
  margin-bottom: 0;
  color: var(--text-soft);
  font-size: 0.86rem;
}

.inquiry-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(28px, 4vw, 48px);
  align-items: start;
}

.inquiry-sidebar {
  position: static;
  max-width: 760px;
}

.inquiry-sidebar .section-title {
  margin-bottom: 25px;
}

/* Three long section headings that the 15ch base measure pins at three or more
   lines at every size, because ch scales with the type. Widening the measure is
   enough wherever the surrounding column has the room. Measured in em so the
   font-display: swap fallback cannot change the wrap. */
#conversation-title,
#resin-detail-title,
#inquiry-title {
  max-width: 12em;
}

/* Retain the established desktop heading size in the stacked layout. */
@media not all and (max-width: 980px) {
  #inquiry-title {
    font-size: clamp(1.5rem, 2.4vw, 2.3rem);
  }
}

.inquiry-sidebar .section-copy {
  margin-bottom: 0;
}

.inquiry-form {
  display: grid;
  gap: 22px;
  padding: clamp(26px, 4vw, 58px);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background:
    linear-gradient(140deg, rgba(255, 255, 255, 0.025), transparent 38%),
    var(--ink-850);
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: 20px;
}

/* An error belongs below its own control; it must not push a neighboring
   field's input down by stretching that field's label row. */
.form-field {
  display: grid;
  grid-template-rows: 1fr auto;
  gap: 8px;
}

.form-field--full {
  grid-column: 1 / -1;
}

.form-field label,
.form-legend {
  color: var(--text-soft);
  font-size: 0.86rem;
  font-weight: 750;
  letter-spacing: 0.03em;
}

.form-field .required {
  color: var(--ember-light);
}

/* The footer signup input is deliberately NOT in this group any more
   (2026-08-29): its border, radius and background moved onto the form, which
   is now the field. See .site-footer__signup-form. */
.form-field input,
.form-field select,
.form-field textarea {
  width: 100%;
  min-height: 50px;
  padding: 11px 14px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 8px;
  background: rgba(var(--scrim-rgb), 0.74);
  color: var(--text);
  transition:
    border-color 150ms ease,
    box-shadow 150ms ease;
}

.form-field textarea {
  min-height: 145px;
  resize: vertical;
}

.form-field input:hover,
.form-field select:hover,
.form-field textarea:hover {
  border-color: rgba(255, 255, 255, 0.34);
}

.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(46, 159, 245, 0.14);
  outline: 0;
}

/* Bare control inside the form-as-field: no chrome, and no focus ring of its
   own. The wrapper's :focus-within carries the indicator, so suppressing the
   global focus-visible outline here prevents a second ring drawn inside the
   field. Removing this line does not lose the indicator, it doubles it. */
.site-footer__signup-form input {
  width: 100%;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text);
}

.site-footer__signup-form input:focus,
.site-footer__signup-form input:focus-visible {
  box-shadow: none;
  outline: 0;
}

.form-field input::placeholder,
.form-field textarea::placeholder,
.site-footer__signup-form input::placeholder {
  color: #646b74;
}

.form-field select {
  color-scheme: dark;
}

.checkbox-group {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  padding: 0;
  border: 0;
}

.checkbox {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  min-height: 48px;
  padding: 13px;
  border: 1px solid rgba(255, 255, 255, 0.13);
  border-radius: 8px;
  background: rgba(var(--scrim-rgb), 0.45);
  color: var(--text-soft);
  cursor: pointer;
  font-size: 0.86rem;
}

.checkbox input {
  width: 17px;
  height: 17px;
  margin: 1px 0 0;
  accent-color: var(--ember);
}

.checkbox--confirmation {
  grid-column: 1 / -1;
  min-height: auto;
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
  align-items: center;
  padding-top: 8px;
}

.form-status {
  min-height: 1.4em;
  margin: 0;
  color: var(--success);
  font-size: 0.86rem;
}

/* The status line is the form's only voice, so a rejection has to read as one
   rather than as a green confirmation in different words. Server-rendered on
   the no-JS path, toggled by site.js on the enhanced path. */
.form-status--error {
  color: var(--danger);
}

.form-error {
  margin: 0;
  color: var(--danger);
  font-size: 0.86rem;
  line-height: 1.5;
}

.form-field:has(.form-error:not([hidden])) {
  grid-template-rows: 1fr auto auto;
}

.checkbox-group > .form-error {
  grid-column: 1 / -1;
}

.checkbox:has(input[aria-invalid="true"]) {
  border-color: var(--danger);
}

/* Server-rejected fields. The status message names what to fix; this points at
   it, in the same idiom as the focus ring so it reads as part of the field. */
.form-field input[aria-invalid="true"],
.form-field textarea[aria-invalid="true"] {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px rgba(255, 117, 109, 0.14);
}


/* Dial-A-Dose calculator (2026-08-25, owner-confirmed 25 mg/line at the
   average COA; see the numbers-basis note in dose-dial.php). */
/* One component (Mark's spec, 2026-09-02, third pass): heading and intro,
   device and controls, readout, reference and disclaimer on ONE surface
   with one border and one radius, the four regions set apart by spacing
   and two thin dividers (above the readout, above the footer). The old
   inner card is gone; this is the only box. It keeps the one-tool stack's
   760px cap (2026-08-28, widened 2026-09-02 for the render) and is centred
   in the section's container, so the page padding matches the sections
   around it. */
.dose__calculator {
  display: grid;
  gap: 26px;
  max-width: 760px;
  min-width: 0;
  margin: 0 auto;
  padding: clamp(22px, 3vw, 32px);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--ink-850);
}

/* The header keeps the section-heading component's type; its outer
   margin goes, because the component's grid gap now sets the rhythm. */
.dose__header > .section-heading {
  max-width: none;
  margin-bottom: 0;
}

.dose__interactive {
  display: grid;
  gap: 26px;
  min-width: 0;
}

/* THE DEVICE, tied to dose-device-body-sativa-v2.webp (1523x222) and
   dose-device-plunger-sativa-v2.webp (77x222): both cut from Mark's
   transparent Firefly master (review-assets; crop 4610x640+150+1560, scaled
   to 1600 wide, split at x=77 where the white plunger meets the collar).
   Lying as rendered, base to the left, so the wordmark reads. The body is
   lossy but encoded through the BGRA path, which keeps alpha exact: the
   YUVA path leaves the transparent field at alpha 1, a faint box on the card. The plunger sits BEHIND the body with
   its right edge on the body's left edge: translateX(100%) puts it entirely
   behind the opaque black base, translateX(0) shows it fully out as
   rendered, and --extend (dialed lines over the dial length, set by
   site.js) interpolates. The figure keeps the plunger's full travel free on
   its left, so the body is right-aligned and nothing shifts. Re-cut both
   if the art changes. */
.dose__device {
  margin: 0;
}

.dose__device-body {
  position: relative;
  width: calc(100% * 1523 / 1600);
  margin-left: auto;
}

.dose__device-img {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: auto;
}

.dose__plunger {
  position: absolute;
  top: 0;
  right: 100%;
  z-index: 0;
  width: calc(100% * 77 / 1523);
  height: 100%;
  transform: translateX(calc(100% * (1 - var(--extend, 0))));
  transition: transform 150ms ease;
  pointer-events: none;
}

.dose__plunger-img {
  display: block;
  width: 100%;
  height: 100%;
}

@media (prefers-reduced-motion: reduce) {
  .dose__plunger {
    transition: none;
  }
}

/* The two sliders, stacked at every viewport (Mark, 2026-09-02): one
   column reads top to bottom as lines, potency, answer, and the sliders
   keep the card's full width. */
.dose__controls {
  display: grid;
  gap: 24px;
}

/* One control: label and live value on the first row, the slider across
   the second, its scale under that. --thumb and --track are shared by the
   slider's fill and the scale, which both place things along the thumb's
   travel (half a thumb in from either end). */
.dose__control {
  --thumb: 24px;
  --track: 6px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px 16px;
  align-items: baseline;
}

.dose__control label {
  color: var(--text-soft);
  font-size: 0.86rem;
  font-weight: 600;
}

.dose__control output {
  font-size: 1.1rem;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  text-align: right;
  white-space: nowrap;
}

.dose__range,
.dose__scale,
.dose__helper {
  grid-column: 1 / -1;
}

/* The slider: a native range, restyled. --ratio (the thumb's position
   along the track, 0 to 1, inline from PHP for the first paint and kept by
   site.js) paints the ember fill up to the thumb's centre in WebKit and
   Blink; Firefox has ::-moz-range-progress for the same thing. */
.dose__range {
  --ratio: 0;
  width: 100%;
  height: var(--thumb);
  margin: 0;
  background: transparent;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

.dose__range::-webkit-slider-runnable-track {
  height: var(--track);
  border-radius: 999px;
  background: linear-gradient(
    to right,
    var(--ember) calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--ratio)),
    rgba(255, 255, 255, 0.14) 0
  );
}

.dose__range::-webkit-slider-thumb {
  width: var(--thumb);
  height: var(--thumb);
  margin-top: calc((var(--track) - var(--thumb)) / 2);
  border: 3px solid var(--ink-850);
  border-radius: 50%;
  background: var(--ember);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.22), 0 2px 8px rgba(0, 0, 0, 0.55);
  -webkit-appearance: none;
  appearance: none;
}

.dose__range::-moz-range-track {
  height: var(--track);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.14);
}

.dose__range::-moz-range-progress {
  height: var(--track);
  border-radius: 999px;
  background: var(--ember);
}

.dose__range::-moz-range-thumb {
  width: calc(var(--thumb) - 6px);
  height: calc(var(--thumb) - 6px);
  border: 3px solid var(--ink-850);
  border-radius: 50%;
  background: var(--ember);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.22), 0 2px 8px rgba(0, 0, 0, 0.55);
}


/* The scale under a slider: each mark sits at --at (0 to 1) along the
   thumb's travel, the same geometry as the fill above it. Decorative; the
   output and aria-valuetext carry the value. */
.dose__scale {
  position: relative;
  height: 1em;
  margin: -6px 0 0;
  padding: 0;
  color: var(--text-dim);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  letter-spacing: 0.04em;
  list-style: none;
}

.dose__scale li {
  position: absolute;
  top: 0;
  left: calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--at, 0));
  transform: translateX(-50%);
  line-height: 1;
}

.dose__helper {
  margin: 0;
}

/* The potency value doubles as the way in to typed entry (Mark's spec,
   2026-09-02): a button showing the value, which site.js swaps for a
   compact number field in the same cell of the control grid. Both sit at
   the same height so the row does not move. The dotted underline and the
   pencil say "editable" to sighted users; the button's hidden ", edit" and
   its described-by hint say it to everyone else. */
.dose__value {
  display: flex;
  justify-content: flex-end;
  min-height: 32px;
}

.dose__value-button {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  margin: 0;
  padding: 2px 6px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 1.1rem;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  text-decoration: underline dotted rgba(255, 255, 255, 0.4);
  text-underline-offset: 4px;
  cursor: pointer;
}

.dose__value-button:hover {
  background: rgba(255, 255, 255, 0.08);
  text-decoration-color: var(--ember);
}

.dose__value-icon {
  flex: none;
  color: var(--text-dim);
}

.dose__value-button:hover .dose__value-icon {
  color: var(--ember);
}

.dose__value-input {
  width: 7ch;
  min-height: 32px;
  padding: 2px 8px;
  border: 1px solid rgba(255, 255, 255, 0.34);
  border-radius: 6px;
  background: rgba(var(--scrim-rgb), 0.74);
  color: var(--text);
  font: inherit;
  font-size: 1.1rem;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  text-align: right;
  -moz-appearance: textfield;
  appearance: textfield;
}

.dose__value-input::-webkit-outer-spin-button,
.dose__value-input::-webkit-inner-spin-button {
  margin: 0;
  -webkit-appearance: none;
  appearance: none;
}

.dose__value-input:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(46, 159, 245, 0.14);
  outline: 0;
}

.dose__value-button[hidden],
.dose__value-input[hidden] {
  display: none;
}

/* The readout (Mark's mockup, 2026-09-03): one framed panel inside the
   card with the estimated Total THC centred in it, value above its term. A
   description list keeps term then value in the DOM (column-reverse puts
   the value first visually). The dividers above the result and the footer
   went with the frame: the panel does the separating now. The auto-fit grid
   stays so a second cell could return without a relayout. */
.dose__result {
  display: grid;
  gap: 14px;
  margin: 0;
}

.dose__readout {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 170px), 1fr));
  gap: 14px 24px;
  margin: 0;
  padding: clamp(24px, 3vw, 30px) 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--ink-800);
}

.dose__readout-item {
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: 8px;
  min-width: 0;
  margin: 0;
  text-align: center;
}

.dose__readout-label {
  color: var(--text-dim);
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.dose__readout-value {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(2.2rem, 4vw, 3.4rem);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  line-height: 1.05;
  overflow-wrap: anywhere;
}

.dose__readout-item--thc .dose__readout-value {
  color: var(--ember);
}

/* The no-JS pointer line, at the top of the interactive region, shows
   only under html.no-js. */
.dose__nojs {
  display: none;
  margin: 0;
}

.no-js .dose__nojs {
  display: block;
}

/* Footer (Mark's spec, 2026-09-02): the reference accordion and the one
   supporting note, under the component's second divider. The summary is
   the only control and carries no border of its own, so that divider is
   the one line; the details grows with its content, nothing clips it.
   The reference is the no-JS and print fallback: the print rules reveal
   the content where ::details-content is supported, and site.js opens it
   on beforeprint. Grid items default to min-width:auto, which lets the
   seven-column table widen the track and the page instead of scrolling;
   min-width:0 down the chain is what makes overflow-x:auto engage (found
   at 390px: 99px of page overflow). */
.dose__footer {
  display: grid;
  gap: 16px;
  min-width: 0;
  padding-top: 4px;
}

.dose__reference {
  min-width: 0;
}

.dose__reference summary {
  padding: 4px 0;
  cursor: pointer;
  font-size: 0.94rem;
  font-weight: 600;
}

.dose__formula {
  margin: 14px 0 0;
}

.dose__table-wrap {
  min-width: 0;
  max-width: 100%;
  margin-top: 12px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.dose__table caption {
  padding-bottom: 12px;
  color: var(--text-soft);
  text-align: left;
  font-size: 0.875rem;
}

.dose__table-hint {
  margin: 12px 0 0;
}

.dose__table-hint[hidden] {
  display: none;
}

@media print {
  .dose__table-hint {
    display: none;
  }

  .dose__reference::details-content {
    content-visibility: visible;
  }

  .dose__reference summary {
    list-style: none;
  }
}

.dose__table {
  width: 100%;
  margin-top: 4px;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.dose__table th {
  color: var(--text-dim);
  white-space: nowrap;
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

.dose__table th,
.dose__table td {
  padding: 12px 8px;
  border-bottom: 1px solid var(--line);
  font-size: 0.94rem;
  text-align: left;
}

/* The one supporting note, squared to the component (Mark, 2026-09-01:
   alignment with the thing it qualifies matters more than measure; the
   microcopy 44em cap is lifted so it runs the component's width). */
.dose__disclaimer {
  max-width: none;
  margin: 0;
}

/* How-to steps (usage-steps component, 2026-08-27): image-topped numbered
   cards in an ordered list. Single column on phones, 2x2 through the mid
   band, four across from the 1081px desktop step; the queries reuse the
   documented 561-1080 range and 1081 step, no new breakpoints. Numbering is
   a CSS counter so the ol stays the one source of order. */
.how-to__steps {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(22px, 3vw, 34px);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: how-to-step;
}

.how-to__step {
  counter-increment: how-to-step;
}

/* Image-led round (Mark, 2026-08-28): steps went 1:1 to the proof-card 4:5
   portrait so the photography dominates and both grids crop consistently. */
.how-to__media img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-md);
  object-fit: cover;
}

/* Number + action as one heading ("1 Open"): the numeral comes from the
   list counter, so the ol stays the single source of order. */
.how-to__step-title {
  margin: 14px 0 0;
  font-family: var(--font-display);
  font-size: 1.44rem;
  font-weight: 500;
  line-height: 1.15;
}

.how-to__step-title::before {
  content: counter(how-to-step) " ";
}

.how-to__text {
  margin: 6px 0 0;
  color: var(--text-soft);
  font-size: 0.94rem;
}

.how-to__note {
  max-width: 62ch;
  margin: clamp(24px, 3vw, 34px) 0 0;
  color: var(--text-soft);
}

.how-to__small {
  max-width: 62ch;
  margin-top: 10px;
}

@media (min-width: 561px) and (max-width: 1080px) {
  .how-to__steps {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1081px) {
  .how-to__steps {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* FAQ: one-line title with the native-disclosure accordion stacked below,
   ember +/- markers (Mark, 2026-08-25; the prototype's sticky side column
   was tried and replaced with this stack the same day). */
.faq__grid {
  display: grid;
  gap: clamp(26px, 3vw, 40px);
}

/* Same one-line discipline as the lineup and HYH titles: nowrap on desktop,
   natural wrap below 780px. */
.faq__intro .section-title {
  max-width: none;
  white-space: nowrap;
  font-size: clamp(2.3rem, 4.14vw, 4.68rem);
}

.faq__intro .section-copy {
  margin-top: 14px;
}

/* Row width tracks the answer measure (62ch at 0.94rem plus the marker
   gutter), so the hairlines end where the text does instead of running on
   into dead space (Mark's catch, 2026-08-25). */
.faq__items {
  max-width: 720px;
}

.faq__items {
  border-top: 1px solid var(--line);
}

.faq__items details {
  border-bottom: 1px solid var(--line);
}

.faq__items summary {
  position: relative;
  padding: 24px 42px 24px 0;
  color: var(--text);
  cursor: pointer;
  font-size: 1.08rem;
  font-weight: 750;
  list-style: none;
}

.faq__items summary::-webkit-details-marker {
  display: none;
}

.faq__items summary::after {
  position: absolute;
  top: 22px;
  right: 4px;
  color: var(--ember-light);
  content: "+";
  font-size: 1.5rem;
  font-weight: 400;
}

.faq__items details[open] summary::after {
  content: "\2212";
}

.faq__items details p {
  max-width: 62ch;
  margin: 0;
  padding: 0 42px 24px 0;
  color: var(--text-soft);
  font-size: 0.94rem;
}

/* Process story (2026-08-25, Mark's references; relaid out 2026-08-31 to his
   stacked mockup): a full-width intro above four numbered image-led steps,
   ember arrows between the step images. Steps without art render a
   deliberate awaiting-art tile (faint flame on ink); spec in
   process-story.php. */
.process-story__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(34px, 4.5vw, 60px);
}

/* The intro spans the container now rather than sitting in a 240px column,
   so the 08-25 override (2.75rem, sized for that column) is retired. Only
   the ramp is overridden here, set to Mark's mockup rather than the shared
   4.5vw/5.22rem, which renders ~80px at desktop and overpowers the step row
   the heading introduces. The measure and text-wrap come from
   --authored; the 1.8rem floor is that rule's floor, kept deliberately,
   because 2.3rem renders "Fresh-Frozen Flower" wider than a 320px content
   box and breaks the authored two-line promise. */
.process-story__intro .section-title {
  font-size: clamp(1.8rem, 3.7vw, 4rem);
}

.process-story__intro .section-copy {
  max-width: 52ch;
  margin-top: 14px;
}

/* The amber link idiom the lineup cards use (Mark, 2026-08-25), with the
   arrow nudge on the link's own hover since there is no card wrapper. */
.process-story__cta {
  display: inline-flex;
  gap: 9px;
  align-items: center;
  margin-top: 22px;
  color: var(--ember-light);
  font-size: 0.86rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-decoration: none;
  text-transform: uppercase;
}

/* No arrow: this CTA points at our own category pages. See the site-wide
   outbound-only arrow rule at .lineup-card__link. If a caller ever passes an
   external href here, mark that case explicitly rather than reinstating the
   arrow for every caller. */

/* --step-gap is read back by the arrow offset below, so the gap and the
   arrow's placement cannot drift apart the way two copies of the clamp
   could. */
.process-story__steps {
  --step-gap: clamp(20px, 2.2vw, 36px);
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 20px var(--step-gap);
  margin: 0;
  padding: 0;
  counter-reset: process-step;
  list-style: none;
}

.process-story__step {
  counter-increment: process-step;
}

.process-story__media {
  position: relative;
  aspect-ratio: 4 / 3;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--ink-850);
}

.process-story__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  object-fit: cover;
}

.process-story__media img.process-story__placeholder {
  /* inset resets the shorthand first; top/left then re-place the flame
     (the reverse order silently un-centers it). */
  inset: auto;
  top: 50%;
  left: 50%;
  width: auto;
  height: 46%;
  transform: translate(-50%, -50%);
  opacity: 0.14;
}

/* The arrow rides the media box so it stays centered on the image row at
   every column width; no overflow clipping on the media, the image carries
   the radius itself. */
.process-story__step:not(:last-child) .process-story__media::after {
  content: "";
  position: absolute;
  top: 50%;
  right: calc((var(--step-gap) / 2 + 5px) * -1);
  width: 8px;
  height: 8px;
  border-top: 2px solid var(--ember);
  border-right: 2px solid var(--ember);
  transform: translateY(-50%) rotate(45deg);
}

.process-story__step h3 {
  margin: 16px 0 8px;
  font-size: 1.08rem;
  font-weight: 700;
}

/* The ol already carries the sequence; this only makes it visible, per
   Mark's mockup. Screen readers announce the list position anyway, so the
   ember numeral is decoration on top of existing semantics. */
.process-story__step h3::before {
  content: counter(process-step) ".";
  margin-right: 9px;
  color: var(--ember);
}

.process-story__step p {
  margin-bottom: 0;
  color: var(--text-dim);
  font-size: 0.86rem;
}

@media (max-width: 980px) {
  .process-story__grid {
    grid-template-columns: 1fr;
    gap: 30px;
  }

  .process-story__steps {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 28px 20px;
  }

  .process-story__step:not(:last-child) .process-story__media::after {
    display: none;
  }
}

@media (max-width: 780px) {
  .faq__intro .section-title {
    white-space: normal;
  }
}

/* Legal and 404 */

.legal-hero {
  padding: calc(var(--header-height) + clamp(60px, 6vw, 88px)) 0
    clamp(48px, 5vw, 76px);
  border-bottom: 1px solid var(--line);
  background:
    radial-gradient(circle at 82% 25%, rgba(46, 159, 245, 0.12), transparent 25rem),
    var(--ink-950);
}

.legal-hero .display {
  max-width: 16ch;
  margin-bottom: 24px;
}

.legal-hero h1 {
  max-width: 16ch;
  margin-bottom: 24px;
  font-family: var(--font-display);
  font-size: clamp(2.7rem, 5.85vw, 6.3rem);
  font-weight: 500;
  letter-spacing: normal;
  line-height: 1.05;
}

.legal-hero .container > p {
  max-width: 690px;
  margin-bottom: 0;
  color: var(--text-soft);
  font-size: clamp(1rem, 1.3vw, 1.2rem);
}

.legal-layout {
  display: grid;
  grid-template-columns: 240px minmax(0, 750px);
  gap: clamp(50px, 9vw, 140px);
  align-items: start;
  padding-block: clamp(68px, 8vw, 120px);
}

.legal-nav {
  position: sticky;
  top: calc(var(--header-height) + 38px);
  display: grid;
  gap: 7px;
}

/* Functional list label, not an eyebrow: it names the link group beneath it
   ("On this page", "Explore ZMOKE") and stays after the 2026-08-24 eyebrow
   removal, minus the old rule-line ornament. Sizes are the documented label
   step (0.75rem) at nav-link weight. */
.legal-nav__label {
  margin-bottom: 13px;
  color: var(--text-dim);
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.17em;
  line-height: 1.2;
  text-transform: uppercase;
}

.legal-nav ul {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.legal-nav a {
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
  color: var(--text-dim);
  font-size: 0.86rem;
  text-decoration: none;
}

.legal-nav a:hover,
.legal-nav a:focus-visible {
  color: var(--text);
}

.legal-content {
  min-width: 0;
}

.legal-content > aside {
  margin-bottom: 48px;
  padding: 18px 20px;
  border: 1px solid rgba(239, 145, 60, 0.36);
  border-radius: var(--radius-sm);
  background: rgba(239, 145, 60, 0.07);
  color: #d8bea6;
  font-size: 0.86rem;
  line-height: 1.65;
}

.legal-content section {
  padding: 0 0 50px;
  scroll-margin-top: calc(var(--header-height) + 30px);
}

.legal-content h2 {
  margin-bottom: 17px;
  font-family: var(--font-display);
  font-size: clamp(1.52rem, 2.4vw, 2.16rem);
  font-weight: 500;
  letter-spacing: normal;
}

.legal-content h3 {
  margin: 26px 0 10px;
  font-size: 1rem;
}

/* Legal body ran 93-102 characters per line above 1280px. 38em holds it near
   79, inside the comfortable 45-80 band. */
.legal-content p,
.legal-content li {
  max-width: 38em;
  color: var(--text-soft);
  line-height: 1.75;
}

.legal-content li + li {
  margin-top: 8px;
}

.page-not-found .legal-hero {
  min-height: 66svh;
}

.page-not-found .legal-hero .container > p:last-child {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 31px;
}

/* Footer */

.site-footer {
  --footer-container: 110rem;
  padding: clamp(64px, 8vw, 110px) 0 28px;
  border-top: 1px solid var(--line);
  background: var(--ink-950);
}

.site-footer__inner {
  width: min(calc(100% - (2 * var(--gutter))), var(--container));
  margin-inline: auto;
}

/* Three areas since 2026-08-25: brand, nav groups, and the Stay Connected
   signup column (moved here from the homepage band the same day).

   Named areas, not source order (2026-08-29): the markup now runs nav >
   signup > brand so that the MOBILE stack reads and tabs in the same order it
   is painted. Desktop paints the brand back on the left from here.

   The minmax minimums total 810px and must not be allowed to apply once the
   container is narrower than they are: with the two gaps at 6vw they forced a
   918px grid inside an 813px container around 900px wide, which is the ~69px
   of sitewide horizontal overflow that was tracked against the footer signup.
   The single-column collapse below now happens at 980px, before the minimums
   can bite, rather than at 780px. */
.site-footer__main {
  display: grid;
  grid-template-columns: minmax(200px, 0.7fr) minmax(300px, 0.9fr) minmax(300px, 1fr);
  /* Social gets a second row under the brand, filling the dead space the
     brand column used to leave below the tagline. Nav and signup deliberately
     do NOT span into that row: a spanning item's height is distributed across
     the rows it covers, which inflated row one and stranded the icons 180px
     below the tagline. Row two is the brand column alone. */
  /* Contact took the two empty cells of row two (2026-09-01). They were
     already dead space, so the block costs the footer no extra height at any
     width where the desktop grid applies; spanning both tracks also gives its
     note a measure wide enough to read on one or two lines instead of five in
     a single column. */
  grid-template-areas:
    "brand  nav     signup"
    "social contact contact";
  gap: clamp(28px, 4vw, 72px);
  row-gap: 26px;
  /* Halved from clamp(54px, 7vw, 92px) (Mark, 2026-08-25). */
  padding-bottom: clamp(27px, 3.5vw, 46px);
}

.site-footer__brand {
  grid-area: brand;
}

.site-footer__navigation {
  grid-area: nav;
}

.site-footer__signup {
  grid-area: signup;
}

.site-footer__contact {
  grid-area: contact;
}

.site-footer__main > .social-row {
  grid-area: social;
  align-self: start;
}

.site-footer__brand {
  max-width: 340px;
}

.site-footer__brand img {
  width: 180px;
  margin-bottom: 24px;
}

/* The tagline takes the brand's serif voice (Mark, 2026-08-25) so it reads
   as the signature phrase, not as metadata alongside the disclosures. */
.site-footer__brand p {
  color: var(--text-soft);
  font-family: var(--font-display);
  font-size: 1.08rem;
  font-weight: 500;
}

/* Two tracks, matching the two groups. It was three from when Legal was a
   nav group; after Legal moved to the bottom band the third track stayed and
   left a phantom column of dead space between Connect and Stay Connected. */
.site-footer__navigation {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 30px;
}

.site-footer__heading {
  margin-bottom: 18px;
  color: var(--text-dim);
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.site-footer__navigation ul {
  display: grid;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-footer__navigation a {
  /* Keep padded targets in flow so adjacent links cannot overlap. */
  display: inline-block;
  padding-block: 8px;
  color: var(--text-soft);
  font-size: 0.86rem;
  text-decoration: none;
}

.site-footer__navigation a:hover,
.site-footer__navigation a:focus-visible {
  color: var(--ember-light);
}

/* Queried by the signup field below, which must react to ITS OWN width rather
   than the viewport's: the footer's three-column desktop grid hands this block
   only 250px at 1024 while giving it 887px at 980, so a viewport breakpoint
   gets the narrow case exactly backwards. */
.site-footer__signup {
  container-type: inline-size;
}

/* The measure and balance replace an authored <br> (2026-08-29). 25rem holds
   the copy to two even lines at every width instead of only at the 328px
   desktop column the break was written for. */
.site-footer__signup > p {
  max-width: 25rem;
  margin-bottom: 14px;
  color: var(--text-dim);
  font-size: 0.86rem;
  text-wrap: balance;
}

/* The FORM is the field. Border, radius and background sit here rather than on
   the input so the field and Subscribe read as a single control (Mark's second
   mockup, 2026-08-29). Values are the input's own former ones, moved up. */
.site-footer__signup-form {
  display: grid;
  /* Shares the 25rem measure of the copy above it (Mark, 2026-08-31): the
     field ran the full 491px column while the sentence stopped at 400px, so
     the control overhung the block it belongs to. The ≤980px rule below
     keeps its own wider 30rem cap for the centred layout. */
  max-width: 25rem;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
  padding: 5px 5px 5px 14px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 12px;
  background: rgba(var(--scrim-rgb), 0.74);
  transition:
    border-color 150ms ease,
    box-shadow 150ms ease;
}

.site-footer__signup-form:hover {
  border-color: rgba(255, 255, 255, 0.34);
}

/* The input has no chrome of its own now, so its focus indicator has to live
   on the wrapper or keyboard focus becomes invisible. Same blue and ring the
   input used to carry. */
.site-footer__signup-form:focus-within {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(46, 159, 245, 0.14);
}

.site-footer__signup-icon {
  display: flex;
  color: var(--text-dim);
}

/* Below 300px of column the button drops to its own row INSIDE the field. The
   unified field costs about 50px of typing room to the icon, its gap and the
   wrapper padding, which is free at 328px and up but left only 77px of input
   in the 250px column the desktop grid hands this block between 981 and 1180.
   Stacking returns the full width to the input and keeps the single-control
   look. */
@container (max-width: 300px) {
  .site-footer__signup-form {
    grid-template-columns: auto minmax(0, 1fr);
    padding: 10px 12px;
  }

  .site-footer__signup-form .button {
    width: 100%;
    grid-column: 1 / -1;
  }
}

.site-footer__signup-form .button {
  flex: none;
}

.site-footer__signup .form-status {
  margin-top: 8px;
}

/* Social row. Entries with no url render as spans rather than anchors (see
   components/social-row.php), so both states share this class and look
   identical; only the cursor and the hover lift are gated on being a link. */
/* No top margin: the row is a grid item of .site-footer__main now, so the
   grid's row-gap owns the spacing. Leaving the old 22px here stacked on top
   of that gap and pushed the icons away from the brand block. Mobile sets its
   own margin, where the row follows the field rather than a grid row. */
.social-row {
  display: flex;
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Ring, not a filled disc (2026-08-29). The border value is the one
   .site-header__toggle already uses for its 44px circle, so this is the site's
   established circular treatment rather than a second one. */
.social-row__item {
  display: inline-flex;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.24);
  border-radius: 50%;
  color: var(--text-soft);
  transition:
    border-color 180ms ease,
    background 180ms ease,
    color 180ms ease;
}

a.social-row__item:hover,
a.social-row__item:focus-visible {
  border-color: rgba(255, 255, 255, 0.62);
  background: rgba(255, 255, 255, 0.07);
  color: var(--white);
}

/* PUBLIC CONTACT BLOCK. One template (components/contact-block.php) renders
   in the footer and as a short band on /about/ and /where-to-buy/, so these
   rules cover both; .contact-block--section carries the page-band differences.

   auto-fit rather than a breakpoint: the block sits in containers that range
   from a ~600px footer row to a full 1200px page container to a ~330px phone
   column, and the only question at every one of them is whether the note fits
   beside the address or has to go under it. min(320px, 100%) is what stops the
   320px minimum from overflowing a container narrower than itself. */
.contact-block {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: 18px clamp(28px, 4vw, 64px);
  align-items: start;
}

.contact-block > h2 {
  grid-column: 1 / -1;
}

/* Browsers italicise <address>; the element is correct here, the italic is
   not. */
.contact-block address {
  font-style: normal;
}

/* Footer contact is one compact utility row. Its DOM order is also its visual
   order: email, phone, postal address. That keeps keyboard, screen-reader and
   responsive reading order in agreement instead of using CSS order to mimic
   the desktop reference. */
.site-footer__contact-list {
  display: grid;
  gap: 2px;
  justify-items: center;
  margin: 0;
}

.site-footer__contact-item {
  display: inline-flex;
  min-width: 0;
  min-height: 44px;
  gap: 10px;
  align-items: center;
  justify-content: center;
  color: var(--text-soft);
  font-size: 0.86rem;
  font-style: normal;
  line-height: 1.55;
  text-decoration: none;
  overflow-wrap: anywhere;
}

.site-footer__contact-item--address {
  align-items: flex-start;
  padding-top: 10px;
}

.site-footer__contact-item[href]:hover,
.site-footer__contact-item[href]:focus-visible {
  color: var(--ember-light);
}

.site-footer__contact-icon {
  width: 20px;
  height: 20px;
  flex: none;
}

.site-footer__contact-item--address .site-footer__contact-icon {
  margin-top: 2px;
}

.site-footer__contact-copy,
.site-footer__contact-copy > span {
  display: block;
  min-width: 0;
}

.contact-block__address,
.contact-block__reach {
  margin: 0;
  color: var(--text-soft);
  font-size: 0.86rem;
}

.contact-block__address {
  line-height: 1.75;
}

/* The blank line in the source copy between the postal address and the two
   ways to reach it. It is a deliberate gap, not spacing to tune away. */
.contact-block__reach {
  margin-top: 14px;
  /* 2 rather than 1.75 so the two padded links below clear each other. At
     1.75 their tap targets overlapped by a couple of pixels, which makes a
     tap near the boundary land on whichever box paints last. */
  line-height: 2;
}

/* inline-block and NOTHING ELSE. A plain inline link's hit area follows the
   font, about 17px, so it needs the block box; but an inline-block takes its
   height from its OWN line box, which the line-height: 2 above already makes
   27.5px. The footer nav's padding-plus-negative-margin trick is wrong here
   and was measured wrong on staging first: it grew each box to 38px inside a
   27.5px rhythm, so the two links overlapped by 10px and a tap near the
   boundary landed on whichever painted last. Do not add padding back. */
.contact-block__reach a {
  display: inline-block;
  color: var(--text-soft);
  text-decoration: none;
}

.contact-block__reach a:hover,
.contact-block__reach a:focus-visible {
  color: var(--ember-light);
}

.contact-block__note {
  margin: 0;
  max-width: 46em;
}

/* Underlined, unlike the footer nav: this one is an inline link inside dim
   running text, where colour alone would not identify it. */
.contact-block__note a {
  color: var(--text-soft);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.contact-block__note a:hover,
.contact-block__note a:focus-visible {
  color: var(--ember-light);
}

/* The heading spacing is the grid row-gap here, not the footer heading's own
   margin, or the two would stack. */
.site-footer__contact .site-footer__heading {
  margin-bottom: 0;
}

/* Desktop footer (Mark, 2026-09-04): the reference is a wide editorial band,
   not the site's standard content container. The outer grid scales to 110rem
   while preserving its three semantic regions: brand, navigation, signup.
   Contact stays in the two right-hand grid areas but deliberately caps at the
   reference's ~42rem rule, leaving quiet space below Stay Connected. */
@media (min-width: 981px) {
  .site-footer {
    padding-top: clamp(96px, 7.15vw, 148px);
    padding-bottom: clamp(52px, 4vw, 82px);
  }

  .site-footer__inner {
    width: min(calc(100% - (2 * var(--gutter))), var(--footer-container));
  }

  .site-footer__main {
    grid-template-columns: minmax(200px, 0.8fr) minmax(300px, 1fr) minmax(300px, 0.91fr);
    column-gap: clamp(40px, 3.6vw, 74px);
    row-gap: clamp(28px, 1.55vw, 32px);
    padding-bottom: clamp(52px, 3.5vw, 60px);
  }

  .site-footer__brand {
    max-width: 440px;
  }

  .site-footer__brand img {
    width: clamp(180px, 10.7vw, 220px);
    margin-bottom: 30px;
  }

  .site-footer__brand p {
    font-size: clamp(1.08rem, 0.84rem + 0.2vw, 1.25rem);
  }

  .site-footer__heading {
    margin-bottom: clamp(18px, 1.15vw, 24px);
    font-size: clamp(0.75rem, 0.6rem + 0.27vw, 0.9375rem);
  }

  .site-footer__navigation ul {
    gap: clamp(10px, 0.6vw, 13px);
  }

  .site-footer__navigation a,
  .site-footer__signup > p {
    font-size: clamp(0.86rem, 0.68rem + 0.34vw, 1.125rem);
  }

  .site-footer__signup > p {
    max-width: none;
    margin-bottom: clamp(16px, 1vw, 20px);
  }

  .site-footer__signup-form {
    max-width: 32.5rem;
    min-height: 70px;
    padding: 8px 8px 8px 18px;
  }

  .site-footer__signup-icon svg {
    width: 22px;
    height: 22px;
  }

  .site-footer__signup-form input {
    font-size: clamp(1rem, 0.84rem + 0.14vw, 1.0625rem);
  }

  .site-footer__signup-form .button {
    min-height: 52px;
    padding-inline: 28px;
    font-size: 0.95rem;
  }

  .social-row {
    gap: clamp(12px, 0.7vw, 15px);
  }

  .social-row__item {
    width: clamp(44px, 2.75vw, 56px);
    height: clamp(44px, 2.75vw, 56px);
  }

  .social-row__item svg {
    width: clamp(18px, 1.15vw, 22px);
    height: clamp(18px, 1.15vw, 22px);
  }

  .site-footer__contact {
    display: block;
    width: min(100%, 42rem);
    padding-top: clamp(28px, 1.55vw, 32px);
    border-top: 1px solid var(--line);
  }

  .site-footer__contact .site-footer__heading {
    margin: 0 0 clamp(14px, 0.9vw, 18px);
  }

  .site-footer__contact-list {
    display: grid;
    grid-template-columns: max-content max-content minmax(0, 1fr);
    gap: 0;
    align-items: start;
    justify-items: stretch;
  }

  .site-footer__contact-item {
    justify-content: flex-start;
    font-size: clamp(0.86rem, 0.65rem + 0.28vw, 0.9375rem);
    white-space: nowrap;
  }

  .site-footer__contact-item--address {
    padding-top: 0;
    white-space: normal;
  }

  .site-footer__contact-item + .site-footer__contact-item::before {
    width: 1px;
    height: 24px;
    flex: none;
    margin: 4px clamp(14px, 1.05vw, 20px) 0;
    background: var(--line-strong);
    content: "";
  }

}

/* PAGE-BAND VARIANT. Quieter than the page's other section titles on purpose:
   this is a short utility block sitting between "Rooted in South Dakota" and
   the closing CTA, and at the full clamp(2.3rem, 4.5vw, 5.22rem) it announced
   itself as a peer of them. */
.contact-block--section {
  gap: 26px clamp(28px, 4vw, 64px);
}

.contact-block--section .contact-block__heading {
  max-width: none;
  font-size: clamp(1.45rem, 2.4vw, 2.05rem);
}

.contact-block--section .contact-block__address,
.contact-block--section .contact-block__reach {
  font-size: 0.95rem;
}

/* One bottom band since 2026-08-25 (Mark's formatting pass): bold
   21+/license line and copyright share a single strip. The paragraph keeps
   a readable measure (full-width fine print ran to 166 characters a line,
   which is why the old block was narrow). The availability paragraph that
   used to sit between them was removed 2026-09-04 (Mark), so the band is
   two cells: the line, then the legal links and copyright. */
.site-footer__compliance {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px clamp(36px, 5vw, 90px);
  align-items: center;
  width: min(calc(100% - (2 * var(--gutter))), var(--container));
  margin-inline: auto;
  padding: 22px 0 0;
  border-top: 1px solid var(--line);
}

/* Legal links and the copyright share the band's end cell (2026-08-29),
   which is where the copyright already sat. */
.site-footer__closing {
  display: grid;
  gap: 6px;
  justify-items: end;
}

.site-footer__legal {
  display: flex;
  gap: 6px 14px;
  flex-wrap: wrap;
  align-items: center;
}

/* Separator as a pseudo-element, not a character in the markup: it stays out
   of the accessibility tree and out of the PHP. */
.site-footer__legal a + a {
  padding-left: 14px;
  border-left: 1px solid var(--line-strong);
}

/* Padded legal targets stay in flow, including when they wrap. */
.site-footer__legal a {
  display: inline-block;
  padding-block: 8px;
  color: var(--text-dim);
  font-size: 0.75rem;
  text-decoration: none;
}

.site-footer__legal a:hover,
.site-footer__legal a:focus-visible {
  color: var(--ember-light);
}

.site-footer__compliance p {
  max-width: 42em;
  margin-bottom: 0;
  color: var(--text-dim);
  font-size: 0.75rem;
  line-height: 1.7;
}

.site-footer__compliance strong {
  color: #c4c8cd;
}

.site-footer__copyright {
  justify-self: end;
}

/* These declarations follow the base compliance rules so the desktop
   reference's wider rule and larger fine-print scale win the cascade. */
@media (min-width: 981px) {
  .site-footer__compliance {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 16px clamp(52px, 4.4vw, 90px);
    width: min(calc(100% - (2 * var(--gutter))), var(--footer-container));
    padding-top: clamp(30px, 1.75vw, 36px);
  }

  .site-footer__compliance p,
  .site-footer__legal a {
    font-size: clamp(0.75rem, 0.58rem + 0.32vw, 1rem);
  }
}

/* Mobile footer. Groups stack full width between hairlines, and the whole
   thing is CENTRED end to end (Mark, 2026-08-29, choosing the fully centred
   composition from his second mockup over the half-left/half-centred hybrid
   the first pass shipped).

   Links are a centred wrapping row rather than a two-up grid: centring a grid
   leaves each cell's text stranded on its own left edge, and a row that wraps
   only when it must keeps the short groups on one line.

   980px, not 780px, is also what retires the tracked horizontal overflow:
   see .site-footer__main above for why the three-column minimums could not
   survive the 800-980 band. */
@media (max-width: 980px) {
  .site-footer__main {
    grid-template-columns: 1fr;
    grid-template-areas:
      "nav"
      "signup"
      "contact"
      "social"
      "brand";
    grid-template-rows: auto;
    gap: 0;
  }

  .site-footer__navigation {
    grid-template-columns: 1fr;
    gap: 0;
  }

  .site-footer__group {
    padding-block: 18px;
    border-bottom: 1px solid var(--line);
    text-align: center;
  }

  .site-footer__group:first-child {
    border-top: 1px solid var(--line);
  }

  .site-footer__heading {
    margin-bottom: 12px;
  }

  .site-footer__navigation ul {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 4px 24px;
  }

  .site-footer__signup {
    padding-top: 26px;
    text-align: center;
  }

  .site-footer__signup > p {
    margin-inline: auto;
  }

  /* The field would run the full width of the footer otherwise, which reads
     as a stretched control rather than a considered one. */
  .site-footer__signup-form {
    max-width: 30rem;
    margin-inline: auto;
  }

  /* The block is centred but the control is not: a centred caret and a
     centred placeholder read as broken in a text field. */
  .site-footer__signup-form input {
    text-align: left;
  }

  /* Stacked so neither the field nor Subscribe is squeezed. */
  .site-footer__signup-form {
    display: grid;
    gap: 10px;
  }

  .site-footer__signup-form .button {
    width: 100%;
  }

  /* Centred like everything else in the mobile footer. justify-items rather
     than text-align alone: the block is a grid, so its two auto-fit tracks
     have to be centred as well as the text inside them. */
  .site-footer__contact {
    justify-items: center;
    margin-top: 26px;
    text-align: center;
  }

  .site-footer__contact .contact-block__note {
    margin-inline: auto;
  }

  .social-row {
    justify-content: center;
    margin-top: 26px;
  }

  /* The footer opens on a hairline and a section label rather than a large
     brand block, so it does not need the desktop band's lead-in. */
  .site-footer {
    padding-top: 40px;
  }

  /* The brand is the sign-off here, not the opening. */
  .site-footer__brand {
    max-width: none;
    margin-top: 30px;
    padding-top: 26px;
    border-top: 1px solid var(--line);
    text-align: center;
  }

  /* margin-inline, not text-align: images are display:block globally, so the
     centring the surrounding text-align gives the tagline does nothing here. */
  .site-footer__brand img {
    width: 150px;
    margin-bottom: 14px;
    margin-inline: auto;
  }

  /* No second rule here: the brand block's own top border already separates
     the sign-off from the content above, and a divider between the tagline
     and the fine print would split what reads as one closing block. */
  .site-footer__compliance {
    grid-template-columns: 1fr;
    justify-items: center;
    padding-top: 0;
    border-top: 0;
    text-align: center;
  }

  /* Dissolve the wrapper so the legal row and the copyright become items of
     the compliance grid directly. That lets the legal row move up under the
     logo, where the reference puts it, while desktop keeps them paired in the
     band's end cell. */
  .site-footer__closing {
    display: contents;
  }

  .site-footer__legal {
    order: -1;
    justify-content: center;
    margin-bottom: 4px;
  }

  .site-footer__copyright {
    justify-self: center;
  }
}

/* Age gate */

/* While the gate holds body { overflow: hidden }, the closed off-canvas
   drawer's translateX(105%) bounds make mobile Chrome expand the layout
   viewport past the device width (390 -> 750), rendering the gate dialog
   desktop-size and cut off. The drawer is unusable under the gate anyway
   (site shell is inert), so remove it from layout until the gate releases. */
body.age-gate-open .site-header__nav {
  display: none;
}

.age-gate {
  position: fixed;
  z-index: 9999;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 20px;
  overflow-y: auto;
  background:
    radial-gradient(circle at 74% 42%, rgba(46, 159, 245, 0.17), transparent 28rem),
    radial-gradient(circle at 27% 70%, rgba(239, 145, 60, 0.13), transparent 24rem),
    rgba(var(--scrim-rgb), 1);
  opacity: 1;
  transition:
    opacity 320ms ease,
    visibility 320ms ease;
}

.age-gate__backdrop {
  position: absolute;
  inset: 0;
}

.age-gate[hidden] {
  display: none;
}

.age-gate.is-closing {
  visibility: hidden;
  opacity: 0;
}

.age-gate__panel {
  position: relative;
  width: min(100%, 590px);
  overflow: hidden;
  padding: clamp(34px, 6vw, 66px);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--radius-lg);
  background:
    linear-gradient(145deg, rgba(255, 255, 255, 0.036), transparent 42%),
    rgba(8, 11, 14, 0.96);
  box-shadow: 0 45px 120px rgba(0, 0, 0, 0.72);
  text-align: center;
}

.age-gate__panel::after {
  position: absolute;
  top: -120px;
  right: -90px;
  width: 250px;
  height: 250px;
  border: 1px solid rgba(46, 159, 245, 0.17);
  border-radius: 50%;
  box-shadow: 0 0 70px rgba(46, 159, 245, 0.1);
  content: "";
}

.age-gate__logo {
  position: relative;
  z-index: 2;
  width: min(73%, 300px);
  margin: 0 auto 42px;
}

.age-gate h2 {
  position: relative;
  z-index: 2;
  margin-bottom: 17px;
  font-family: var(--font-display);
  font-size: clamp(1.8rem, 4.5vw, 3.15rem);
  font-weight: 500;
  letter-spacing: normal;
  line-height: 1.05;
}

.age-gate p {
  position: relative;
  z-index: 2;
  max-width: 46ch;
  margin: 0 auto 29px;
  color: var(--text-soft);
  font-size: 0.94rem;
}

.age-gate__actions {
  position: relative;
  z-index: 2;
  display: flex;
  justify-content: center;
  gap: 12px;
}

/* Reveal behavior */

.js .reveal {
  opacity: 0;
  transform: translateY(22px);
  transition:
    opacity 650ms var(--ease),
    transform 650ms var(--ease);
}

.js .reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* An anchor-targeted reveal must not be translated: the browser latches the
   scroll endpoint onto the translated position, so the entrance slide leaves
   the target sitting under the header. Fade still runs. The descendant form
   is the one that actually matches in this codebase: fragment ids live on
   wrapping sections, never on the .reveal element itself. */
.js .reveal:target,
.js :target .reveal {
  transform: none;
  transition-property: opacity;
}

/* Ultra-wide: photo-led shells step up to a wider stage while text sections
   hold at --container (copy is capped by its own measure). Raster art in the
   tier stays contain/cover-bound below natural width at this size; nothing
   here may upscale an asset past its source pixels. */
@media (min-width: 112.5em) {
  :root {
    --container-wide: 1560px;
  }

  /* Serif h1 at the 6.66rem cap needs a wider measure than the 13ch base or
     long keyword headlines wrap to 4 lines against the 3-line desktop limit. */
  .product-hero__copy .display {
    max-width: 16ch;
  }

  .container.product-hero__inner,
  .container.format-story,
  .container:has(> .lineup-grid),
  .container:has(> .variant-grid),
  .container:has(> .cross-sell) {
    width: min(calc(100% - (2 * var(--gutter))), var(--container-wide));
  }

  .liquid-drop__inner {
    width: min(calc(100% - (2 * clamp(42px, 6vw, 94px))), 1600px);
  }
}

/* Past ~1888px the full-bleed cover crop outruns the desktop master: the
   section is pinned at 900px tall (the min-height clamp's cap) while cover
   scales the 1672x945 art with the viewport, so the pens lose their caps and
   tips off the top and bottom edges. Pin the art to its natural 900px-tall
   footprint on the right instead (900 * 1672/945 = 1592px wide), and fade its
   left edge into the section's own ink so the hand-off is seamless. The 900px
   figure is the .liquid-drop min-height cap; change them together. */
@media (min-width: 118em) {
  .liquid-drop__backdrop img {
    left: auto;
    width: calc(900px * (1672 / 945));
    object-position: right center;
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 180px);
    mask-image: linear-gradient(90deg, transparent 0, #000 180px);
  }
}

/* Responsive */

@media (max-width: 1180px) {
  :root {
    --header-height: 76px;
  }

  .site-header__inner {
    grid-template-columns: 170px minmax(0, 1fr) auto;
    gap: 18px;
  }

  .site-header__brand {
    width: 158px;
  }

  .site-header__nav {
    gap: 16px;
  }

  .site-header__link {
    min-height: var(--header-height);
    font-size: 0.75rem;
  }

  .format-story {
    gap: 60px;
  }

  /* Superseded by the stacked card (2026-08-31): height is now the square
     media plus the body panel, so a fixed floor only adds dead ink. */
  .lineup-card {
    min-height: 0;
  }

  .lineup-card.lineup-card--lead {
    min-height: clamp(300px, 26vw, 390px);
  }

  .product-hero__inner {
    grid-template-columns: minmax(320px, 0.88fr) minmax(390px, 1.12fr);
  }
}

@media (max-width: 1080px) {
  .liquid-drop {
    min-height: clamp(680px, 68vw, 760px);
  }

  .liquid-drop__inner {
    width: min(calc(100% - 64px), 1000px);
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.9fr);
    gap: 12px;
  }

  .liquid-drop__copy {
    padding-block: 68px 58px;
  }

  .liquid-drop__title {
    font-size: clamp(3.42rem, 6.03vw, 4.82rem);
  }

  .liquid-drop__summary {
    font-size: clamp(0.96rem, 1.5vw, 1.08rem);
  }
}

@media (min-width: 981px) {
  .liquid-drop__title {
    font-size: clamp(3.42rem, 4.5vw, 4.5rem);
  }
}

@media (min-width: 781px) and (max-width: 980px) {
  .liquid-drop__title {
    font-size: clamp(2.79rem, 5.4vw, 3.42rem);
  }
}

@media (max-width: 980px) {
  .site-header__inner {
    grid-template-columns: 1fr auto auto;
  }

  /* flex, not block: the base rule centres the bars with flex column + gap. */
  .site-header__toggle {
    display: flex;
  }

  .site-header__nav {
    grid-column: auto;
    position: fixed;
    z-index: -1;
    top: 0;
    right: 0;
    width: min(88vw, 430px);
    height: 100svh;
    padding: calc(var(--header-height) + 52px) 34px 40px;
    display: flex;
    align-items: stretch;
    flex-direction: column;
    justify-content: flex-start;
    gap: 0;
    /* Opaque, not 0.98: two percent is enough to read large bright hero type
       straight through the drawer, which is what made the Products panel look
       dirty over /retailers/ (Mark, 2026-08-29). The gradient still supplies
       the depth the alpha was reaching for. */
    background:
      radial-gradient(circle at 100% 0, rgba(46, 159, 245, 0.14), transparent 22rem),
      #06080b;
    box-shadow: -30px 0 80px rgba(0, 0, 0, 0.55);
    transform: translateX(105%);
    /* Hidden while closed so the off-canvas links are not keyboard tab stops;
       the visibility flip is delayed until the slide-out finishes. */
    visibility: hidden;
    transition:
      transform 350ms var(--ease),
      visibility 0s 350ms;
  }

  .site-header__links {
    width: 100%;
    flex: 0 0 auto;
    align-items: stretch;
    flex-direction: column;
    gap: 0;
    justify-self: stretch;
  }

  .site-header__links li {
    width: 100%;
  }

  .site-header__actions {
    width: 100%;
    margin-top: 24px;
    padding-top: 24px;
    border-top: 1px solid var(--line);
    align-items: stretch;
    flex-direction: column;
    gap: 12px;
    justify-self: stretch;
  }

  .site-header__actions .button,
  .site-header__link--carry {
    width: 100%;
    justify-content: center;
  }

  .nav-open .site-header__nav {
    visibility: visible;
    transform: translateX(0);
    transition:
      transform 350ms var(--ease),
      visibility 0s 0s;
  }

  /* Drawer nav rows: big serif entries with a hairline between them. Scoped to
     the nav list, NOT to every .site-header__link, because For Dispensaries
     also carries that class while living in the actions block beside a button.
     It used to inherit this whole treatment - serif face, 1.36rem, 63px row,
     bottom divider - and land next to a 0.86rem bold sans button, which is why
     it read as belonging to a different design (Mark, 2026-08-29). It also
     silently beat that link's own min-height: 54px on source order. */
  .site-header__links .site-header__link {
    min-height: 63px;
    border-bottom: 1px solid var(--line);
    font-family: var(--font-display);
    font-size: 1.36rem;
    font-weight: 500;
    letter-spacing: normal;
  }

  /* For Dispensaries is the secondary ACTION, so it takes the button system's
     metrics and the established ghost treatment rather than a bespoke pill.
     Everything here is matched to .button + .button--small + .button--ghost so
     the pair reads as one set; only the properties that must be reasserted
     over .site-header__link are repeated. */
  .site-header__link--carry {
    min-height: 43px;
    align-items: center;
    padding: 0 21px;
    border: 1px solid rgba(255, 255, 255, 0.28);
    border-radius: var(--radius-sm);
    background: rgba(var(--scrim-rgb), 0.22);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.07),
      0 1px 2px rgba(0, 0, 0, 0.3);
    color: var(--text);
    font-family: inherit;
    font-size: 0.86rem;
    font-weight: 650;
    letter-spacing: 0.012em;
    line-height: 1;
    backdrop-filter: blur(8px);
  }

  .site-header__link--carry:hover,
  .site-header__link--carry:focus-visible {
    border-color: rgba(255, 255, 255, 0.62);
    background: rgba(255, 255, 255, 0.09);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.12),
      0 5px 18px rgba(0, 0, 0, 0.22);
  }

  /* The nav-row underline animation belongs to the list, not to a button. */
  .site-header__link--carry::after {
    display: none;
  }

  /* In the drawer the Products dropdown becomes an inline disclosure: the
     panel loses its floating chrome and indents under the summary. */
  .site-header__menu-item {
    width: 100%;
  }

  /* The <details> is a flex item, so it shrink-wrapped to its widest row and
     left the panel 240px inside a 343px drawer. That is what squeezed each
     row's label and note together (Mark, 2026-08-29). */
  .site-header__menu {
    width: 100%;
  }

  .site-header__menu summary {
    justify-content: space-between;
    width: 100%;
  }

  /* Keep the desktop card here rather than stripping it back to bare text:
     the panel stays a bordered, rounded plate with hairlines between rows, so
     the disclosure reads the same way on a phone as it does on desktop. Two
     deliberate differences: it is positioned in flow instead of floating, and
     it lifts with white at low alpha instead of the near-opaque scrim, which
     would be invisible against the drawer's own near-black. The deep shadow
     is dropped for the same reason - inside a dark drawer it reads as dirt,
     not depth. */
  .site-header__menu-panel {
    position: static;
    width: 100%;
    margin-top: 12px;
    padding: 2px 15px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: rgba(255, 255, 255, 0.035);
    box-shadow: none;
    transform: none;
  }

  .site-header__menu-panel a {
    padding: 13px 0;
  }

  .site-header__menu-panel a + a {
    border-top: 1px solid var(--line);
  }

  .lineup-grid {
    grid-template-columns: 1fr;
  }

  /* Superseded by the stacked card (2026-08-31): height is now the square
     media plus the body panel, so a fixed floor only adds dead ink. */
  .lineup-card {
    min-height: 0;
  }

  .lineup-card.lineup-card--lead {
    min-height: 400px;
  }

  .lineup-card--lead .lineup-card__media img {
    object-position: center;
    padding: 0;
    transform: none;
  }

  .product-hero {
    min-height: auto;
  }

  .product-hero__inner {
    grid-template-columns: 1fr;
    min-height: auto;
  }

  .product-hero__copy {
    max-width: 720px;
  }

  .product-hero__visual {
    min-height: 580px;
  }

  .product-hero__visual::before {
    width: min(72vw, 640px);
  }

  .format-story {
    grid-template-columns: 1fr;
  }

  .format-story--reverse .format-story__media {
    order: 0;
  }

  .format-story__copy {
    max-width: 720px;
  }

  /* Four process beats drop to a 2x2 block. The :nth-child rules must stay
     after the base .process-steps__item + .process-steps__item border rule,
     which they override at equal specificity on source order alone. */
  .process-steps {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .process-steps__item {
    padding-bottom: 30px;
  }

  .process-steps__item:nth-child(odd) {
    padding-left: 0;
    border-left: 0;
  }

  .process-steps__item:nth-child(even) {
    padding-right: 0;
  }

  .carry-hero__inner {
    grid-template-columns: 1fr;
  }

  .carry-hero__copy {
    max-width: 760px;
  }

  .inquiry-layout {
    grid-template-columns: 1fr;
  }

  .inquiry-sidebar {
    position: static;
    max-width: 720px;
  }
}

/* Phone: the drawer disclosure's label-beside-note row runs out of room.
   Measured at 390px the two blocks leave 18px between them where desktop
   leaves 105px, and below 360px they no longer fit at all. Tablet keeps the
   side-by-side reading because it has the width for it; phones stack the note
   under its label, which is roomier than squeezing two columns into 245px.

   This is its OWN block on purpose. Splitting the 980 block above to hold it
   silently demoted every rule after the split - .site-header__inner,
   .product-hero, .home-hero, .format-story and more - to 480px only, which
   broke the whole 481-980 band. Never close that block early to insert one
   rule. */
@media (max-width: 480px) {
  .site-header__menu-panel a {
    grid-template-columns: minmax(0, 1fr);
    gap: 3px;
  }
}

@media (max-width: 780px) {
  :root {
    --header-height: 66px;
    --gutter: 20px;
  }

  .site-header__inner {
    grid-template-columns: 1fr auto;
    gap: 12px;
  }

  .site-header__brand {
    width: 136px;
  }

  .section-heading {
    grid-template-columns: 1fr;
    gap: 23px;
    align-items: start;
  }

  .section-heading--lineup .section-title,
  .section-heading--one-line .section-title {
    white-space: normal;
  }

  /* `max(840px, 100svh)` made the hero EXACTLY the viewport on a 390x844
     phone, which is the worst available value: a screen that looks
     deliberately complete, with nothing to suggest the page continues. Given
     the age gate has already spent one interaction before anyone sees this,
     a dead-end first screen is expensive.

     Reserving 80px leaves a visible sliver of the next band. The 760px floor
     means a cramped 360x640 phone overflows by ~120px instead, which reads as
     "continues" rather than "ends" and is the correct failure direction. */
  /* Redefining --hero-h rather than min-height keeps the frame and the cluster
     derived from the same number here too. The cluster's own width is
     overridden below for the stacked layout, so --cluster-cap goes unused on
     this breakpoint; it stays correct rather than stale. */
  .home-hero {
    --hero-h: max(760px, calc(100svh - 80px));
  }

  /* Portrait stage covers the frame edge to edge; the cluster centers in the
     lower band with the copy zone structurally above it. */
  .home-hero__art img {
    object-position: 55% 70%;
  }

  /* Scene layers: the phone plate is purpose-built 9:16 with the products in
     the bottom band, so both layers anchor to the frame's BOTTOM edge. Centring
     was wrong here: this breakpoint spans narrow phones (slot ~0.5, no vertical
     crop, anchor irrelevant) AND short-wide slots like iPad Mini portrait at
     744 (slot ~0.8), where a centred cover crop removed 15% of the canvas
     bottom and cut the products off mid-body. Bottom anchor pushes the whole
     crop into the canvas top, which is empty smoke. Declaring the group
     alongside the art is the registration contract: they must move as one. */
  .home-hero--scene .home-hero__art img,
  .home-hero--scene .home-hero__group img {
    object-position: 50% 100%;
  }

  /* Bleeding past both edges closes the dead band of empty plate between the
     CTAs and the products, and buys back the label legibility the contained
     version lost. */
  .home-hero__cluster {
    right: 50%;
    bottom: 8px;
    width: min(116vw, 660px);
    transform: translateX(50%);
  }

  /* Portrait frame: the veil has to overshoot both edges, or its feathered
     rectangle still resolves as a shape in the narrow column. */
  .home-hero__veil img {
    right: -34%;
    bottom: -18%;
    width: 168%;
  }

  /* The tall frame puts far more empty floor under the cluster than the
     landscape crop does, so the floor sinks harder and the mid-frame stays
     open for the copy. */
  .home-hero__scrim {
    background:
      radial-gradient(150% 46% at 50% 112%, rgba(var(--scrim-rgb), 0.94) 0%, rgba(var(--scrim-rgb), 0.6) 40%, transparent 74%),
      radial-gradient(96% 54% at 50% 62%, transparent 52%, rgba(var(--scrim-rgb), 0.5) 100%);
  }

  /* Portrait puts the amber at the frame's right shoulder rather than its
     lower corner, where it would otherwise sit under the cluster. */
  .home-hero__tint {
    background:
      radial-gradient(52% 34% at 100% 56%, rgba(239, 145, 60, 0.3), transparent 66%),
      radial-gradient(76% 34% at 44% 40%, rgba(46, 159, 245, 0.14), transparent 72%);
  }

  .home-hero__wash {
    background:
      linear-gradient(180deg, transparent calc(100% - 110px), var(--ink-950) 100%),
      linear-gradient(
        180deg,
        rgba(var(--scrim-rgb), 0.97) 0%,
        rgba(var(--scrim-rgb), 0.58) 30%,
        rgba(var(--scrim-rgb), 0.18) 55%,
        transparent 75%
      ),
      linear-gradient(90deg, rgba(var(--scrim-rgb), 0.3), transparent 76%);
  }

  .home-hero__content {
    padding-top: calc(var(--header-height) + 44px);
  }

  .home-hero__lockup {
    width: min(90vw, 380px);
  }

  .home-hero__intro {
    max-width: 330px;
    margin-top: 22px;
    font-size: 0.9rem;
    line-height: 1.48;
  }

  .home-hero__actions {
    margin-top: 21px;
  }

  .liquid-drop {
    min-height: 0;
  }

  /* The device trio spans ~730 of the mobile master's 1003px width; the tall
     cover window at phone widths can only show 617px, so cover always cuts
     the subject. Contain shows the full master width, bottom-anchored, with
     a top fade so the image edge dissolves into the ink behind the copy. */
  .liquid-drop__backdrop img {
    top: auto;
    bottom: 0;
    /* The image box gets its own height budget instead of the whole section.
       The mobile master is 1003x1510 (150.5vw displayed at full width) with
       the pen caps ~26% down, i.e. the pens are the bottom ~74% of any
       contained render. The 86.5svh term is the art row's 64svh cap divided
       by 0.74: past it, a height-bound contain would push the pen caps above
       the row top and under the CTA (seen at 768px, where 150.5vw outruns
       the viewport height and contain flips from width-bound to
       height-bound). */
    height: min(150.5vw, 86.5svh, 100%);
    object-fit: contain;
    object-position: center bottom;
    -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 70px);
    mask-image: linear-gradient(180deg, transparent 0, #000 70px);
  }

  .liquid-drop__veil {
    background:
      linear-gradient(180deg, rgba(var(--scrim-rgb), 0.86) 0%, rgba(var(--scrim-rgb), 0.66) 32%, rgba(var(--scrim-rgb), 0.08) 60%, rgba(var(--scrim-rgb), 0.18) 100%),
      linear-gradient(90deg, rgba(var(--scrim-rgb), 0.22), transparent 72%);
  }

  .liquid-drop__inner {
    display: grid;
    width: 100%;
    grid-template-columns: 1fr;
    /* The art row tracks the pens, not the whole image. The mobile master is
       1003x1510 with the pen caps ~26% down, so the contained, bottom-anchored
       image displays at ~150.5vw tall and the pens occupy the bottom ~111vw.
       111vw of pens + ~10vw of breathing room = 121vw; the svh term stops a
       short landscape-ish viewport from letting the pens ride up into the
       copy. The smoke headroom above the caps overlaps behind the copy and is
       dissolved by the backdrop's top mask, instead of being reserved as an
       empty black band between the CTA and the product. */
    grid-template-rows: auto min(121vw, 64svh);
    gap: 0;
  }

  /* Left-aligned like every other section heading (Mark, 2026-08-24; the
     centered phone treatment read as a different voice than "Meet the ZMOKE
     Lineup" / "Look for ZMOKE..."). The size floor steps up to match the
     shared section-title floor while staying nowrap-safe at 320px. */
  .liquid-drop__copy {
    align-items: flex-start;
    justify-content: flex-start;
    padding: clamp(78px, 18vw, 108px) 22px 0;
    text-align: left;
  }

  .liquid-drop__title {
    position: relative;
    z-index: 2;
    margin-inline: 0;
    font-size: clamp(1.8rem, 9.8vw, 3.42rem);
  }

  .liquid-drop__summary {
    position: relative;
    z-index: 4;
    max-width: 36ch;
    margin-top: 24px;
    margin-inline: 0;
    font-size: clamp(0.94rem, 3.6vw, 1.05rem);
    line-height: 1.55;
  }

  .liquid-drop__families {
    justify-content: flex-start;
    gap: clamp(10px, 3vw, 18px);
    margin-top: 20px;
  }

  .liquid-drop__family + .liquid-drop__family {
    padding-left: clamp(10px, 3vw, 18px);
  }

  .liquid-drop__cta {
    position: relative;
    z-index: 4;
    width: min(100%, 360px);
    min-height: 58px;
    margin-top: 28px;
    font-size: 0.86rem;
  }

  .liquid-drop__stage {
    position: relative;
    z-index: 3;
    /* Height belongs to the grid row above; any min-height or margin here
       re-inflates the row past the pens and recreates the dead band. */
    min-height: 0;
    margin-top: 0;
    overflow: hidden;
  }

  .carry-band__inner {
    grid-template-columns: 48px 1fr;
    padding-block: 36px;
  }

  .carry-band__mark {
    width: 42px;
  }

  .carry-band__inner .button {
    grid-column: 2;
    justify-self: start;
  }

  .process-steps {
    grid-template-columns: 1fr;
  }

  /* :nth-child(n) matches every item at the same specificity as the base
     `.process-steps__item + .process-steps__item` column-divider rule, so
     this single-column reset actually wins. A plain .process-steps__item
     reset loses to it and leaves items 02 and 04 wearing a left border that
     only exists to divide columns, which reads as a broken alternating
     "boxed card" treatment when stacked. */
  .process-steps__item:nth-child(n) {
    padding: 26px 0;
    border-left: 0;
  }

  .product-hero__visual {
    min-height: 500px;
  }

  .product-hero__image--liquid {
    width: 115%;
  }

  .format-story__media {
    min-height: 520px;
  }

  .variant-grid {
    grid-template-columns: 1fr;
  }

  .variant-card {
    min-height: 540px;
  }

  .variant-card__media {
    height: 340px;
    padding-top: 24px;
  }

  /* The night card's body is one serif name + caption, so the base 540px
     floor left ~140px of dead ink between the glow and the name when the
     cards stack on phones. Media grows a little (the pen is vertical art),
     the floor drops to fit. */
  .variant-card--night {
    min-height: 480px;
  }

  .variant-card--night .variant-card__media {
    height: 370px;
  }

  .form-grid,
  .checkbox-group {
    grid-template-columns: 1fr;
  }

  .legal-layout {
    grid-template-columns: 1fr;
  }

  .legal-nav {
    position: static;
  }

  .legal-nav ul {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* The footer's single-column collapse and its two-up link grid moved to the
     980px block beside the footer's own rules (2026-08-29). */
}

@media (max-width: 480px) {
  /* Serif carry headline at the 1.98rem floor measures 3 lines on phones;
     this step holds it to 2 for copy up to ~33 characters, protecting
     whatever the owner-voice rewrite lands here later. */
  .carry-band h2 {
    font-size: clamp(1.6rem, 7vw, 3.96rem);
  }

  .button-row {
    align-items: flex-start;
    flex-direction: column;
  }

  .button-row .button {
    width: 100%;
  }

  .home-hero__actions {
    width: auto;
  }

  .home-hero__actions .button {
    width: auto;
  }

  .home-hero__actions .button:not(.button--text) {
    padding-inline: 42px;
  }

  .liquid-drop {
    min-height: 0;
  }

  .liquid-drop__copy {
    padding-top: clamp(72px, 18vw, 88px);
  }

  .liquid-drop__families {
    width: 100%;
    justify-content: flex-start;
    gap: 12px;
  }

  .liquid-drop__family {
    grid-template-columns: 7px auto;
    gap: 6px;
  }

  .liquid-drop__family + .liquid-drop__family {
    padding-left: 8px;
  }

  .liquid-drop__family-dot {
    width: 7px;
  }

  .liquid-drop__cta {
    width: 100%;
  }

  /* Superseded by the stacked card (2026-08-31): height is now the square
     media plus the body panel, so a fixed floor only adds dead ink. */
  .lineup-card {
    min-height: 0;
  }

  .lineup-card__body {
    padding-inline: 21px;
  }

  .lineup-card__title {
    padding-inline: 21px;
  }

  .product-hero__visual {
    min-height: 420px;
  }

  .product-hero__caption {
    display: none;
  }

  .format-story__media {
    min-height: 430px;
  }

  .detail-list > div {
    grid-template-columns: 1fr;
    gap: 7px;
  }

  .carry-hero__facts {
    grid-template-columns: 1fr;
  }

  .carry-fact {
    min-height: 130px;
  }

  .carry-fact span {
    margin-bottom: 32px;
  }

  .inquiry-form {
    padding: 22px 18px;
    border-radius: var(--radius-md);
  }

  .form-actions .button {
    width: 100%;
  }

  /* The footer nav deliberately does NOT drop to one column here any more:
     two-up is what keeps the phone footer short, and it holds to 320px. */

  .legal-nav ul {
    grid-template-columns: 1fr;
  }

  .age-gate__actions {
    align-items: stretch;
    flex-direction: column;
  }

  .age-gate__actions .button {
    width: 100%;
  }

}

/* The supporting imagery is portrait-oriented. Tablets keep that geometry in
   a two-column composition instead of stretching each card into a landscape
   row. The final card is centered so the four-product hierarchy remains 1+2+1. */
@media (min-width: 561px) and (max-width: 1080px) {
  .lineup-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* Superseded by the stacked card (2026-08-31): height is now the square
     media plus the body panel, so a fixed floor only adds dead ink. */
  .lineup-card {
    min-height: 0;
    aspect-ratio: auto;
  }

  .lineup-card.lineup-card--lead {
    grid-column: 1 / -1;
    width: auto;
    min-height: 0;
    height: clamp(280px, 30vw, 330px);
    aspect-ratio: auto;
  }

  .lineup-card--lead .lineup-card__media img {
    padding: 0;
    object-position: center;
  }

  .lineup-card--closing {
    grid-column: 1 / -1;
    width: calc((100% - 18px) / 2);
    justify-self: center;
  }
}

@media (min-width: 561px) and (max-width: 760px) {
  .lineup-card--lead .lineup-card__body p {
    max-width: 22ch;
  }

  .lineup-card:not(.lineup-card--lead) {
    aspect-ratio: 2 / 3;
  }
}

@media (max-width: 560px) {
  .lineup-family-selector {
    width: 100%;
    margin-top: 16px;
  }

  .lineup-family-option {
    min-height: 44px;
    gap: 6px;
    padding-inline: 8px;
    font-size: 0.75rem;
  }

  .lineup-grid {
    grid-template-columns: 1fr;
  }

  /* Superseded by the stacked card (2026-08-31): height is now the square
     media plus the body panel, so a fixed floor only adds dead ink. */
  .lineup-card {
    width: auto;
    height: auto;
    min-height: 0;
    aspect-ratio: auto;
  }

  .lineup-card.lineup-card--lead {
    height: clamp(390px, 110vw, 480px);
    min-height: 0;
    aspect-ratio: auto;
  }

  .lineup-card--closing {
    grid-column: auto;
    width: auto;
  }
}

/* Lab Results (2026-08-25): batch-COA cards. Ships in SAMPLE mode first —
   every value is a placeholder for Roy's layout review; the page is noindex
   and out of the sitemap until real COAs land and Greg clears the copy.
   Family chips key off the same three family classes as everywhere else
   (tokens only, per the Classification Rule); PASS values stay neutral
   text-strong because green never decorates. */
.coa-notice {
  max-width: 720px;
  margin-top: 26px;
  padding: 16px 20px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--ember);
  border-radius: var(--radius-sm);
  background: var(--ink-850);
  color: var(--text-soft);
  font-size: 0.86rem;
}

.coa-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  gap: 18px;
  margin-top: 38px;
}

.coa-card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 24px 22px 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--ink-850);
}

.coa-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.coa-card__format {
  color: var(--text-soft);
  font-size: 0.75rem;
  font-weight: 750;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.coa-card__name {
  margin-top: 6px;
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 500;
}

.coa-card__family {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--text-soft);
  font-size: 0.75rem;
  font-weight: 750;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  white-space: nowrap;
}

.coa-card__family::before {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  background: var(--family-color, var(--text-soft));
  content: "";
}

.coa-card__family--blue {
  --family-color: var(--blue);
}

.coa-card__family--green {
  --family-color: var(--green);
}

.coa-card__family--purple {
  --family-color: var(--purple);
}

.coa-card__rows {
  margin: 4px 0 0;
}

.coa-card__row {
  display: flex;
  justify-content: space-between;
  gap: 14px;
  padding-block: 8px;
  border-top: 1px solid var(--line);
}

.coa-card__row dt {
  color: var(--text-soft);
  font-size: 0.75rem;
  font-weight: 750;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.coa-card__row dd {
  margin: 0;
  color: var(--text-strong);
  font-size: 0.86rem;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.coa-card__pdf {
  margin-top: auto;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  color: var(--text-soft);
  font-size: 0.86rem;
}

/* Learn library (2026-09-03). Dedicated editorial cards replace the product
   cross-sell treatment on /learn/ without changing product-page navigation.
   The hub stays inside the existing ink, type, spacing, and breakpoint system;
   square-edged 5px cards distinguish reading material from product tiles. */
.page-learn .learn-intro {
  max-width: 64rem;
  margin-bottom: clamp(56px, 7vw, 92px);
}

.page-learn .learn-intro__title {
  max-width: none;
}

.page-learn .learn-intro__copy {
  max-width: 48.5em;
  margin-top: 24px;
  color: color-mix(in srgb, var(--text) 72%, var(--text-soft));
  line-height: 1.7;
}

.page-learn .learn-library__results {
  scroll-margin-top: calc(var(--header-height) + 24px);
}

.page-learn .learn-library__section + .learn-library__section {
  margin-top: clamp(56px, 6vw, 82px);
}

.page-learn .learn-library__section--topics {
  margin-bottom: 36px;
  padding-block: clamp(32px, 4vw, 46px);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.page-learn .learn-library__context {
  margin: 0 0 20px;
  color: var(--text-dim);
  font-size: 0.95rem;
}

.learn-article-back {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-bottom: 20px;
  color: var(--text-soft);
  font-size: 0.95rem;
}

/* Keep long article-title words readable inside a narrow phone column. */
@media (max-width: 400px) {
  .page-learn-article .legal-hero h1 {
    font-size: clamp(2.2rem, 11.25vw, 2.7rem);
  }
}

.learn-article-footer {
  margin-top: 48px;
  padding-top: 32px;
  border-top: 1px solid var(--line);
}

.learn-article-meta {
  color: var(--text-dim);
  font-size: 0.9rem;
}

.learn-article-footer__links {
  margin: 0;
  padding: 0;
  list-style: none;
}

.learn-article-footer__links a {
  display: inline-block;
  padding-block: 10px;
}

.learn-article-footer__links p {
  margin: 0 0 16px;
  color: var(--text-dim);
}

.page-learn .learn-library__heading {
  margin: 0 0 clamp(24px, 3vw, 34px);
  font-family: var(--font-display);
  font-size: clamp(2rem, 3.2vw, 3.35rem);
  font-weight: 500;
  letter-spacing: normal;
  line-height: 1.08;
  text-wrap: balance;
}

.page-learn .learn-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-auto-rows: 1fr;
  gap: 24px;
}

.page-learn .learn-card {
  min-width: 0;
  height: 100%;
}

.page-learn .learn-card__link {
  display: flex;
  height: 100%;
  overflow: hidden;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--ink-850);
  color: var(--text);
  text-decoration: none;
  transition: border-color 180ms ease, background-color 180ms ease;
}

.page-learn .learn-card__link:hover,
.page-learn .learn-card__link:focus-visible {
  border-color: color-mix(in srgb, var(--ember-light) 68%, var(--line));
  background: color-mix(in srgb, var(--ink-850) 94%, var(--ember));
}

.page-learn .learn-card__media {
  overflow: hidden;
  aspect-ratio: 3 / 2;
  flex: none;
  border-radius: 5px 5px 0 0;
  background: var(--ink-900);
}

.page-learn .learn-card__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 280ms ease;
}

/* The existing South Dakota art is portrait. Its upper-fifth crop keeps the
   cartridge and battery recognizable inside the required 3:2 card frame. */
.page-learn .learn-card__image--south-dakota-medical-program {
  object-position: center 20%;
}

.page-learn .learn-card__link:hover .learn-card__image,
.page-learn .learn-card__link:focus-visible .learn-card__image {
  transform: scale(1.02);
}

.page-learn .learn-card__body {
  display: flex;
  min-width: 0;
  height: 100%;
  flex-direction: column;
  padding: clamp(28px, 2.2vw, 32px);
}

.page-learn .learn-card__category {
  margin: 0 0 13px;
  color: var(--ember-light);
  font-size: 0.74rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  line-height: 1.2;
  text-transform: uppercase;
}

.page-learn .learn-card__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.62rem, 2.25vw, 2.18rem);
  font-weight: 500;
  letter-spacing: normal;
  line-height: 1.12;
  text-wrap: balance;
}

.page-learn .learn-card__excerpt {
  margin: 18px 0 0;
  color: var(--text-soft);
  font-size: 0.96rem;
  line-height: 1.65;
}

.page-learn .learn-card__date {
  margin: 17px 0 0;
  color: var(--text-dim);
  font-size: 0.78rem;
  line-height: 1.45;
}

.page-learn .learn-card__cta {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: 9px;
  margin-top: auto;
  padding-top: 28px;
  color: var(--ember-light);
  font-size: 0.8rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  line-height: 1.2;
  text-transform: uppercase;
}

.page-learn .learn-card__cta-label {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}

/* The Learn brief explicitly calls for a right arrow on internal article
   cards, superseding the general no-arrow convention for this component. */
.page-learn .learn-card__arrow {
  transition: transform 180ms ease;
}

.page-learn .learn-card__link:hover .learn-card__arrow,
.page-learn .learn-card__link:focus-visible .learn-card__arrow {
  transform: translateX(4px);
}

.page-learn .learn-card--latest .learn-card__body {
  padding: 24px;
}

.page-learn .learn-card--latest .learn-card__title {
  font-size: clamp(1.45rem, 1.9vw, 1.82rem);
}

.page-learn .learn-topics__list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.page-learn .learn-topics__link {
  display: inline-flex;
  min-height: 42px;
  align-items: center;
  padding: 9px 14px;
  border: 1px solid var(--line);
  border-radius: 5px;
  color: var(--text-soft);
  font-size: 0.82rem;
  font-weight: 700;
  line-height: 1.25;
  text-decoration: none;
  transition: border-color 180ms ease, color 180ms ease, background-color 180ms ease;
}

.page-learn .learn-topics__link:hover,
.page-learn .learn-topics__link:focus-visible,
.page-learn .learn-topics__link.is-active {
  border-color: color-mix(in srgb, var(--ember-light) 60%, var(--line));
  background: rgba(255, 255, 255, 0.035);
  color: var(--text);
}

.page-learn .learn-library__more {
  display: inline-flex;
  margin-top: 30px;
  color: var(--ember-light);
  font-size: 0.86rem;
  font-weight: 750;
  text-underline-offset: 4px;
}

@media (max-width: 980px) {
  .page-learn .learn-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .page-learn .learn-grid--featured .learn-card:last-child:nth-child(odd) {
    grid-column: 1 / -1;
    width: calc((100% - 24px) / 2);
    justify-self: center;
  }
}

@media (max-width: 560px) {
  .page-learn .learn-grid {
    grid-template-columns: 1fr;
    grid-auto-rows: auto;
  }

  .page-learn .learn-grid--featured .learn-card:last-child:nth-child(odd) {
    grid-column: auto;
    width: 100%;
  }

  .page-learn .learn-card__body {
    padding: 24px;
  }

  .page-learn .learn-topics__list {
    gap: 8px;
  }

  .page-learn .learn-topics__link {
    min-height: 44px;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }

  .js .reveal {
    opacity: 1;
    transform: none;
  }

  /* Hero entrance collapses to its final frame; the stage holds still. */
  .js .home-hero__lockup,
  .js .home-hero__intro,
  .js .home-hero__actions,
  .js .home-hero__cluster img {
    animation: none;
  }

  .home-hero__art img,
  .home-hero__veil img {
    animation: none;
  }

  /* site.js already declines to build the clip under reduced motion; this is
     the belt to that braces, so a cached/stale script can never leave looping
     video running for someone who asked for stillness. */
  .home-hero__motion-veil {
    display: none;
  }

  /* `animation: none` has to be repeated here. The foreground-pass rule above
     targets .home-hero.has-motion-veil and so outranks the plain
     .home-hero__veil img reset in this block on specificity, which would let a
     stale script re-animate the plate for someone who asked for stillness. */
  .home-hero.has-motion-veil .home-hero__veil img {
    animation: none;
    opacity: 0.5;
  }

  .page-learn .learn-card__link:hover .learn-card__image,
  .page-learn .learn-card__link:focus-visible .learn-card__image,
  .page-learn .learn-card__link:hover .learn-card__arrow,
  .page-learn .learn-card__link:focus-visible .learn-card__arrow {
    transform: none;
  }
}

/* Short viewports: width-driven vertical spacing overshoots when the screen is
   short (laptop displays at high scaling). Trim the vertical budget and pin
   hero copy to the top of its row so page tops don't read as empty. */
@media (max-height: 640px) {
  :root {
    --section-space: clamp(48px, 10vh, 80px);
  }

  .section--tight {
    padding-block: clamp(40px, 8vh, 60px);
  }

  .product-hero__inner {
    padding-block: clamp(28px, 6vh, 48px);
  }

  .product-hero__copy {
    align-self: start;
  }

  .home-hero__content {
    padding-top: calc(var(--header-height) + clamp(32px, 6vh, 68px));
  }

  .carry-hero {
    padding-top: calc(var(--header-height) + clamp(36px, 7vh, 64px));
    padding-bottom: clamp(48px, 8vh, 78px);
  }
}

/* Landscape phones (drawer-nav widths at short heights): the bottom-anchored
   hero tableau rises into the text zone, so the intro keeps a narrower measure
   than its 545px desktop width. Wider short viewports (1016x504 and up) keep
   the approved look untouched. */
@media (max-height: 640px) and (min-width: 781px) and (max-width: 980px) {
  .home-hero__intro {
    max-width: 400px;
  }
}

/* G11 combined closing band: primary retailer-finder action, secondary trade link. */
.carry-band--combined .carry-band__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px clamp(24px, 2.5vw, 36px);
}
.carry-band--combined .carry-band__trade {
  flex: 0 0 auto;
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.6;
}
.carry-band--combined .carry-band__trade a {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  color: var(--text-soft);
  text-decoration: underline;
  text-underline-offset: 4px;
}
.carry-band--combined .carry-band__actions .button {
  flex: 0 0 auto;
  grid-column: auto;
  white-space: nowrap;
}
@media (max-width: 780px) {
  .carry-band--combined .carry-band__actions {
    grid-column: 2;
    min-width: 0;
  }
}
