/* Audio Botanist — "sciencey/elevated" treatment (Jul 2026 design round).
   Cream ground, olive ink, one deep green for everything audio/action.
   Surface logic: cream page → lifted white paper cards (Sophie's words +
   anything tappable) → green = sound. All-sans type ("modern science"):
   Inter Tight 600 titles, Inter body/UI, one shared caps-label style.
   Hierarchy unchanged from the indigo version.
   Full exploration + inspiration trail: tmp/audio-inspiration/ (greg repo). */

:root {
  /* card breathing (Kiersten-picked "airy", Jul 8): generous inset, wider
     card than the chip column, half-step-smaller type with tall lines */
  --card-pad-x: 16px; --card-pad-y: 16px; --card-bleed: 0px;   /* 22 -> 18 (Jul 13): ~20% tighter card, margin traded to the hero */
  --body-size: 15.5px; --body-lh: 25px;
  /* Jade won the Jul 21 huddle (Alex: "more planty and calmer"; Martin:
     "a nice notch up" from spruce; kelly/shamrock read neon). Promoted from
     ?theme=jade to the baseline so a fresh load holds it — previously the
     baseline was `final`'s emerald and jade only lived behind the cycler,
     which is why the page "kept jumping back". Ivory/spruce stays ?theme=spruce. */
  --cream:     #FAFCFA;                 /* page ground — cool green-white */
  --card:      #FFFFFF;                 /* lifted paper — bubble, chips, inputs */
  --ink:       rgba(53,52,43,0.8);      /* soft olive-charcoal — 6.5:1, well above the 4.5:1 body floor */
  --green:     #03875B;                 /* THE accent: play, mic, progress, voice — jade, 4.54:1 under white */
  --accent-text: #166B4F;               /* small accent text + icons (links, ask chip) — one family with --green, quieter volume */
  --accent-rgb: 3, 135, 91;             /* --green as a triplet — shadows/tints derive from it so themes swap in one place */
  --see-more:  #166B4F;                 /* the 80%-alpha fallback sat at 2.6:1 — pin it to the deep accent */
  --green-glyph: #FFFFFF;               /* glyphs on green */
  --muted:     rgba(53,52,43,0.70);     /* small labels — 70% for contrast (45-60 audience) */
  --hairline:  rgba(53,52,43,0.22);     /* tappable-card borders */
  --hairline-lo: rgba(53,52,43,0.08);   /* passive-card borders */
  --wave-off:  rgba(53,52,43,0.20);     /* waveform at rest — ink, never green */
  --err:       #A94436;
  --pin:       #5C5B51;
  /* locked hero framing (Kiersten, Jul 17): zoom 138%, pan 48/35.
     Two layers (Jul 22): the locked framing rides on top; the same image at
     `cover` sits beneath, so if 138%-of-width renders shorter than the
     bleed-extended element (it did at narrower frames — white band at the
     top), the fallback fills the gap with photo instead of cream. */
  --hero-bs: 138% auto, cover; --hero-zoom: 1.38; --hero-bp: 48% 35%, 48% 35%; --hero-x: 48; --hero-y: 35;                 /* polish pass: pin joins the ink family — green reserved for voice + CTAs */
  --desk:      #DEE9DF;                 /* desktop stage around the phone frame — themes restage it */
  /* text-bearing green surfaces (onboarding primary, gate, CTA) get their own
     pair so themes can fix button contrast without touching the sound/motion
     green (play, mic, waveform — non-text UI, held to 3:1 not 4.5:1) */
  --btn-bg:    var(--green);
  --btn-text:  var(--green-glyph);
  --btn-ring:  transparent;             /* optional hairline around tint-pill buttons */
  /* commit surfaces (gate, conversion CTA) can diverge from the choosing
     surfaces (onboarding fork) — "tint = choose, solid = commit" hybrid.
     Defaults chain to --btn-*, so single-treatment themes set nothing extra. */
  --cta-bg:    var(--btn-bg);
  --cta-text:  var(--btn-text);
}

/* theme plumbing: the two map pins carry inline fills in the HTML — CSS wins
   over presentation attributes, so themes recolor them here */
/* locpill pin follows --pin (finals deepen it to match its ink label — fern
   is reserved for the voice channel there); the big onboarding-map pin is
   illustration, not UI, and stays fern in every treatment */
.locpin path { fill: var(--pin, var(--green)); }
.ob-pin path { fill: var(--green); }

/* transient pill naming the active theme while cycling with "c" */
.theme-toast {
  position: fixed; left: 50%; bottom: 34px; transform: translateX(-50%) translateY(6px);
  background: rgba(53,52,43,0.88); color: #FDF8EF; border-radius: 999px;
  padding: 8px 16px; font: 600 13px "Inter", sans-serif; letter-spacing: 0.04em;
  opacity: 0; pointer-events: none; transition: opacity .18s ease, transform .18s ease;
  z-index: 200;
}
.theme-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { margin: 0; padding: 0; }
body {
  font-family: "Inter", system-ui, sans-serif;
  background: var(--cream);
  color: var(--ink);
  min-height: 100svh;
}

.app {
  max-width: 460px;
  margin: 0 auto;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  position: relative;
  background: var(--cream);
}
.hidden { display: none !important; }

/* ---------- hero ----------
   Tall photo fading to full cream just above the player; the caption sits in
   the fully-faded zone so title contrast is guaranteed for ANY species photo. */
.hero { position: relative; flex-shrink: 0; }
.hero-photo {
  position: absolute; inset: 0 0 calc(1px - var(--photo-bleed, 0px));   /* tucked under the fade: at scaled (phone-frame) rendering, independent pixel-snapping of photo vs fade can leak a 1px photo row at the hero/content seam */
  background-size: var(--hero-bs, cover); background-position: var(--hero-bp, 50% 35%);
  background-repeat: no-repeat;
  /* fallback gradient until a real photo is set */
  background-image: radial-gradient(120% 90% at 70% 20%, #DCE8DC 0%, #C9DCC9 45%, var(--cream) 100%);
  display: grid; place-items: center;
}
.hero-photo.has-image .hero-emoji { display: none; }
.hero-emoji { font-size: 92px; opacity: 0.85; margin-bottom: 84px; }
.hero-fade {
  position: absolute; inset: 0 0 calc(-1px - var(--photo-bleed, 0px)); pointer-events: none;   /* overshoots the seam */
  /* bottom-anchored, fixed-length ramp (not proportional): the photo's
     terminal fade keeps a constant 150px thickness at ANY bleed depth, so
     dragging the photo down no longer stretches a pale wash over everything
     behind the cards — the photo stays rich until its own end. */
  background: linear-gradient(180deg,
    rgba(250,252,250,0) calc(100% - 150px),
    rgba(250,252,250,0.55) calc(100% - 74px),
    var(--cream) calc(100% - 10px));
}
.hero-caption {
  /* 246 -> 176 (Jul 13, Kiersten-tuned): with Safari's URL bar up, 246 pushed
     chips 2-4 below the fold; 118 fit all four chips but gutted the photo.
     176 + tighter card (--card-pad 22->18) = fuller flower band, 3 question
     chips above the fold, ask-chip peeks as scroll cue. */
  position: relative; padding: 164px 24px 0;
  /* gap 5 (Jul 13, measured): name-ink->city-ink ~10px = dotted-bar->card 9px
     + its leading; box gaps are 7 vs 9 but the city text's line-box leading
     (~3px above caps) makes equal-box read unequal-ink */
  display: flex; flex-direction: column; align-items: flex-start; gap: 5px;
}
.plantName {
  margin: 0;
  font-family: "Inter Tight", "Inter", sans-serif;
  font-weight: 600; font-size: 34px; line-height: 1.04; letter-spacing: -0.02em;  /* scale, not mass */
}
/* location: dotted underline is the single "changeable" cue (no caret);
   padding halo gives a 44px-class tap target without shifting layout */
.locpill {
  /* gap 4 reads as ~6.5 — the pin SVG carries ~2.3px of built-in transparent
     padding per side (art spans 14 of its 24 viewBox units) */
  display: inline-flex; align-items: center; gap: 4px; max-width: 100%;
  background: none; border: none; cursor: pointer;
  color: var(--ink); font: 600 15.5px "Inter", sans-serif;   /* the one mid-scale size (matches body/chips) */
  padding: 10px 8px; margin: -8px -8px -10px;
}
.locpill .locpin {
  flex-shrink: 0;
  /* -3px counters the SVG's internal padding so the pin ink sits optically
     flush with "Hydrangea" above; vertical alignment comes free from flex
     centering now that the 14px pin's ink height matches the cap height */
  margin-left: -3px;
}
.locpill #loclabel {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.locpill .locdot, .locpill .caret { flex-shrink: 0; }
/* chevron sits a hair low of geometric center — optically level with the
   text's midline. Full ink + text-matched stroke (Jul 22, Kiersten: same
   weight and color as the location label — was 0.55 muted / 2.6 stroke) */
.locpill .caret { transform: translateY(0.5px); }

/* ---------- content ---------- */
.content {
  position: relative; z-index: 1;   /* above the bleed-extended photo/fade */
  flex: 1;
  display: flex; flex-direction: column;
  padding: 12px 24px max(24px, env(safe-area-inset-bottom));
  /* card breathing room centered (Jul 13): 13px visual above AND below the
     card — this 9 + pill slack above; 14 gap - 1 tryasking margin below */
  gap: 16px;
}

/* voice-message header: identity leads (avatar + name, left), the act closes
   (green play, right) — two circles on either side, per the huddle. The
   duration is the promise, under her name; it becomes a countdown with a
   live equalizer while she speaks. */
.vhead { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.vavatar {
  width: 44px; height: 44px; border-radius: 50%; flex-shrink: 0;
  /* Jul 15 exploration winner: the original woman, copper hair, wound back
     ~10yr, on warm bone off-white. The inset hairline draws the circle's
     edge on ANY container (white pill, card, photo) — separation comes from
     the frame, not the ground. Alternatives still at ?avatar= variants. */
  background: url("img/botanist-n4.jpg") center/cover #DDDBD4;
}
.vwho { flex: 1; min-width: 0; }
.vdur { display: flex; align-items: center; gap: 10px; margin-top: 5px;
  width: 0; min-width: 100%;
  /* width:0 keeps the wave row from inflating the column's intrinsic size
     (the label alone sets it); min-width stretches the row back to match */
}
/* permanent header waveform: Butter glyph at fixed density spanning the label
   column, countdown at its right; green fills with real playback progress */
.waveform { flex: 1 1 auto; min-width: 0; overflow: hidden;
  display: flex; align-items: center; gap: 2.5px; height: 16px; }
.waveform .bar { width: 2.5px; border-radius: 2px; flex-shrink: 0;
  background: var(--wave-off); height: 6px; }
.waveform .bar.played { background: var(--green); }
.waveform.playing .bar { animation: wavebreath 1.1s ease-in-out infinite; }
@keyframes wavebreath { 0%,100% { transform: scaleY(0.82); } 50% { transform: scaleY(1); } }
.playBtn {
  flex-shrink: 0; width: 48px; height: 48px; border-radius: 50%;
  margin-left: auto;   /* pinned to the card's right regardless of column width */
  background: var(--green); border: none; cursor: pointer;
  display: grid; place-items: center;
}
.playBtn svg { fill: var(--green-glyph); }
.playBtn .ic-play { display: none; transform: translateX(1px); }  /* optical centering */
.playBtn.paused .ic-pause { display: none; }
.playBtn.paused .ic-play { display: block; }
.playBtn.needs-tap { animation: pulse 1.4s ease-in-out infinite; }  /* removed on first play (app.js) */
@keyframes pulse {
  0%,100% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb),0.4); }
  50%     { box-shadow: 0 0 0 12px rgba(var(--accent-rgb),0); }
}
@media (prefers-reduced-motion: reduce) {
  .playBtn.needs-tap, .waveform.playing .bar, .dots i { animation: none !important; }
}
.timer {
  flex-shrink: 0;
  font: 600 11px "Inter", sans-serif; letter-spacing: 0.14em;
  font-variant-numeric: tabular-nums;
  /* no width reservation: when the countdown arrives, setTimer (app.js)
     detects the timer's width change and refits the bars — the waveform
     always fills exactly the space that's actually left */
  color: var(--muted); min-width: 2px;
}

/* transcript: lifted paper card; 3-line karaoke window */
.bubble {
  position: relative; margin-top: 0;   /* 4 -> 0 (Jul 13): tighter city->card */
  background: var(--card); color: var(--ink);
  border: 1px solid var(--hairline-lo);
  border-radius: 18px; padding: var(--card-pad-y) var(--card-pad-x);
  margin-left: calc(-1 * var(--card-bleed)); margin-right: calc(-1 * var(--card-bleed));
  box-shadow: 0 10px 26px rgba(53,52,43,0.12);
}
/* pre-play (team 7/7): show the words, not an instruction — first two lines
   of the real intro + a quiet see-more, so silent readers are served too.
   Play remains the audio path; see-more sits far below it in hierarchy. */
.bubble.preplay .kara { transform: none !important; }
.bubble.expanded .bubble-text { height: auto; }
.bubble.expanded .kara { transform: none !important; }
/* paragraph seams (display-only; hidden while the window is clamped) */
.kbr { display: block; height: 10px; }
.bubble:not(.expanded) .kbr { display: none; }
/* see-more: clean cut — the preview ends at the last whole word that leaves
   room, then "… See more" continues inline (no gradient, no ghost letters).
   app.js hides the tail words (.cut) and parks the label right after the
   last one. Preview-state only; once she reads, the karaoke owns the box. */
.see-more { display: none; margin: 0; border: none; cursor: pointer; padding: 0;
  /* 80% accent (Kiersten-tuned, Jul 15): full strength fought the play
     button for first glance; 60% washed into the unread copy. This sits
     between — clearly clickable, clearly not text. */
  background: none; color: var(--see-more, var(--muted));
  font-weight: 600; font-family: "Inter", sans-serif;
  font-size: var(--body-size); line-height: var(--body-lh); }
.bubble.preplay:not(.expanded) .see-more,
.bubble.paused:not(.expanded) .see-more { display: block; position: absolute; bottom: var(--card-pad-y); }
.bubble.preplay:not(.expanded) .kw.cut,
.bubble.paused:not(.expanded) .kw.cut { display: none; }
.bubble.expanded .see-more { display: block; width: 100%; text-align: right; padding: 0; }
.bubble.expanded { padding-bottom: 16px; }   /* Kiersten-tuned (spacing tuner, Jul 9) */
.see-more[hidden] { display: none !important; }
.bubble .bubble-text { cursor: pointer; }

/* curtain emergence (kept for hidden-bubble paths): scales out of ▶ —
   transform-origin sits at the tail, which lives directly under the button */
.bubble.emerge {
  animation: bubbleEmerge .42s cubic-bezier(.2,.85,.3,1.06);
  transform-origin: 39px -18px;
}
@keyframes bubbleEmerge {
  from { transform: scale(0.05); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .bubble.emerge { animation: none; } }
/* voice eyebrow: green dot + the shared caps-label style (matches TRY ASKING).
   (Sophie photo-identity experiment lives in git history + img/sophie.jpg —
   revisit when the team picks a face/name.) */
.pro { display: flex; align-items: center; gap: 7px; }
.pro > span:last-child {
  /* matches the header text (plantName inherits --ink) — the label is
     identity, not accent (Kiersten, Jul 15) */
  font: 600 11px "Inter", sans-serif; letter-spacing: 0.14em; color: var(--ink);
}
.pro.sophie > span:last-child { font: inherit; letter-spacing: 0; color: inherit; }
/* ?pro=sophie variant: photo avatar + "Sophie · Your Plant Pro" on one line */
.pro.sophie { gap: 9px; margin-bottom: 10px; }
.pro-avatar { width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0;
  background: url("img/sophie.jpg") center/cover var(--green); }
.pro-who { display: flex; align-items: baseline; gap: 5px; }
.pro-name { font: 600 13.5px "Inter", sans-serif; letter-spacing: 0; color: var(--ink); }
.pro-role { font: 500 12px "Inter", sans-serif; letter-spacing: 0.02em; color: var(--muted); }
/* karaoke window: exposes 3 lines, scrolls with the voiceover */
.bubble-text {
  margin: 0;
  font-weight: 400; font-size: var(--body-size); line-height: var(--body-lh);
  color: rgba(53,52,43,0.9);   /* reading text runs a hair stronger — 400 weight needs it where 600 doesn't */
  height: calc(var(--kara-lines, 3) * var(--body-lh));   /* N-line window; live A/B via ?lines= */
  overflow: hidden; position: relative;
}
.kara { transition: transform 0.35s ease; will-change: transform; }
.kw { color: inherit; opacity: 0.32; transition: opacity 0.18s ease; }
.kara.lit .kw.spoken { opacity: 1; }     /* karaoke: dim upcoming, light spoken */
.kara:not(.lit) .kw { opacity: 1; }      /* idle/paused: fully readable */
/* armed preview starts in the unread state — the dim words imply a reading
   yet to be completed; play "fills them in". Expanding restores full
   opacity so silent readers still get a legible transcript. */
.bubble.preplay:not(.expanded) .kw { opacity: 0.52; }
/* thinking / loading indicator — typing dots in the botanist's green */
.dots { display: inline-flex; gap: 6px; padding: 6px 0; }
.dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--green); animation: dotpulse 1.2s infinite; }
.dots i:nth-child(2) { animation-delay: 0.18s; }
.dots i:nth-child(3) { animation-delay: 0.36s; }
@keyframes dotpulse { 0%, 60%, 100% { opacity: 0.3; transform: translateY(0); } 30% { opacity: 1; transform: translateY(-4px); } }

.grow { flex: 1; min-height: 12px; }

/* ===== ask zone — "the room": blooms under Sophie's card when the sage
   ask-chip (the door) opens it. Voice-first; typing is a quiet mode. ===== */
.askzone { position: relative; display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding: 16px; animation: rise .28s ease;
  background: color-mix(in srgb, var(--green) 5%, var(--card)); border: 1px solid rgba(var(--accent-rgb),0.15); border-radius: 18px; }
@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.ask-close {
  position: absolute; top: 8px; right: 8px; width: 30px; height: 30px;
  border: none; border-radius: 50%; cursor: pointer;
  background: rgba(53,52,43,0.06); color: var(--muted);
  display: grid; place-items: center;
}

.voicemode { display: flex; flex-direction: column; align-items: center; gap: 12px; }

/* push-to-talk circle: green disc, cream glyph; square while listening */
.micCircle {
  width: 72px; height: 72px; border-radius: 50%; border: none; flex-shrink: 0; cursor: pointer;
  background: var(--green);
  display: grid; place-items: center; box-shadow: 0 8px 22px rgba(var(--accent-rgb),0.30);
  transition: transform .12s ease;
}
.micCircle:active { transform: scale(0.95); }
.micCircle svg { fill: var(--green-glyph); stroke: var(--green-glyph); }
.micCircle .ic-stop { display: none; width: 24px; height: 24px; border-radius: 6px; background: var(--green-glyph); }
.micCircle.listening { animation: ring 1.4s infinite; }
.micCircle.listening .ic-mic { display: none; }
.micCircle.listening .ic-stop { display: block; }
@keyframes ring {
  0%   { box-shadow: 0 0 0 0 rgba(var(--accent-rgb),0.45); }
  70%  { box-shadow: 0 0 0 16px rgba(var(--accent-rgb),0); }
  100% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb),0); }
}
@media (prefers-reduced-motion: reduce) { .micCircle.listening { animation: none; } }

.mic-cap { font: 700 var(--body-size) "Inter", sans-serif; color: var(--ink); line-height: 1.2;
  max-width: min(340px, 86vw); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; }
/* the spoken question, persisted in place after asking — quieter than the prompt */
.mic-cap.echo { font-weight: 600; font-style: italic; color: var(--muted); }

/* subtle "Type instead" / "Speak instead" link */
.type-link { background: none; border: none; color: var(--muted);
  font: 600 var(--body-size) "Inter", sans-serif; cursor: pointer;
  display: inline-flex; align-items: center; gap: 7px; }
.type-link svg { fill: none; stroke: currentColor; stroke-width: 1.8; }

/* type mode: chat input + send, with a "Speak instead" link below */
.typemode { display: flex; flex-direction: column; align-items: center; gap: 10px; width: 100%;
  padding-top: 26px; }  /* clears the corner X (ends 38px from card top) — the full-width typebar would otherwise slide under it */
.typebar {
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: var(--card); border: 1px solid var(--hairline);
  border-radius: 999px; padding: 6px 6px 6px 18px;
}
.typebar-input { flex: 1; min-width: 0; background: transparent; border: none; outline: none;
  color: var(--ink); font: 500 var(--body-size) "Inter", sans-serif; }
.typebar-input::placeholder { color: var(--muted); }
.sendBtn { flex-shrink: 0; width: 44px; height: 44px; border-radius: 50%; border: none; cursor: pointer;
  background: var(--green); display: grid; place-items: center; }
.sendBtn svg { stroke: var(--green-glyph); fill: none; }

/* try asking — lifted paper pills, fixed rhythm (no viewport stretch) */
.tryasking { display: flex; flex-direction: column; gap: 12px; }  /* 14 (content gap) - 1 = 13, centering the card's breathing room */
.ta-label { font: 600 11px "Inter", sans-serif; letter-spacing: 0.14em; color: var(--muted); }  /* matches .pro label */
.chips { display: flex; flex-direction: column; gap: 12px; justify-content: flex-start; }
.chip {
  display: flex; align-items: center; justify-content: center; width: 100%; text-align: center;
  background: var(--card); border: 1px solid var(--hairline); border-radius: 999px;
  padding: 15px 18px; color: var(--ink); font: 500 15.5px "Inter", sans-serif; cursor: pointer;
  /* slow-out fill: press/select lands fast (:active below), release fades */
  transition: transform .1s ease, background .25s ease, border-color .25s ease, color .25s ease;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.chip:active { transform: scale(0.985); transition-duration: .1s, .05s, .05s, .05s; }
/* .selected persists after the tap ("this is what you asked") so the green
   fill reads as state, not a flash — cleared when another chip is tapped */
.chip:active { background: var(--green); border-color: var(--green); color: var(--green-glyph); }
/* selected persists = a commit state, so it wears the commit pair (falls back
   to the classic green fill when no theme splits the commit surfaces) */
.chip.selected {
  background: var(--sel-bg, var(--cta-bg)); border-color: var(--sel-ring, var(--cta-bg));
  color: var(--sel-text, var(--cta-text)); box-shadow: inset 0 0 0 1.5px var(--sel-ring, var(--btn-ring));
}
/* the special 4th chip: ask-your-own — sage tint, mic, green text */
.chip.ask {
  background: var(--ask-bg, color-mix(in srgb, var(--green) 9%, var(--card)));
  border-color: var(--ask-ring, rgba(var(--accent-rgb),0.25));
  border-style: var(--ask-style, solid);
  color: var(--ask-text, var(--accent-text)); gap: 9px;
}
/* the mic glyph can diverge from the label (two-green grammar: it summons
   the "alive" voice system, so it may carry bright fern while the label
   stays deep) — currentColor children pick this up */
.chip.ask svg { color: var(--ask-icon, var(--green)); width: 19px; height: 19px; margin: -2px 0;
  transition: color .25s ease; }
.chip.ask:active { background: var(--green); border-color: var(--green); color: var(--green-glyph); }
/* room open: the door wears the exact selected fill of its siblings
   (declared after .chip.ask so the sage base doesn't win the cascade) */
.chip.ask.selected {
  background: var(--sel-bg, var(--cta-bg)); border-color: var(--sel-ring, var(--cta-bg));
  color: var(--sel-text, var(--cta-text)); box-shadow: inset 0 0 0 1.5px var(--sel-ring, var(--btn-ring));
}
.chip.ask.selected svg { color: var(--sel-text, var(--cta-text)); }

/* ---------- below the fold: Altman V2 product-page structure, new skin ---------- */
.belowfold { display: flex; flex-direction: column; padding-top: 0; }
.bf-h { margin: 30px 0 16px; font: 600 22px "Inter Tight", "Inter", sans-serif; color: var(--ink); }
.bf-center { text-align: center; }
.bf-text { margin: 0 0 16px; font: 400 var(--body-size)/1.6 "Inter", sans-serif; color: rgba(53,52,43,0.9); padding: 0 6px; }  /* matches transcript body */
.bf-text strong { font-weight: 600; }
/* conversion story: one spacing rhythm, hairline-separated section */
.convert { border-top: 1px solid var(--hairline-lo); margin-top: 0; padding-top: 0; }  /* divider lands 24 below the chips */
.convert .notif { margin-top: 16px; }

/* Plant Facts: full label, always expanded (Jul 21 — peek/see-more retired).
   Same material as the notif card + mini-player: hairline + soft lift, so the
   whole below-the-fold shares one surface language. */
.nlabel { border: 1px solid var(--hairline-lo); border-radius: 18px;
  padding: 14px 16px 16px; background: var(--card); margin-top: 24px;
  box-shadow: 0 8px 20px rgba(53,52,43,0.06); }
.nl-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px;
  padding: 2px 0 4px; }   /* masthead air — the signature bar below does the separating */
.nl-title { font: 700 22px/1 "Inter Tight", "Inter", sans-serif; color: var(--ink); }
.nl-sub { font: 400 var(--body-size) "Inter", sans-serif; color: var(--muted); margin-top: 7px; }  /* matches the question chips' size */

/* page foot: official store badges — quiet, canonical, expected at the end */
.storefoot { display: flex; justify-content: center; align-items: center; gap: 16px;
  padding: 24px 0 24px; }  /* real end-of-page clearance: keeps the badges out of
  the phone frame's corner curve at scroll end */
.storefoot img { height: 44px; display: block; }
.nl-bar { height: 6px; background: var(--cta-bg); border-radius: 3px; margin: 12px 0 8px; }  /* signature bar wears the CTA green */
.nl-bar.thin { height: 3px; background: rgba(53,52,43,0.40); opacity: .35; }
/* rows are buttons now: the fact expands in place and she talks it through */
.nl-item { border-bottom: 1px solid var(--hairline-lo); }
.nl-row { display: flex; justify-content: space-between; align-items: center; gap: 10px;
  width: 100%; padding: 8px 0; margin: 0; background: none; border: 0; cursor: pointer;
  text-align: left;
  font: 400 var(--body-size) "Inter", sans-serif; color: rgba(53,52,43,0.9); }  /* content step — matches transcript body */
/* no hairline where a section bar or the card edge already draws the line */
.nl-item:has(+ .nl-bar), .nl-body > .nl-item:last-child { border-bottom: none; }
.nl-row b { font-weight: 600; font-variant-numeric: tabular-nums; }
.nl-val { display: inline-flex; align-items: center; gap: 7px; }
/* line-level chevron (Martin): lots of them, but barely-there quiet */
.nl-caret { color: var(--muted); opacity: .55; flex-shrink: 0; transition: transform .22s ease, opacity .22s ease; }
.nl-item.open .nl-caret { transform: rotate(180deg); opacity: .8; }
/* while she speaks a fact the row keeps its ink (Kiersten, Aug 3) — liveness
   already lives in the mini-player's waveform + green play button; a green
   label read as selection, not sound */
.nl-item.speaking .nl-caret { opacity: 1; }

/* the expanded fact: her voice message at whisper scale. The panel is always
   in the DOM at 0fr; opening grows the grid row (height animates for free)
   while the card inside fades down into place a beat later. */
.nl-expand { display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows .34s cubic-bezier(.4,0,.2,1); }
.nl-expand.grown { grid-template-rows: 1fr; }
.fx { overflow: hidden; min-height: 0; }
.fx-in { margin: 3px 1px 12px; padding: 12px 13px 13px;
  background: var(--card); border: 1px solid var(--hairline-lo); border-radius: 16px;
  box-shadow: 0 8px 20px rgba(53,52,43,0.08);       /* same lift as the notif card */
  opacity: 0; transform: translateY(-5px);
  transition: opacity .26s ease .1s, transform .26s ease .1s; }
.nl-expand.grown .fx-in { opacity: 1; transform: none; }
.fx-in .dots { display: block; text-align: center; padding: 4px 0; }
/* header mirrors the hero bubble: identity leads (avatar, left), the act
   closes (green ▶, right), waveform + countdown between */
.fx-head { display: flex; align-items: center; gap: 10px; margin-bottom: 9px; }
.fx-avatar { width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0;
  background: url("img/botanist-n4.jpg") center/cover #DDDBD4;
  box-shadow: inset 0 0 0 1px rgba(53,52,43,0.10); }
.fx-wave { flex: 0 0 auto;   /* content-width: the timer docks at the bars' end */
  display: flex; align-items: center; gap: 2.5px; height: 16px; }
.fx-wave .bar { width: 2.5px; border-radius: 2px; flex-shrink: 0;
  background: var(--wave-off); }
.fx-wave .bar.played { background: var(--green); }
.fx-wave.playing .bar { animation: wavebreath 1.1s ease-in-out infinite; }
.fx-timer { flex-shrink: 0; font: 600 11px "Inter", sans-serif;
  letter-spacing: 0.14em; color: var(--muted); font-variant-numeric: tabular-nums; }
.fact-play { width: 36px; height: 36px; border-radius: 50%; flex-shrink: 0;
  margin-left: auto;   /* bars + timer sit left as one voice-note unit; ▶ closes the line */
  display: grid; place-items: center; border: 0; cursor: pointer;
  background: var(--green); }
.fact-play svg { fill: var(--green-glyph); }
.fact-play .ic-play { display: none; transform: translateX(1px); }  /* optical centering */
.fact-play.paused .ic-pause { display: none; }
.fact-play.paused .ic-play { display: block; }
.fact-play.needs-tap { animation: pulse 1.4s ease-in-out infinite; }  /* armed, waiting for the tap */
/* her words, lit as she says them — the bubble's karaoke at rest size.
   Idle/paused = fully readable (matches .kara:not(.lit)) */
.fact-text { margin: 0; font: 400 14.5px/1.6 "Inter", sans-serif; color: rgba(53,52,43,0.92);
  position: relative; }   /* anchors the absolutely-positioned "… See more" */
.fact-text .fw { opacity: 1; transition: opacity .18s ease; }
.fact-text .fbr { display: block; height: 9px; }   /* paragraph seam, same idiom as .kbr */
/* See more / See less — the bubble's model verbatim: cut words hide only in
   the preview state; the label is bottom-anchored on line 3 (left set by the
   fit, baseline matches because font metrics match the text); expanded, it
   becomes a full-width right-aligned "See less" below the text. */
.fx-in:not(.expanded) .fact-text .fw.cut,
.fx-in:not(.expanded) .fact-text .fbr.cut { display: none; }
.fact-more { display: block; margin: 0; border: none; padding: 0; background: none; cursor: pointer;
  position: absolute; bottom: 0;
  color: var(--see-more, var(--muted));            /* same 80%-accent as the bubble's label */
  font: 600 14.5px/1.6 "Inter", sans-serif; }
.fx-in.expanded .fact-more { position: static; width: 100%; text-align: right; }
.fact-more[hidden] { display: none !important; }
.fact-text { cursor: pointer; }
.fact-text.lit .fw { opacity: 0.32; }
.fact-text.lit .fw.spoken { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .fx-wave.playing .bar, .fact-play.needs-tap { animation: none !important; }
  .nl-expand, .fx-in, .nl-caret { transition: none !important; }
}
/* smart-reminders CTA: the V2 phone-notification moment, rebuilt natively —
   an iOS-style notification card with THIS plant as the thumbnail */
.notif {
  display: flex; align-items: center; gap: 11px;
  margin: 24px 0 16px; padding: 13px 14px;
  background: var(--card); border: 1px solid var(--hairline-lo);
  border-radius: 18px; box-shadow: 0 8px 20px rgba(53,52,43,0.08);
}
.notif-icon {
  width: 34px; height: 34px; border-radius: 9px; flex-shrink: 0;
  background: color-mix(in srgb, var(--green) 16%, var(--card)); display: grid; place-items: center; font-size: 18px;
}
.notif-body { flex: 1; min-width: 0; }
.notif-top { display: flex; justify-content: space-between; gap: 8px;
  font: 400 13.5px "Inter", sans-serif; color: rgba(53,52,43,0.9); }
.notif-ago { color: var(--muted); flex-shrink: 0; }
.notif-msg { font: 600 var(--body-size)/1.35 "Inter", sans-serif; color: rgba(53,52,43,0.9); margin-top: 2px; }
.notif-thumb {
  width: 38px; height: 38px; border-radius: 8px; flex-shrink: 0;
  background-size: cover; background-position: center;
  background-color: var(--bubble);
}
.notif { position: relative; z-index: 2; }        /* card floats over the beam */
.notif-phone { display: block; width: 100%; margin: -10px 0 4px; opacity: 0.82; }
.bf-cta {
  display: block; text-align: center; text-decoration: none;
  margin-top: 16px; padding: 15px 18px; border-radius: 999px;
  background: var(--cta-bg); color: var(--cta-text);
  box-shadow: inset 0 0 0 1.5px var(--btn-ring);
  font: 600 var(--body-size) "Inter", sans-serif;   /* identical metrics to the question chips */
}
.bf-cta-top { margin-top: 8px; }

/* ---------- onboarding (2 screens; ?onboard=0 bypasses) ----------
   P1 centered unit: everything one vertical mass, breathing room above and
   below. Screen 1: disc + badge -> claim -> the fork. Screen 2: muted USDA
   zone map -> claim -> the permission. Visual slots are height-matched
   (~150px disc + badge overhang vs ~174px map) so titles and buttons land
   at the same y across the transition. */
.onboard {
  position: fixed; inset: 0; z-index: 90; background: var(--cream);
  display: flex; justify-content: center; overflow-y: auto;
}
.ob-screen {
  width: 100%; max-width: 460px; min-height: 100svh;
  display: flex; flex-direction: column;
  padding: 26px 24px max(24px, env(safe-area-inset-bottom));
  position: relative; box-sizing: border-box;
}
.ob-skip {
  position: absolute; top: 22px; right: 24px; background: none; border: none; cursor: pointer;
  font: 600 13.5px "Inter", sans-serif; color: var(--muted);
  padding: 8px 10px; margin: -8px -10px;
}
/* shared fixed-height visual slot: disc (screen 1) and map (screen 2) center
   in the same box, so title/body/buttons land at identical y on both screens */
.ob-slot { height: 176px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.ob-visual { position: relative; margin: 0 auto; width: 150px; flex-shrink: 0; }
.ob-disc {
  width: 150px; height: 150px; border-radius: 50%;
  border: 1px solid var(--hairline-lo);
  box-shadow: 0 10px 26px rgba(53,52,43,0.12);
  background: #E7F0EA center 30%/cover;
  display: grid; place-items: center;
}
.ob-disc.photo { background-image: var(--ob-hero);
  background-size: 135%; background-position: center 28%; }  /* zoom past the baked-in photo credit */
.ob-disc svg { stroke: var(--green); stroke-width: 1.6; fill: none;
  stroke-linecap: round; stroke-linejoin: round; }
.ob-badge {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: -18px;
  display: flex; align-items: center; gap: 8px; white-space: nowrap;
  background: var(--card); border: 1px solid var(--hairline-lo); border-radius: 999px;
  padding: 8px 15px 8px 9px; box-shadow: 0 8px 20px rgba(53,52,43,0.08);
}
.ob-badge .ob-dot {
  width: 30px; height: 30px; border-radius: 50%; background: var(--green);
  display: grid; place-items: center; flex-shrink: 0;
}
.ob-badge .ob-dot svg { fill: var(--green-glyph); stroke: none; }
.ob-badge .ob-bars { display: flex; align-items: center; gap: 2.5px; }
.ob-badge .ob-bars span { width: 2.5px; border-radius: 2px; background: var(--green); opacity: .75; }
.ob-badge .ob-cap { font: 600 13px "Inter", sans-serif; color: var(--muted); }
.ob-badge.text { padding: 6px 18px 6px 7px; }
.ob-badge-avatar { width: 38px; height: 38px; border-radius: 50%; flex-shrink: 0;
  background: url("img/botanist-n4.jpg") center/cover #DDDBD4; }
.ob-map { position: relative; width: 280px; margin: 0 auto; flex-shrink: 0; }
.ob-map img { width: 100%; height: 174px; display: block; filter: drop-shadow(0 14px 26px rgba(53,52,43,0.18)); }
/* integer height (vs intrinsic 173.9px) keeps slot centering on whole pixels */
.ob-map .ob-pin {
  position: absolute; left: 47.1%; top: 43.0%; width: 54px;
  /* center-anchored; values hand-tuned via pin-tune.html */
  transform: translate(-50%, -50%);
  filter: drop-shadow(0 5px 8px rgba(53,52,43,0.35));
}
.ob-title {
  margin: 20px 0 12px; text-align: center;
  /* visual->title gap (~23px incl. slot slack) matches the body->buttons 22px,
     so the text block sits centered between its neighbors */
  font: 600 30px/1.1 "Inter Tight", "Inter", sans-serif; letter-spacing: -0.02em; color: var(--ink);
}
.ob-body {
  margin: 0 auto; max-width: 320px; min-height: 50px; text-align: center;
  font: 400 16.5px/1.5 "Inter", sans-serif; color: rgba(53,52,43,0.9);
}
/* title+body share one fixed box spanning visual->buttons — on BOTH screens
   (Jul 22: screen 1 lacked it, so its text region ran ~3px shorter and the
   flex spacers spread the difference — slot and buttons drifted between
   screens). Same box height = same latitudes by construction. Centering it
   also means the title-only state (body hidden via .ob-screen[data-sub="0"])
   floats centered in that same region without the buttons shifting. */
.ob2-text { display: grid; align-items: center; min-height: 118px; }
/* real + ghost stack in one cell: the box is as tall as the TALLER screen's
   text at the current width, so button latitude can never drift — even when
   a line wraps at a narrow frame */
.ob2-text > .ob-text-real, .ob2-text > .ob-text-ghost { grid-area: 1 / 1; }
.ob2-text > .ob-text-ghost { visibility: hidden; }
.ob-screen[data-sub="0"] #ob2Body { display: none; }
.ob-sp { flex: 1; min-height: 22px; }
.ob-screen > .ob-sp:first-child { flex: 0.65; }
.ob-btns { width: 100%; margin-top: 22px; }
.ob-btn {
  display: flex; align-items: center; justify-content: center; gap: 9px; width: 100%;
  border-radius: 18px; padding: 19px 20px; margin-top: 12px; cursor: pointer;
  font: 600 17px "Inter", sans-serif; box-sizing: border-box;
  transition: transform .1s ease;
}
.ob-btns .ob-btn:first-child { margin-top: 0; }
.ob-btn:active { transform: scale(.985); }
.ob-btn.primary { background: var(--btn-bg); color: var(--btn-text); border: none; box-shadow: inset 0 0 0 1.5px var(--btn-ring), 0 8px 20px rgba(var(--accent-rgb),0.28); }
.ob-btn.secondary { background: var(--card); color: rgba(53,52,43,0.9); border: 1px solid var(--hairline); }
.ob-btn svg { flex-shrink: 0; width: 22px; height: 22px; stroke: currentColor; fill: none;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
/* (ob-quiet retired Jul 10 — "Select my city" is a real secondary button now,
   per Alex: too substantive an action for skip styling. Both screens carry a
   primary+secondary pair, so button alignment holds by construction.) */

/* ---------- location sheet ---------- */
.sheet-backdrop {
  position: fixed; inset: 0; background: rgba(53,52,43,0.35);
  display: flex; align-items: flex-end; justify-content: center; z-index: 50;
  animation: fade .2s ease;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.sheet {
  background: var(--cream); width: 100%; max-width: 460px;
  border-radius: 26px 26px 0 0; padding: 12px 22px max(22px, env(safe-area-inset-bottom));
  box-shadow: 0 -10px 40px rgba(53,52,43,0.25); animation: slideup .26s ease;
  border-top: 1px solid var(--hairline-lo);
}
@keyframes slideup { from { transform: translateY(100%); } to { transform: none; } }
.sheet-handle { width: 44px; height: 5px; border-radius: 3px; background: var(--hairline); margin: 4px auto 14px; }
.sheet-title { font: 600 21px "Inter Tight", "Inter", sans-serif; margin: 0 0 16px; color: var(--ink); }
.loc-options { display: flex; flex-direction: column; gap: 8px; }
.loc-opt {
  text-align: left; background: var(--card); border: 1px solid var(--hairline);
  border-radius: 14px; padding: 14px 16px; font: 600 15px "Inter", sans-serif; color: var(--ink); cursor: pointer;
}
.loc-opt.active { border-color: var(--green); box-shadow: 0 0 0 2px rgba(var(--accent-rgb),0.25); }
/* The search field exits the tappable-paper language (Kiersten, Aug 3):
   lifted white pills = tap me, so the input sits RECESSED — a sunken tint on
   the sheet with a magnifier — and only lifts to white + jade ring while
   it's being typed in. Full pill radius is the third tell. */
.loc-search {
  width: 100%; box-sizing: border-box;
  background: rgba(53,52,43,0.07) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%235C5B51' stroke-width='2.4' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.8-3.8'/%3E%3C/svg%3E") 15px center / 16px no-repeat;
  border: 1px solid transparent; border-radius: 999px; padding: 12px 16px 12px 41px;
  font: 500 15px "Inter", sans-serif; color: var(--ink); outline: none;
  margin-bottom: 12px; -webkit-appearance: none;
  transition: background-color .16s ease, border-color .16s ease, box-shadow .16s ease;
}
.loc-search::placeholder { color: var(--muted); }
.loc-search:focus { background-color: var(--card); border-color: var(--green);
  box-shadow: 0 0 0 2px rgba(var(--accent-rgb),0.18); }
.loc-results { display: flex; flex-direction: column; gap: 8px; max-height: 264px; overflow-y: auto; margin-bottom: 4px; }
.loc-result-sub { color: var(--muted); font-weight: 500; }
.loc-none { color: var(--muted); font: 500 14px "Inter", sans-serif; padding: 10px 4px; }
.loc-use-real { width: 100%; margin-top: 14px; background: none; border: none; color: var(--accent-text); font: 600 14px "Inter", sans-serif; cursor: pointer; padding: 8px; }

/* ---------- passcode gate ---------- */
.gate {
  position: fixed; inset: 0; z-index: 100; background: var(--cream);
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
.gate-card { display: flex; flex-direction: column; align-items: center; gap: 10px; width: 100%; max-width: 320px; text-align: center; }
.gate-emoji { font-size: 44px; }
.gate-title { font: 600 26px "Inter Tight", "Inter", sans-serif; letter-spacing: -0.02em; color: var(--ink); }
.gate-sub { color: var(--muted); font: 500 14px "Inter", sans-serif; margin-bottom: 6px; }
.gate-input {
  width: 100%; text-align: center; background: var(--card);
  border: 1px solid var(--hairline); border-radius: 14px; padding: 14px;
  color: var(--ink); font: 600 18px "Inter", sans-serif; letter-spacing: 0.04em; outline: none;
}
.gate-input::placeholder { color: var(--muted); letter-spacing: 0; }
.gate-btn {
  width: 100%; background: var(--cta-bg); color: var(--cta-text); border: none;
  box-shadow: inset 0 0 0 1.5px var(--btn-ring);
  border-radius: 14px; padding: 14px; font: 700 16px "Inter", sans-serif; cursor: pointer;
}
.gate-err { color: var(--err); font: 500 13px "Inter", sans-serif; }

/* ---------- desktop: phone frame (screen-share mode) ----------
   Wide viewports get the app inside an iPhone-ish bezel, scrolling within.
   Phones are untouched (media query never matches). The fixed overlays
   (onboarding, city sheet, passcode gate) adopt the same frame box so they
   stay inside the "phone" instead of covering the desktop window. */
@media (min-width: 700px) and (min-height: 520px) {
  body { background: var(--desk); }
  /* content renders at a reduced scale inside the frame (like a real phone's
     density), so the card + all four question chips sit above the fold */
  /* Kiersten-tuned (frame-tuner, Jul 8, round 2): 358x728 frame, content 0.89.
     Live-adjustable: open the page with ?tune to get sliders for these. */
  :root {
    --frame-w: 402px;
    --frame-h: 728px;
    --frame-scale: 0.89;
  }
  .app {
    position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%) scale(var(--frame-scale));
    width: var(--frame-w); max-width: var(--frame-w); min-height: 0;
    height: calc(min(var(--frame-h), 100vh - 64px) / var(--frame-scale));
    overflow-y: auto; overscroll-behavior: contain; border-radius: 47px;
    box-shadow: 0 0 0 12px #262624, 0 0 0 15px #4A4A46, 0 34px 90px rgba(38,38,36,0.4);
    scrollbar-width: none;
  }
  .app::-webkit-scrollbar { display: none; }
  .onboard, .sheet-backdrop, .gate {
    position: fixed; inset: auto; left: 50%; top: 50%; transform: translate(-50%, -50%) scale(var(--frame-scale));
    width: var(--frame-w); height: calc(min(var(--frame-h), 100vh - 64px) / var(--frame-scale));
    border-radius: 47px; overflow: hidden;
  }

  /* iOS chrome inside the frame: status bar (time / signal / wifi / battery)
     + Dynamic Island, overlaying the hero like a real edge-to-edge app.
     Fixed descendants position against .app (it has a transform), so these
     ride the frame, not the browser window. Repeated for the overlays so the
     chrome persists through onboarding and the passcode gate. */
  .app::after, .onboard::after, .gate::after {
    content: "9:41";
    position: fixed; top: 0; left: 0; right: 0; height: 52px; z-index: 96;
    display: flex; align-items: center; padding: 4px 0 0 36px;
    font: 600 15.5px -apple-system, "Inter", sans-serif; color: #1F1E1A;
    pointer-events: none;
    background:
      url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'122'%20height%3D'35'%20viewBox%3D'0%200%20122%2035'%3E%3Crect%20x%3D'0'%20y%3D'0'%20width%3D'122'%20height%3D'35'%20rx%3D'17.5'%20fill%3D'%23141412'%2F%3E%3C%2Fsvg%3E") center 10px / 122px 35px no-repeat,
      url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'78'%20height%3D'14'%20viewBox%3D'0%200%2078%2014'%3E%20%3Crect%20x%3D'0'%20y%3D'8.5'%20width%3D'3'%20height%3D'4.5'%20rx%3D'1'%20fill%3D'%231F1E1A'%2F%3E%20%3Crect%20x%3D'4.7'%20y%3D'6.2'%20width%3D'3'%20height%3D'6.8'%20rx%3D'1'%20fill%3D'%231F1E1A'%2F%3E%20%3Crect%20x%3D'9.4'%20y%3D'3.8'%20width%3D'3'%20height%3D'9.2'%20rx%3D'1'%20fill%3D'%231F1E1A'%2F%3E%20%3Crect%20x%3D'14.1'%20y%3D'1.2'%20width%3D'3'%20height%3D'11.8'%20rx%3D'1'%20fill%3D'%231F1E1A'%2F%3E%20%3Cpath%20d%3D'M24.5%205.4%20a9.5%209.5%200%200%201%2013.4%200'%20stroke%3D'%231F1E1A'%20stroke-width%3D'2'%20fill%3D'none'%20stroke-linecap%3D'round'%2F%3E%20%3Cpath%20d%3D'M27.1%208.4%20a5.9%205.9%200%200%201%208.2%200'%20stroke%3D'%231F1E1A'%20stroke-width%3D'2'%20fill%3D'none'%20stroke-linecap%3D'round'%2F%3E%20%3Ccircle%20cx%3D'31.2'%20cy%3D'11.6'%20r%3D'1.7'%20fill%3D'%231F1E1A'%2F%3E%20%3Crect%20x%3D'46'%20y%3D'1'%20width%3D'27'%20height%3D'12'%20rx%3D'3.6'%20stroke%3D'%231F1E1A'%20stroke-opacity%3D'0.35'%20stroke-width%3D'1.2'%20fill%3D'none'%2F%3E%20%3Cpath%20d%3D'M74.6%204.8%20v4.4%20a2.4%202.4%200%200%200%200%20-4.4'%20fill%3D'%231F1E1A'%20fill-opacity%3D'0.4'%2F%3E%20%3Crect%20x%3D'47.6'%20y%3D'2.6'%20width%3D'23.8'%20height%3D'8.8'%20rx%3D'2.4'%20fill%3D'%231F1E1A'%2F%3E%20%3Ctext%20x%3D'59.5'%20y%3D'10.3'%20text-anchor%3D'middle'%20font-family%3D'-apple-system%2CHelvetica%2CArial%2Csans-serif'%20font-size%3D'8.6'%20font-weight%3D'700'%20fill%3D'%23FDF8EF'%3E100%3C%2Ftext%3E%20%3C%2Fsvg%3E") right 24px top 21px / auto 14px no-repeat;
  }

}



/* ---------- photo-bleed exploration (Alex, Jul 17) ----------
   --photo-bleed extends photo + fade below the hero, behind the cards.
   Name + location ride a liquid-glass panel so they hold up over any photo. */
/* the hero fade, generalized: a full-width band where the photo melts into
   a blurred, cream-tinted version of itself, then re-sharpens below. Varies
   along ONE axis only — no corners, no lateral edges, nothing to see except
   the photo calming down where the words live. */
.hero-caption { position: relative; z-index: 0; }
/* Text-shaped ambient — the region is generated FROM the lockup: app.js
   measures each rendered line (title, location) and wraps it in a soft
   elliptical pool — blur + measured cream lift — feathered from the text's
   own edge outward. The two pools merge into one organic silhouette that
   scales with any species name; everything beyond it stays sharp. Blur is
   11px: enough to melt detail behind glyphs, no more. Lift opacity comes
   from the luminance sampler, so contrast passes on any photo by math. */
.ambient { position: absolute; inset: 0; z-index: -1; pointer-events: none; }
.ambient b { position: absolute; display: block; opacity: 0; }   /* JS reveals once painted */
.ambient b canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
/* final glass (Jul 20): the pool is painted on the canvas — linear-light
   blur + per-line dim + a dissolve that starts at the letters — so no
   backdrop-filter or mask is needed here */
.glass-caption {
  align-self: flex-start; display: flex; flex-direction: column; gap: 5px;
  max-width: 100%; position: relative; z-index: 0;
}
/* GLYPH-SHAPED contrast (Jul 20, the anchor-break): every box-shaped field
   — blurred, dimmed, feathered — crosses the photo's object boundaries and
   reads as a smudge ON the photo. So the darkness now belongs to the
   LETTERFORMS: layered soft shadows feathering from each stroke's edge —
   the true epicenter. The photo itself is never touched. */
.glass-caption .plantName, .glass-caption .locpill {
  color: var(--hero-ink, #FFFFFF);
  /* thinned (Jul 21): the violet pool carries the contrast guarantee; these
     keep stroke definition only — the old 34px/0.50 aura was the darkest
     element on screen and read as a charcoal cloud */
  text-shadow:
    0 1px 3px rgba(12, 15, 12, 0.55),
    0 2px 12px rgba(12, 15, 12, 0.35);
}
.glass-caption .locpin path { fill: var(--pin, #FFFFFF); }
.glass-caption .locpill svg {
  filter: drop-shadow(0 1px 2px rgba(12, 15, 12, 0.5)) drop-shadow(0 2px 10px rgba(12, 15, 12, 0.3));
}
.glass-caption .locpill { max-width: none; }
.glass-caption .locpill #loclabel { overflow: visible; }


