/* Forest — calm, minimalist focus app.
   Mobile-first. Soft, low-contrast palette. Two views: Focus + Forest. */

:root {
  --sky-1: #dff0e2;
  --sky-2: #eaf4e5;
  --sky-3: #e3efdd;
  --ink: #3a4a3c;
  --ink-soft: #6d7d6c;
  --ink-faint: #9aa899;
  --leaf: #6ba368;
  --leaf-deep: #4e8154;
  --card: rgba(255, 255, 255, 0.72);
  --card-solid: #ffffff;
  --line: rgba(78, 129, 84, 0.16);
  --shadow: 0 10px 30px rgba(60, 90, 60, 0.10);
  --radius: 22px;
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-t: env(safe-area-inset-top, 0px);
  font-size: 16px;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  height: 100%;
  overscroll-behavior: none;
}

/*
 * The page paints under the status bar and the home indicator
 * (viewport-fit=cover + status-bar-style=default). The app gradient used to
 * live on `html` with `background-attachment: fixed`, on the theory that a
 * fixed background always covers the full viewport including both safe-area
 * strips. On real iOS devices it doesn't: `background-attachment: fixed`
 * combined with `viewport-fit=cover` safe areas can leave a real (not
 * sub-pixel) gap at the bottom, exposing the flat `background-color`
 * fallback there instead of the gradient — confirmed by pixel-sampling a
 * real screenshot, where the strip was an exact match for that fallback
 * color. `html`/`body` never actually scroll in this app (`body` is
 * `overflow: hidden`, every scrollable area is a child with its own
 * `overflow-y`), so `fixed` vs the default `scroll` attachment was never
 * doing anything here anyway — it was free to be the buggy option.
 *
 * Fixed the same way `.session`'s equivalent gap was fixed: a dedicated,
 * plain `position: fixed` element (`#bg-fill`, first child of `body`, see
 * index.html) that deliberately overshoots the viewport on every side.
 * `position: fixed` elements are always clipped to the true viewport by the
 * browser, so overshooting is free and guarantees the gradient paints under
 * any gap this class of bug leaves, regardless of why the gap exists.
 * `html`/`body` keep only a flat fallback color now — `#bg-fill` is the
 * authoritative background.
 */
html, body { background: var(--sky-2); }

#bg-fill {
  position: fixed; top: -8px; right: -8px; left: -8px; bottom: -80px; z-index: 0;
  background: linear-gradient(180deg, var(--sky-1) 0%, var(--sky-2) 46%, var(--sky-3) 100%);
  pointer-events: none;
}
/* iOS reserves both safe-area strips — the status bar at the top and the home
   indicator at the bottom — and paints them from the document's own flat
   background (`html`), never from anything drawn inside the page. A dialog's
   scrim is a fixed inset:0 element like any other, so it dimmed the whole app
   *except* the status-bar band, which stayed pale and read as a bug. The colour
   here is `.overlay`'s scrim composited over the top of the gradient, so the
   strip lands on the same tone as the page right below it. Declared above
   `html.sleeping` on purpose: same specificity, so the blackout wins if a sheet
   is somehow open under it. */
html.dimmed { background: #a4b6a8; }
html { transition: background-color .25s ease; }

/* While Lights Out is active everything back to html itself goes black too —
   the same belt-and-suspenders reasoning as .session-blackout overshooting:
   in the astronomically unlikely case #bg-fill's own 80px overshoot isn't
   enough, html's flat fallback should still be black, not pale, underneath it. */
html.sleeping { background: #000; }
html.sleeping #bg-fill { background: #000; }

body {
  font-family: "Nunito", ui-rounded, "SF Pro Rounded", "Segoe UI", system-ui, -apple-system, sans-serif;
  color: var(--ink);
  background: transparent;
  -webkit-font-smoothing: antialiased;
  /* This is a tap-driven app, not a document: a long press anywhere should do
     nothing, the way it does in a native app. The `-webkit-` prefix is the
     half that actually matters on the platform this went wrong on — Safari
     only understood the unprefixed property from 17.0, so with that alone an
     iPhone long-press still ran a selection across the tree name, the blurb,
     the carousel and the plant button, complete with drag handles.
     `touch-callout` is the other half: without it the same press raises iOS's
     Copy / Look Up sheet and the magnifier loupe over the artwork. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  overflow: hidden;   /* each view manages its own height, the shell never scrolls */
}

/* ...but a text field is still a text field, and the two that matter most are
   read/write in opposite directions: the share link has to be selectable to be
   copied by hand, and the visit box needs iOS's own callout to paste into.
   Both are exactly what the rules above suppress, so they opt back in. */
input, textarea {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}

#app {
  max-width: 520px;
  margin: 0 auto;
  height: 100%;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  padding: calc(var(--safe-t) + 8px) 20px calc(var(--safe-b) + 76px);
  position: relative;
}

button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }

/* Beats any display the element would otherwise take, including the `display:
   grid` .icon-btn carries — which is what the install button needs, since it
   is hidden far more often than it is shown. */
.hidden { display: none !important; }

/* ---------- top bar ---------- */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 2px 10px;
}
.topbar .mark { display: flex; align-items: center; gap: 8px; font-weight: 800; letter-spacing: .2px; }
.topbar .mark svg { width: 26px; height: 26px; }
.topbar .mark span { font-size: 1.05rem; }
.topbar-actions { display: flex; align-items: center; gap: 8px; }
.icon-btn {
  width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--card); box-shadow: var(--shadow);
  font-size: 1.05rem; color: var(--ink-soft);
  transition: transform .15s ease;
}
.icon-btn:active { transform: scale(0.92); }
.icon-btn svg { width: 20px; height: 20px; display: block; }
#sound-btn.muted, #session-sound-btn.muted { color: var(--ink-faint); }

/* ---------- view plumbing ---------- */
.view { display: none; flex: 1; min-height: 0; flex-direction: column; animation: fade .35s ease; }
.view.active { display: flex; }
@keyframes fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* ---------- FOCUS: home / picker ----------
   Deliberately never scrolls. The hero is the only flexible row; the info
   block, carousel and buttons are fixed-height, so everything from the tree
   down to the tab bar always fits on one screen. */
.stage {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;   /* the whole column sits centred in what's left */
  text-align: center;
  overflow: hidden;
}

.tree-hero {
  /* flex-shrink: 0 — every tree's picture claims exactly this much space,
     never squeezed smaller to make room for a sibling (like the window pill
     below) that only some trees have. Without this, the hero's own box size
     silently varied tree to tree and dragged everything below it along. */
  flex: 0 0 auto;
  width: min(58vw, 228px);
  aspect-ratio: 1 / 1;
  max-height: 34vh;
  display: grid;
  place-items: center;
  filter: drop-shadow(0 12px 18px rgba(70, 110, 70, 0.14));
}
.tree-hero .tree-svg { width: 100%; height: 100%; max-height: 100%; }
.tree-hero.pulse { animation: sway 6s ease-in-out infinite; }
@keyframes sway { 0%,100% { transform: rotate(-1deg); } 50% { transform: rotate(1deg); } }

.pick-info { flex: none; padding: 2px 0 0; }
.pick-name { font-size: 1.42rem; font-weight: 800; margin: 0; }
.pick-dur { color: var(--leaf-deep); font-weight: 800; font-size: .98rem; margin: 3px 0 0; }
/* [hidden] normally collapses to display:none, which drops the pill's own
   height and shunts the carousel/button up for every tree that doesn't have
   a time window. Keep the box always, just make it invisible, so a tree
   without a window reserves exactly the same space as one with one. */
.pick-window[hidden] { display: inline-block; visibility: hidden; }
.pick-window {
  margin: 5px auto 0; display: inline-block;
  font-size: .68rem; font-weight: 800; letter-spacing: .3px;
  color: #8a6a2a; background: #fdf3dd; padding: 3px 10px; border-radius: 999px;
}
.pick-blurb {
  color: var(--ink-soft); font-size: .84rem; max-width: 30ch;
  margin: 6px auto 0; line-height: 1.4;
  height: 2.35em; overflow: hidden;      /* fixed height so rows never reflow */
}

/* the swipe strip */
.picker { flex: none; width: 100%; }
.carousel {
  --cw: 72px;
  display: flex; gap: 10px; overflow-x: auto;
  scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
  padding: 12px calc(50% - var(--cw) / 2);
  scrollbar-width: none;
}
.carousel::-webkit-scrollbar { display: none; }
.ccard {
  scroll-snap-align: center;
  flex: 0 0 auto; width: var(--cw);
  background: var(--card-solid); border-radius: 16px;
  padding: 8px 4px 7px;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  box-shadow: var(--shadow);
  opacity: .58; transform: scale(.88);
  transition: transform .18s ease, opacity .18s ease, box-shadow .18s ease;
  position: relative;
}
.ccard-art { width: 42px; height: 42px; }
.ccard-art .tree-svg { width: 100%; height: 100%; }
.ccard-dur { font-size: .6rem; font-weight: 800; color: var(--ink-faint); }
.ccard.sel {
  opacity: 1; transform: scale(1.1);
  box-shadow: 0 0 0 2.5px var(--leaf-deep), var(--shadow);
}
.ccard.rec:not(.sel) { box-shadow: 0 0 0 2px rgba(216, 169, 45, .5); }
.ccard-var {
  position: absolute; top: -5px; right: -3px;
  width: 17px; height: 17px; border-radius: 50%;
  background: #fff; color: #d9a017; font-size: .6rem; line-height: 1;
  display: grid; place-items: center;
  box-shadow: 0 2px 6px rgba(60, 90, 60, .22);
}
.ccard-new {
  position: absolute; top: -5px; left: -3px;
  padding: 2px 6px; border-radius: 999px;
  background: var(--leaf-deep); color: #fff;
  font-size: .5rem; font-weight: 800; letter-spacing: .3px; text-transform: uppercase;
  line-height: 1.3;
  box-shadow: 0 2px 6px rgba(60, 90, 60, .22);
}

.pick-actions { flex: none; width: 100%; }

.badge {
  font-size: .6rem; font-weight: 800; text-transform: uppercase; letter-spacing: .6px;
  color: #b57a3f; background: #fbeede; padding: 3px 8px; border-radius: 999px;
  vertical-align: middle; margin-left: 6px; white-space: nowrap;
}

.btn-plant {
  margin-top: 4px;
  width: 100%;
  padding: 15px;
  border-radius: 18px;
  background: linear-gradient(180deg, #6fae6c, var(--leaf-deep));
  color: #fff; font-weight: 800; font-size: 1.02rem; letter-spacing: .3px;
  box-shadow: 0 10px 22px rgba(70, 130, 74, 0.32);
  transition: transform .15s ease, filter .15s ease;
}
.btn-plant:active { transform: translateY(2px) scale(0.99); filter: brightness(0.97); }

/* ---------- FOCUS: session (growing) ---------- */
.session {
  /* Overshoot past the true viewport edges, especially the bottom: some iOS
     builds leave a real (not sub-pixel) gap between a fixed inset:0 box and
     the actual screen edge inside the safe-area strip, exposing html's own
     background underneath. position:fixed is always clipped to the real
     viewport, so overshooting here is free — padding below is padded out by
     the same amount so on-screen content doesn't shift. */
  position: fixed; top: -8px; right: -8px; left: -8px; bottom: -80px; z-index: 40;
  display: none; flex-direction: column; align-items: center; justify-content: center;
  padding: calc(var(--safe-t) + 20px + 8px) calc(24px + 8px) calc(var(--safe-b) + 28px + 80px);
  background:
    radial-gradient(150% 105% at 50% -12%, #eef8ea 0%, #e2f1dc 42%, #d5ebd2 78%, #cfe7d3 100%);
  text-align: center;
  isolation: isolate;
  overflow: hidden;
}
.session.active { display: flex; }
/* soft dappled light that drifts very slowly behind everything */
.session::before {
  content: ""; position: absolute; inset: -12%; z-index: 0; pointer-events: none;
  background:
    radial-gradient(36% 30% at 30% 24%, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0) 70%),
    radial-gradient(30% 26% at 76% 62%, rgba(205, 233, 196, 0.5), rgba(205, 233, 196, 0) 70%);
  animation: bgDrift 22s ease-in-out infinite alternate;
}
/* the faintest vignette to settle attention on the tree */
.session::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: radial-gradient(120% 92% at 50% 44%, rgba(0, 0, 0, 0) 56%, rgba(58, 88, 58, 0.10) 100%);
}
.session > * { position: relative; z-index: 1; }
/* The mute control, parked in the session's top-right and out of the way of
   the tree. It has to live *after* the rule above — that one is `.session > *`,
   the same specificity, so anything earlier loses on source order and the
   button just sits in the flex flow. The +8px on both offsets cancels
   .session's own viewport overshoot; z-index stays under the blackout's 5 so
   Lights Out still covers it. */
.session-sound {
  position: absolute;
  top: calc(var(--safe-t) + 14px + 8px); right: calc(16px + 8px);
  z-index: 2; background: rgba(255, 255, 255, .78);
}
.session-blackout {
  /* same viewport overshoot as .session, and for the same reason */
  position: fixed; top: -8px; right: -8px; left: -8px; bottom: -80px; z-index: 5;
  background: #000;
  opacity: 0; visibility: hidden;
  transition: opacity .45s ease, visibility 0s linear .45s;
}
.session.sleeping .session-blackout {
  opacity: 1; visibility: visible;
  transition: opacity .45s ease, visibility 0s linear 0s;
}
@keyframes bgDrift {
  from { transform: translate3d(-1.5%, -1%, 0) scale(1); }
  to   { transform: translate3d(1.5%, 1.5%, 0) scale(1.06); }
}
/* The stage flexes so the whole column (tree, name, clock, hint, button) sits
   optically centred on any screen height instead of the tree reserving a fixed
   square and shoving everything else downward. */
.session-stage {
  position: relative;
  flex: 0 1 auto;
  width: min(66vw, 260px); height: min(66vw, 260px);
  max-height: 33vh;
  margin-bottom: 6px;
  display: grid; place-items: center;
}
/* a warm halo that breathes gently behind the growing tree */
.session-stage::before {
  content: ""; position: absolute; left: 50%; top: 50%; width: 128%; height: 128%;
  transform: translate(-50%, -50%); z-index: 0; pointer-events: none;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.5) 0%, rgba(255, 255, 255, 0.12) 42%, rgba(255, 255, 255, 0) 72%);
  animation: haloBreathe 7s ease-in-out infinite;
}
@keyframes haloBreathe {
  0%, 100% { opacity: .5;  transform: translate(-50%, -50%) scale(1); }
  50%      { opacity: .85; transform: translate(-50%, -50%) scale(1.08); }
}
.session .tree-hero {
  width: 100%; height: 100%; position: relative; z-index: 1;
  place-items: center;
  filter: drop-shadow(0 16px 26px rgba(48, 86, 54, 0.18));
}
.tree-fx { position: absolute; inset: 0; pointer-events: none; overflow: visible; z-index: 2; }
.session-tree-name { font-weight: 800; font-size: 1.15rem; }
.time-left { font-size: 2.9rem; font-weight: 800; letter-spacing: 1px; margin: 8px 0 2px; font-variant-numeric: tabular-nums; }
.session-hint {
  color: var(--ink-soft); font-size: .88rem; max-width: 26ch; line-height: 1.5;
  margin: 14px auto 0;
  display: flex; align-items: center; gap: 8px; justify-content: center;
  text-align: center;
}
.session-hint svg { width: 20px; height: 20px; flex: none; }
.session-actions { margin-top: 22px; display: flex; gap: 10px; justify-content: center; }
/* Give up exists for the two trees that would otherwise have no way to end:
   Yggdrasil, where it means "harvest" rather than "abandon", and the Phoenix,
   whose session lasts a whole day and does have to be abandonable. Hidden for
   every other tree, where leaving already withers it. */
#give-btn { display: none; }
.session.unlimited #give-btn,
.session.cumulative #give-btn { display: inline-block; }
.btn-give {
  color: var(--ink-faint); font-size: .86rem; font-weight: 700;
  padding: 10px 18px; border-radius: 12px; text-decoration: underline; text-underline-offset: 3px;
}
.btn-sleep {
  color: var(--ink-soft); font-size: .86rem; font-weight: 700;
  padding: 10px 18px; border-radius: 12px;
  background: var(--card-solid); box-shadow: var(--shadow);
}
.btn-sleep:active { transform: scale(.96); }
.session.fragile #sleep-btn { display: none; }

/* progress ring behind the tree */
.ring { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); pointer-events: none; }

/* ---------- FOREST view ---------- */
.forest-head { text-align: center; margin: 4px 0 10px; }
.forest-head h2 { margin: 0; font-size: 1.4rem; font-weight: 800; }
.forest-stats { display: flex; justify-content: center; gap: 26px; margin-top: 10px; }
.forest-stats .s-num { font-size: 1.4rem; font-weight: 800; color: var(--leaf-deep); line-height: 1; }
.forest-stats .s-lab { font-size: .72rem; color: var(--ink-faint); font-weight: 700; text-transform: uppercase; letter-spacing: .5px; margin-top: 3px; }

.grove {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;
  margin-top: 14px; padding-bottom: 8px;
}
.grove-cell {
  background: var(--card); border-radius: 18px; box-shadow: var(--shadow);
  aspect-ratio: 1 / 1; display: flex; align-items: flex-end; justify-content: center;
  position: relative; overflow: hidden; padding: 6px; cursor: pointer;
}
.grove-cell:active { transform: scale(.96); }

.sort-opt {
  display: flex; align-items: center; justify-content: space-between; width: 100%;
  text-align: left; padding: 12px 4px; border-top: 1px solid var(--line);
  font-weight: 700; font-size: .95rem; color: var(--ink);
}
.sort-opt:first-of-type { border-top: none; }
.sort-opt .check { color: var(--leaf-deep); font-weight: 800; opacity: 0; }
.sort-opt.active .check { opacity: 1; }
.grove-cell .tree-svg { width: 92%; height: 92%; }
.grove-cell.special { background: linear-gradient(180deg, #fff8e8, #fdf1d6); }
.grove-cell .cell-name {
  position: absolute; bottom: 5px; left: 0; right: 0; text-align: center;
  font-size: .62rem; font-weight: 800; color: var(--ink-soft);
}
.empty-grove {
  text-align: center; color: var(--ink-faint); margin: 40px auto; max-width: 26ch;
  font-size: .95rem; line-height: 1.6;
}
.empty-grove svg { width: 64px; height: 64px; opacity: .55; margin-bottom: 8px; }

/* ---------- bottom nav ---------- */
.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  display: flex; justify-content: center; gap: 8px;
  padding: 8px 12px calc(10px + var(--safe-b));
  background: none;   /* the page gradient runs behind, uninterrupted */
  pointer-events: none;
}
.tabbar-inner {
  pointer-events: auto;
  display: flex; gap: 6px; background: var(--card-solid);
  padding: 6px; border-radius: 999px; box-shadow: var(--shadow);
}
.tab {
  display: flex; align-items: center; gap: 7px;
  padding: 10px 20px; border-radius: 999px;
  color: var(--ink-faint); font-weight: 800; font-size: .9rem;
  transition: color .2s, background .2s;
}
.tab svg { width: 19px; height: 19px; }
.tab.active { color: #fff; background: var(--leaf-deep); }

/* ---------- overlays (tutorial / result) ---------- */
/* The sheet caps its own height against the visible viewport, so a sheet longer
   than the screen scrolls inside itself instead of running off the top and
   bottom with the dismiss button stranded below the fold. */
.overlay {
  position: fixed; inset: 0; z-index: 60;
  display: none; align-items: center; justify-content: center;
  padding: calc(var(--safe-t) + 16px) 20px calc(var(--safe-b) + 16px);
  background: rgba(40, 60, 45, 0.32); backdrop-filter: blur(4px);
}
.overlay.active { display: flex; animation: fade .25s ease; }
.sheet {
  position: relative;
  background: var(--card-solid); border-radius: 26px; box-shadow: 0 24px 60px rgba(40,70,45,.28);
  width: 100%; max-width: 380px;
  max-height: calc(100vh - var(--safe-t) - var(--safe-b) - 32px);
  max-height: calc(100dvh - var(--safe-t) - var(--safe-b) - 32px);
  display: flex; flex-direction: column; text-align: center;
  animation: pop .3s cubic-bezier(.2,.9,.3,1.2);
}
.sheet-body {
  overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  padding: 28px 24px; scrollbar-width: none;
}
.sheet-body::-webkit-scrollbar { display: none; }
.sheet-close {
  position: absolute; top: 10px; right: 10px; z-index: 2;
  width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center;
  background: #eef3ea; color: var(--ink-soft); font-size: .95rem; font-weight: 800;
}
.sheet-close:active { transform: scale(.92); }
@keyframes pop { from { transform: scale(.9); opacity: 0; } to { transform: none; opacity: 1; } }
.sheet .sheet-art { width: 132px; height: 132px; margin: 0 auto 6px; display: grid; place-items: end center; }
.sheet h3 { margin: 8px 0 6px; font-size: 1.3rem; font-weight: 800; }
.sheet p { color: var(--ink-soft); line-height: 1.55; font-size: .96rem; margin: 0 auto; max-width: 30ch; }
.sheet .sheet-dur { color: var(--leaf-deep); font-weight: 800; font-size: 1.02rem; margin-bottom: 6px; }
.sheet .sheet-track { color: var(--ink-faint); font-weight: 700; font-size: .8rem; margin-top: 10px; }
.sheet .btn-plant { margin-top: 20px; }
.sheet .btn-text { margin-top: 12px; color: var(--ink-faint); font-weight: 700; font-size: .88rem; padding: 8px; }

.dots { display: flex; gap: 7px; justify-content: center; margin-top: 18px; }
.dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--line); transition: background .2s, width .2s; }
.dots i.on { background: var(--leaf-deep); width: 18px; border-radius: 999px; }

.result-emoji { font-size: 2.2rem; }

/* ---------- add-to-home-screen sheet ----------
   The words come from install.js; these are the only styles they need. The
   glyph is inline in a sentence ("Tap [share] Share"), so it sizes off the
   text rather than off a fixed px, and is nudged down to sit on the baseline
   the way a letter does. */
.sheet .install-sub { margin-bottom: 4px; }
.sheet .install-steps {
  margin: 14px auto 0; padding: 0; list-style: none; counter-reset: step;
  text-align: left; max-width: 30ch;
  color: var(--ink-soft); font-size: .94rem; line-height: 1.5;
}
.sheet .install-steps li {
  counter-increment: step;
  position: relative; padding-left: 34px; margin-bottom: 12px;
}
.sheet .install-steps li:last-child { margin-bottom: 0; }
.sheet .install-steps li::before {
  content: counter(step);
  position: absolute; left: 0; top: -1px;
  width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center;
  background: #eaf3e6; color: var(--leaf-deep);
  font-size: .76rem; font-weight: 800;
}
.sheet .install-steps b { color: var(--ink); font-weight: 800; }
.install-glyph { width: 1.05em; height: 1.05em; vertical-align: -0.2em; color: var(--leaf-deep); }

/* ============================================================
   FOREST — grove picture, share/visit
   ============================================================ */

.forest-top {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 2px 0 6px;
}
.forest-name {
  font-size: 1.15rem; font-weight: 800; color: var(--ink);
  display: flex; align-items: center; gap: 6px; padding: 4px 2px;
}
.forest-name .pencil { color: var(--ink-faint); font-size: .9rem; }
.forest-actions { display: flex; gap: 8px; }
.chip {
  padding: 8px 16px; border-radius: 999px; font-weight: 800; font-size: .85rem;
  background: var(--card-solid); color: var(--ink-soft); box-shadow: var(--shadow);
}
.chip:active { transform: scale(.95); }
.chip-primary { background: var(--leaf-deep); color: #fff; }

.grove-toolbar { display: flex; justify-content: flex-end; padding: 6px 2px 0; }

.forest-scroll {
  flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 4px 2px 8px;
}

/* visiting banner */
.visit-banner {
  position: fixed; top: 0; left: 0; right: 0; z-index: 45;
  display: none; padding: calc(var(--safe-t) + 8px) 16px 10px;
  background: linear-gradient(180deg, #2f6b3f, #2f6b3fee);
  color: #fff; box-shadow: 0 6px 20px rgba(40, 70, 45, .3);
}
.visit-banner.show { display: block; }
.vb-inner { max-width: 520px; margin: 0 auto; display: flex; align-items: center; gap: 12px; justify-content: space-between; }
.vb-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.vb-text strong { font-size: .98rem; }
.vb-text span { font-size: .8rem; opacity: .9; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.visit-banner .chip { flex: none; background: #fff; color: var(--leaf-deep); }

/* ---------- portrait guard ----------
   The app is portrait-only, and only one of the three platforms can be told
   so: Android honours `orientation` in the web manifest, and the Capacitor
   wrapper sets android:screenOrientation outright. iOS Safari honours neither
   — there is no API a web page can call to lock orientation there — so a phone
   turned sideways gets this instead, which is the closest thing to a lock that
   exists on that platform.

   The query is deliberately narrow: `pointer: coarse` keeps it off desktop
   entirely (a 900x500 browser window is not a phone on its side), and the
   height cap keeps it off tablets, which have the room to render the column
   layout landscape without it looking broken. z-index sits above everything,
   the session's blackout (5) and the sheet overlay (60) included. */
.rotate-guard { display: none; }
@media (orientation: landscape) and (pointer: coarse) and (max-height: 560px) {
  .rotate-guard {
    position: fixed; inset: 0; z-index: 90;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 10px; padding: 20px 32px; text-align: center;
    background: linear-gradient(180deg, var(--sky-1), var(--sky-3));
    color: var(--leaf-deep);
  }
  .rotate-guard svg { width: 56px; height: 56px; opacity: .7; margin-bottom: 2px; }
  .rotate-title { margin: 0; font-size: 1.12rem; font-weight: 800; }
  .rotate-sub {
    margin: 0; max-width: 34ch; font-size: .86rem; font-weight: 700;
    color: var(--ink-soft);
  }
  /* Lights Out wins: the point of it is a screen that stays dark, and someone
     who blacked the screen and then knocked the phone sideways should not get
     a pale one back. The tree is fine either way — neither of these is a hide. */
  html.sleeping .rotate-guard { display: none; }
}

/* share / edit dialogs */
.share-link {
  width: 100%; margin: 6px 0 14px; padding: 12px 14px; border-radius: 12px;
  border: 1.5px solid var(--line); background: #f6f8f3; color: var(--ink-soft);
  font-size: .82rem; font-family: inherit;
}
.fld { display: block; text-align: left; font-weight: 800; font-size: .82rem; color: var(--ink-soft); margin: 10px 0 4px; }
.fld input, .fld textarea {
  width: 100%; margin-top: 6px; padding: 12px 14px; border-radius: 12px;
  border: 1.5px solid var(--line); background: #f8faf5; color: var(--ink);
  font-size: .95rem; font-family: inherit; font-weight: 700; resize: none;
}
.fld input:focus, .fld textarea:focus { outline: none; border-color: var(--leaf); }

/* ---------- special trees ---------- */
.link-special {
  display: block; margin: 18px auto 4px;
  color: var(--ink-faint); font-weight: 800; font-size: .82rem;
  padding: 6px 10px; border-radius: 10px;
}
.link-special:active { color: var(--leaf-deep); }
.badge.sun { color: #b5762a; background: #fdeecb; }
.badge.moon { color: #4a4f86; background: #e6e8fb; }
.badge.candy { color: #c14d80; background: #fde3ee; }
.badge.study { color: #3f7d8a; background: #e2f0f3; }
.badge.lunch { color: #a34b2f; background: #fbeed6; }
.badge.money { color: #8a6d1a; background: #faf0c8; }
.badge.new { color: #3f7d54; background: #dbf0df; }
.spec-intro { margin-bottom: 6px; }
.spec-row {
  display: flex; align-items: center; gap: 12px; text-align: left;
  padding: 10px 4px; border-top: 1px solid var(--line);
}
.spec-row:first-of-type { border-top: none; }
.spec-row .tree-svg { width: 46px; height: 46px; flex: none; }
.spec-row b { display: block; font-size: .98rem; color: var(--ink); }
.spec-row span { display: block; font-size: .82rem; color: var(--ink-soft); line-height: 1.4; margin-top: 2px; }
/* A locked tree is dimmed, not hidden — seeing the shape of what's coming is
   most of the reason to go and get it. */
.spec-row.locked b { color: var(--ink-soft); }
.spec-row .prog {
  display: inline-block; margin-left: 4px; padding: 1px 7px; border-radius: 999px;
  background: #e6eee4; color: var(--ink-soft);
  font-style: normal; font-weight: 800; font-size: .74rem; white-space: nowrap;
}
.spec-row .new {
  display: inline-block; margin-left: 4px; padding: 1px 7px; border-radius: 999px;
  background: #dbf0df; color: #3f7d54;
  font-style: normal; font-weight: 800; font-size: .74rem; white-space: nowrap;
}

/* the newly-unlocked species on the result sheet: drawn, not just named */
/* ---------- the unlock screen ----------
   Its own sheet now, ahead of the "your tree has grown" one. The brief is
   celebratory but calm: a bloom of warm light behind the species, the tree
   easing up into it, a slow ring going out. No confetti — the reward for an
   hour of quiet shouldn't be a firework. Every part of it is skipped under
   prefers-reduced-motion. */
.unlock-stage {
  position: relative; width: 172px; height: 172px;
  margin: 2px auto 0; display: grid; place-items: center;
}
.unlock-burst {
  position: absolute; inset: 0; border-radius: 50%;
  background: radial-gradient(circle at 50% 52%,
    rgba(255, 236, 176, .95) 0%, rgba(255, 226, 150, .5) 38%,
    rgba(196, 233, 198, .28) 62%, rgba(196, 233, 198, 0) 76%);
}
.unlock-art { position: relative; width: 148px; height: 148px; place-self: center; }
.unlock-art .tree-svg { width: 100%; height: 100%; }
.unlock-kicker {
  color: #b58a24; font-weight: 800; font-size: .76rem;
  text-transform: uppercase; letter-spacing: 1.2px; margin: 2px 0 0;
}
.sheet .unlock-when {
  color: var(--ink-faint); font-weight: 700; font-size: .82rem; margin-top: 8px;
}

@media (prefers-reduced-motion: no-preference) {
  .unlock-burst {
    animation: unlockBloom 2.6s cubic-bezier(.2, .7, .3, 1) both;
  }
  @keyframes unlockBloom {
    0%   { opacity: 0; transform: scale(.55); }
    35%  { opacity: 1; transform: scale(1.06); }
    100% { opacity: .78; transform: scale(1); }
  }
  /* a single ring going out, once — the "something happened" beat */
  .unlock-stage::after {
    content: ""; position: absolute; inset: 14%;
    border-radius: 50%; border: 2px solid rgba(255, 219, 133, .85);
    animation: unlockRing 1.5s ease-out both;
  }
  @keyframes unlockRing {
    0%   { opacity: 0;   transform: scale(.4); }
    18%  { opacity: .9;  }
    100% { opacity: 0;   transform: scale(1.55); }
  }
  .unlock-art { animation: unlockRise .95s cubic-bezier(.2, .9, .3, 1) both .1s; }
  @keyframes unlockRise {
    0%   { opacity: 0; transform: translateY(14px) scale(.82); }
    100% { opacity: 1; transform: none; }
  }
  .unlock-kicker { animation: unlockFade .7s ease both .55s; }
  .sheet .unlock-stage ~ h3 { animation: unlockFade .7s ease both .68s; }
  @keyframes unlockFade {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
  }
}

/* ============================================================
   TREE ANIMATION — living, per-tree ambience
   ------------------------------------------------------------
   Only "alive" trees (the focus session, the picker preview, the
   result art) animate — never the little grove thumbnails.
   ============================================================ */
@media (prefers-reduced-motion: no-preference) {

  /* whole-tree life: the session tree sways slowly from its base, with a
     barely-there breathe — an unhurried, organic drift rather than a metronome */
  .session .tree-svg.alive {
    animation: treeSway 8s ease-in-out infinite;
    transform-origin: 50% 92%;
  }
  @keyframes treeSway {
    0%   { transform: rotate(-1deg)   scale(1); }
    30%  { transform: rotate(0.6deg)  scale(1.006); }
    50%  { transform: rotate(1deg)    scale(1.004); }
    70%  { transform: rotate(0.3deg)  scale(1.008); }
    100% { transform: rotate(-1deg)   scale(1); }
  }

  /* every fx element rotates/scales about its own centre by default */
  .tree-svg.alive [class^="fx-"],
  .tree-svg.alive [class*=" fx-"] { transform-box: fill-box; transform-origin: center; }

  /* haloes breathe (world tree, phoenix, sunrise, cactus) */
  .tree-svg.alive .fx-glow { animation: fxGlow 4.2s ease-in-out infinite; }
  @keyframes fxGlow {
    0%, 100% { opacity: .45; transform: scale(1); }
    50%      { opacity: .85; transform: scale(1.09); }
  }

  /* phoenix flames flicker and lean */
  .tree-svg.alive .fx-flame {
    transform-origin: 50% 100%;
    animation: fxFlame 1.05s ease-in-out infinite;
  }
  @keyframes fxFlame {
    0%, 100% { transform: scaleY(1)    scaleX(1)    skewX(0deg); }
    25%      { transform: scaleY(1.2)  scaleX(.92)  skewX(4deg); }
    50%      { transform: scaleY(.88)  scaleX(1.06) skewX(-3deg); }
    75%      { transform: scaleY(1.1)  scaleX(.96)  skewX(2deg); }
  }

  /* twinkles — stars, embers, blossoms, gumdrop glints, moonlight */
  .tree-svg.alive .fx-star,
  .tree-svg.alive .fx-bloom,
  .tree-svg.alive .fx-glint { animation: fxTwinkle 2.1s ease-in-out infinite; }
  @keyframes fxTwinkle {
    0%, 100% { opacity: .35; }
    50%      { opacity: 1; }
  }

  /* four-point sparkles pulse in and spin a touch (world tree) */
  .tree-svg.alive .fx-spark { animation: fxSpark 2.4s ease-in-out infinite; }
  @keyframes fxSpark {
    0%, 100% { opacity: .3;  transform: scale(.7)  rotate(0deg); }
    50%      { opacity: 1;   transform: scale(1.15) rotate(35deg); }
  }

  /* sun disc pulses; its rays wheel slowly (sunrise, cactus) */
  .tree-svg.alive .fx-sun { animation: fxSun 3.6s ease-in-out infinite; }
  @keyframes fxSun {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.13); }
  }
  .tree-svg.alive .fx-rays {
    transform-box: view-box; transform-origin: 94px 22px;
    animation: fxSpin 26s linear infinite;
  }
  @keyframes fxSpin { to { transform: rotate(360deg); } }

  /* candy lollipop swirls */
  .tree-svg.alive .fx-spin { animation: fxSpin 7s linear infinite; }

  /* money coins bob and tilt, each on its own beat */
  .tree-svg.alive .fx-coin { animation: fxCoin 2.6s ease-in-out infinite; }
  @keyframes fxCoin {
    0%, 100% { transform: translateY(0)    rotate(-5deg); }
    50%      { transform: translateY(-2px) rotate(5deg); }
  }

  /* the moon drifts; banyan roots sway from where they hang */
  .tree-svg.alive .fx-moon { animation: fxMoon 6s ease-in-out infinite; }
  @keyframes fxMoon {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-2.5px); }
  }
  .tree-svg.alive .fx-root {
    transform-origin: 50% 0;
    animation: fxRoot 5s ease-in-out infinite;
  }
  @keyframes fxRoot {
    0%, 100% { transform: rotate(-1.6deg); }
    50%      { transform: rotate(1.6deg); }
  }
}

/* ---------- particle layer (session only) ---------- */
.particle { position: absolute; top: 0; left: 0; will-change: transform, opacity; border-radius: 50%; }
.p-petal  { border-radius: 60% 0 60% 0; filter: blur(0.3px); }
.p-leaf   { border-radius: 0 65% 0 65%; filter: blur(0.3px); }
.p-fly    { filter: blur(0.4px); }
.p-spark  { border-radius: 0; clip-path: polygon(50% 0, 60% 40%, 100% 50%, 60% 60%, 50% 100%, 40% 60%, 0 50%, 40% 40%); }
.p-bill   { border-radius: 2px; box-shadow: inset 0 0 0 1px rgba(232, 246, 224, .55); }

.p-fall    { animation: pFall  var(--dur) ease-in-out forwards; }
.p-rise    { animation: pRise  var(--dur) ease-out    forwards; }
.p-float   { animation: pFloat var(--dur) ease-in-out forwards; }
.p-twinkle { animation: pTwinkle var(--dur) ease-in-out forwards; }
.p-burst   { animation: pBurst var(--dur) cubic-bezier(.16,.68,.36,1) forwards; }

/* a fluttering descent — drifts sideways at the midpoint before settling */
@keyframes pFall {
  0%   { transform: translate(0, 0) rotate(0); opacity: 0; }
  14%  { opacity: var(--op, .82); }
  50%  { transform: translate(var(--mx), calc(var(--dy) * 0.52)) rotate(calc(var(--rot) * 0.5)); }
  86%  { opacity: var(--op, .82); }
  100% { transform: translate(var(--dx), var(--dy)) rotate(var(--rot)); opacity: 0; }
}
@keyframes pRise {
  0%   { transform: translate(0, 0) scale(1); opacity: 0; }
  18%  { opacity: var(--op, .95); }
  100% { transform: translate(var(--dx), var(--dy)) scale(.3); opacity: 0; }
}
@keyframes pFloat {
  0%   { transform: translate(0, 0); opacity: 0; }
  22%  { opacity: var(--op, .9); }
  50%  { opacity: .28; }
  78%  { opacity: var(--op, .9); }
  100% { transform: translate(var(--dx), var(--dy)); opacity: 0; }
}
@keyframes pTwinkle {
  0%   { transform: scale(0)   rotate(0);    opacity: 0; }
  50%  { transform: scale(1)   rotate(45deg); opacity: var(--op, 1); }
  100% { transform: scale(0)   rotate(90deg); opacity: 0; }
}
@keyframes pBurst {
  0%   { transform: translate(0, 0) scale(1); opacity: .95; }
  100% { transform: translate(var(--dx), var(--dy)) scale(.25); opacity: 0; }
}

/* completion flash ring — a soft light bloom expanding outward */
.fx-ring {
  position: absolute; left: 50%; top: 44%; width: 26px; height: 26px; margin: -13px 0 0 -13px;
  border-radius: 50%; border: 2px solid rgba(255, 255, 255, 0.9); opacity: .8;
  animation: fxRing 1.1s cubic-bezier(.16,.68,.36,1) forwards;
}
@keyframes fxRing {
  from { transform: scale(.35); opacity: .8; }
  to   { transform: scale(6.5); opacity: 0; }
}

/* ---------- soil ripple (tap feedback on the growing screen) ---------- */
.soil-ripple {
  position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px;
  border-radius: 50%; pointer-events: none;
  border: 2px solid rgba(94, 128, 88, 0.5);
  animation: soilRipple .6s cubic-bezier(.16,.68,.36,1) forwards;
}
@keyframes soilRipple {
  from { transform: scale(.4); opacity: .75; }
  to   { transform: scale(5);  opacity: 0; }
}

/* the Touch-Me-Not warns you the screen itself is off limits */
.session.fragile .session-hint { color: #a4553f; }

/* an open-ended tree counts up, so the clock is the quieter element */
.session.unlimited .time-left { font-size: 2.5rem; color: var(--ink-soft); }

/* ---------- special-trees sheet ---------- */
.spec-head {
  margin: 18px 0 2px; font-size: .74rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .6px; color: var(--ink-faint);
  text-align: left;
}
.spec-credit {
  margin-top: 18px; font-size: .74rem; line-height: 1.5;
  color: var(--ink-faint);
}
