/* ===========================================================================
   VELVET EMBRACE — STYLES
   ---------------------------------------------------------------------------
   She asked for a sage green background, and this takes her at her word: the
   page ground IS sage, not white-with-sage-accents. It is the PALE sage from
   her logo's marble, with the deeper sage used for bands and the deepest for
   type — because the sampled sage is 2.6:1 on white and cannot carry text.
   Run tools/sample-brand.py to see that worked out.

   Fraunces and Figtree, SELF-HOSTED — see tools/fetch-fonts.py. No
   third-party request is made by this site for anything: it takes names and
   addresses, and leaking every visitor to a font host to borrow a typeface
   is not a trade the visitor agreed to.
   ========================================================================= */

/* ⚠ NO @import HERE, DELIBERATELY.
   styles.css used to pull fonts.css and tokens.css in with @import, which
   the browser cannot see until styles.css has arrived and parsed — three
   round trips in a chain instead of three in parallel, all of them blocking
   the first paint. The three files are <link>ed from the HTML instead, in
   the same order. Custom properties do not care which sheet defines them, so
   nothing else changed.
   ⚠ Any page that loads styles.css must load fonts.css and tokens.css too. */

/* =========================================================================
   RESET
   ========================================================================= */

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

/* ⚠ [hidden] HAS TO WIN, and by default it does not.
   The attribute is styled by the UA sheet at the very bottom of the cascade,
   so ANY display rule of ours beats it — and .btn is display:inline-flex.
   That is how the finder shipped with a "Back" button on question one and a
   "Next" that should not have been there. Everything on this site that hides
   itself does so with the hidden attribute, so this rule is load-bearing. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1.0625rem;
  line-height: 1.65;
  /* clip, NOT hidden. `overflow-x: hidden` on body makes body a scroll
     container, which silently kills position:sticky on the header. That bug
     has now been shipped on two other sites in this stable. */
  overflow-x: clip;
}

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

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 400;
  font-variation-settings: var(--display-settings);
  margin: 0;
}

/* Fraunces' optical size axis, used as intended: bigger type gets a higher
   opsz, which blooms the serifs and opens the counters. Set per size rather
   than left at one value, because that axis is the whole reason this face was
   chosen over a static serif. */
.h1  { font-variation-settings: 'SOFT' 40, 'WONK' 0, 'opsz' 110; }
.h2  { font-variation-settings: 'SOFT' 35, 'WONK' 0, 'opsz' 60; }
.h3  { font-variation-settings: 'SOFT' 30, 'WONK' 0, 'opsz' 28; }

a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--sage-deep);
  outline-offset: 3px;
  border-radius: 2px;
}

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.skip {
  position: absolute; left: 1rem; top: -4rem; z-index: 200;
  background: var(--sage-deep); color: var(--white);
  padding: .7rem 1.1rem; border-radius: var(--radius);
  transition: top .2s var(--ease-soft);
}
.skip:focus { top: 1rem; }

/* =========================================================================
   LAYOUT
   ========================================================================= */

.wrap {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--pad);
}
.wrap--narrow { --max: 900px; }

.section { padding-block: var(--section); position: relative; }

/* Every anchor target clears the sticky header. Without this, "How to order"
   in the nav scrolls the section heading UNDER the bar and the first thing
   you see is the second paragraph. --header-h is measured by site.js; the
   fallback is close enough that nothing jumps before the script runs. */
:target,
section[id],
[id^="order"],
#top { scroll-margin-top: calc(var(--header-h, 4.5rem) + 1.25rem); }

/* The two alternating grounds. --paper is the default body ground; these are
   the other two the page moves between. */
.bg-white { background: var(--white); }
.bg-sage  { background: var(--sage-wash); }

.section-head { margin-bottom: clamp(2.25rem, 4.5vw, 3.5rem); max-width: 42ch; }
.section-head--center { margin-inline: auto; text-align: center; }

/* ⚠ --sage-deep, NOT --sage-mid. The mid tone reads beautifully and measures
   3.8:1 on white and 3.29:1 over the hero's tint — both below the 4.5:1 that
   small text needs. Caught by tools/check-a11y.py walking the rendered page,
   not by looking at it, which is exactly the kind of failure that survives a
   design review: it looks fine to whoever picked it. */
.eyebrow {
  display: inline-flex; align-items: center; gap: .7rem;
  font-size: .7rem; font-weight: 620;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--sage-deep);
  margin: 0 0 1rem;
}
.eyebrow::before {
  content: ''; width: 1.6rem; height: 1px; background: currentColor;
  flex: none;
}
.section-head--center .eyebrow::after {
  content: ''; width: 1.6rem; height: 1px; background: currentColor; flex: none;
}

.h1 {
  font-size: clamp(2.1rem, 5.6vw, 3.5rem);
  line-height: 1.06;
  letter-spacing: -.02em;
  text-wrap: balance;
}
.h2 { font-size: clamp(1.6rem, 3.4vw, 2.25rem); line-height: 1.15; text-wrap: balance; }
.h3 { font-size: 1.3rem; line-height: 1.25; }

.lede {
  font-size: clamp(1.03rem, 1.5vw, 1.15rem);
  color: var(--ink-soft);
  margin: 1.1rem 0 0;
  max-width: var(--max-text);
}
.section-head--center .lede { margin-inline: auto; }

/* =========================================================================
   BUTTONS
   ========================================================================= */

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: .55rem;
  padding: .95rem 1.7rem;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background: var(--sage-deep);
  color: var(--white);
  font-family: var(--sans);
  font-size: .95rem; font-weight: 560; letter-spacing: .01em;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .3s var(--ease-soft), transform .3s var(--ease),
              box-shadow .3s var(--ease-soft);
}
.btn:hover { background: var(--sage-mid); transform: translateY(-1px); box-shadow: var(--shadow); }
.btn:active { transform: translateY(0); }
.btn[disabled], .btn[aria-disabled="true"] {
  opacity: .5; pointer-events: none;
}

.btn--ghost {
  background: transparent; color: var(--sage-deep);
  border-color: var(--sage-soft);
}
.btn--ghost:hover { background: var(--sage-wash); border-color: var(--sage-mid); }

.btn--lg { padding: 1.1rem 2.1rem; font-size: 1rem; }
.btn--block { width: 100%; }

.btn__arrow { width: 1rem; height: 1rem; flex: none; }

/* =========================================================================
   HEADER
   ========================================================================= */

.header {
  position: sticky; top: 0; z-index: 60;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  border-bottom: 1px solid transparent;
  transition: border-color .35s var(--ease-soft), background-color .35s var(--ease-soft);
}
/* The frosting goes on a PSEUDO-ELEMENT, never on .header itself. An element
   with backdrop-filter becomes the containing block for its position:fixed
   descendants, which traps the mobile nav panel inside a 60px bar. */
.header::before {
  content: ''; position: absolute; inset: 0; z-index: -1;
  backdrop-filter: saturate(1.4) blur(12px);
  -webkit-backdrop-filter: saturate(1.4) blur(12px);
}
.header.is-stuck { border-bottom-color: var(--sage-soft); }

.header__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem;
  padding-block: .85rem;
}

.brand {
  display: flex; align-items: center; text-decoration: none;
  flex: none;
}
.brand__lockup { height: clamp(2.4rem, 4.6vw, 3rem); width: auto; }
/* 2.75rem, not 2.5: at 40px this was the one control on the phone
   layout under the 44px touch minimum. */
.brand__emblem { height: 2.75rem; width: auto; display: none; }

/* Below this the lockup's wordmark is competing with the menu button for the
   same 40px. The emblem alone still says Velvet Embrace — it has the word in
   it — and it says it legibly. */
@media (max-width: 420px) {
  .brand__lockup { display: none; }
  .brand__emblem { display: block; }
}

.nav { display: flex; align-items: center; gap: 1.9rem; }
/* Padded to a real target. WCAG 2.2 asks 24x24 for a pointer; these were
   23px tall, which is the kind of miss nobody finds by eye. The padding is
   vertical only, so the visual rhythm of the bar is unchanged. */
.nav__link {
  display: inline-flex; align-items: center;
  min-height: 2.75rem;
  padding-block: .35rem;
  font-size: .87rem; font-weight: 500; letter-spacing: .02em;
  color: var(--ink-soft); text-decoration: none;
  transition: color .25s var(--ease-soft);
}
.nav__link:hover { color: var(--sage-deep); }

.nav-toggle {
  display: none;
  width: 2.75rem; height: 2.75rem;
  border: 0; background: transparent; cursor: pointer;
  padding: 0; flex-direction: column; gap: 5px;
  align-items: center; justify-content: center;
}
.nav-toggle__bar {
  width: 22px; height: 1.5px; background: var(--ink);
  transition: transform .35s var(--ease), opacity .25s var(--ease-soft);
}
.nav.is-open ~ .nav-toggle .nav-toggle__bar:nth-child(1),
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

@media (max-width: 860px) {
  .nav-toggle {
    display: flex;
    /* ⚠ ABOVE THE PANEL, and this is not cosmetic. The panel is fixed and
       full height, so it covers the top-right corner the toggle lives in —
       which made the close button untappable and left the menu with no
       visible way out. Caught by the browser test, not by looking at it. */
    position: relative;
    z-index: 2;
  }
  .nav {
    position: fixed; inset: 0 0 0 auto;
    z-index: 1;
    width: min(20rem, 82vw);
    background: var(--white);
    flex-direction: column; align-items: flex-start; justify-content: center;
    gap: 1.6rem;
    /* Clears the header bar so the first link is never under the logo. */
    padding: calc(var(--header-h, 4.5rem) + 1.5rem) var(--pad) 2rem;
    transform: translateX(101%);
    transition: transform .45s var(--ease);
    box-shadow: var(--shadow-lg);
    overflow-y: auto;
  }
  .nav.is-open { transform: translateX(0); }
  /* Padded to a real thumb target. At desktop sizes these are 1.4rem tall
     text links, which is fine for a cursor and not for a finger — the
     browser suite asserts 40px minimum here. */
  .nav__link {
    font-size: 1.15rem;
    width: 100%;
    padding-block: .55rem;
  }
  .nav .btn { width: 100%; }

  /* Tapping the page behind the menu closes it, which is what everyone
     expects and what stops the panel feeling like a trap. */
  .nav-scrim {
    position: fixed; inset: 0; z-index: 55;
    background: rgba(47, 52, 46, .38);
    opacity: 0; pointer-events: none;
    transition: opacity .35s var(--ease-soft);
  }
  .nav-scrim.is-on { opacity: 1; pointer-events: auto; }
}

/* =========================================================================
   HERO
   ========================================================================= */

.hero {
  position: relative;
  padding-block: clamp(3.5rem, 9vw, 7rem) clamp(3.5rem, 8vw, 6rem);
  overflow: hidden;
}

/* A soft sage bloom behind the type. Two radial gradients, no image — it is
   atmosphere, and an image here would be one more thing to load. */
/* ⚠ inset:0, NOT an oversized box. This used to be inset:-20% -10% auto -10%
   with height:130%, which is invisible (the hero clips) but still extends the
   hero's SCROLLABLE overflow — leaving a section that can be scrolled inside
   itself by a keyboard or a trackpad, for no reason and with nothing to see.
   The bleed now comes from the gradient's own geometry instead. */
.hero::before {
  content: ''; position: absolute; inset: 0;
  background:
    radial-gradient(70% 66% at 18% 22%, color-mix(in srgb, var(--sage) 34%, transparent) 0%, transparent 70%),
    radial-gradient(60% 58% at 86% 76%, color-mix(in srgb, var(--sage-soft) 55%, transparent) 0%, transparent 72%);
  z-index: -1;
  pointer-events: none;
}

/* The sillage canvas. Behind everything in the hero, never in the way of a
   pointer, and it never contributes to layout — position:absolute so a
   resize cannot make the hero grow to fit its own background. */
.sillage {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  z-index: 0;
  pointer-events: none;
}
.hero > .wrap { position: relative; z-index: 1; }

.hero__grid {
  display: grid;
  gap: clamp(2.5rem, 5vw, 4rem);
  align-items: center;
}
@media (min-width: 900px) {
  /* The sillage canvas. Behind everything in the hero, never in the way of a
   pointer, and it never contributes to layout — position:absolute so a
   resize cannot make the hero grow to fit its own background. */
.sillage {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  z-index: 0;
  pointer-events: none;
}
.hero > .wrap { position: relative; z-index: 1; }

.hero__grid { grid-template-columns: 1.05fr .95fr; }
}

.hero__title { margin-bottom: .3rem; }
.hero__accent {
  display: block;
  color: var(--sage-deep);
  font-style: italic;
}
.hero__intro { max-width: 40ch; }

.hero__actions {
  display: flex; flex-wrap: wrap; gap: .8rem;
  margin-top: 2.2rem;
}

/* ---- the drawn panel, used when she has sent no photograph ---- */
.hero__figure {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--white);
  box-shadow: var(--shadow);
  /* Matches the illustration's viewBox, so there is never a band of
     empty panel under the shelf. A photograph replaces it and brings its
     own ratio, which is what object-fit: cover below is for. */
  aspect-ratio: 400 / 442;
}
.hero__figure img { width: 100%; height: 100%; object-fit: cover; }
.hero__panel { width: 100%; height: 100%; }

/* =========================================================================
   ORDER ROUTES
   ========================================================================= */

.routes {
  display: grid; gap: var(--gap);
  grid-template-columns: 1fr;
}
@media (min-width: 780px) { .routes { grid-template-columns: 1fr 1fr; } }

.route {
  display: flex; flex-direction: column;
  background: var(--white);
  border: 1px solid var(--sage-soft);
  border-radius: var(--radius-lg);
  padding: clamp(1.6rem, 3vw, 2.4rem);
  box-shadow: var(--shadow-sm);
  transition: transform .35s var(--ease), box-shadow .35s var(--ease-soft);
}
.route:hover { transform: translateY(-3px); box-shadow: var(--shadow); }

.route__eyebrow {
  /* --sage-deep for contrast; see the note on .eyebrow */
  font-size: .68rem; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--sage-deep);
  margin: 0 0 .9rem;
}
.route__title { margin-bottom: .8rem; }
.route__body { margin: 0; color: var(--ink-soft); }

.route__points { list-style: none; margin: 1.3rem 0 0; padding: 0; }
.route__point {
  display: flex; gap: .65rem; align-items: flex-start;
  margin-bottom: .55rem; font-size: .95rem; color: var(--ink-soft);
}
.route__tick { width: 1.05rem; height: 1.05rem; color: var(--sage-mid); flex: none; margin-top: .32rem; }

.route__cta { margin-top: auto; padding-top: 1.8rem; }

/* ---- the difference table ---- */
.compare { margin-top: clamp(2.5rem, 5vw, 3.5rem); }
.compare__scroll { overflow-x: auto; }
.compare__table {
  width: 100%; border-collapse: separate; border-spacing: 0;
  min-width: 30rem;
  background: var(--white);
  border: 1px solid var(--sage-soft);
  border-radius: var(--radius-lg);
  overflow: hidden;
  font-size: .95rem;
}
.compare__table th, .compare__table td {
  text-align: left; padding: .95rem 1.15rem;
  box-shadow: inset 0 -1px 0 var(--sage-wash);
}
.compare__table thead th {
  font-family: var(--sans);
  font-size: .68rem; font-weight: 700;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--sage-deep);
  background: var(--sage-wash);
  box-shadow: none;
}
.compare__table tbody tr:last-child th,
.compare__table tbody tr:last-child td { box-shadow: none; }
.compare__label { font-weight: 500; color: var(--ink); }
.compare__table td { color: var(--ink-soft); }

/* ---- the line that points at the join panel ---------------------------
   Small, centred, and deliberately NOT a card or a button. It is an aside
   about a thing that is not an order route, and the moment it starts to look
   like one of the two options above it, it has broken the separation those
   two sections exist to keep. See the note in index.html. */
.nudge {
  margin: clamp(1.75rem, 3.5vw, 2.5rem) auto 0;
  max-width: 58ch;
  text-align: center;
  font-size: .95rem;
  color: var(--ink-soft);
}

/* ⚠ A REAL TAP TARGET, EVEN MID-SENTENCE. As a plain inline link this was
     133x18 and failed the 44px minimum — a link somebody is meant to press
     with a thumb on a phone. inline-flex with a min-height keeps it inside
     the paragraph and gives it a box worth aiming at; the drawn underline is
     then repositioned to sit under the WORDS rather than under the box. */
.nudge .link-draw {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding-inline: .2rem;
}
.nudge .link-draw::after { bottom: .68rem; }

/* =========================================================================
   RANGE
   ========================================================================= */

.range__grid {
  display: grid; gap: var(--gap);
  grid-template-columns: 1fr;
}
@media (min-width: 620px) { .range__grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 980px) { .range__grid { grid-template-columns: repeat(4, 1fr); } }

.cat {
  background: var(--white);
  border-radius: var(--radius-lg);
  padding: 1.6rem;
  border: 1px solid var(--sage-soft);
}
.cat__figure {
  border-radius: var(--radius); overflow: hidden;
  margin-bottom: 1.2rem; aspect-ratio: 4 / 3; background: var(--sage-wash);
}
.cat__figure img { width: 100%; height: 100%; object-fit: cover; }
.cat__rule { width: 2rem; height: 2px; background: var(--sage); margin-bottom: 1rem; }
.cat__title { font-size: 1.18rem; margin-bottom: .5rem; }
.cat__body { margin: 0; font-size: .95rem; color: var(--ink-soft); }

/* =========================================================================
   THE SCENT FINDER
   -------------------------------------------------------------------------
   One card that changes its contents rather than four screens that slide.
   Sliding panels here would mean either a fixed height (wrong for four
   questions of different lengths) or a jumping page. Instead the card grows
   and shrinks with a transition on its own height, so the eye stays put.
   ========================================================================= */

.finder {
  background: var(--white);
  border: 1px solid var(--sage-soft);
  border-radius: var(--radius-lg);
  padding: clamp(1.5rem, 4vw, 3rem);
  box-shadow: var(--shadow);
  position: relative;
  overflow: hidden;
}
/* A very faint bloom, so the card is not a flat white slab on a pale ground */
.finder::before {
  content: ''; position: absolute; inset: auto -20% -60% 40%;
  aspect-ratio: 1;
  background: radial-gradient(circle, color-mix(in srgb, var(--sage) 22%, transparent), transparent 68%);
  pointer-events: none;
}
.finder > * { position: relative; }

/* ---- progress ---- */
.finder__dots {
  list-style: none; margin: 0 0 1.75rem; padding: 0;
  display: flex; gap: .45rem;
}
.finder__dot {
  height: 3px; flex: 1 1 0;
  background: var(--sage-wash);
  border-radius: 2px;
  overflow: hidden;
  position: relative;
}
.finder__dot::after {
  content: ''; position: absolute; inset: 0;
  background: var(--sage-mid);
  transform: scaleX(0); transform-origin: left;
  transition: transform .5s var(--ease);
}
.finder__dot.is-done::after { transform: scaleX(1); }
.finder__dot.is-now::after  { transform: scaleX(.35); }

.finder__count {
  margin: 0 0 .5rem;
  font-size: .72rem; font-weight: 650;
  letter-spacing: .15em; text-transform: uppercase;
  color: var(--sage-deep);
  font-variant-numeric: tabular-nums;
}
/* The stage animates between question heights rather than being pinned to
   the tallest. Pinning stopped the jump but left half a screen of white
   under the three-option questions on a phone — a cure worse than the
   disease. Growing smoothly keeps the card's top still, which is the thing
   that actually mattered. */
.finder__stage {
  transition: height .42s var(--ease);
  overflow: hidden;
}
@media (prefers-reduced-motion: reduce) {
  .finder__stage { transition: none; }
}

.finder__q { margin: 0; }
.finder__hint { margin: .7rem 0 0; color: var(--ink-faint); font-size: .9rem; }

/* ---- the options ---- */
.finder__opts {
  display: grid; gap: .65rem;
  grid-template-columns: 1fr;
  margin-top: 1.75rem;
}
@media (min-width: 560px) {
  .finder__opts { grid-template-columns: 1fr 1fr; }
  .finder__opts[data-cols="3"] { grid-template-columns: repeat(3, 1fr); }
}

.chip {
  display: flex; flex-direction: column; gap: .12rem;
  text-align: left;
  padding: .95rem 1.1rem;
  min-height: 3.4rem;
  border: 1px solid var(--sage-soft);
  border-radius: var(--radius);
  background: var(--paper-warm);
  color: var(--ink);
  font-family: var(--sans); font-size: .97rem;
  cursor: pointer;
  transition: border-color .22s var(--ease-soft), background-color .22s var(--ease-soft),
              transform .22s var(--ease), box-shadow .22s var(--ease-soft);
}
.chip:hover { border-color: var(--sage-mid); transform: translateY(-1px); }
.chip__label { font-weight: 500; }
.chip__detail { font-size: .8rem; color: var(--ink-faint); }

/* The tick appears INSIDE the chip rather than changing its whole colour: a
   selected chip that goes solid dark makes the unselected ones read as
   disabled, and this is a question where several answers are fine. */
.chip[aria-pressed="true"] {
  border-color: var(--sage-deep);
  background: var(--white);
  box-shadow: inset 0 0 0 1px var(--sage-deep), var(--shadow-sm);
}
.chip[aria-pressed="true"] .chip__label::after {
  content: '✓';
  margin-left: .5rem;
  color: var(--sage-deep);
  font-weight: 700;
}

.finder__nav {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; margin-top: 1.9rem;
}
.finder__back {
  display: inline-flex; align-items: center; gap: .4rem;
  background: none; border: 0; cursor: pointer;
  color: var(--ink-soft); font-family: var(--sans); font-size: .9rem;
  padding: .5rem .2rem;
}
.finder__back:hover { color: var(--sage-deep); }
.finder__back svg { width: 1rem; height: 1rem; }
.finder__nav .btn { margin-left: auto; }

/* ---- the result ---- */
.finder__result-label {
  margin: 0 0 .9rem;
  font-size: .72rem; font-weight: 650;
  letter-spacing: .15em; text-transform: uppercase;
  color: var(--sage-deep);
}
.finder__quote {
  margin: 0;
  padding: 1.35rem 1.5rem;
  background: var(--sage-wash);
  border-radius: var(--radius);
  font-family: var(--display);
  font-variation-settings: 'SOFT' 30, 'WONK' 0, 'opsz' 24;
  font-size: clamp(1.05rem, 2.2vw, 1.28rem);
  line-height: 1.5;
  color: var(--ink);
}
.finder__result-actions {
  display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 1.6rem;
}

/* =========================================================================
   JOIN
   ========================================================================= */

.join {
  background: var(--sage-deep);
  color: var(--white);
  border-radius: var(--radius-lg);
  padding: clamp(2rem, 5vw, 3.5rem);
  position: relative;
  overflow: hidden;
}
.join::after {
  content: ''; position: absolute; right: -12%; top: -40%;
  width: 60%; aspect-ratio: 1; border-radius: 50%;
  background: color-mix(in srgb, var(--white) 7%, transparent);
  pointer-events: none;
}
.join__inner { position: relative; max-width: 46ch; }
/* --sage-wash, not --sage-soft: the softer tone is 4.31:1 on the deep green
   panel and just misses. The wash is 5.34:1. */
.join .eyebrow { color: var(--sage-wash); }
.join__title { color: var(--white); }
.join__body { color: color-mix(in srgb, var(--white) 82%, transparent); margin: 1.1rem 0 0; }
.join__note {
  margin: 1.1rem 0 0; font-size: .85rem;
  color: color-mix(in srgb, var(--white) 66%, transparent);
}
.join .btn {
  background: var(--white); color: var(--sage-deep); margin-top: 1.9rem;
}
.join .btn:hover { background: var(--sage-wash); }

/* ---- the two columns, and the card that carries her registration link ---
   ⚠  THE LAYOUT LIVES HERE, NOT IN motion.css. The panel holding the only
      link on the site to something a customer cannot reach any other way
      must survive that optional sheet being deleted. Only the drift, the
      lift and the shine are over there.
   The card is glass over the deep sage rather than another solid block:
   two solid rectangles inside one panel read as a mistake. */
.join__grid {
  display: grid;
  gap: clamp(1.75rem, 4vw, 3rem);
  align-items: center;
}
@media (min-width: 860px) { .join__grid { grid-template-columns: 1.25fr .75fr; } }

.join__inner { max-width: 46ch; }

/* ---- the card that holds her actual link ------------------------------
   Somebody who wants to register does not want to be told about it; they
   want the link. So it is on the page, legibly, next to a button that copies
   it — which is also the fastest way for HER to grab it when somebody asks
   in a message. */
.join__card {
  background: color-mix(in srgb, var(--white) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--white) 26%, transparent);
  border-radius: var(--radius-lg);
  padding: clamp(1.3rem, 3vw, 1.75rem);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  text-align: center;
}
/* ⚠ HER LOGO NEEDS A GROUND ON THIS PANEL. The mark is a sage-green disc,
     and the card it sits on is deep sage: on the reversed vector emblem that
     was fine (it is pale line-work), but her own logo simply disappeared into
     the panel — a picture of nothing, in the one place a customer is deciding
     whether to trust the person behind it. So it is printed on a pale tile,
     the way a label is printed on a box. */
.join__seal {
  display: block;
  width: 5.4rem; height: auto;
  margin: 0 auto 1.15rem;
  padding: .45rem;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--paper) 92%, var(--white));
  box-shadow: 0 6px 18px rgba(20, 30, 20, .22);
}
.join__card-label {
  margin: 0 0 .6rem;
  font-size: .68rem; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
  color: var(--sage-wash);
}
/* inline-block, so the dashed rule under it is as wide as the address and
   reads as a link. As a block it spanned the whole card and read as a
   divider with a URL sitting on top of it. */
.join__url {
  display: inline-block;
  max-width: 100%;
  margin: 0 0 1.3rem;
  font-size: 1rem;
  color: var(--white);
  text-decoration: none;
  overflow-wrap: anywhere;
  border-bottom: 1px dashed color-mix(in srgb, var(--white) 45%, transparent);
  padding-bottom: .15rem;
}
.join__url:hover { border-bottom-color: var(--white); }
/* ⚠ min-height, not padding alone. At .6rem of padding round .85rem type
     this measured 39px — under the 44px a thumb needs, and the kind of miss
     that only a tool catches because it looks perfectly fine. */
.join__copy {
  background: transparent;
  color: var(--white);
  border-color: color-mix(in srgb, var(--white) 45%, transparent);
  font-size: .85rem;
  min-height: 2.75rem;
  padding: .6rem 1.3rem;
}
.join__copy:hover {
  background: color-mix(in srgb, var(--white) 14%, transparent);
  color: var(--white);
  border-color: var(--white);
}
.join__copied {
  margin: .7rem 0 0;
  min-height: 1.1rem;
  font-size: .8rem;
  color: var(--sage-wash);
}

.join__actions { display: flex; flex-wrap: wrap; gap: .8rem; align-items: center; margin-top: 1.9rem; }
.join__actions .btn { margin-top: 0; }

/* =========================================================================
   ORDER FORM
   ========================================================================= */

.order__panel {
  background: var(--white);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  overflow: hidden;
  display: grid;
  grid-template-columns: 1fr;
}
@media (min-width: 900px) { .order__panel { grid-template-columns: .85fr 1.15fr; } }

.order__aside {
  background: var(--sage-wash);
  padding: clamp(1.75rem, 3.5vw, 2.75rem);
}
/* The form column is much taller than the heading and the preview, which
   left a tall empty green panel. Sticking the contents to the top turns that
   dead space into the thing it should have been all along: the message stays
   beside you, updating, while you fill the form in.
   ⚠ Only above the breakpoint. Stacked on a phone the aside sits ABOVE the
     form, and a sticky block there would sit on top of the fields. */
@media (min-width: 900px) {
  .order__aside-inner {
    position: sticky;
    top: calc(var(--header-h, 4.5rem) + 1.5rem);
  }
}
.order__form { padding: clamp(1.75rem, 3.5vw, 2.75rem); }

.field { margin-bottom: 1.3rem; border: 0; padding: 0; }
.field > label, .field__legend {
  display: block; margin-bottom: .45rem;
  font-size: .82rem; font-weight: 600; letter-spacing: .02em;
  color: var(--ink);
}
.field input[type="text"],
.field input[type="email"],
.field input[type="tel"],
.field select,
.field textarea {
  width: 100%;
  padding: .8rem .95rem;
  font-family: var(--sans); font-size: 1rem;
  color: var(--ink);
  background: var(--paper-warm);
  border: 1px solid var(--sage-soft);
  border-radius: var(--radius);
  transition: border-color .25s var(--ease-soft), background-color .25s var(--ease-soft);
}
.field textarea { min-height: 7rem; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus {
  background: var(--white); border-color: var(--sage-mid); outline-offset: 1px;
}
.field__hint { display: block; margin-top: .4rem; font-size: .8rem; color: var(--ink-faint); }

.field__row { display: grid; gap: 1rem; }
@media (min-width: 540px) { .field__row { grid-template-columns: 1fr 1fr; } }

/* ---- the route picker inside the form ---- */
.picker { display: grid; gap: .6rem; }
@media (min-width: 540px) { .picker { grid-template-columns: 1fr 1fr; } }

.opt {
  display: flex; gap: .7rem; align-items: flex-start;
  padding: .85rem 1rem;
  border: 1px solid var(--sage-soft); border-radius: var(--radius);
  cursor: pointer; background: var(--paper-warm);
  transition: border-color .25s var(--ease-soft), background-color .25s var(--ease-soft);
}
.opt:hover { border-color: var(--sage-mid); }
.opt input { margin: .28rem 0 0; accent-color: var(--sage-deep); flex: none; }
.opt:has(input:checked) { border-color: var(--sage-deep); background: var(--white); }
.opt__name { font-size: .92rem; font-weight: 500; }
.opt__detail { display: block; font-size: .8rem; color: var(--ink-faint); margin-top: .15rem; }

/* ---- what happens next ---- */
.steps { list-style: none; margin: 1.9rem 0 0; padding: 0; display: grid; gap: 1.05rem; }
.step { display: flex; gap: .85rem; align-items: flex-start; }
.step__n {
  flex: none;
  width: 1.6rem; height: 1.6rem;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--sage-deep); color: var(--white);
  font-size: .78rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
  margin-top: .12rem;
}
.step__body { font-size: .93rem; color: var(--ink-soft); }

/* ---- the live preview of what gets sent ---- */
.preview { margin-top: 1.6rem; }
.preview__label {
  /* --sage-deep for contrast; see the note on .eyebrow */
  font-size: .72rem; font-weight: 650; letter-spacing: .13em;
  text-transform: uppercase; color: var(--sage-deep); margin: 0 0 .6rem;
}
.preview__bubble {
  background: var(--sage-wash);
  border-radius: var(--radius);
  padding: 1rem 1.15rem;
  font-size: .92rem; color: var(--ink-soft);
  white-space: pre-wrap;
  max-height: 15rem; overflow-y: auto;
}

.order__send { margin-top: 1.5rem; display: grid; gap: .7rem; }
.order__note { margin: 1.1rem 0 0; font-size: .82rem; color: var(--ink-faint); }

.copied {
  font-size: .82rem; color: var(--sage-deep); font-weight: 560;
  margin: .6rem 0 0; min-height: 1.2rem;
}

/* =========================================================================
   FAQ
   ========================================================================= */

.faq { border-top: 1px solid var(--sage-soft); }
.faq__item { border-bottom: 1px solid var(--sage-soft); }
.faq__q {
  width: 100%; display: flex; gap: 1rem; align-items: center;
  justify-content: space-between; text-align: left;
  padding: 1.4rem 0;
  background: none; border: 0; cursor: pointer;
  font-family: var(--display); font-size: 1.2rem; color: var(--ink);
}
.faq__icon {
  width: 1.1rem; height: 1.1rem; flex: none; color: var(--sage-mid);
  transition: transform .35s var(--ease);
}
.faq__q[aria-expanded="true"] .faq__icon { transform: rotate(45deg); }
.faq__a { margin: 0; padding: 0 0 1.5rem; color: var(--ink-soft); max-width: 62ch; }
.faq__a[hidden] { display: none; }

/* The box whose HEIGHT is animated while the answer inside it is switched on
   and off with the hidden attribute. overflow lives here rather than in
   motion.css: site.js sets a pixel height on this element whether or not
   that sheet was loaded, and an unclipped box mid-close would spill its
   answer over the question below it. */
.faq__wrap { overflow: hidden; }

/* =========================================================================
   FOOTER
   ========================================================================= */

.footer {
  background: var(--ink);
  color: color-mix(in srgb, var(--white) 76%, transparent);
  padding-block: clamp(3rem, 6vw, 4.5rem) 2rem;
}
.footer__grid {
  display: grid; gap: 2.5rem;
  grid-template-columns: 1fr;
}
@media (min-width: 720px) { .footer__grid { grid-template-columns: 1.4fr 1fr 1fr; } }

.footer__name { font-family: var(--display); font-size: 1.5rem; color: var(--white); }
.footer__blurb { margin: .9rem 0 0; font-size: .92rem; max-width: 34ch; }
.footer__h {
  font-size: .7rem; font-weight: 700; letter-spacing: .15em;
  text-transform: uppercase; color: var(--sage-soft); margin: 0 0 1rem;
}
.footer__list { list-style: none; margin: 0; padding: 0; display: grid; gap: .6rem; }
.footer__list a { text-decoration: none; font-size: .92rem; }
.footer__list a:hover { color: var(--white); text-decoration: underline; }

.footer__base {
  margin-top: 3rem; padding-top: 1.5rem;
  border-top: 1px solid color-mix(in srgb, var(--white) 14%, transparent);
  display: flex; flex-wrap: wrap; gap: 1rem; justify-content: space-between;
  font-size: .8rem; color: color-mix(in srgb, var(--white) 55%, transparent);
}
.footer__base a { color: inherit; }

/* =========================================================================
   THE RIBBON DIVIDER
   -------------------------------------------------------------------------
   The one ornament on the site, and it is not an ornament: it is the silver
   ribbon out of her own logo, flattened into a rule. Sections that would
   otherwise butt up against each other in the same colour get one, so the
   page reads as chapters rather than as a scroll.

   Drawn in CSS rather than as an SVG file because it is three boxes, and a
   network request for three boxes is a poor trade.
   ========================================================================= */

.divider {
  display: flex; align-items: center; justify-content: center;
  gap: .9rem;
  padding-block: clamp(1.5rem, 4vw, 2.5rem);
}
.divider::before,
.divider::after {
  content: '';
  height: 1px; flex: 1 1 0; max-width: 11rem;
  background: linear-gradient(to right,
    transparent, var(--sage-soft) 35%, var(--sage-soft) 65%, transparent);
}
.divider__mark {
  width: 2.1rem; height: .85rem;
  flex: none;
  position: relative;
  background: var(--silver-pale);
  border-radius: 2px;
}
/* The two notched tails, same geometry as the logo's banner. */
.divider__mark::before,
.divider__mark::after {
  content: '';
  position: absolute; top: .12rem;
  width: .62rem; height: .61rem;
  background: var(--silver);
}
.divider__mark::before {
  left: -.5rem;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 38% 50%);
}
.divider__mark::after {
  right: -.5rem;
  clip-path: polygon(0 0, 100% 0, 62% 50%, 100% 100%, 0 100%);
}

/* =========================================================================
   MICRO-INTERACTIONS
   -------------------------------------------------------------------------
   Small, and each one earns its place by answering a question the interface
   would otherwise leave open: is this a link, did that press register, where
   am I. Nothing here moves unless it is being touched.
   ========================================================================= */

/* Text links draw their underline rather than having one. The line grows
   from the left on hover, which reads as "this goes somewhere". */
.link-draw {
  position: relative;
  text-decoration: none;
  color: var(--sage-deep);
  font-weight: 500;
  white-space: nowrap;
}
.link-draw::after {
  content: '';
  position: absolute; left: 0; right: 0; bottom: -2px;
  height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: right;
  transition: transform .35s var(--ease);
}
.link-draw:hover::after,
.link-draw:focus-visible::after { transform: scaleX(1); transform-origin: left; }

/* Cards lift a little and their shadow softens. The border warms at the same
   time, which is what stops the lift reading as a glitch. */
.route, .cat {
  transition: transform .35s var(--ease), box-shadow .35s var(--ease-soft),
              border-color .35s var(--ease-soft);
}
.route:hover, .cat:hover { border-color: var(--sage-mid); }
.cat:hover { transform: translateY(-3px); box-shadow: var(--shadow); }

/* A pressed chip dips. On a touch screen this is the only feedback there is
   between the finger going down and the answer registering. */
.chip:active { transform: translateY(1px) scale(.995); }

/* The arrow on an outbound button leans out on hover — a tiny thing that
   says "this leaves the site" without a word of copy. */
.btn__arrow { transition: transform .35s var(--ease); }
.btn:hover .btn__arrow { transform: translateX(3px); }

/* =========================================================================
   THE DETAILS NOBODY NOTICES UNTIL THEY ARE WRONG
   ========================================================================= */

/* Selected text in her colours rather than the browser's blue, which is the
   one bit of chrome that otherwise punches straight through a palette. */
::selection { background: var(--sage-soft); color: var(--ink); }

/* Firefox and Chromium both honour this; Safari ignores it and keeps its own,
   which is fine — a scrollbar is not worth a hack. */
* { scrollbar-color: var(--sage-soft) transparent; scrollbar-width: thin; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--sage-soft);
  border-radius: 999px;
  border: 3px solid var(--paper);
}
::-webkit-scrollbar-thumb:hover { background: var(--sage-mid); }

/* Anything scrollable inside a white card gets the white surround instead. */
.compare__scroll::-webkit-scrollbar-thumb,
.preview__bubble::-webkit-scrollbar-thumb { border-color: var(--white); }

/* Long words — a pasted URL in the order box, a long email in the footer —
   break rather than pushing the page sideways. */
.preview__bubble, .footer__list a, .finder__quote { overflow-wrap: anywhere; }

/* iOS zooms the page in when a focused input is under 16px. Everything here
   is 1rem already; this is the belt to that pair of braces. */
@supports (-webkit-touch-callout: none) {
  .field input, .field select, .field textarea { font-size: max(16px, 1rem); }
}

/* =========================================================================
   BACK TO TOP
   Appears only once there is a reason for it, and never over the mobile
   WhatsApp bar — it sits above it.
   ========================================================================= */

.to-top {
  position: fixed; right: 1.2rem; bottom: 1.4rem; z-index: 50;
  width: 2.9rem; height: 2.9rem;
  display: grid; place-items: center;
  border: 1px solid var(--sage-soft);
  border-radius: 50%;
  background: color-mix(in srgb, var(--white) 88%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--sage-deep);
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  opacity: 0; transform: translateY(10px); pointer-events: none;
  transition: opacity .35s var(--ease-soft), transform .35s var(--ease),
              background-color .25s var(--ease-soft);
}
.to-top.is-on { opacity: 1; transform: none; pointer-events: auto; }
.to-top:hover { background: var(--white); }
.to-top svg { width: 1.1rem; height: 1.1rem; }

/* =========================================================================
   THE ACTION BAR
   -------------------------------------------------------------------------
   Phone only, and built by site.js only when there is something real to put
   in it. See the long note there for why it exists at all.

   ⚠  THE PAGE GAINS PADDING WHILE IT IS UP. A fixed bar with nothing under it
      sits on top of the last paragraph of the footer, and on a page whose
      footer carries her contact details that is the worst possible thing to
      cover.
   ========================================================================= */

.action-bar { display: none; }

@media (max-width: 720px) {
  .action-bar {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 52;
    display: flex;
    gap: .6rem;
    padding: .7rem var(--pad) calc(.7rem + env(safe-area-inset-bottom, 0px));
    background: color-mix(in srgb, var(--paper) 92%, transparent);
    backdrop-filter: blur(14px) saturate(1.3);
    -webkit-backdrop-filter: blur(14px) saturate(1.3);
    border-top: 1px solid var(--sage-soft);
    transform: translateY(105%);
    transition: transform .45s var(--ease);
  }
  .action-bar.is-on { transform: none; }
  .action-bar .btn { flex: 1 1 0; padding-inline: .8rem; font-size: .9rem; }
  .action-bar__msg { background: var(--white); }

  /* --action-bar-h is measured by site.js; the rem value is only the
     fallback for the frame before it runs. */
  body.has-action-bar { padding-bottom: calc(var(--action-bar-h, 5.2rem) + .4rem); }
  /* Back-to-top climbs above the bar rather than sitting behind it. */
  body.has-action-bar .to-top { bottom: calc(var(--action-bar-h, 5.2rem) + .8rem); }
}

@media print { .action-bar { display: none !important; } }

/* =========================================================================
   PRINT
   Somebody will print this to show a friend, or to keep the order they sent.
   Make it survive: no fixed furniture, no dark grounds eating a cartridge,
   and every link's destination spelled out because paper cannot be clicked.
   ========================================================================= */

@media print {
  .header, .nav, .nav-toggle, .nav-scrim, .to-top, .sillage,
  .skip, .finder__nav, .hero__actions, .route__cta,
  .order__send, .finder__result-actions { display: none !important; }

  body { background: #fff; color: #000; font-size: 11pt; }
  .section { padding-block: 1.2rem; break-inside: avoid; }
  .hero { padding-block: 0 1.5rem; }

  .join, .footer { background: #fff !important; color: #000 !important; }
  .join__title, .join__body, .join__note, .footer__name { color: #000 !important; }
  .route, .cat, .finder, .order__panel, .compare__table {
    box-shadow: none; border: 1px solid #ccc; break-inside: avoid;
  }
  .order__aside, .finder__quote, .preview__bubble { background: #f4f4f4 !important; }

  /* The two links that matter most on paper are the two that cannot be
     typed from memory. */
  a[href^="http"]::after {
    content: ' (' attr(href) ')';
    font-size: .85em; color: #555; word-break: break-all;
  }
}

/* =========================================================================
   REVEAL ON SCROLL
   ========================================================================= */

.reveal { opacity: 0; transform: translateY(18px); }
.reveal.is-in {
  opacity: 1; transform: none;
  transition: opacity .7s var(--ease-soft), transform .7s var(--ease);
}
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; }
}

/* =========================================================================
   WITHOUT JAVASCRIPT
   -------------------------------------------------------------------------
   `no-js` is on <html> in the file and removed by an inline script in the
   <head>, so it applies only to somebody whose scripts never ran: switched
   off, blocked by a proxy, failed on bad wifi, or a crawler that does not
   execute them.

   dist/index.html is PRE-RENDERED (see tools/build.js), so all the words and
   both of her links are already in the markup. What is left is making sure
   the CSS does not hide them, and that the three components which genuinely
   need scripting stand down gracefully rather than sitting there dead.

   ⚠  THE FIRST RULE IS THE IMPORTANT ONE. `.reveal` starts at opacity 0 and
      is switched on by an IntersectionObserver. Without the observer, every
      revealed element stays invisible — so the pre-rendered page would be a
      blank sage rectangle. Baking the content in and then hiding it with CSS
      would have been worse than not baking it at all.
   ========================================================================= */

.no-js .reveal { opacity: 1; transform: none; }

/* The FAQ is an accordion driven by JS. Open it all out instead — a list of
   questions and answers is a perfectly good page. Beats [hidden] on purpose;
   see the note on that rule at the top of this file. */
.no-js .faq__a[hidden] { display: block !important; }
.no-js .faq__q { cursor: default; }
.no-js .faq__icon { display: none; }

/* The scent finder cannot work at all without scripting: it exists to
   compose a sentence. Better absent than present and unresponsive. */
.no-js #finder { display: none; }

/* The order form writes a message and hands it to WhatsApp or the clipboard.
   None of that can happen, so the fields and the live preview go, and the
   fallback below takes their place. */
.no-js .order__form,
.no-js .preview { display: none; }
.no-js .order__panel { grid-template-columns: 1fr; }
.no-js .order-fallback { display: block; }
.order-fallback { display: none; margin-top: 1.75rem; }
.order-fallback .btn { margin-top: 1.1rem; }

/* The mobile menu is a fixed panel slid off-screen by a class the script
   adds. With no script it can never come back, so the links become an
   ordinary wrapped row and the button that does nothing is removed. */
@media (max-width: 860px) {
  .no-js .nav-toggle { display: none; }
  .no-js .nav {
    position: static; transform: none; box-shadow: none;
    width: 100%; flex-direction: row; flex-wrap: wrap;
    justify-content: flex-start; gap: .35rem 1.1rem;
    padding: .25rem 0 0; background: transparent; overflow: visible;
  }
  .no-js .nav__link { width: auto; font-size: .92rem; padding-block: .3rem; }
  .no-js .nav .btn { width: auto; padding: .55rem 1.1rem; font-size: .85rem; }
  .no-js .header__inner { flex-wrap: wrap; }
}

/* Back-to-top and the hero drift are created by script, so they simply never
   exist here — nothing to hide. */
