/* pixeltrees — design system, September 2026.
   Apple-style presentation: near-black type on white, soft grey bands, huge
   display headings, wide breathing room, pill buttons, translucent nav.
   Every id/class the scripts touch (hub.js, reveal.js, account.js, success)
   keeps its name. admin.html and wall.html (grid game) stay on legacy.css. */

* { box-sizing: border-box; margin: 0; }
[hidden] { display: none !important; }

:root {
  --bg: #ffffff;
  --grey: #f5f5f7;
  --grey-2: #e8e8ed;
  --ink: #1d1d1f;
  --text: #1d1d1f;
  --muted: #6e6e73;
  --line: #d2d2d7;
  --navy: #1E3A5F;
  --sage: #7C9A6D;
  --green: #2e7d4f;
  --green-deep: #1f5f3b;
  --accent: var(--green);
  --error: #c0392b;
  --gold-bg: #fff8e1;
  --gold-line: #e2b93b;
  --gold-text: #6e5200;
  --dark: #0b0f0c;
  --dark-2: #161c17;
  --panel: #ffffff;
  --r-sm: 12px; --r: 18px; --r-lg: 28px;
  --shadow-sm: 0 1px 2px rgba(0,0,0,.05), 0 4px 14px rgba(0,0,0,.05);
  --shadow: 0 2px 6px rgba(0,0,0,.06), 0 18px 48px rgba(0,0,0,.10);
  --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-display: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --wrap: 1080px; --measure: 720px;
  --sec: clamp(72px, 10vw, 128px);
}

/* clip, NOT hidden. Both guard the same thing — a horizontally-scrolling
   row (the board stage slideshow, .cards.teasers on mobile) should never
   make the PAGE itself pannable sideways (19 Sep 2026 bug report: the
   whole page drifted right on phones once the slideshow shipped).
   But overflow-x: hidden makes html/body a SCROLL CONTAINER: per spec the
   other axis computes from visible to auto. position: sticky then resolves
   against that container instead of the viewport and never engages, so the
   "How it works" section's sticky grid scrolled away and left a blank white
   screen mid-section (20 Sep 2026 owner report, reproduced: the grid sat at
   top -1266px). overflow-x: clip does the same clipping WITHOUT creating a
   scroll container, which is exactly why sticky survives it.
   The real cause of the original drift is fixed at source anyway (the board
   slides size in vw now), so this is only a backstop. */
html { background: var(--bg); -webkit-text-size-adjust: 100%; scroll-behavior: smooth; overflow-x: clip; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
body {
  background: var(--bg); color: var(--text); font-family: var(--font);
  font-size: 17px; line-height: 1.47; letter-spacing: -0.01em;
  display: flex; flex-direction: column; min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}
a { color: var(--green-deep); text-decoration: none; }
a:hover { text-decoration: underline; }
img { max-width: 100%; }
b, strong { font-weight: 600; }

/* ---------- type scale ---------- */
h1, h2, h3, .display { font-family: var(--font-display); color: var(--ink); font-weight: 600; letter-spacing: -0.02em; }
h1 { font-size: clamp(2.2rem, 5.6vw, 4.5rem); line-height: 1.04; letter-spacing: -0.03em; }
h2 { font-size: clamp(1.6rem, 3.4vw, 2.8rem); line-height: 1.1; }
h3 { font-size: 1.25rem; line-height: 1.25; }
.eyebrow { color: var(--sage); font-weight: 600; font-size: .95rem; letter-spacing: .01em; text-transform: none; }
.lede { font-size: clamp(1.1rem, 1.6vw, 1.45rem); line-height: 1.4; color: var(--muted); font-weight: 400; letter-spacing: -0.012em; }
.tagline { color: var(--muted); font-size: 1.05rem; margin-top: 8px; }
.strapline { color: var(--sage); font-weight: 600; margin-top: 6px; }
.hint { color: var(--muted); font-size: .85rem; margin-top: 6px; }
.counter { margin-top: 6px; font-variant-numeric: tabular-nums; }
.trees { color: var(--green); font-size: .95rem; margin-top: 4px; }
.next-tree { color: #8a5b00; font-size: .9rem; margin-top: 4px; }
.error { color: var(--error); font-size: .88rem; margin-top: 8px; min-height: 1em; }

/* ---------- buttons ---------- */
button, .btn {
  font: inherit; font-weight: 500; cursor: pointer; border-radius: 999px;
  padding: 12px 22px; border: 1px solid var(--line); background: #fff; color: var(--ink);
  transition: background .15s, border-color .15s, transform .15s, box-shadow .15s;
  text-decoration: none; display: inline-block; line-height: 1.2;
}
button:hover, .btn:hover { text-decoration: none; border-color: var(--ink); }
button:disabled { opacity: .5; cursor: not-allowed; }
.btn-primary, button.primary { background: var(--green); border-color: var(--green); color: #fff; }
.btn-primary:hover, button.primary:hover { background: var(--green-deep); border-color: var(--green-deep); }
.btn-ghost { background: transparent; border-color: var(--line); color: var(--ink); }
.btn-lg { padding: 15px 30px; font-size: 1.05rem; }
.linklike, .panel button.linklike {
  display: inline; width: auto; margin: 0; padding: 0; background: none; border: none;
  color: var(--green-deep); font: inherit; font-weight: 600; text-decoration: underline; cursor: pointer;
}

/* ---------- nav ---------- */
.topnav {
  position: sticky; top: 0; z-index: 50; height: 52px;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 0 max(16px, calc((100% - var(--wrap)) / 2));
  background: rgba(255,255,255,.72); backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid rgba(0,0,0,.08);
}
/* flex-shrink: 0 (24 Sep 2026) — without it, the brand competed for space
   with .tn-links under the same pressure that made .tn-links scroll
   instead of wrap, and being the only one with no overflow handling of
   its own, its text just spilled into "Home" next to it ("pixeltreesHome",
   reported by the owner) instead of staying full width. */
.tn-brand { display: flex; align-items: center; gap: 9px; color: var(--ink); font-weight: 600; font-size: 1rem; letter-spacing: -0.01em; flex-shrink: 0; }
.tn-brand:hover { text-decoration: none; }
.tn-brand img { height: 28px; width: 28px; border-radius: 50%; }
/* 23 Sep 2026: the nav grew to 10 items (Home/Why us/Walls/How it works/
   About us/Trees/Gift a greener future/Contest/Sign in/Claim blocks) —
   too many to fit one line below ~1100px without wrapping each link's own
   text onto two lines (the bug the owner flagged). Below that width the
   row scrolls horizontally instead of wrapping; the burger menu still
   takes over entirely at the existing 760px breakpoint. */
.tn-links {
  display: flex; gap: 2px; flex-shrink: 1; min-width: 0;
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
}
.tn-links::-webkit-scrollbar { display: none; }
.tn-links a { color: var(--ink); font-size: .88rem; padding: 7px 10px; border-radius: 999px; opacity: .85; white-space: nowrap; flex-shrink: 0; }
.tn-links a:hover { background: rgba(0,0,0,.05); text-decoration: none; opacity: 1; }
.tn-links a[aria-current="page"] { background: var(--grey-2); color: var(--ink); font-weight: 600; opacity: 1; box-shadow: inset 0 -2px 0 0 var(--green); }
.tn-burger { display: none; flex-direction: column; gap: 5px; background: none; border: none; padding: 8px; border-radius: 0; }
.tn-burger span { width: 20px; height: 1.5px; background: var(--ink); display: block; transition: transform .2s, opacity .2s; }
.topnav.open .tn-burger span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.topnav.open .tn-burger span:nth-child(2) { opacity: 0; }
.topnav.open .tn-burger span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }
@media (max-width: 760px) {
  .tn-burger { display: flex; }
  .topnav { flex-wrap: wrap; height: auto; min-height: 48px; padding-block: 6px; }
  .tn-links { display: none; }
  .topnav.open .tn-links { display: flex; flex-direction: column; width: 100%; padding: 8px 0 6px; gap: 0; }
  .topnav.open .tn-links a { font-size: 1.15rem; padding: 12px 4px; border-radius: 0; border-top: 1px solid rgba(0,0,0,.06); }
}
body.dark .topnav { background: rgba(11,15,12,.72); border-bottom-color: rgba(255,255,255,.1); }
body.dark .tn-brand, body.dark .tn-links a { color: #f5f5f7; }
body.dark .tn-links a:hover { background: rgba(255,255,255,.08); }
body.dark .tn-burger span { background: #f5f5f7; }
body.dark .topnav.open .tn-links a { border-top-color: rgba(255,255,255,.1); }

/* ---------- presence banner ---------- */
.presence-banner {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  padding: 7px 12px; font-size: .82rem; color: var(--ink); opacity: .8;
  background: var(--grey-2); border-bottom: 1px solid rgba(0,0,0,.06);
}
.presence-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--green);
  box-shadow: 0 0 0 0 rgba(34,140,72,.5); animation: presencePulse 2s ease-out infinite;
}
@keyframes presencePulse {
  0% { box-shadow: 0 0 0 0 rgba(34,140,72,.5); }
  70% { box-shadow: 0 0 0 6px rgba(34,140,72,0); }
  100% { box-shadow: 0 0 0 0 rgba(34,140,72,0); }
}
body.dark .presence-banner { background: rgba(255,255,255,.05); border-bottom-color: rgba(255,255,255,.08); color: #f5f5f7; }
@media (prefers-reduced-motion: reduce) { .presence-dot { animation: none; } }

/* ---------- magic-block home banner ---------- */
.magic-banner {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: center;
  gap: 4px 10px; padding: 10px 16px; text-align: center; color: #fff;
  background: linear-gradient(90deg, var(--green-deep), var(--green));
  font-size: .92rem; font-weight: 500;
}
.magic-banner:hover { text-decoration: none; filter: brightness(1.05); }
.magic-banner strong { font-weight: 700; }
.magic-banner-sub { width: 100%; font-size: .76rem; font-weight: 400; opacity: .88; }
@media (max-width: 640px) { .magic-banner { font-size: .85rem; } }

/* ---------- arcade-game home banner ---------- */
.arcade-banner {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: center;
  gap: 4px 10px; padding: 10px 16px; text-align: center; color: #fff2d6;
  background: linear-gradient(90deg, #15163B, #23265E);
  font-size: .92rem; font-weight: 500;
}
.arcade-banner:hover { text-decoration: none; filter: brightness(1.1); }
.arcade-banner-sub { width: 100%; font-size: .76rem; font-weight: 400; opacity: .8; }
@media (max-width: 640px) { .arcade-banner { font-size: .85rem; } }

/* ---------- sections ---------- */
.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 22px; }
.sec { padding-block: var(--sec); }
.sec-tight { padding-block: calc(var(--sec) * .6); }
.sec-grey { background: var(--grey); }
.sec-dark { background: var(--dark); color: #f5f5f7; }
.sec-dark h1, .sec-dark h2, .sec-dark h3 { color: #fff; }
.sec-dark .lede, .sec-dark .tagline, .sec-dark .hint { color: #a1a1a6; }
/* .btn-ghost's text uses var(--ink), which flips with the site's OWN
   light/dark toggle — in light mode --ink is near-black, invisible on
   this section's always-dark background (24 Sep 2026 bug report: "text
   color matched the background... in dark mode it is good"). This
   section is dark regardless of that toggle, so its ghost buttons need
   their own fixed light text, not the toggle-dependent variable. */
.sec-dark .btn-ghost { color: #f5f5f7; border-color: rgba(245,245,247,.4); }
.sec-dark .btn-ghost:hover { border-color: #f5f5f7; background: rgba(245,245,247,.08); }
.center { text-align: center; }
.sec-head { max-width: 760px; margin: 0 auto 40px; text-align: center; }
.sec-head .lede { margin-top: 14px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; }
.grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
@media (max-width: 860px) { .grid-3, .grid-2 { grid-template-columns: 1fr; } }
.tile {
  background: #fff; border-radius: var(--r-lg); padding: 34px 30px; box-shadow: var(--shadow-sm);
  display: flex; flex-direction: column; gap: 10px;
}
.sec-grey .tile { box-shadow: none; }
.tile .num { font-size: 2.6rem; font-weight: 600; letter-spacing: -0.04em; line-height: 1; color: var(--green); }
.tile h3 { margin-top: 4px; }
.tile p { color: var(--muted); }

/* scroll reveal — armed by ui.js; without JS everything is simply visible */
.js .fade-up { opacity: 0; transform: translateY(18px); transition: opacity .7s ease, transform .7s ease; }
.js .fade-up.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .js .fade-up { opacity: 1; transform: none; transition: none; } }

/* ---------- footer ---------- */
footer {
  margin-top: auto; background: var(--grey); color: var(--muted); font-size: .8rem;
  padding: 34px max(22px, calc((100% - var(--wrap)) / 2)) 42px; line-height: 1.6;
  text-align: center;
}
footer > * { max-width: 760px; margin-left: auto; margin-right: auto; }
footer .links { margin: 0 auto 4px; }
body.dark footer { background: #000; color: #86868b; }
footer a { color: #5f6368; }
/* 24 Sep 2026 scan: footer links were 15–18px tall; 24px is the tap-target floor. */
footer .links a { display: inline-block; padding: 5px 3px; }
body.dark footer a { color: #a1a1a6; }
footer .copyright { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); font-size: .78rem; }
body.dark footer .copyright { border-top-color: #2a2a2e; }
.footer-contest {
  margin: 0 auto 14px; padding: 12px 16px; border-radius: var(--r-sm);
  background: var(--gold-bg); border: 1px solid #f1dc9a; color: var(--gold-text); font-size: .85rem;
}
.footer-contest a { color: var(--gold-text); font-weight: 600; }
.disclaimer { margin: 6px auto 0; }

/* ============================================================================
   HOME
   ============================================================================ */
.hero { text-align: center; padding: clamp(56px, 9vw, 120px) 22px 28px; max-width: 900px; margin: 0 auto; }
.hero .brand-badge { height: 72px; width: 72px; border-radius: 50%; box-shadow: var(--shadow-sm); margin-bottom: 22px; }
.hero h1 { margin: 0 auto; max-width: 14ch; }
.hero .tagline { font-size: clamp(1.15rem, 2vw, 1.55rem); color: var(--muted); margin: 18px auto 0; max-width: 34ch; letter-spacing: -0.012em; line-height: 1.35; }
.hero .strapline { font-size: clamp(1.1rem, 1.6vw, 1.3rem); margin: 0 0 14px; }
.hero-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-top: 30px; }
.hero-actions .btn { font-size: 1.05rem; padding: 14px 26px; }
.hero-art { max-width: var(--wrap); margin: 0 auto; padding: 0 22px 26px; }
/* A greyscale preview in the same dark frame as the wall page: the finished
   picture is what buyers reveal, so the home page must not give it away. */
.hero-art .frame {
  padding: clamp(8px, 1.2vw, 14px); border-radius: var(--r-lg);
  background: linear-gradient(160deg, #1b231d, #0e130f); box-shadow: var(--shadow);
}
.hero-art img { display: block; width: 100%; height: auto; border-radius: calc(var(--r-lg) - 10px); filter: grayscale(1) contrast(.9) brightness(.85); }
.hero-art figcaption { text-align: center; color: var(--muted); font-size: .9rem; margin-top: 14px; }

/* Board stage: a horizontal slideshow of every wall (live + planned),
   19 Sep 2026 — replaces the single board-peek. Each slide is a scroll-snap
   item sized so neighbours peek at the edges; the centred slide gets a
   scale/opacity lift (IntersectionObserver toggles .on in home.js) for a
   Stage-Manager-ish "this one's active" emphasis, without the weight of a
   real animation library. */
/* perspective lives on the NON-scrolling parent, not .board-stage-track
   itself — perspective combined directly with overflow-x: auto on the same
   element is a known cross-browser rendering trap (children can paint at
   the wrong size/position once rotated). Slides still need
   transform-style: preserve-3d up the chain to actually render in 3D. */
/* width: 100% + min-width: 0 on both .board-stage and .board-stage-track
   is the real fix for the "wide card cut off at the edge" report. Root
   cause: .board-stage is a flex item of body's own column layout, so its
   width:auto only resolves once the flex algorithm settles — and each
   .board-slide's OLD percentage flex-basis needed a definite width on
   .board-stage-track to resolve against. While that chain was still
   settling, the browser fell back to sizing the track off each slide's
   own content instead of the viewport, so the whole row (and its
   .board-stage ancestor) rendered far wider than the phone's screen. */
.board-stage { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 22px 26px; position: relative; perspective: 1200px; min-width: 0; box-sizing: border-box; }
.board-stage-track {
  display: flex; gap: 16px; overflow-x: auto; scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch; padding: 6px 0 14px; scroll-padding-left: 22px;
  transform-style: preserve-3d; width: 100%; min-width: 0;
}
.board-stage-track::-webkit-scrollbar { display: none; }
.board-slide {
  /* 90%, not the old 82% — a mobile bug report: at 82% the next slide's
     text visibly bled in from the right edge, reading as a broken/cropped
     page rather than an intentional peek. 90% keeps just enough neighbour
     visible for the coverflow effect below without that crowded look —
     each wall now reads as "one wall, one screen" the way it was asked. */
  /* vw, not %: a % flex-basis needs a DEFINITE main size on the flex
     container to resolve against. If that width is still being worked
     out (this row sits inside an auto-width ancestor chain), browsers
     fall back to sizing each slide off its own content instead — the
     root cause of an earlier "cards render wider than the viewport"
     bug. vw has no such dependency: it always resolves off the
     viewport directly. */
  flex: 0 0 86vw; scroll-snap-align: center; cursor: pointer;
  /* Short + linear: this transform is recomputed every scroll frame
     (home.js's syncActive), so it needs to track the finger closely, not
     lag behind it — a longer/eased transition would feel sluggish. */
  transition: transform .12s linear, opacity .12s linear; transform-style: preserve-3d;
  will-change: transform;
}
@media (min-width: 761px) { .board-slide { flex-basis: 44vw; } }
.board-slide-frame {
  padding: clamp(8px, 1.2vw, 14px); border-radius: var(--r-lg);
  background: linear-gradient(160deg, #1b231d, #0e130f); box-shadow: var(--shadow);
  aspect-ratio: 4/3; display: grid; place-items: center; position: relative;
}
.board-slide-frame canvas { display: block; width: 100%; height: auto; border-radius: calc(var(--r-lg) - 10px); }
/* Decorative trees over the live wall's own slide (22 Sep 2026 — the
   owner, first: "can we make bit of artwork display in the homepage -
   wall section", a near-empty board otherwise reads as a flat grey tile
   with nothing happening on it; then, directly: "Is it possible to show
   a tree appear and disappear on home page instead of green colour
   appearing on the blocks/bubbles/dots" — replacing the original green
   pulsing-dot version of this same idea. A small tree glyph fades in,
   holds, and fades out on a loop, staggered per position so the board
   reads as a living grove rather than a synchronised blink. Purely
   decorative, positioned in home.js's buildBoardStageSlides(). */
.board-slide-pulses {
  position: absolute; inset: clamp(8px, 1.2vw, 14px);
  border-radius: calc(var(--r-lg) - 10px);
  overflow: hidden; pointer-events: none;
}
.board-slide-pulse {
  position: absolute; font-size: 1.4rem; line-height: 1; transform: translate(-50%, -50%) scale(.6);
  opacity: 0; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .15));
  animation: boardSlideTree 4.2s ease-in-out infinite;
}
.board-slide-pulse.p1 { top: 26%; left: 32%; }
.board-slide-pulse.p2 { top: 62%; left: 66%; animation-delay: 1.4s; }
.board-slide-pulse.p3 { top: 46%; left: 50%; animation-delay: 2.8s; }
@keyframes boardSlideTree {
  0%, 100% { opacity: 0; transform: translate(-50%, -50%) scale(.5); }
  15%, 55% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  75% { opacity: 0; transform: translate(-50%, -50%) scale(.7); }
}
@media (prefers-reduced-motion: reduce) { .board-slide-pulse { animation: none; opacity: .9; transform: translate(-50%, -50%) scale(1); } }
.board-slide-frame.teaser-frame { background: linear-gradient(160deg, #e3f1e6, var(--grey-2)); }
.board-slide-frame .teaser-emoji { font-size: 4rem; }
.board-slide-frame.impact-frame { background: linear-gradient(160deg, #2a3b2e, #16211a); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; position: relative; overflow: hidden; }
.impact-frame .impact-emoji, .impact-frame .impact-stat { position: relative; z-index: 1; }
.impact-frame .impact-emoji { font-size: 2.6rem; }
.impact-frame .impact-stat { font-size: 1.7rem; font-weight: 800; color: #fff; letter-spacing: -.01em; }
/* A real photo slide (23 Sep 2026 — the three air-quality infographics)
   fills the whole frame edge to edge, no padding, unlike the emoji+stat
   frames above it inherits from. */
.board-slide-frame.impact-frame.has-photo { padding: 0; }
/* HTML-built "Why it matters" cards (24 Sep 2026, owner: "better and
   equivalent content ... reference aqi.in"; the three news screenshots
   are gone). Each fills the 4:3 frame; text sizes scale with the frame
   (cqw) so the phone and desktop slides read the same. */
.board-slide-frame.impact-frame.has-card { padding: 0; container-type: inline-size; }
.impact-card { position: relative; z-index: 1; width: 100%; height: 100%; padding: 4.5cqw 6cqw 4cqw; display: flex; flex-direction: column; color: #fff; text-align: left; box-sizing: border-box; }
.diwali-wall-name { font-size: 1.15rem; margin: 0 0 6px; }
.impact-card .ic-kicker { font-size: 3cqw; letter-spacing: .08em; text-transform: uppercase; color: rgba(255,255,255,.62); margin: 0 0 1.5cqw; font-weight: 600; }
.impact-card .ic-foot { margin-top: auto; font-size: 2.8cqw; color: rgba(255,255,255,.62); line-height: 1.3; }
/* Live AQI board */
.aqi-worst { display: flex; align-items: center; gap: 2.5cqw; margin: 0 0 1.5cqw; }
.aqi-worst .aqi-city { font-size: 5.2cqw; font-weight: 800; letter-spacing: -.01em; line-height: 1.05; flex: 1; min-width: 0; }
.aqi-worst .aqi-val { font-size: 8cqw; font-weight: 800; line-height: 1; }
.aqi-worst .aqi-band { font-size: 3.4cqw; font-weight: 700; padding: .8cqw 2.2cqw; border-radius: 999px; color: #0e130f; }
.aqi-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .8cqw 4cqw; }
.aqi-row { display: flex; align-items: center; justify-content: space-between; gap: 2cqw; font-size: 3.4cqw; padding: .8cqw 0; border-bottom: 1px solid rgba(255,255,255,.1); }
.aqi-row .aqi-pill { min-width: 9cqw; text-align: center; font-weight: 800; padding: .6cqw 1.6cqw; border-radius: 999px; color: #0e130f; font-size: 3.4cqw; }
/* CPCB scale card */
.aqi-scale { display: grid; gap: .6cqw; margin: 0 0 1.5cqw; }
.aqi-scale .as-row { display: grid; grid-template-columns: 3.6cqw 1fr auto; align-items: center; gap: 2cqw; font-size: 3.2cqw; }
.aqi-scale .as-swatch { width: 4cqw; height: 4cqw; border-radius: 1cqw; }
.aqi-scale .as-label { font-weight: 700; }
.aqi-scale .as-range { color: rgba(255,255,255,.7); font-variant-numeric: tabular-nums; }
.aqi-scale .as-text { grid-column: 2 / -1; font-size: 2.5cqw; color: rgba(255,255,255,.62); margin-top: -1.2cqw; line-height: 1.2; }
/* Big single fact */
.impact-fact .if-num { font-size: 15cqw; font-weight: 800; line-height: 1; letter-spacing: -.02em; color: #ffd27a; margin: 2cqw 0 2cqw; }
.impact-fact .if-text { font-size: 4.4cqw; line-height: 1.3; max-width: 34ch; }
/* Motto + live counters */
.impact-moto .im-motto { font-family: Georgia, 'Times New Roman', serif; font-size: 7.5cqw; line-height: 1.1; margin: 1cqw 0 4cqw; }
.impact-moto .im-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 3cqw; }
.impact-moto .im-stat b { display: block; font-size: 9cqw; font-weight: 800; line-height: 1; color: #9fe0b5; }
.impact-moto .im-stat span { font-size: 3.2cqw; color: rgba(255,255,255,.7); }
.impact-moto .im-rule { margin-top: 3cqw; font-size: 3.6cqw; }
@supports not (width: 1cqw) {
  .impact-card { padding: 20px; font-size: 14px; }
  .aqi-worst .aqi-val { font-size: 40px; } .aqi-worst .aqi-city { font-size: 24px; } .aqi-row { font-size: 13px; }
  .impact-fact .if-num { font-size: 52px; } .impact-fact .if-text { font-size: 16px; } .impact-moto .im-motto { font-size: 26px; } .impact-moto .im-stat b { font-size: 32px; }
}
/* object-fit: contain (24 Sep 2026, owner: "display the entire images")
   — was cover, which cropped whichever edge didn't match the 4:3 frame.
   A plain background colour fills the letterboxed edges instead of
   leaving them transparent (the frame itself is already dark). */
.impact-photo { width: 100%; height: 100%; object-fit: contain; background: #0e130f; border-radius: var(--r-lg); display: block; }
/* Any image wired through home.js's makeZoomable (24 Sep 2026): a visible
   affordance that tapping it opens the full-size lightbox. */
.zoomable-img { cursor: zoom-in; }
.zoomable-img:focus-visible { outline: 2px solid var(--green-deep); outline-offset: 2px; }
/* Ambient background animation, matched to each card's own content (22
   Sep 2026, owner: "can we give bit more animation on the background
   context accordingly"). Soft blurred particles only — never legible as
   anything, so they never compete with the stat itself. */
.impact-bg { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
.impact-particle { position: absolute; border-radius: 50%; filter: blur(10px); opacity: 0; }
/* "haze" (the two pollution cards): warm grey smog drifting slowly sideways. */
.theme-haze .impact-particle { background: rgba(200, 195, 180, .5); animation: impactHaze 14s ease-in-out infinite; }
.theme-haze .p1 { width: 90px; height: 46px; top: 18%; left: -10%; animation-delay: 0s; }
.theme-haze .p2 { width: 120px; height: 54px; top: 55%; left: 30%; animation-delay: -4s; }
.theme-haze .p3 { width: 80px; height: 40px; top: 75%; left: -5%; animation-delay: -8s; }
.theme-haze .p4 { width: 100px; height: 48px; top: 10%; left: 55%; animation-delay: -11s; }
.theme-haze .p5 { width: 70px; height: 36px; top: 40%; left: 70%; animation-delay: -6s; }
@keyframes impactHaze {
  0% { opacity: 0; transform: translateX(0); }
  20% { opacity: .55; }
  80% { opacity: .35; }
  100% { opacity: 0; transform: translateX(60px); }
}
/* "grow" (the two tree/planting cards): soft green particles rising, like
   life drifting upward instead of smoke drifting sideways. */
.theme-grow .impact-particle { background: rgba(122, 200, 150, .55); animation: impactGrow 9s ease-in-out infinite; }
.theme-grow .p1 { width: 26px; height: 26px; top: 80%; left: 15%; animation-delay: 0s; }
.theme-grow .p2 { width: 18px; height: 18px; top: 85%; left: 42%; animation-delay: -2s; }
.theme-grow .p3 { width: 30px; height: 30px; top: 78%; left: 65%; animation-delay: -4.5s; }
.theme-grow .p4 { width: 16px; height: 16px; top: 90%; left: 80%; animation-delay: -6.5s; }
.theme-grow .p5 { width: 22px; height: 22px; top: 82%; left: 28%; animation-delay: -3.2s; }
@keyframes impactGrow {
  0% { opacity: 0; transform: translateY(0) scale(.8); }
  15% { opacity: .5; }
  85% { opacity: .25; }
  100% { opacity: 0; transform: translateY(-160px) scale(1.1); }
}
@media (prefers-reduced-motion: reduce) { .impact-particle { animation: none; opacity: 0; } }
.board-slide-frame.other-wall-frame { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; padding: 24px; }
.other-wall-bar { width: 80%; height: 10px; border-radius: 5px; background: rgba(255,255,255,.12); overflow: hidden; }
.other-wall-bar div { height: 100%; background: var(--green); }
.other-wall-pct { color: #fff; font-size: 1.6rem; font-weight: 600; letter-spacing: -0.03em; }
.board-slide-cap { text-align: center; padding: 14px 10px 0; }
.board-slide-cap .chip { display: inline-block; font-size: .72rem; padding: 4px 10px; border-radius: 999px; background: var(--grey); color: var(--muted); text-transform: uppercase; letter-spacing: .06em; font-weight: 600; margin-bottom: 8px; }
.board-slide-cap .chip.live { background: #e3f1e6; color: var(--green); }
.board-slide-cap h3 { font-size: 1.1rem; margin: 0 0 4px; }
.board-slide-cap p { color: var(--muted); font-size: .88rem; margin: 0 0 10px; }
.board-stage-nav {
  position: absolute; top: 40%; width: 40px; height: 40px; border-radius: 50%;
  background: var(--panel); border: 1px solid var(--line); display: grid; place-items: center;
  box-shadow: var(--shadow-sm); z-index: 2;
}
.board-stage-nav.prev { left: -4px; } .board-stage-nav.next { right: -4px; }
@media (max-width: 860px) { .board-stage-nav { display: none; } }
.board-stage-dots { display: flex; justify-content: center; gap: 6px; margin-top: 4px; }
.board-stage-dots i { width: 6px; height: 6px; border-radius: 50%; background: var(--grey-2); display: block; }
.board-stage-dots i.on { background: var(--green); width: 18px; border-radius: 3px; }
@media (prefers-reduced-motion: reduce) { .board-slide { transition: none; } }

/* stats strip — hub.js fills #heroStrip with <span>emoji <b>n</b> label…</span> */
/* width: 100% because this is a flex item of body's own column layout, and
   without it the grid sizes to its max-content — two 240px columns plus
   padding — which overflowed a 390px phone and clipped the second tile's
   text with no way to scroll to it (20 Sep 2026, on /walls: body
   scrollWidth 538 against a 390 viewport). It only surfaced there because
   the strip is display:none on the home page. */
.hero-strip {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 14px;
  width: 100%; box-sizing: border-box;
  max-width: var(--wrap); margin: 42px auto 0; padding: 0 22px; font-size: 1rem; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.hero-strip span { background: var(--grey); border-radius: var(--r); padding: 26px 24px; display: block; }
.hero-strip b { display: block; font-size: 2.4rem; letter-spacing: -0.04em; color: var(--ink); line-height: 1; margin-bottom: 8px; }

.search { max-width: 520px; margin: 40px auto 0; padding: 0 22px; width: 100%; }
.search input {
  width: 100%; padding: 14px 18px; border-radius: 999px; border: 1px solid var(--line);
  background: #fff; color: var(--ink); font: inherit; font-size: 1rem;
}
.search input:focus { outline: none; border-color: var(--ink); }

.cards {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 18px;
  padding: 26px 22px var(--sec); max-width: var(--wrap); margin: 0 auto; width: 100%;
}
.pcard {
  background: var(--grey); border-radius: var(--r-lg); padding: 30px; display: flex; flex-direction: column; gap: 10px;
  cursor: pointer; transition: transform .2s, box-shadow .2s;
}
.pcard:hover { transform: translateY(-3px); box-shadow: var(--shadow); }
.pcard h3 { font-size: 1.4rem; }
.pcard .ptag { color: var(--muted); }
.pcard .pstats { font-size: .9rem; color: var(--muted); }
.pcard .pbar { height: 6px; border-radius: 3px; background: var(--grey-2); overflow: hidden; }
.pcard .pbar div { height: 100%; background: var(--green); }
.pcard .chip { align-self: flex-start; font-size: .72rem; padding: 4px 10px; border-radius: 999px; background: #fff; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; font-weight: 600; }
.pcard .chip.live { background: #e3f1e6; color: var(--green); }
.pcard.featured {
  grid-column: 1 / -1; padding: clamp(30px, 5vw, 64px);
  background: radial-gradient(120% 140% at 0% 0%, color-mix(in srgb, var(--green) 12%, var(--grey)) 0%, var(--grey) 55%, var(--panel) 100%);
}
.pcard.featured h3 { font-size: clamp(1.8rem, 3.4vw, 2.8rem); letter-spacing: -0.03em; }
.pcard.featured .ptag { font-size: 1.15rem; max-width: 60ch; }
.pcard.featured .pbar div { transition: width 1s ease; }
.pcard.featured .cta { align-self: flex-start; margin-top: 14px; padding: 14px 28px; border-radius: 999px; background: var(--green); color: #fff; font-weight: 600; font-size: 1.05rem; border: none; }
.pcard.featured .cta:hover { background: var(--green-deep); }
@media (prefers-reduced-motion: reduce) { .pcard, .pcard.featured .pbar div { transition: none; } .pcard:hover { transform: none; } }

/* ============================================================================
   WALL PAGE (reveal walls) — light, the board fills the first screen, the
   claim card comes on tap or scroll (changes_3.md items 8 + 9).
   Mechanics kept: art-full, lens, zoom-tools, packs, steps, preview, toast.
   ============================================================================ */
body.wall-page { background: var(--bg); color: var(--text); }
body.wall-page .wall-main { display: block; width: 100%; flex: 1; padding: 0 0 60px; }
.stage {
  position: relative; width: 100%; height: calc(100vh - 52px); height: calc(100dvh - 52px);
  min-height: 420px; background: var(--bg); overflow: hidden;
}
/* touch-callout / user-select off: a held finger is the loupe, not a callout */
#wall { cursor: grab; touch-action: none; -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
/* Reveal-artwork blur-in, 21 Sep 2026: the canvas redraws its entire
   content instantly in JS (regions vs. artwork — see reveal.js render()),
   which used to cut between the two with no transition at all. This is a
   pure CSS crossfade layered on top: reveal.js toggles body.reveal-art a
   frame BEFORE the new content paints (setRevealArtMode), so the browser
   catches the filter/opacity change and animates from the "entering" state
   below back to clear — the artwork resolves into focus instead of
   snapping in. prefers-reduced-motion gets the instant cut it already had.

   ROOT-CAUSE BUG, found and fixed 22 Sep 2026: this comment used to be
   closed with an HTML-style comment closer instead of a real CSS one,
   from the day it was written. Real CSS parsers do not recognise that
   HTML closer at all, so EVERYTHING from here down to the next genuine
   closer (below the third #wall rule that follows) was silently invalid
   and ignored the entire time: the blur-in rules right after this one,
   AND the #wall sizing rule (display, width, height), AND the cursor
   rules after that. The canvas fell back to the browser's built-in
   default size, 300 by 150 pixels, which is why the board rendered as a
   small sliver instead of filling the stage, why "zoom to fit" and every
   pan and zoom calculation (all of which read the canvas element's real
   width and height) were wrong, and very likely the real explanation
   behind "the canvas is not displaying properly" and "the zoom pane...
   nothing is working properly", reported since this comment shipped. */
#wall { transition: filter .5s ease, opacity .5s ease; }
body.reveal-art-entering #wall { filter: blur(10px); opacity: .55; }
@media (prefers-reduced-motion: reduce) { #wall { transition: none; } }
body.wall-page #wall {
  display: block; width: 100%; height: 100%; aspect-ratio: auto; background: var(--bg);
  border: none; border-radius: 0;
}
/* Infinite-canvas feel (24 Sep 2026, owner: "hand icons to pinch and
   hold like that feel" — the Superdesign canvas): an open hand at rest,
   a closed hand while panning, and a pointer only over a claimed block
   (the one place a tap opens something). The magnifier glyph that used
   to sit here read as "click to zoom", which is not what a drag does. */
body.wall-page #wall { cursor: grab; }
body.wall-page #wall.over-claim { cursor: pointer; }
body.wall-page #wall.dragging, #wall.dragging { cursor: grabbing; }
/* changes-16sep2026.md item 1: drop the on-canvas name tag ("Wall of Green
   Warriors"); proj.name/tagline still load, just not rendered over the board. */
.stage-head { display: none; }
.stage-head > div { background: color-mix(in srgb, var(--bg) 78%, transparent); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); border: 1px solid var(--line); border-radius: 16px; padding: 8px 14px; max-width: min(560px, 100%); }
.stage-head h1 { font-size: clamp(1.15rem, 2.2vw, 1.7rem); line-height: 1.15; margin: 0; }
.stage-head .tagline { margin: 2px 0 0; font-size: .9rem; }
.stage-head #wallTagline { display: none; }
.stage-foot { position: absolute; left: 0; right: 0; bottom: 18px; z-index: 5; display: flex; flex-direction: column; align-items: center; gap: 8px; pointer-events: none; }
.stage-foot .stage-cta { pointer-events: auto; padding: 14px 26px; font-size: 1.05rem; box-shadow: var(--shadow); animation: cta-nudge 2.6s ease-in-out infinite; }
.stage-hint { margin: 0; font-size: .82rem; color: var(--muted); background: color-mix(in srgb, var(--bg) 78%, transparent); padding: 4px 12px; border-radius: 999px; }
@keyframes cta-nudge { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(-5px) } }
@media (prefers-reduced-motion: reduce) { .stage-foot .stage-cta { animation: none; } }
.board-full { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 7; background: var(--panel); border: 1px solid var(--line); border-radius: var(--r); padding: 22px 26px; text-align: center; display: grid; gap: 12px; box-shadow: var(--shadow); }
.board-full b { font-size: 1.2rem; }
/* Board UX follow-up, 17 Sep: jump-to chips fill the space the on-canvas
   name tag used to occupy (now hidden). Horizontal-scroll on narrow phones
   rather than wrap, so the board underneath stays uncluttered. */
.stage-jump { position: absolute; top: 14px; left: 14px; right: 60px; z-index: 5; display: flex; gap: 6px; overflow-x: auto; scrollbar-width: none; }
.stage-jump::-webkit-scrollbar { display: none; }
.stage-jump button {
  flex: none; white-space: nowrap; font-size: .82rem; font-weight: 600; padding: 7px 13px; border-radius: 999px;
  background: color-mix(in srgb, var(--panel) 88%, transparent); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--line); color: var(--ink);
}
.stage-jump button:hover { background: var(--panel); }
.stage-jump a {
  flex: none; white-space: nowrap; font-size: .82rem; font-weight: 600; padding: 7px 13px; border-radius: 999px;
  background: color-mix(in srgb, var(--panel) 88%, transparent); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--line); color: var(--ink); text-decoration: none;
}
.stage-jump a:hover { background: var(--panel); }
/* Category filter lives in the same scrolling pill row as the jump chips
   (merged 19 Sep — a separate stacked row plus the phone's native <select>
   overlay was cramped and covered the zoom buttons/board on narrow
   screens). The trigger button sits in that row; the menu itself is a
   sibling of #stageJump (not nested inside it — that row's
   overflow-x: auto also clips vertical overflow, which hid the menu
   entirely). reveal.js sets its left/top in JS, relative to #stage, each
   time it opens. */
.cat-filter { position: relative; flex: none; }
.cat-filter-menu {
  position: absolute; z-index: 6; min-width: 210px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--shadow);
  padding: 6px; display: flex; flex-direction: column; gap: 2px;
}
.cat-filter-menu button {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; font-size: .85rem; font-weight: 500;
  padding: 8px 10px; border-radius: 8px; background: none; border: none; color: var(--ink); white-space: normal;
}
.cat-filter-menu button:hover, .cat-filter-menu button:focus-visible { background: color-mix(in srgb, var(--ink) 8%, transparent); }
.cat-filter-menu button[aria-selected="true"] { font-weight: 700; }
.cat-filter-menu .dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
/* City filter (20 Sep 2026): same detached menu as the category filter, but
   its list is as long as the wall's real city count, so it scrolls and is
   capped against the phone viewport. reveal.js keeps the menu inside the
   stage (positionMenuUnder). */
#cityFilterMenu { max-height: min(320px, 46vh); overflow-y: auto; }
#cityFilterMenu button { justify-content: space-between; gap: 12px; }
.cat-filter-menu .city-n { flex: none; color: var(--muted); font-size: .78rem; font-variant-numeric: tabular-nums; }
.cat-filter-menu .search-empty { margin: 0; padding: 8px 10px; font-size: .82rem; color: var(--muted); }
/* Wall switcher (20 Sep 2026): the only menu in this family whose rows are
   real links to other pages, so they are <a> and need the same row styling
   the <button> rows already have. 18 walls is longer than a phone, so it
   scrolls and is capped against the viewport, exactly like #cityFilterMenu. */
#wallSwitchMenu { max-height: min(340px, 48vh); overflow-y: auto; min-width: 240px; }
.cat-filter-menu a {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%; box-sizing: border-box; text-align: left; font-size: .85rem; font-weight: 500;
  padding: 8px 10px; border-radius: 8px; color: var(--ink); text-decoration: none; white-space: normal;
}
.cat-filter-menu a:hover, .cat-filter-menu a:focus-visible { background: color-mix(in srgb, var(--ink) 8%, transparent); }
.cat-filter-menu a[data-active="true"] { font-weight: 700; }
/* Sticky at the foot of the scrolling list: with 18 walls the way out of
   this menu otherwise scrolls off the bottom, which is the one row that
   must never be hard to reach. */
.cat-filter-menu .wall-switch-all {
  position: sticky; bottom: -6px; margin-top: 4px;
  border-top: 1px solid var(--line); border-radius: 0 0 8px 8px;
  padding-top: 10px; padding-bottom: 12px; font-weight: 600; color: var(--green);
  background: var(--panel);
}
/* Search by name (19 Sep 2026): same detached-menu pattern as the category
   filter above, and the same overflow-clip reason for living outside
   #stageJump. min-width is bigger than the category menu — a result row
   carries a name plus a block count and needs the room. */
.search-menu { min-width: 260px; max-width: min(320px, calc(100vw - 32px)); }
.search-menu input {
  width: 100%; box-sizing: border-box; padding: 8px 10px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink); font-size: .9rem;
}
.search-menu #searchResults { display: flex; flex-direction: column; gap: 2px; margin-top: 4px; max-height: 260px; overflow-y: auto; }
.search-menu .search-hit {
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
  width: 100%; text-align: left; font-size: .85rem; padding: 8px 10px; border-radius: 8px;
  background: none; border: none; color: var(--ink); cursor: pointer;
}
.search-menu .search-hit:hover, .search-menu .search-hit:focus-visible { background: color-mix(in srgb, var(--ink) 8%, transparent); }
.search-menu .search-hit b { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.search-menu .search-hit span { flex: none; color: var(--muted); font-size: .78rem; }
.search-menu .search-empty { padding: 8px 10px; font-size: .82rem; color: var(--muted); }
.minimap-wrap {
  position: absolute; right: 14px; bottom: 88px; z-index: 5; border-radius: 10px; overflow: hidden;
  /* A stronger edge than var(--line): the minimap's own fill is the board
     colour, which is nearly the page background, so a hairline border left
     it reading as a blank patch of page (20 Sep 2026). */
  border: 1px solid color-mix(in srgb, var(--ink) 28%, transparent);
  box-shadow: var(--shadow); cursor: pointer; line-height: 0;
}
#minimap { display: block; background: var(--bg); }
/* Docked under the zoom buttons, not the bottom band: .stage-foot's height
   varies (CTA + hint + contest link can wrap), and bottom:14px overlapped
   the contest pill on narrow phones (found 17 Sep). Under zoom-tools is
   clear at any phone size, since that column is otherwise empty canvas. */
/* Phones (21 Sep 2026, evening): back, docked top-right UNDER the zoom
   tools — the 17 Sep position — at 0.7 scale. It was hidden for a day
   because a bottom-anchored offset kept colliding with the reveal caption,
   whose height AND width grow as it wraps. Anchored to the TOP instead, it
   shares nothing with the stage foot: the zoom column is 4 × 38px + 3 × 6px
   = 170px tall from top:14px, so top:196px clears it, and the scaled
   minimap ends about 280px down a 792px stage while the foot's tallest
   stack (caption + filmstrip + CTA row + hint + contest pill) starts well
   below 500px. The centred empty-board panel that used to sit in the
   middle is gone (a line under the toolbar now), so nothing else lives in
   this corner. Unchanged at ≥761px. */
@media (max-width: 760px) {
  .minimap-wrap { top: 196px; right: 14px; bottom: auto; transform: scale(.7); transform-origin: top right; }
}
.zoom-tools { position: absolute; top: 14px; right: 14px; display: flex; flex-direction: column; gap: 6px; z-index: 5; }
.zoom-tools button {
  width: 38px; height: 38px; padding: 0; font-size: 1.1rem; line-height: 1; border-radius: 12px;
  background: color-mix(in srgb, var(--panel) 92%, transparent); border: 1px solid var(--line); color: var(--ink); font-weight: 600;
  box-shadow: var(--shadow-sm);
}
.zoom-tools button:hover { background: var(--panel); }
/* Live zoom readout under the zoom buttons (24 Sep 2026), 100% = the
   default close-in view; tapping it shows the whole wall. */
.zoom-tools .zoom-pct { width: auto; min-width: 38px; height: 26px; padding: 0 8px; font-size: .72rem; font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; border-radius: 999px; }
body.art-full { overflow: hidden; }
body.art-full .stage { position: fixed; inset: 0; z-index: 60; height: 100vh; height: 100dvh; margin: 0; background: #000; }
body.art-full .stage-head, body.art-full .stage-foot, body.art-full .stage-jump, body.art-full .minimap-wrap { display: none; }
body.art-full .zoom-tools { top: 14px; right: 14px; }
.lens {
  position: absolute; z-index: 6; pointer-events: none; border-radius: 50%;
  border: 2px solid var(--panel); background: #f5f5f3;
  box-shadow: 0 0 0 1px rgba(0,0,0,.25), 0 14px 40px rgba(0,0,0,.25);
}
/* changes-16sep2026.md follow-up: pixel count + link on the magnifier, so a
   visitor can tell what a claimed block is without opening it. */
.lens-cap {
  position: absolute; z-index: 6; margin: 0; pointer-events: none;
  background: rgba(20, 20, 20, .82); color: #fff; font-size: .8rem; font-weight: 600;
  padding: 5px 12px; border-radius: 999px; white-space: nowrap;
}
.art-caption { padding: 26px 22px 0; text-align: center; }
.art-caption .counter { margin-top: 12px; font-size: clamp(1.2rem, 2.2vw, 1.7rem); font-weight: 600; letter-spacing: -0.02em; color: var(--ink); }
.art-caption .trees { color: var(--green); font-size: 1rem; }
.art-caption .next-tree { color: var(--gold-text); }
.art-caption .hint { color: var(--muted); }
.reveal-progress { height: 6px; border-radius: 3px; background: var(--grey-2); overflow: hidden; max-width: 560px; margin: 0 auto; }
.reveal-progress div { height: 100%; width: 0; background: linear-gradient(90deg, var(--green), #8fd3a8); transition: width .6s; }
/* align-items: stretch (the grid default, not "start"), 22 Sep 2026: with
   "start", .wall-side rendered at only its OWN content height (spot
   preview + Supporters), not the taller row track .claim-col sets — so
   the sticky pane inside it still ran out of room to stick within once a
   visitor scrolled past both, and vanished again for the rest of the
   claim form (the photo-upload area and below). Stretching .wall-side to
   the full row height gives the sticky pane blank flex space to keep
   sticking into for the whole scroll, not just the first two panels. */
.wall-below { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 28px; padding: 28px 22px 0; }
.wall-below .claim-col { grid-column: 1; min-width: 0; }
.wall-below .wall-side { grid-column: 2; min-width: 0; display: flex; flex-direction: column; gap: 28px; }
/* Sticky beside the claim form (22 Sep 2026, third fix on this one bug —
   see git history for the two before it). .wall-side is a flex column:
   #spotPreview is sticky, but the Supporters panel right after it in DOM
   order sits in ordinary flow, positioned according to #spotPreview's
   NATURAL (un-stuck) flow height, not where it visually sits once stuck.
   At most scroll depths that puts Supporters' own opaque white background
   at the same screen coordinates #spotPreview is stuck at — and with
   neither element given a stacking context, the LATER one in DOM order
   (Supporters) painted on top, hiding the sticky pane completely even
   though it was genuinely still there (confirmed via getBoundingClientRect
   before this fix — it measured correctly, it just wasn't visible). A
   z-index on the sticky element is what actually creates its own stacking
   context, so it now always paints above its normal-flow siblings. 76px
   clears the site's fixed top nav. */
/* Spot preview, inline beside the pack cards since 24 Sep 2026 (it was a
   sticky pane in the right column — see git history for the three sticky
   fixes that pane needed; none of that applies in normal flow). Shows on
   phones too now, under the packs. */
.packs-row { display: flex; gap: 14px; align-items: flex-start; margin: 12px 0 16px; }
.packs-row .packs { flex: 1; min-width: 0; margin: 0; }
.spot-preview {
  flex: none; width: 168px; background: var(--grey); border-radius: 16px;
  padding: 10px; text-align: center;
}
.spot-preview canvas {
  display: block; width: 100%; height: auto; aspect-ratio: 1; border-radius: 12px;
  background: #fff; margin-bottom: 8px; cursor: pointer; border: 1px solid var(--grey-2);
}
.spot-preview .hint { margin: 0; font-size: .74rem; line-height: 1.3; }
@media (max-width: 560px) {
  .packs-row { flex-direction: column; }
  .spot-preview { width: 100%; display: flex; align-items: center; gap: 12px; text-align: left; }
  .spot-preview canvas { width: 96px; margin: 0; }
}
@media (max-width: 980px) {
  .wall-below { grid-template-columns: minmax(0, 1fr); padding: 18px 14px 0; gap: 18px; }
  .wall-below .wall-side { grid-column: 1; }
}
@media (max-width: 760px) {
  .stage { height: calc(100vh - 52px); height: calc(100dvh - 52px); }
  .stage-head { right: 66px; }
  .stage-head > div { padding: 6px 10px; }
  .stage-foot { bottom: 14px; }
}
.panel {
  position: relative; background: #fff; color: var(--ink); border-radius: var(--r-lg);
  padding: 28px; box-shadow: var(--shadow);
}
.panel h2 { font-size: 1.5rem; margin-bottom: 6px; letter-spacing: -0.02em; }
.panel h3 { font-size: 1rem; }
.panel .hint { color: var(--muted); }
.panel .label, .label { display: block; margin-top: 14px; font-size: .88rem; color: var(--muted); font-weight: 500; }
.panel input, .panel select, .panel textarea {
  width: 100%; margin-top: 6px; padding: 13px 14px; border-radius: 12px; border: 1px solid var(--line);
  background: #fff; color: var(--ink); font: inherit; font-size: 1rem;
}
.panel textarea { resize: vertical; box-sizing: border-box; }
.panel input:focus, .panel select:focus, .panel textarea:focus { outline: none; border-color: var(--ink); }
.panel input[type="file"] { padding: 10px; font-size: .9rem; }
.panel button { margin-top: 16px; width: 100%; padding: 15px; border-radius: 999px; border: none; background: var(--green); color: #fff; font-weight: 600; font-size: 1.05rem; }
.panel button:hover { background: var(--green-deep); border-color: var(--green-deep); }
.panel button:disabled { opacity: .55; }
.panel .close-btn { position: absolute; top: 12px; right: 14px; width: auto; margin: 0; padding: 2px 8px; background: none; border: none; color: var(--muted); font-size: 1.5rem; line-height: 1; }
.panel .close-btn:hover { color: var(--ink); background: none; }
.panel.pulse { animation: panel-pulse 1.1s; }
@keyframes panel-pulse { 0% { box-shadow: 0 0 0 0 rgba(143,211,168,.8); } 100% { box-shadow: 0 0 0 18px rgba(143,211,168,0); } }
.total { margin-top: 12px; font-weight: 600; font-size: 1.1rem; }
.total.flash { animation: total-flash .7s; }
@keyframes total-flash { 0% { color: var(--green); } 100% { color: var(--ink); } }
.success-panel { border: 1px solid #cfe5d6; }
.wa-btn { display: block; text-align: center; margin-top: 14px; padding: 14px; border-radius: 999px; background: #25d366; color: #fff; font-weight: 600; }
.wa-btn:hover { text-decoration: none; background: #1fb85a; }
/* Board UX Group C, 17 Sep: shareable block card (account page).
   Fixed 20 Sep 2026 ("the share your block option is not showing properly"):
   the card is taller than a phone screen once the rendered share image and
   the four buttons are in it (891px against an 844px viewport at 390px
   wide), and `align-items: center` on a non-scrolling overlay pushed the
   top — including the ✕ that closes it — off screen with no way to reach
   it. overflow-y + `margin: auto` on the card keeps it centred when it
   fits and scrollable from the very top when it does not. */
.overlay {
  position: fixed; inset: 0; z-index: 40; background: rgba(10, 14, 12, .55);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 20px; overflow-y: auto; overscroll-behavior: contain;
}
.share-card {
  width: min(340px, 100%); margin: auto; background: linear-gradient(160deg, var(--green-deep), var(--green)); color: #fff;
  border-radius: 22px; padding: 28px 24px 24px; display: flex; flex-direction: column; gap: 16px;
  box-shadow: 0 30px 60px rgba(0,0,0,.35); position: relative;
}
/* The rendered PNG already carries the brand line, the headline and the
   same three stats. Once it is on screen the HTML copies above it are a
   duplicate of the picture right under them, which is what read as
   "not showing properly". They stay as the fallback for a browser where
   the canvas render failed. */
.share-card:has(#shareCardCanvas:not([hidden])) .share-brand,
.share-card:has(#shareCardCanvas:not([hidden])) h3,
.share-card:has(#shareCardCanvas:not([hidden])) .share-stats { display: none; }
/* …and with them gone the ✕ would sit on top of the picture's own title. */
.share-card:has(#shareCardCanvas:not([hidden])) { padding-top: 56px; }
/* The success panel's ✕ sits in the same corner as its heading, which wraps
   into it on a phone (found 20 Sep 2026). */
.success-panel > h2 { padding-right: 34px; }
.share-card .share-close { position: absolute; top: 14px; right: 14px; width: 30px; height: 30px; border-radius: 50%; border: none; background: rgba(255,255,255,.18); color: #fff; font-size: .95rem; cursor: pointer; }
.share-brand { margin: 0; padding-right: 34px; font-size: .76rem; letter-spacing: .06em; text-transform: uppercase; opacity: .85; }
.share-card h3 { margin: 0; padding-right: 34px; font-size: 1.4rem; line-height: 1.3; }
.share-stats { display: flex; gap: 10px; }
.share-stat { background: rgba(255,255,255,.14); border-radius: 12px; padding: 12px 10px; flex: 1; text-align: center; }
.share-stat b { display: block; font-size: 1.25rem; }
.share-stat span { font-size: .72rem; opacity: .85; }
.share-stat[hidden] { display: none; }

/* "Claim your blocks" from /walls or the board's own CTA lands here via
   #buyPanel — the site's every "Claim blocks" link has pointed straight
   at this anchor by design, to save a buyer a scroll. But a first-time
   visitor landing here on PAGE LOAD (not a same-page click) gets the
   browser's native instant fragment jump, which scroll-behavior:smooth
   (html, above) does not apply to at all — so it skipped straight past
   both the board and the free contest card with no transition (owner,
   22 Sep 2026, screenshot: "blocking the contest and entire board at
   1st glance"). scroll-margin-top IS honoured by that native jump
   (unlike scroll-behavior), so the fix is CSS-only: reserve enough room
   above #buyPanel that the contest card (sized on the real page, both
   viewports) lands fully in view rather than being scrolled past.
   The board itself stays above that and out of view either way — that
   trade-off is inherent to jumping straight to the form at all. */
.claim-card { scroll-margin-top: 380px; }
@media (max-width: 760px) { .claim-card { scroll-margin-top: 440px; } }

/* claim card steps */
.claim-card .step { border-top: 1px solid var(--grey-2); margin-top: 22px; padding-top: 18px; }
.claim-card .step:first-of-type { border-top: none; margin-top: 14px; padding-top: 0; }
.step-h { display: flex; align-items: center; gap: 10px; font-size: 1rem; color: var(--ink); font-weight: 600; }
.step-n { display: inline-grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; background: var(--ink); color: #fff; font-size: .78rem; font-weight: 600; flex: none; }
.preview-row { display: flex; gap: 14px; align-items: center; margin: 12px 0 6px; }
.block-preview { margin: 0; text-align: center; flex: none; }
.block-preview canvas { width: 96px; height: 96px; display: block; border-radius: 14px; border: 1px solid var(--grey-2); background: #eceee8; }
.block-preview figcaption { font-size: .72rem; color: var(--muted); margin-top: 6px; }
.preview-note { margin: 0; }
.defaults-note { background: var(--gold-bg); border: 1px solid #f1dc9a; border-radius: var(--r-sm); padding: 10px 12px; color: var(--gold-text); margin-top: 10px; }
.defaults-note b { color: var(--gold-text); }

/* packs (Single / Sapling / Grove / Forest) inside the claim card; the selected
   card carries .price-card.on (shared v2 block) */
.claim-card .packs, .packs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 12px 0 16px; }
.claim-card .price-card, .price-card {
  position: relative; width: auto; margin: 0; padding: 16px 10px 12px; border-radius: 16px; cursor: pointer;
  display: flex; flex-direction: column; gap: 3px; align-items: center; text-align: center;
  background: var(--grey); color: var(--ink); border: 1.5px solid transparent; font-weight: 400;
  transition: border-color .15s, transform .15s;
}
.claim-card .price-card:hover, .price-card:hover { border-color: var(--ink); background: var(--grey); transform: none; }
.claim-card .price-card .pc-name, .price-card .pc-name { font-weight: 600; color: var(--ink); font-size: .92rem; }
.claim-card .price-card .pc-price, .price-card .pc-price { color: var(--ink); font-size: 1.45rem; font-weight: 600; letter-spacing: -0.03em; }
.claim-card .price-card .pc-detail, .price-card .pc-detail { color: var(--muted); font-size: .74rem; line-height: 1.35; }
@media (prefers-reduced-motion: reduce) { .price-card, .proof-toast { transition: none; } .total.flash, .panel.pulse { animation: none; } }

/* supporters + scoreboard panel */
.supporters { list-style: none; padding: 0; font-size: .95rem; }
.supporters li { padding: 10px 0; border-bottom: 1px solid var(--grey-2); }
.sup-msg { color: var(--muted); }
.lb-h { margin-top: 26px; }
.leaderboard { font-size: .95rem; padding-left: 22px; }
.leaderboard li { padding: 6px 0; }
.proof-toast {
  position: fixed; left: 16px; bottom: 16px; z-index: 30; max-width: 340px;
  background: rgba(255,255,255,.96); color: var(--ink); border-radius: 16px; padding: 12px 16px; font-size: .88rem;
  box-shadow: var(--shadow); pointer-events: none; opacity: 0; transform: translateY(8px);
  transition: opacity .4s, transform .4s;
}
.proof-toast.show { opacity: 1; transform: translateY(0); }

/* ===== Interactive Diwali wall (24 Sep 2026) — the direction chosen on
   the Superdesign canvas, implemented on the real page: a live status
   strip and grouped toolbar over the board, a floating spot label, a
   next-sapling ring by the CTA, the contest as a gold ribbon, a stepper
   with the spot preview inline, a supporter logo wall and a podium. Every
   existing id and handler is untouched. ===== */

/* Status strip: the first pill in the toolbar row. */
.live-strip {
  flex: none; display: inline-flex; align-items: center; gap: 10px;
  padding: 5px 12px 5px 6px; border-radius: 999px;
  background: color-mix(in srgb, var(--panel) 88%, transparent); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--line); color: var(--ink); font-size: .82rem; white-space: nowrap;
}
.live-strip img { width: 26px; height: 26px; border-radius: 50%; }
.live-strip b { font-weight: 700; }
.live-strip .ls-fig { color: #575f5a; font-weight: 600; font-variant-numeric: tabular-nums; padding-left: 10px; border-left: 1px solid var(--grey-2); }
.live-strip .ls-fig[hidden] { display: none; }
/* Grouped controls: a tiny label rides above each cluster. */
/* The row clips vertical overflow (overflow-x: auto), so the captions
   live INSIDE each group's box: the group is 12px taller and the row
   bottom-aligns its items, which keeps every pill on one baseline. */
.stage-jump { align-items: flex-end; }
.jump-group { flex: none; display: inline-flex; gap: 6px; align-items: center; position: relative; padding-top: 12px; }
.jump-group::before { content: attr(data-label); position: absolute; left: 8px; top: -1px; font-size: .6rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); opacity: .85; }
.jump-group:empty { display: none; }
@media (max-width: 760px) {
  .live-strip b { display: none; }
  .live-strip .ls-fig { padding-left: 8px; }
  .jump-group::before { display: none; }
  .jump-group { padding-top: 0; margin-top: 0; }
}

/* Floating spot label: positioned by render() under the highlight rect. */
.spot-label {
  position: absolute; z-index: 6; transform: translate(-50%, 0); white-space: nowrap;
  padding: 6px 12px; border-radius: 999px; border: none;
  background: var(--green-deep); color: #fff; font-size: .78rem; font-weight: 600; box-shadow: var(--shadow-sm);
  animation: spot-bob 2.2s ease-in-out infinite;
}
.spot-label::before { content: ''; position: absolute; left: 50%; top: -6px; margin-left: -6px; border: 6px solid transparent; border-top: 0; border-bottom-color: var(--green-deep); }
@keyframes spot-bob { 0%, 100% { margin-top: 0 } 50% { margin-top: 3px } }
@media (prefers-reduced-motion: reduce) { .spot-label { animation: none; } }

/* Next-sapling ring beside the CTA. */
.sapling-ring {
  pointer-events: auto; display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 14px 8px 8px; border-radius: 999px; font-size: .8rem; font-weight: 600; color: var(--ink);
  background: color-mix(in srgb, var(--panel) 92%, transparent); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--line); box-shadow: var(--shadow-sm);
}
.sapling-ring svg { width: 28px; height: 28px; transform: rotate(-90deg); flex: none; }
.sapling-ring .sr-bg { fill: none; stroke: var(--grey-2); stroke-width: 4; }
.sapling-ring .sr-fg { fill: none; stroke: var(--green); stroke-width: 4; stroke-linecap: round; stroke-dasharray: 97.4; stroke-dashoffset: 97.4; transition: stroke-dashoffset .8s ease; }
.stage-cta-row { display: flex; gap: 10px; align-items: center; justify-content: center; flex-wrap: wrap; }

/* Contest as a gold ribbon across both columns. */
.contest-ribbon {
  grid-column: 1 / -1; display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 16px 20px; background: var(--gold-bg); border: 1px solid #f1dc9a; box-shadow: none;
}
.contest-ribbon .ribbon-diya { font-size: 1.6rem; flex: none; }
.contest-ribbon .pill { margin: 0; flex: none; }
.contest-ribbon .ribbon-text { flex: 1 1 260px; min-width: 0; }
.contest-ribbon h2 { font-size: 1.05rem; margin: 0 0 2px; }
.contest-ribbon .prize-line-wall, .contest-ribbon #contestClose { margin: 0; font-size: .85rem; color: var(--gold-text); }
.contest-ribbon .contest-enter { flex: none; width: auto; margin: 0; padding: 12px 20px; }
.contest-ribbon .ribbon-foot { flex-basis: 100%; margin: 4px 0 0; font-size: .74rem; }

/* The claim-card stepper lives further down (".claim-steps", boxed
   tabs). An earlier flex version with a ::before connector line was
   removed on 24 Sep 2026: both rule sets applied at once and the line
   drew straight across the boxed steps (owner's screenshot). Only the
   scroll-margin survives — requestTab() scrolls the strip under the
   52px sticky nav after a tab change. */
.claim-steps { scroll-margin-top: 72px; }

/* Pack cards: a mini block-shape glyph and sapling marks. */
.price-card .pc-shape { display: inline-grid; gap: 1px; margin: 4px 0 2px; }
.price-card .pc-shape b { width: 5px; height: 5px; background: var(--navy); border-radius: 1px; opacity: .85; }
.price-card .pc-trees { font-size: .8rem; letter-spacing: -2px; line-height: 1; }

/* Supporters panel sized to the screen, not to its list (24 Sep 2026,
   owner: "supporters on the right is too big, can it be dynamic size"):
   sticky beside the claim card, never taller than the viewport, with
   the feed scrolling inside it. One column (≤980px): normal flow, the
   feed itself capped and scrollable. */
.wall-side > .panel { position: sticky; top: 72px; max-height: calc(100vh - 96px); max-height: calc(100dvh - 96px); overflow-y: auto; overscroll-behavior: contain; }
.supporters { max-height: 40vh; overflow-y: auto; padding-right: 4px; }
@media (max-width: 980px) {
  .wall-side > .panel { position: static; max-height: none; overflow: visible; }
  .supporters { max-height: 300px; }
}

/* Supporter logo wall + podium. */
.supporter-wall { display: flex; flex-wrap: wrap; gap: 8px; margin: 6px 0 10px; }
.supporter-wall .sw-chip {
  width: 44px; height: 44px; border-radius: 50%; overflow: hidden; border: 2px solid var(--panel); box-shadow: var(--shadow-sm);
  display: grid; place-items: center; background: var(--grey-2); color: var(--ink); font-weight: 700; font-size: .85rem; padding: 0; margin: 0; cursor: pointer;
}
.supporter-wall .sw-chip img { width: 100%; height: 100%; object-fit: cover; }
.supporter-wall .sw-chip.recent { box-shadow: 0 0 0 3px rgba(46,125,79,.35); animation: sw-pulse 2s ease-out 2; }
@keyframes sw-pulse { 0% { box-shadow: 0 0 0 0 rgba(46,125,79,.55); } 100% { box-shadow: 0 0 0 10px rgba(46,125,79,0); } }
.supporter-wall .sw-more { background: var(--grey); color: var(--muted); font-size: .72rem; }
.podium { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; align-items: end; margin: 10px 0 6px; }
.podium .po { text-align: center; font-size: .74rem; color: var(--muted); }
.podium .po b { display: block; color: var(--ink); font-size: .8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.podium .po .po-bar { border-radius: 8px 8px 0 0; margin: 6px auto 4px; color: #fff; font-weight: 700; font-size: .78rem; display: grid; place-items: center; }
.podium .po-1 .po-bar { height: 44px; background: var(--gold-line); }
.podium .po-2 .po-bar { height: 32px; background: #b8bcc4; }
.podium .po-3 .po-bar { height: 24px; background: #c47a3a; }
.podium .po-1 { order: 2; } .podium .po-2 { order: 1; } .podium .po-3 { order: 3; }






/* ============================================================================
   EDITORIAL PAGES — about, trees, legal
   ============================================================================ */
.doc-hero { text-align: center; padding: clamp(56px, 8vw, 110px) 22px 30px; max-width: 900px; margin: 0 auto; }
.doc-hero .eyebrow { margin-bottom: 14px; }
.doc-hero h1 { margin: 0 auto; max-width: 16ch; }
.doc-hero .lede { margin: 20px auto 0; max-width: 40ch; }
.doc-hero .about-badge { display: block; width: 110px; height: 110px; margin: 0 auto 26px; border-radius: 50%; box-shadow: var(--shadow); }
.doc { max-width: var(--measure); margin: 0 auto; padding: 10px 22px var(--sec); width: 100%; line-height: 1.6; }
.doc h2 { font-size: clamp(1.35rem, 2.2vw, 1.75rem); margin-top: 52px; letter-spacing: -0.02em; }
.doc h2:first-child { margin-top: 0; }
.doc p, .doc li { color: var(--text-2); font-size: 1.05rem; margin-top: 12px; }
.doc ul { padding-left: 22px; }
.doc li { margin-top: 8px; }
.doc table { width: 100%; border-collapse: collapse; margin-top: 18px; font-size: .95rem; border-radius: var(--r-sm); overflow: hidden; }
.doc th, .doc td { border-bottom: 1px solid var(--grey-2); padding: 12px 12px; text-align: left; vertical-align: top; }
.doc th { background: var(--grey); font-weight: 600; color: var(--ink); }
.doc .callout { background: var(--grey); border-radius: var(--r); padding: 22px 26px; margin-top: 26px; }
.doc .callout p { margin-top: 0; }
.logo-story { display: flex; gap: 22px; align-items: center; background: var(--grey); border-radius: var(--r); padding: 22px; margin-top: 16px; }
.logo-story img { width: 96px; height: 96px; flex: none; border-radius: 50%; }
.logo-story p { margin-top: 0; }
@media (max-width: 560px) { .logo-story { flex-direction: column; text-align: center; } }
/* legal: contents list built by ui.js from h2s */
.doc.legal .toc { background: var(--grey); border-radius: var(--r); padding: 18px 22px; margin: 6px 0 30px; }
.doc.legal .toc p { margin: 0 0 6px; font-weight: 600; font-size: .9rem; color: var(--muted); }
.doc.legal .toc ol { margin: 0; padding-left: 20px; columns: 2; column-gap: 24px; }
.doc.legal .toc li { font-size: .95rem; margin-top: 4px; break-inside: avoid; }
@media (max-width: 560px) { .doc.legal .toc ol { columns: 1; } }
.doc.legal .updated { color: var(--muted); font-size: .95rem; }
.band { background: var(--grey); }
.band .stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 18px; max-width: var(--wrap); margin: 0 auto; padding: 0 22px; }
.band .stat { text-align: center; padding: 30px 10px; }
.band .stat b { display: block; font-size: clamp(2rem, 4vw, 3.2rem); letter-spacing: -0.04em; color: var(--ink); line-height: 1; }
.band .stat span { color: var(--muted); display: block; margin-top: 10px; }

/* ============================================================================
   AUTH / ACCOUNT / SUCCESS
   ============================================================================ */
header.page-head { text-align: center; padding: clamp(48px, 7vw, 90px) 22px 10px; }
header.page-head .nav { display: flex; justify-content: center; gap: 18px; margin-top: 16px; font-size: .95rem; }
.authwrap { max-width: 560px; margin: 0 auto; padding: 16px 22px var(--sec); width: 100%; flex: 1; }
.card2 { background: var(--grey); border-radius: var(--r-lg); padding: 28px; margin-top: 16px; }
.card2 label { display: block; font-size: .9rem; color: var(--muted); font-weight: 500; }
.card2 input { width: 100%; margin-top: 6px; padding: 13px 14px; border-radius: 12px; border: 1px solid var(--line); background: #fff; color: var(--ink); font: inherit; font-size: 1rem; }
.card2 input:focus { outline: none; border-color: var(--ink); }
/* Password show/hide eye (23 Sep 2026, owner's second batch) — the input
   keeps its normal .card2 styling; the button sits inside the same
   rounded field on the right, so it reads as one control, not two. */
.card2 .pw-field { position: relative; display: block; margin-top: 6px; }
.card2 .pw-field input { margin-top: 0; padding-right: 44px; }
.pw-eye { position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: 34px; height: 34px; border: none; background: none; cursor: pointer;
  font-size: 1.05rem; border-radius: 8px; display: grid; place-items: center; }
.pw-eye:hover { background: var(--grey-2); }
.card2 button.primary { width: 100%; margin-top: 14px; padding: 15px; }
.orders-table { width: 100%; font-size: .9rem; border-collapse: collapse; }
.orders-table td, .orders-table th { padding: 10px 8px; border-bottom: 1px solid var(--grey-2); text-align: left; }
.status-chip { font-size: .72rem; padding: 4px 10px; border-radius: 999px; background: #fff; white-space: nowrap; font-weight: 600; }
.status-chip.live { background: #e3f1e6; color: var(--green); }
.status-chip.paid_pending_review { background: var(--gold-bg); color: var(--gold-text); }
.status-chip.rejected, .status-chip.refunded, .status-chip.cancelled { background: #fbe9e7; color: #a13a2f; }
.linkbtn { background: none; border: none; color: var(--green-deep); cursor: pointer; padding: 0; font-size: .85rem; text-decoration: underline; border-radius: 0; }
.totals { display: flex; gap: 12px; margin-top: 16px; }
.totals .tot { flex: 1; background: var(--grey); border-radius: var(--r); padding: 18px 12px; text-align: center; }
.totals .tot b { display: block; font-size: 1.6rem; letter-spacing: -0.03em; }
.totals .tot span { font-size: .78rem; color: var(--muted); }
/* "Your impact" (21 Sep 2026): the totals row wraps to hold a few more
   quiet tiles — a free-block note, a week-over-week chip, the Founding mark. */
.impact-title { margin: 18px 0 -6px; font-size: .78rem; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; font-weight: 600; }
.totals { flex-wrap: wrap; }
.totals .tot { flex: 1 1 150px; min-width: 0; }
.totals .tot b small { font-size: .72rem; font-weight: 400; color: var(--muted); letter-spacing: 0; white-space: nowrap; }
.totals .tot .delta { display: inline-block; margin-top: 6px; font-size: .72rem; font-weight: 600; padding: 2px 8px; border-radius: 999px; background: var(--grey-2); color: var(--muted); font-variant-numeric: tabular-nums; }
.totals .tot .delta.up { background: color-mix(in srgb, var(--green) 14%, var(--grey)); color: var(--green); }
.totals .tot .delta.down { background: color-mix(in srgb, var(--error) 12%, var(--grey)); color: var(--error); }
.totals .tot.founding { background: var(--gold-bg); }
.totals .tot.founding b, .totals .tot.founding span { color: var(--gold-text); }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
.success-wrap { max-width: 560px; margin: 0 auto; text-align: center; padding: clamp(48px, 7vw, 90px) 22px var(--sec); flex: 1; }
.success-wrap h1 { font-size: clamp(1.8rem, 4vw, 3rem); }
.success-wrap .tagline { font-size: 1.1rem; margin-top: 16px; }
.success-wrap #card { width: 100%; max-width: 420px; border-radius: var(--r); margin-top: 26px; box-shadow: var(--shadow); }
.sharebtns { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-top: 22px; }

/* ============================================================================
   INTERACTIVE PASS — dark mode, living hero, live numbers, calculator,
   scroll story, bento, Diwali, FAQ, mobile bar, micro-interactions
   ============================================================================ */
:root { --pixel: #e3e3e8; --brand: #1E3A5F; --leaf: #2e7d4f; --trunk: #8a5a2b; --glass: rgba(255,255,255,.55); --text-2: #3a3a3c; }
html[data-theme="dark"] {
  --bg: #0b0f0c; --grey: #141a16; --grey-2: #232b26; --ink: #f5f5f7; --text: #f5f5f7; --muted: #a1a1a6;
  --line: #2f3a33; --panel: #141a16; --pixel: #1f2822; --glass: rgba(20,26,22,.55);
  --shadow-sm: 0 1px 2px rgba(0,0,0,.4), 0 4px 14px rgba(0,0,0,.35); --shadow: 0 2px 6px rgba(0,0,0,.5), 0 18px 48px rgba(0,0,0,.55);
  --gold-bg: #1f1a08; --gold-text: #f1d98a; --text-2: #c7c7cc;
}
/* Light is the default on every page (changes_3.md item 9). Dark only via the toggle. */
html { background: var(--bg); transition: background .3s; }
body { transition: background .3s, color .3s; }
a { color: var(--green); }
.tile, .pcard .chip, .status-chip, button, .btn, .search input, .card2 input { background: var(--panel); color: var(--ink); }
.tile { background: var(--panel); }
.sec-grey .tile { background: var(--panel); }
/* Bug found 21 Sep 2026 (live since 11 Sep): the plain "button, .btn" rule
   just above has the same specificity as .btn-primary/.btn-ghost/.tn-cta
   and sits later in the file, so it silently won the cascade — every
   primary button (Claim blocks, Pay, Enter the contest) rendered as a
   plain white/panel button with dark text in both themes, instead of its
   intended green fill. Restoring the intended styling here, after the
   rule that was overriding it, rather than reordering the original
   button rules above (app/public/styles.css:82-93). */
.btn-primary, button.primary { background: var(--green); border-color: var(--green); color: #fff; }
.btn-primary:hover, button.primary:hover { background: var(--green-deep); border-color: var(--green-deep); }
.btn-ghost { background: transparent; }
.footer-contest { border-color: transparent; }
.topnav { background: color-mix(in srgb, var(--bg) 72%, transparent); border-bottom-color: var(--line); }
.tn-brand, .tn-links a { color: var(--ink); }
.tn-links a:hover { background: var(--grey-2); }
.tn-burger span { background: var(--ink); }
.tn-cta { background: var(--green) !important; color: #fff !important; margin-left: 6px; }
.tn-cta:hover { background: var(--green-deep) !important; }
.tn-theme {
  width: 34px; height: 34px; padding: 0; border-radius: 50%; border: 1px solid var(--line); background: var(--panel);
  display: grid; place-items: center; margin-left: 8px; flex: none;
}
.tn-theme .sun { width: 14px; height: 14px; border-radius: 50%; background: var(--ink); box-shadow: inset -4px -3px 0 0 var(--panel); transition: box-shadow .3s, background .3s; }
html[data-theme="dark"] .tn-theme .sun { box-shadow: inset 0 0 0 0 var(--panel); background: #ffd27a; }
@media (max-width: 760px) { .tn-brand { order: -2; } .tn-theme { order: -1; margin-left: 4px; margin-right: 4px; } .tn-burger { order: 0; } .topnav .tn-links { order: 3; } }
/* Sign in stays visible on phones (cloned by ui.js next to the theme toggle). */
.tn-signin-m { display: none; }
@media (max-width: 760px) {
  .tn-signin-m { display: inline-flex; order: -1; margin-left: auto; font-size: .92rem; font-weight: 600; color: var(--ink); padding: 6px 10px; border-radius: 999px; border: 1px solid var(--line); text-decoration: none; }
  .tn-signin-m:hover { background: var(--grey-2); }
  body.dark .tn-signin-m { color: #f5f5f7; border-color: rgba(255,255,255,.2); }
}
/* Four planned walls: a 2×2 grid on desktop, no orphan card. */
@media (min-width: 761px) {
  .cards.teasers { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 820px; margin-inline: auto; }
}
/* the wall page's white panels keep light tokens in every theme */
.panel { --ink: #1d1d1f; --text: #1d1d1f; --muted: #6e6e73; --grey: #f5f5f7; --grey-2: #e8e8ed; --line: #d2d2d7; --panel: #fff; --gold-bg: #fff8e1; --gold-text: #6e5200; color: #1d1d1f; }


/* hero: canvas behind, text in front, gradient headline */
.hero-live { position: relative; overflow: hidden; padding: 0; max-width: none; width: 100%; margin: 0; min-height: calc(100vh - 52px); min-height: calc(100dvh - 52px); display: grid; place-items: center; }
.bloom { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; }
.hero-inner { position: relative; z-index: 1; text-align: center; padding: 60px 22px 90px; max-width: 900px; }
.hero-inner h1 { font-size: clamp(2.6rem, 7vw, 5.6rem); max-width: none; }
.grad { background: linear-gradient(92deg, #2e7d4f, #8fd3a8 50%, #7C9A6D); -webkit-background-clip: text; background-clip: text; color: transparent; }
.hero-inner .tagline { max-width: 40ch; }
.hero-note { color: var(--muted); font-size: .95rem; margin-top: 22px; }
.btn-glass { background: var(--glass); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); border-color: color-mix(in srgb, var(--ink) 15%, transparent); color: var(--ink); }
.btn-glass:hover { border-color: var(--ink); }
/* In-flow, not pinned to the hero's own bottom edge (24 Sep 2026): the
   hero grew taller this session (steps strip, chip, banners), so a
   fixed bottom:22px offset started landing on top of that new content
   on shorter viewports. Sitting after .hero-chips in flow means it can
   never overlap whatever the hero ends with. */
.scroll-cue { position: relative; display: block; margin: 28px auto 0; width: 26px; height: 42px; border: 1.5px solid var(--line); border-radius: 14px; z-index: 1; }
.scroll-cue span { position: absolute; left: 50%; top: 8px; width: 4px; height: 8px; margin-left: -2px; border-radius: 2px; background: var(--muted); animation: cue 1.8s infinite; }
@keyframes cue { 0% { transform: translateY(0); opacity: 1 } 70% { transform: translateY(14px); opacity: 0 } 100% { opacity: 0 } }

/* micro-interactions */
.magnetic { will-change: transform; transition: transform .25s cubic-bezier(.2,.8,.2,1), background .15s, border-color .15s; }
.tilt { transform-style: preserve-3d; transition: transform .35s cubic-bezier(.2,.8,.2,1), box-shadow .35s; }
.btn-primary:active, .btn:active { transform: scale(.97); }
body { animation: page-in .5s ease both; }
@keyframes page-in { from { opacity: 0 } to { opacity: 1 } }

/* marquee */
.marquee { overflow: hidden; border-block: 1px solid var(--line); background: var(--grey); padding: 12px 0; -webkit-mask: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); mask: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
.marquee .track { display: flex; gap: 40px; width: max-content; animation: marquee 48s linear infinite; font-size: .95rem; color: var(--muted); white-space: nowrap; }
.marquee .track b { color: var(--ink); }
.marquee:hover .track { animation-play-state: paused; }
/* changes-16sep2026.md item 4: recent-activity counter, centred under the marquee */
.recent-claims { text-align: center; margin: 16px 0 0; padding: 8px 16px; font-size: .92rem; color: var(--green-deep); }
.recent-claims b { font-size: 1.05em; }
/* Board UX Group B, 17 Sep: home page spotlight of opted-in buyers. */
.spotlight-card {
  display: grid; grid-template-columns: 96px 1fr auto; gap: 16px; align-items: center;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r); padding: 18px 20px;
  box-shadow: var(--shadow-sm);
}
.spotlight-img { width: 96px; height: 96px; border-radius: 14px; object-fit: cover; background: var(--grey); display: block; }
.spotlight-body b { display: block; font-size: 1.05rem; }
.spotlight-body span { color: var(--muted); font-size: .88rem; }
.spotlight-dots { display: flex; gap: 6px; margin-top: 8px; }
.spotlight-dots i { width: 6px; height: 6px; border-radius: 50%; background: var(--line); display: block; }
.spotlight-dots i.on { background: var(--green); }
.spotlight-visit { justify-self: end; padding: 10px 18px; border-radius: 999px; border: 1px solid var(--green); color: var(--green-deep); font-weight: 700; font-size: .85rem; white-space: nowrap; }
@media (max-width: 620px) {
  .spotlight-card { grid-template-columns: 72px 1fr; grid-template-rows: auto auto; }
  .spotlight-img { width: 72px; height: 72px; }
  .spotlight-visit { grid-column: 1 / -1; justify-self: stretch; text-align: center; }
}
@keyframes marquee { to { transform: translateX(-50%); } }

/* live numbers */
.live-grid { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 22px; align-items: stretch; }
@media (max-width: 860px) { .live-grid { grid-template-columns: 1fr; } }
.ring-card { background: var(--grey); border-radius: var(--r-lg); padding: 30px; display: grid; place-items: center; position: relative; }
.ring { width: 200px; height: 200px; transform: rotate(-90deg); }
.ring circle { fill: none; stroke-width: 12; stroke-linecap: round; }
.ring-bg { stroke: var(--grey-2); }
.ring-fg { stroke: url(#ringGrad), var(--green); stroke: var(--green); stroke-dasharray: 364; stroke-dashoffset: 364; transition: stroke-dashoffset 1.6s cubic-bezier(.2,.8,.2,1); }
.ring-label { position: absolute; text-align: center; }
.ring-label b { display: block; font-size: 2.4rem; letter-spacing: -0.04em; line-height: 1; }
.ring-label span { color: var(--muted); font-size: .85rem; display: block; margin-top: 6px; max-width: 12ch; }
.stat-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.stat-tile { background: var(--grey); border-radius: var(--r); padding: 26px 24px; }
.stat-tile b { display: block; font-size: clamp(2rem, 3.4vw, 2.8rem); letter-spacing: -0.04em; line-height: 1; font-variant-numeric: tabular-nums; }
.stat-tile span { color: var(--muted); display: block; margin-top: 8px; font-size: .95rem; }
@media (max-width: 500px) { .stat-tiles { grid-template-columns: 1fr; } }

/* calculator */
.calc { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 22px; background: var(--panel); border-radius: var(--r-lg); padding: clamp(24px, 4vw, 44px); box-shadow: var(--shadow-sm); }
@media (max-width: 860px) { .calc { grid-template-columns: 1fr; } }
.calc-label { color: var(--muted); font-weight: 500; }
.calc-n { display: block; font-size: clamp(3.4rem, 7vw, 6rem); letter-spacing: -0.05em; line-height: 1; font-weight: 600; margin: 6px 0 18px; font-variant-numeric: tabular-nums; }
#calcRange { --p: 1%; width: 100%; appearance: none; -webkit-appearance: none; height: 10px; border-radius: 5px; background: linear-gradient(90deg, var(--green) var(--p), var(--grey-2) var(--p)); outline: none; }
#calcRange::-webkit-slider-thumb { -webkit-appearance: none; width: 28px; height: 28px; border-radius: 50%; background: #fff; border: 2px solid var(--green); box-shadow: 0 2px 10px rgba(0,0,0,.25); cursor: grab; }
#calcRange::-moz-range-thumb { width: 28px; height: 28px; border-radius: 50%; background: #fff; border: 2px solid var(--green); cursor: grab; }
.calc-presets { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 18px; }
.calc-presets button { padding: 8px 16px; font-size: .9rem; background: var(--grey); border-color: transparent; }
.calc-presets button.on { background: var(--ink); color: var(--bg); }
.calc-right { display: flex; flex-direction: column; gap: 10px; }
.calc-out { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--grey-2); }
.calc-out span { color: var(--muted); }
.calc-out b { font-size: 1.25rem; letter-spacing: -0.02em; text-align: right; }
.calc-out.total b { font-size: 1.9rem; color: var(--green); }
.calc-right .btn { margin-top: 10px; text-align: center; }

/* how it works: stepper (changes_3.md item 10 — the pinned scroll story did not advance on phones) */
.story { background: var(--bg); }
.story-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 40px; align-items: center; }
.story-steps { position: relative; min-height: 250px; }
.story-step { position: absolute; inset: 0; opacity: 0; transform: translateX(18px); transition: opacity .45s, transform .45s; pointer-events: none; }
.story-step.on { opacity: 1; transform: none; pointer-events: auto; }
.story-step h2 { margin-top: 12px; }
.story-step p { color: var(--muted); font-size: 1.15rem; margin-top: 12px; max-width: 34ch; }
.story-step .step-n { width: 30px; height: 30px; font-size: .95rem; }
.story-nav { display: flex; align-items: center; gap: 16px; margin-top: 22px; }
.story-btn { width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--line); background: var(--panel); color: var(--ink); font-size: 1.1rem; display: grid; place-items: center; padding: 0; transition: background .2s, border-color .2s; }
.story-btn:hover { background: var(--grey); border-color: var(--ink); }
.story-dots { display: flex; gap: 8px; }
.story-dots button { width: 28px; height: 6px; padding: 0; border: none; border-radius: 3px; background: var(--grey-2); transition: background .3s, width .3s; }
.story-dots button.on { background: var(--ink); width: 44px; }
.story-art { display: grid; place-items: center; position: relative; }
#morph { width: min(100%, 440px); aspect-ratio: 1; transition: opacity .45s ease; }
#morph rect { transition: fill .4s; }
#morph.dimmed { opacity: .18; }
/* "Pay securely" step (22 Sep 2026, owner: "securely animation can be
   like animation on the lock screen") — a padlock that snaps shut and
   pulses a ring outward, like a phone unlock/secure moment. Shown only
   on the "Pay securely" step (home.js's setStep toggles .on), stacked
   over #morph in the same centred grid cell. */
.story-secure {
  position: absolute; inset: 0; margin: auto; width: min(46%, 200px); aspect-ratio: 1;
  opacity: 0; pointer-events: none; transition: opacity .45s ease;
}
.story-secure.on { opacity: 1; }
.ss-ring { fill: none; stroke: var(--green); stroke-width: 2; opacity: 0; transform-origin: 100px 100px; }
.story-secure.on .ss-ring { animation: ssPulse 2.4s ease-out infinite; }
.story-secure.on .ss-ring2 { animation-delay: 1.2s; }
@keyframes ssPulse { 0% { opacity: .55; transform: scale(.7); } 100% { opacity: 0; transform: scale(1.18); } }
.ss-body { fill: var(--navy); }
.ss-shackle { fill: none; stroke: var(--navy); stroke-width: 10; stroke-linecap: round; transform-origin: 100px 96px; }
.ss-dot, .ss-slot { fill: var(--gold-line); }
.story-secure.on .ss-shackle { animation: ssShackle 2.4s ease-in-out infinite; }
@keyframes ssShackle {
  0% { transform: translateY(-9px) rotate(-5deg); }
  20%, 100% { transform: translateY(0) rotate(0deg); }
}
.story-secure.on .ss-body { animation: ssBodyGlow 2.4s ease-in-out infinite; }
@keyframes ssBodyGlow {
  0%, 16% { filter: none; }
  24% { filter: drop-shadow(0 0 7px rgba(46, 125, 79, .75)); }
  42%, 100% { filter: none; }
}
@media (prefers-reduced-motion: reduce) {
  .story-secure.on .ss-ring { animation: none; opacity: .3; transform: scale(1); }
  .story-secure.on .ss-shackle { animation: none; transform: none; }
  .story-secure.on .ss-body { animation: none; }
}
@media (max-width: 860px) {
  .story-grid { grid-template-columns: 1fr; gap: 10px; }
  .story-art { order: -1; }
  #morph { width: min(70vw, 300px); }
  .story-step p { font-size: 1rem; }
  /* Fallback without JS: all three steps stacked. home.js adds .pinned and the
     section becomes scroll-driven on phones too (16 Sep 2026). */
  .story-steps { min-height: 0; }
  .story-step { position: static; opacity: 1; transform: none; pointer-events: auto; margin-top: 22px; }
  .story-nav { display: none; }
  /* 240vh, not 300vh: the grid unsticks once its bottom meets the section's,
     so any height beyond what the three steps need becomes a stretch of
     blank section scrolling past with nothing in it. */
  /* 400vh, not 240vh (21 Sep 2026): the stepper grew from 3 steps to 5 (the
     buyer's real four steps plus the tree payoff), so the pinned scroll
     distance grows in proportion — same distance per step as before. */
  .story.pinned { height: 400vh; height: 400svh; padding: 0; }
  /* center, not start: the grid is a full sticky screen tall, and packing
     its content to the top left ~320px of dead white below the arrows on a
     phone, which read as a broken section. */
  .story.pinned .story-grid { position: sticky; top: 52px; height: calc(100vh - 52px); height: calc(100svh - 52px); box-sizing: border-box; align-content: center; padding-top: 12px; padding-bottom: 96px; overflow: hidden; }
  .story.pinned #morph { width: min(52vw, 240px); }
  .story.pinned .story-steps { position: relative; min-height: 170px; }
  .story.pinned .story-step { position: absolute; inset: 0; opacity: 0; transform: translateX(18px); pointer-events: none; margin-top: 8px; }
  .story.pinned .story-step.on { opacity: 1; transform: none; pointer-events: auto; }
  .story.pinned .story-step h2 { font-size: clamp(1.6rem, 7vw, 2.1rem); }
  .story.pinned .story-nav { display: flex; margin-top: 12px; }
}

/* bento */
.bento { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
.bento-item { background: var(--grey); border-radius: var(--r-lg); padding: 30px 28px; display: flex; flex-direction: column; gap: 10px; min-height: 220px; }
.bento-item.span2 { grid-column: span 2; }
.bento-item h3 { font-size: 1.3rem; margin-top: auto; }
.bento-item p { color: var(--muted); }
.bento-item .ico { width: 34px; height: 34px; stroke: var(--green); fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.bento-item.gold { background: var(--gold-bg); }
.bento-item.gold p, .bento-item.gold h3 { color: var(--gold-text); }
.bento-item.gold .ico { stroke: #c9962b; }
.bento-item .btn { align-self: flex-start; margin-top: 8px; }
@media (max-width: 980px) { .bento { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .bento { grid-template-columns: 1fr; } .bento-item.span2 { grid-column: auto; } .bento-item { min-height: 0; } }

/* compare (why pixeltrees vs paid ads) — a real table, the pixeltrees
   column pulled out with the same gold treatment the old highlight card
   used, so the "why us" answer is the one your eye lands on first. */
.compare-table-wrap { margin-top: 8px; overflow-x: auto; border-radius: var(--r-lg); background: var(--panel); }
.compare-table { width: 100%; border-collapse: collapse; min-width: 560px; }
.compare-table th, .compare-table td { padding: 16px 20px; text-align: left; border-bottom: 1px solid var(--line); font-size: .95rem; }
.compare-table thead th { font-size: 1rem; font-weight: 600; color: var(--ink); border-bottom-width: 2px; }
.compare-table tbody th { color: var(--muted); font-weight: 500; font-size: .85rem; white-space: nowrap; }
.compare-table tbody tr:last-child th, .compare-table tbody tr:last-child td { border-bottom: none; }
.compare-table td, .compare-table th.highlight { color: var(--text); }
.compare-table .highlight { background: var(--gold-bg); color: var(--gold-text); font-weight: 600; }
.compare-table thead th.highlight { border-radius: 12px 12px 0 0; }
.compare-table tbody tr:last-child td.highlight { border-radius: 0 0 12px 12px; }
@media (max-width: 560px) { .compare-table th, .compare-table td { padding: 12px 14px; font-size: .85rem; } }

/* diwali */
.diwali { background: radial-gradient(90% 70% at 20% 20%, #1a1408 0%, var(--dark) 60%); }
.diwali-grid { display: grid; grid-template-columns: 320px minmax(0, 1fr); gap: 40px; align-items: center; }
@media (max-width: 860px) { .diwali-grid { grid-template-columns: 1fr; } .diya-wrap { max-width: 320px; margin: 0 auto; } }
/* The diya shrinks to make room for the live-wall card below it (24 Sep
   2026, owner: "keep the diwali green wall... [in the] diya lamp shown
   place") — the wall card is the thing to look at now; the diya stays as
   a smaller decorative flourish above it, not the full column width. */
.diya { width: 100%; max-width: 130px; display: block; margin: 0 auto 18px; }
.diwali-wall-preview[hidden] { display: none; }
.flame { transform-origin: 100px 86px; animation: flicker 1.6s ease-in-out infinite alternate; }
.flame-in { transform-origin: 100px 86px; animation: flicker 1.1s ease-in-out infinite alternate-reverse; }
.diya-glow { animation: glow 1.6s ease-in-out infinite alternate; }
@keyframes flicker { from { transform: scaleY(1) rotate(-2deg) } to { transform: scaleY(1.08) rotate(2deg) } }
@keyframes glow { from { opacity: .7 } to { opacity: 1 } }
@media (prefers-reduced-motion: reduce) { .flame, .flame-in, .diya-glow, .scroll-cue span, .marquee .track { animation: none; } .marquee .track { width: auto; flex-wrap: wrap; white-space: normal; padding: 0 22px; } }
.countdown { display: flex; gap: 14px; margin: 26px 0 28px; flex-wrap: wrap; }
.countdown div { background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.1); border-radius: var(--r); padding: 14px 18px; min-width: 84px; text-align: center; }
.countdown b { display: block; font-size: 2.2rem; letter-spacing: -0.04em; line-height: 1; font-variant-numeric: tabular-nums; color: #fff; }
.countdown span { color: #a1a1a6; font-size: .8rem; display: block; margin-top: 6px; }

/* faq */
.wrap.narrow { max-width: 760px; }
.faq details { background: var(--panel); border-radius: var(--r); padding: 0 24px; margin-top: 10px; box-shadow: var(--shadow-sm); }
.sec-grey .faq details { box-shadow: none; }
.faq summary { list-style: none; cursor: pointer; padding: 20px 0; font-weight: 600; font-size: 1.05rem; display: flex; justify-content: space-between; align-items: center; gap: 12px; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: '+'; font-size: 1.5rem; font-weight: 300; color: var(--muted); transition: transform .3s; flex: none; }
.faq details[open] summary::after { transform: rotate(45deg); }
.faq details > div { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .35s ease; }
.faq details[open] > div { grid-template-rows: 1fr; }
.faq details > div > p { overflow: hidden; color: var(--muted); margin: 0; padding-bottom: 0; }
.faq details[open] > div > p { padding-bottom: 22px; }

/* final call + mobile bar */
.cta-final h2 { font-size: clamp(2rem, 4.6vw, 3.6rem); }
.cta-final .lede { margin: 14px auto 28px; }
.mobile-bar { display: none; }
@media (max-width: 760px) {
  .mobile-bar {
    position: fixed; left: 12px; right: 12px; bottom: 12px; z-index: 40;
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    background: var(--glass); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
    border: 1px solid var(--line); border-radius: 999px; padding: 8px 8px 8px 16px; font-size: .85rem; color: var(--ink);
    box-shadow: var(--shadow); transition: transform .3s, opacity .3s;
  }
  .mobile-bar .btn { padding: 10px 16px; font-size: .9rem; flex: none; }
  .mobile-bar.away { transform: translateY(120%); opacity: 0; }
  body.has-bar footer { padding-bottom: 96px; }
}

/* trees page: journey + timeline + sites */
.journey { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; max-width: var(--wrap); margin: 0 auto; padding: 0 22px; counter-reset: j; }
.journey div { background: var(--grey); border-radius: var(--r); padding: 22px 18px; position: relative; }
.journey div::before { counter-increment: j; content: counter(j, decimal-leading-zero); color: var(--green); font-weight: 600; font-size: 1.6rem; letter-spacing: -0.04em; display: block; margin-bottom: 8px; }
.journey b { display: block; font-size: 1.05rem; }
.journey span { color: var(--muted); font-size: .92rem; display: block; margin-top: 6px; }
.journey-ico { position: absolute; top: 20px; right: 16px; font-size: 1.5rem; opacity: .8; }
/* Cascading reveal (22 Sep 2026, owner: "can we animation etc or images"
   on this section) — each card is its own .fade-up now; a per-nth-child
   delay makes them step in left to right instead of all at once. */
.journey div.fade-up { transition-delay: calc((var(--jn, 0)) * .09s); }
.journey div:nth-child(1) { --jn: 0; } .journey div:nth-child(2) { --jn: 1; }
.journey div:nth-child(3) { --jn: 2; } .journey div:nth-child(4) { --jn: 3; }
.journey div:nth-child(5) { --jn: 4; }
@media (prefers-reduced-motion: reduce) { .journey div.fade-up { transition-delay: 0s; } }
@media (max-width: 980px) { .journey { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 500px) { .journey { grid-template-columns: 1fr; } }
.sites { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
@media (max-width: 700px) { .sites { grid-template-columns: 1fr; } }
.site { border-radius: var(--r-lg); padding: 30px; color: #fff; min-height: 220px; display: flex; flex-direction: column; justify-content: flex-end; gap: 6px; }
.site.tn { background: linear-gradient(160deg, #1f5f3b, #0b2a1a); }
.site.ka { background: linear-gradient(160deg, #1E3A5F, #0b1a2e); }
.site b { font-size: 1.6rem; letter-spacing: -0.02em; }
.site span { color: rgba(255,255,255,.75); }
.timeline { list-style: none; padding: 0; margin-top: 20px; border-left: 2px solid var(--grey-2); }
.timeline li { position: relative; padding: 0 0 26px 26px; margin: 0; }
.timeline li::before { content: ''; position: absolute; left: -7px; top: 6px; width: 12px; height: 12px; border-radius: 50%; background: var(--green); box-shadow: 0 0 0 4px var(--bg); }
.timeline li.pending::before { background: var(--grey-2); }
.timeline b { display: block; }
.timeline span { color: var(--muted); font-size: .95rem; }
/* real planting-site photos only (trees.html #plantingPhotoStrip) — a
   horizontal scroll-snap strip of <img> tags, no placeholder art. */
.planting-photos { margin-top: 16px; }
.photo-strip { display: flex; gap: 10px; overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: 6px; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
.photo-strip::-webkit-scrollbar { display: none; }
.photo-strip img { flex: 0 0 260px; aspect-ratio: 4/3; object-fit: cover; border-radius: var(--r-sm); scroll-snap-align: start; }
/* Slideshow chrome (24 Sep 2026, /photo-strip.js): one photo per view,
   arrows over the edges, dots below; the native scrollbar is hidden
   because the dots and arrows now show where you are. */
.photo-strip-wrap { position: relative; }
.photo-strip-wrap .photo-strip { scroll-snap-type: x mandatory; }
.photo-strip-wrap .photo-strip img { flex: 0 0 100%; max-height: 420px; }
.photo-strip:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }
.photo-strip-arrow { position: absolute; top: 50%; transform: translateY(-50%); width: 40px; height: 40px; border-radius: 50%; border: none; background: rgba(13, 33, 22, .55); color: #fff; font-size: 26px; line-height: 1; cursor: pointer; display: grid; place-items: center; z-index: 1; }
.photo-strip-arrow.prev { left: 10px; }
.photo-strip-arrow.next { right: 10px; }
.photo-strip-arrow:hover, .photo-strip-arrow:focus-visible { background: rgba(13, 33, 22, .85); outline: none; }
.photo-strip-dots { display: flex; justify-content: center; gap: 8px; margin-top: 8px; }
.photo-strip-dot { width: 10px; height: 10px; padding: 0; border-radius: 50%; border: none; background: var(--grey-2); cursor: pointer; transition: transform .2s, background .2s; }
.photo-strip-dot.on { background: var(--green); transform: scale(1.3); }
@media (min-width: 720px) { .photo-strip-wrap .photo-strip img { flex-basis: calc(50% - 5px); } }

/* the new home has its own live numbers and countdown; the legacy strip
   stays in the DOM for hub.js but never shows on the home page */
body.home .hero-strip { display: none !important; }

/* ============================================================================
   14 SEP v2 SHARED — contest card, prize chips, consent row, nav tag, footer
   identity line, claim-card v2 pieces, teaser cards. Page-specific rules live
   in the HOME v2 / WALL v2 blocks that follow.
   ============================================================================ */
.contest-card { background: var(--gold-bg); border: 1px solid var(--gold-line); }
.contest-card .pill { display: inline-block; font-size: .72rem; font-weight: 700; padding: 4px 10px; border-radius: 999px; background: var(--gold-line); color: #3d2c00; letter-spacing: .04em; }
.prizes { list-style: none; padding: 0; margin: 12px 0 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.prizes li { background: #fff; border: 1px solid var(--gold-line); border-radius: 12px; padding: 10px 8px; font-size: .82rem; text-align: center; line-height: 1.3; }
@media (max-width: 420px) { .prizes { grid-template-columns: 1fr; } }
.panel .btn-ghost { background: #fff; color: var(--ink); border: 1px solid var(--line); }
.panel .btn-ghost:hover { background: var(--grey); border-color: var(--ink); }
.check { display: flex; gap: 10px; align-items: flex-start; margin-top: 12px; font-size: .85rem; color: var(--muted); }
.check input { width: auto; padding: 0; margin: 3px 0 0; flex: none; }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }
.tagline-echo { font-style: italic; font-size: 1.1rem; margin: 8px 0; color: var(--ink); }
#freeEntryForm, #hcForm { margin-top: 12px; }
.home .panel { box-shadow: var(--shadow-sm); }
.hero-pill { display: inline-block; margin-top: 14px; padding: 8px 14px; border-radius: 999px; background: var(--gold-bg); color: var(--gold-text); font-size: .9rem; font-weight: 600; }
.tn-tag { font-size: .7rem; font-weight: 700; padding: 3px 7px; border-radius: 999px; background: var(--gold-line); color: #3d2c00; vertical-align: middle; margin-left: 2px; }
footer .who { margin: 0 auto 12px; color: var(--ink); }
.stage-foot a.stage-hint { pointer-events: auto; color: var(--gold-text); background: var(--gold-bg); font-weight: 600; }
/* claim card v2 */
.price-card.on { border-color: var(--green); background: #f0f8f2; }
.file-tile { display: block; margin-top: 8px; padding: 16px; border: 1.5px dashed var(--line); border-radius: 14px; text-align: center; color: var(--green-deep); font-weight: 600; cursor: pointer; }
.file-tile input { display: none; }
.claim-card details { margin-top: 12px; border: 1px solid var(--grey-2); border-radius: 12px; padding: 0 12px 8px; }
.claim-card summary { padding: 12px 0; font-weight: 600; cursor: pointer; list-style: none; }
.claim-card summary::-webkit-details-marker { display: none; }
.claim-card summary::after { content: '+'; float: right; color: var(--muted); }
.claim-card details[open] summary::after { content: '−'; }
.pay-dock { margin-top: 18px; padding-top: 12px; border-top: 1px solid var(--grey-2); background: #fff; }
.pay-sum { font-weight: 600; }
.trust { text-align: center; }
/* teaser cards (planned walls) */
.pcard.teaser { cursor: default; }
.pcard.teaser:hover { transform: none; box-shadow: var(--shadow-sm); }
.teaser-art { height: 110px; border-radius: var(--r); display: grid; place-items: center; font-size: 3rem; background: linear-gradient(160deg, #e3f1e6, var(--grey-2)); }
.interest { display: flex; flex-direction: column; gap: 8px; margin-top: auto; }
.interest input[type="email"] { padding: 12px 14px; border-radius: 12px; border: 1px solid var(--line); background: var(--panel); color: var(--ink); font: inherit; }
.interest .btn-primary { padding: 12px; }
.teasers-head { margin-top: 30px; padding-left: 22px; padding-right: 22px; }
.interest-privacy { text-align: center; margin-top: 10px; padding-left: 22px; padding-right: 22px; }
.center { text-align: center; }
@media (max-width: 760px) {
  .cards.teasers { display: flex; overflow-x: auto; scroll-snap-type: x proximity; scroll-padding-left: 22px; gap: 12px; -webkit-overflow-scrolling: touch; padding-bottom: 6px; }
  .cards.teasers .pcard { flex: 0 0 82%; scroll-snap-align: start; }
}

/* ===== WALL v2 (14 Sep) ===== */
/* Page-specific bits for reveal.html; the shared v2 block above owns the
   contest card, prize chips, consent row, file tile, details and pay dock. */
.panel .hp { width: 1px; }                      /* .panel input's width:100% outranks .hp */
.claim-card .price-card.on:hover { border-color: var(--green); background: #f0f8f2; }
.contest-card h2 { margin-top: 10px; }
.contest-card #contestOpen { margin-top: 14px; }
.contest-card .wa-btn { margin-top: 12px; }
#contestDone h3 { margin-top: 12px; }
#contestDone .btn-ghost { margin-top: 12px; }
#cCount { display: block; margin-top: 6px; }
.stage-foot a.stage-contest { text-decoration: none; padding: 10px 16px; font-size: .88rem; min-height: 40px; display: inline-flex; align-items: center; }
.stage-foot a.stage-contest:hover { text-decoration: underline; }
.pay-dock .trust { margin-top: 10px; }
@media (max-width: 760px) {
  .contest-card .prizes li { font-size: .8rem; padding: 8px 6px; }
  /* the fixed mobile bar (ui.js adds body.has-bar) sits over the stage foot:
     lift the CTA, hint and contest link clear of it */
  /* ui.js hides the bar while #stage is on screen; keep a small lift as the no-JS fallback */
  body.has-bar .stage-foot { bottom: 22px; }
  .contest-card .prizes li { padding: 6px 6px; }
  .hero-inner { padding-bottom: 44px; }
}

/* ===== HOME v2 (14 Sep) ===== */
/* Hero: on phones the board frame peeks above the fold. */
@media (max-width: 760px) { .hero-live { min-height: 82vh; min-height: 82dvh; } }
/* Zero-state live numbers: the mission line replaces the four zero tiles and sits beside the ring. */
.live-grid .mission { align-self: center; margin: 0; font-size: clamp(1.05rem, 1.5vw, 1.3rem); }
.live-grid .mission a { font-weight: 600; }
@media (max-width: 860px) { .live-grid .mission { text-align: center; } }
/* Home contest card: collapsed until tapped; the done state stacks its links. */
#homeContest .prizes { margin-top: 0; }
#homeContest > .hint + .hint { margin-top: 4px; }
#hcDone { margin-top: 12px; }
#hcDone .btn-ghost { display: block; text-align: center; margin-top: 12px; }
#hcDone .wa-btn { margin-top: 12px; }
.hero-pill:hover { text-decoration: none; box-shadow: var(--shadow-sm); }

/* ===== HOME v3 (16 Sep) ===== */
/* Board peek: the live mini-view canvas sits in the same dark frame as the old illustration. */
.hero-art canvas { display: block; width: 100%; height: auto; border-radius: calc(var(--r-lg) - 10px); }
/* Prize band under the hero: gold treatment shared with the contest card and the nav tag. Single column on phones. */
.prize-band { background: var(--gold-bg); border-top: 1px solid var(--gold-line); padding: clamp(36px, 5vw, 56px) 22px; text-align: center; }
.prize-band .wrap { max-width: var(--measure); margin: 0 auto; }
.prize-band .eyebrow { color: var(--gold-text); }
/* changes-16sep2026.md item 2: launch/Diwali badge above the contest eyebrow. */
.prize-badge { display: inline-block; padding: 4px 14px; margin-bottom: 10px; border-radius: 999px; background: var(--gold-text); color: var(--gold-bg); font-size: .8rem; font-weight: 700; letter-spacing: .01em; }
.prize-band h2 { margin-top: 10px; color: var(--gold-text); font-size: clamp(1.35rem, 2.8vw, 2.3rem); line-height: 1.35; overflow-wrap: anywhere; }
.prize-chip { display: inline-block; vertical-align: baseline; padding: 2px 12px; margin: 3px 0; border-radius: 999px; background: var(--panel); border: 1px solid var(--gold-line); color: var(--ink); font-size: .78em; font-weight: 600; letter-spacing: -0.01em; white-space: normal; }
.prize-line { color: var(--gold-text); font-size: .95rem; margin-top: 14px; }
.prize-band .btn-primary { margin-top: 20px; }
@media (max-width: 560px) { .prize-band .btn-primary { display: block; } .prize-line { font-size: .9rem; } }
/* Scroll-driven how-it-works (16 Sep 2026): desktop pins the grid while the
   visitor scrolls through a 300vh section; home.js adds .pinned above 860px. */
@media (min-width: 861px) {
  /* 400vh, not 240vh (21 Sep 2026): the stepper grew from 3 steps to 5 (the
     buyer's real four steps plus the tree payoff), so the pinned scroll
     distance grows in proportion — same distance per step as before. */
  .story.pinned { height: 400vh; height: 400svh; padding: 0; }
  .story.pinned .story-grid { position: sticky; top: 52px; height: calc(100vh - 52px); align-content: center; box-sizing: border-box; padding-top: 0; padding-bottom: 0; }
}

/* Board zero-state: the centred panel (16 Sep 2026) was replaced on 21 Sep
   2026 by the one-line .stage-empty-line under the toolbar — see the end
   of this file. */

/* Find my blocks (Release 6 D, 16 Sep 2026): the receipt link zooms the board to the
   buyer's rect and rings it (reveal.js draws the ring on the canvas); this toast names
   it for 5 s. Centred below the stage head, clear of the zoom tools. */
.stage-toast {
  position: absolute; left: 50%; top: 72px; z-index: 6; transform: translate(-50%, -6px);
  max-width: min(360px, calc(100% - 32px)); padding: 9px 16px; border-radius: 999px;
  background: color-mix(in srgb, var(--bg) 92%, transparent); border: 1px solid var(--green);
  color: var(--ink); font-size: .9rem; font-weight: 600; text-align: center; box-shadow: var(--shadow-sm, var(--shadow));
  pointer-events: none; opacity: 0; transition: opacity .35s, transform .35s;
}
.stage-toast.show { opacity: 1; transform: translate(-50%, 0); }
body.art-full .stage-toast { top: 14px; }
@media (prefers-reduced-motion: reduce) { .stage-toast { transition: none; } }

/* Home hero: "Find my blocks →" for a logged-in buyer (ui.js unhides it). */
.hero-note-link { display: inline-block; margin-top: 10px; color: var(--green-deep); font-size: .95rem; font-weight: 600; }
.hero-note-link:hover { text-decoration: underline; }
.hero-note-link + .hero-pill { margin-left: 12px; }
@media (max-width: 760px) { .hero-note-link + .hero-pill { margin-left: 0; display: inline-block; } }

/* Account: one card per order, readable at 390px. */
.order-list { list-style: none; padding: 0; margin: 0; }
/* Grid, not flex (fixed 20 Sep 2026). account.js appends the paid-edit
   panel to the CARD, and under `display:flex` that panel became a third
   flex column: at 390px and at 1200px it printed on top of the wall name,
   the status chip, the block count, the coordinates and the price of the
   very order it was editing. Two fixed tracks plus a full-width row for
   anything appended keeps the panel BELOW the order's details at every
   width, with no change to account.js. */
.order-card { padding: 14px 0; border-top: 1px solid var(--grey-2); display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 14px; align-items: start; }
.order-card > .edit-panel { grid-column: 1 / -1; }
.order-card:first-child { border-top: 0; padding-top: 4px; }
.order-thumb { flex: none; width: 64px; height: 64px; border-radius: 10px; object-fit: cover; background: var(--grey); }
.order-thumb.placeholder { display: grid; place-items: center; font-size: 1.5rem; }
.order-body { flex: 1; min-width: 0; }
.order-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.order-wall { font-weight: 600; color: var(--ink); }
.order-meta { margin: 6px 0 0; font-size: .85rem; color: var(--muted); line-height: 1.45; }
.order-rect { color: var(--muted); font-variant-numeric: tabular-nums; }
.order-actions { display: flex; flex-wrap: wrap; gap: 8px 18px; margin: 10px 0 0; font-size: .9rem; }
.order-actions a { color: var(--green-deep); font-weight: 600; }
.order-actions a:hover { text-decoration: underline; }

/* ===== WALL PAGE, 20 Sep 2026 (evening) =====
   Reveal-mode ghost caption, "just looking" view mode, the claim-card
   progress strip, the short contest card, the offer pill, the bonus line
   and the post-purchase downloads. Appended, not merged: every rule above
   this line is untouched. Colours are the tokens the page already uses, so
   the dark toggle needs nothing extra. */

.stage-foot .reveal-cap { color: var(--ink); background: color-mix(in srgb, var(--panel) 90%, transparent); border: 1px solid var(--line); font-weight: 600; max-width: calc(100% - 28px); text-align: center; }

/* Stage foot: the CTA and the "View the wall" ghost button share a row. */
.stage-cta-row { display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap; pointer-events: none; }
.stage-cta-row .stage-cta { margin: 0; }
.stage-foot .stage-view { pointer-events: auto; padding: 12px 18px; font-size: .92rem; font-weight: 600; background: color-mix(in srgb, var(--panel) 88%, transparent); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid var(--line); color: var(--ink); box-shadow: var(--shadow-sm); }
.stage-foot .stage-view:hover { background: var(--panel); }
body.view-mode .stage-view { display: none; }   /* already looking */

/* "Just looking" chip under the chip row, above the empty-state box. */
.view-chip { position: absolute; left: 50%; top: 62px; z-index: 6; transform: translateX(-50%); width: min(560px, calc(100% - 28px)); display: flex; align-items: flex-start; gap: 8px; padding: 9px 10px 9px 14px; border-radius: 16px; background: color-mix(in srgb, var(--panel) 92%, transparent); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid var(--line); box-shadow: var(--shadow-sm); color: var(--ink); font-size: .85rem; line-height: 1.4; }
.view-chip a { color: var(--green-deep); font-weight: 600; white-space: nowrap; }
.view-chip .view-chip-close { flex: none; width: 28px; height: 28px; margin: -2px -2px 0 0; padding: 0; border: none; background: none; color: var(--muted); font-size: 1.3rem; line-height: 1; border-radius: 50%; }
.view-chip .view-chip-close:hover { color: var(--ink); background: var(--grey-2); }
html[data-theme="dark"] .view-chip a { color: var(--green); }
@media (max-width: 760px) { .view-chip { top: 58px; font-size: .82rem; } }
/* View mode on a phone: the buy bar waits until the board has scrolled off
   the top (reveal.js sets body.past-stage). Nothing is removed — once the
   visitor is below the board, the bar behaves exactly as before. */
body.view-mode:not(.past-stage) .mobile-bar { display: none; }

/* Claim card: the three-step strip at the top of #buyPanel. */
.claim-steps { list-style: none; margin: 0 0 6px; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
.claim-steps li { display: flex; align-items: center; gap: 8px; min-width: 0; padding: 8px 10px; border-radius: 12px; border: 1px solid var(--grey-2); background: var(--grey); color: var(--muted); font-size: .82rem; font-weight: 600; line-height: 1.25; cursor: pointer; }
.claim-steps li:hover { border-color: var(--green); }
.claim-steps li:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }
/* Tab navigation (22 Sep 2026): each numbered step is now a tab —
   #buyForm's step-content and .pay-dock carry a matching data-panel,
   shown or hidden by reveal.js's showTab(). A "Continue →" button ends
   each panel for buyers who never notice the tabs are clickable. */
.step-continue { margin-top: 16px; width: 100%; }
.claim-steps .cs-n { display: inline-grid; place-items: center; flex: none; width: 22px; height: 22px; border-radius: 50%; background: var(--line); color: var(--ink); font-size: .74rem; font-weight: 700; }
.claim-steps .cs-t { min-width: 0; }
.claim-steps li[aria-current="step"] { border-color: var(--green); background: #f0f8f2; color: var(--ink); }
.claim-steps li[aria-current="step"] .cs-n { background: var(--green); color: #fff; }
.claim-steps li.done { color: var(--green-deep); }
.claim-steps li.done .cs-n { background: var(--green-deep); color: #fff; font-size: 0; }
.claim-steps li.done .cs-n::before { content: '✓'; font-size: .74rem; }
.claim-card .claim-steps-note { margin: 0 0 14px; font-size: .8rem; }
@media (max-width: 420px) {
  .claim-steps { gap: 4px; }
  .claim-steps li { flex-direction: column; align-items: flex-start; gap: 4px; padding: 7px 8px; font-size: .74rem; }
}
/* The "required" marker beside a mandatory field's label. */
.shape-nudge { color: var(--red, #b3261e); font-weight: 600; }
.claim-card .req { display: inline-block; vertical-align: middle; margin-left: 6px; padding: 1px 7px; border-radius: 999px; background: var(--gold-bg); color: var(--gold-text); font-size: .66rem; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; }

/* Introductory offer pill above the pack chooser (API-driven). */
.claim-card .offer-pill { display: inline-block; margin: 12px 0 0; padding: 7px 12px; border-radius: 999px; background: var(--gold-bg); border: 1px solid var(--gold-line); color: var(--gold-text); font-size: .82rem; font-weight: 700; line-height: 1.35; }
/* "+N free · M blocks in all" beside the total in the pay dock. */
.pay-dock .bonus-line { margin: 2px 0 0; color: var(--green-deep); font-size: .9rem; font-weight: 600; }
/* Coupon code box (24 Sep 2026) — one input + Apply, sits above the total. */
.coupon-row { display: flex; gap: 8px; margin: 0 0 10px; }
.coupon-row input { flex: 1; min-width: 0; padding: 10px 12px; border: 1px solid var(--grey-2); border-radius: 10px; font: inherit; text-transform: uppercase; letter-spacing: .04em; background: #fff; color: var(--ink); }
.coupon-row input:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }
.coupon-row .btn-ghost { padding: 10px 16px; white-space: nowrap; }
.coupon-msg { margin: -4px 0 10px; font-size: .88rem; color: var(--green-deep); font-weight: 600; }
.coupon-msg.bad { color: var(--red, #b3261e); font-weight: 500; }
html[data-theme="dark"] .coupon-row input { background: var(--panel, #10201a); color: var(--text, #eee); border-color: var(--grey-2); }
html[data-theme="dark"] .coupon-msg { color: var(--green); }
html[data-theme="dark"] .pay-dock .bonus-line { color: var(--green); }
/* The quiet contest line under the pay button. */
.pay-dock .contest-nudge { text-align: center; margin-top: 8px; }
.pay-dock .contest-nudge a { font-weight: 600; }

/* Short contest card: the prize line replaces the three chips. */
.contest-short .prize-line-wall { margin: 10px 0 0; font-size: .92rem; font-weight: 600; color: var(--ink); line-height: 1.5; }
.contest-short .hint + .hint { margin-top: 4px; }
.contest-card .contest-enter { display: block; text-align: center; margin-top: 14px; padding: 14px; }
.contest-card .contest-enter:hover { text-decoration: none; }

/* Post-purchase: the download row sits under the share row. */
.success-panel .share-downloads { margin-top: 8px; }
.success-panel .celebrate:empty { display: none; }
.success-panel .celebrate { margin: 0 0 8px; }

/* 20 Sep 2026 — free digital pack (badge/GIF/certificate/QR card) and the
   purchase-celebration diya animation. See app/public/sharecard.js and
   app/public/celebrate.js. */
.celebrate-box { min-height: 0; display: flex; justify-content: center; }
.celebrate-box:not([hidden]) { margin-top: 18px; }
.promo-pack-strip { margin-top: 18px; font-size: .85rem; color: var(--muted); text-align: center; }
.sharebtns button[disabled], .btn-ghost[disabled] { opacity: .6; cursor: default; }

/* ============================================================================
   21 SEP 2026 — home page: claim chooser, view button, 3-steps strip, Diwali
   intro offer, digital-pack tile, contest teaser, what's-new chip.
   ============================================================================ */

/* The plain commercial line under the tagline (owner's own wording, WhatsApp
   group, 21 Sep 2026): affordability and 12-month tenure against per-click
   ad platforms. The price comes from the payload (home.js). */
.hero-commercial { color: var(--ink); font-weight: 600; font-size: 1.05rem; margin-top: 14px; }

/* Diwali introductory offer pill. Three states: static (no `offer` in the
   payload yet), live (a real remaining count) and ended (quiet, no count) —
   see offerState in home-chooser.js. */
.offer-pill {
  display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap;
  margin-top: 16px; padding: 10px 18px; border-radius: 999px;
  background: var(--gold-bg); border: 1px solid var(--gold-line); color: var(--gold-text);
  font-size: .92rem; font-weight: 600;
}
.offer-pill .offer-emoji { font-size: 1.05em; }
.offer-pill .offer-count { padding: 2px 10px; border-radius: 999px; background: var(--panel); font-size: .82em; font-weight: 700; }
.offer-pill.ended { background: var(--grey); border-color: var(--line); color: var(--muted); font-weight: 500; }

/* The 3-steps strip, directly under the hero CTAs, visible on the first
   screen. */
.steps-strip { margin-top: 26px; }
.steps-strip ol { display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap; list-style: none; padding: 0; margin: 0; }
.steps-strip li { display: flex; align-items: center; gap: 8px; font-size: .92rem; color: var(--muted); font-weight: 500; }
.steps-strip li:not(:last-child)::after { content: '→'; margin-left: 8px; color: var(--line); }
.steps-strip .n { display: inline-grid; place-items: center; width: 20px; height: 20px; border-radius: 50%; background: var(--ink); color: var(--bg); font-size: .72rem; font-weight: 700; flex: none; }
.steps-strip > a { display: block; text-align: center; margin-top: 8px; font-size: .85rem; color: var(--muted); }
@media (max-width: 500px) { .steps-strip li:not(:last-child)::after { margin-left: 4px; } }

.hero-chips { display: flex; align-items: center; justify-content: center; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
.hero-chips .hero-pill { margin-top: 0; }
.new-chip { display: inline-block; padding: 8px 14px; border-radius: 999px; background: var(--grey-2); color: var(--ink); font-size: .9rem; font-weight: 600; }
.new-chip:hover { background: var(--line); text-decoration: none; }

/* Contest teaser card: replaces the inline free-entry form (moved to
   /contest). Reuses .contest-card's gold treatment. */
.contest-teaser .teaser-free { font-weight: 600; color: var(--gold-text); margin: 10px 0 0; }
.contest-teaser .btn { margin-top: 14px; width: auto; }

/* The claim chooser: a dropdown panel on desktop (positioned under whichever
   trigger opened it, via --cx/--cy custom properties), a bottom sheet on
   mobile. role="listbox" + role="option" rows; Escape closes and returns
   focus to the trigger; ArrowUp/ArrowDown move the active option. */
/* Locks the page behind the chooser (same pattern as body.art-full in
   reveal.js): without it the page can still scroll under the fixed
   backdrop/panel while it's open, which on trackpads and phones reads as
   the panel "blocking" scroll — the background moves oddly while the
   fixed panel stays put, or the panel's own position drifts out of sync
   with the trigger it opened under. home.js toggles this class. */
body.chooser-open { overflow: hidden; }
.chooser-backdrop { position: fixed; inset: 0; z-index: 70; background: rgba(10, 14, 12, .35); }
.chooser-panel {
  position: fixed; z-index: 71; left: var(--cx, 50%); top: var(--cy, 50%);
  width: min(360px, calc(100vw - 32px)); max-height: min(480px, calc(100vh - 32px));
  background: var(--panel); color: var(--ink); border-radius: var(--r-lg);
  box-shadow: var(--shadow); border: 1px solid var(--line);
  display: flex; flex-direction: column; overflow: hidden;
}
.chooser-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 14px 16px 10px; border-bottom: 1px solid var(--grey-2); flex: none; }
.chooser-title { font-weight: 700; font-size: 1.02rem; margin: 0; }
.chooser-close { width: 30px; height: 30px; padding: 0; border-radius: 50%; border: none; background: var(--grey); color: var(--muted); font-size: .9rem; }
.chooser-close:hover { background: var(--grey-2); border-color: transparent; }
.chooser-list { list-style: none; margin: 0; padding: 8px; overflow-y: auto; }
.chooser-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; text-align: left; padding: 12px 12px; border-radius: 12px; background: none; border: none;
  color: var(--ink); font: inherit; cursor: pointer;
}
.chooser-row:hover, .chooser-row:focus-visible, li[role="option"].active .chooser-row { background: var(--grey); outline: none; }
.chooser-row-text { min-width: 0; }
.chooser-row-label { display: block; font-weight: 600; font-size: .96rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chooser-row-detail { display: block; color: var(--muted); font-size: .8rem; margin-top: 2px; }
.chooser-view { flex: none; font-size: .78rem; font-weight: 600; color: var(--green-deep); padding: 4px 8px; border-radius: 999px; }
.chooser-view:hover { background: var(--grey-2); text-decoration: none; }
.chooser-sep { border-top: 1px solid var(--grey-2); margin: 6px 4px; }
.chooser-everyone .chooser-row-label { color: var(--green-deep); }
.chooser-empty { padding: 14px 12px; color: var(--muted); font-size: .9rem; }
.chooser-empty a { font-weight: 600; }

/* Image lightbox (24 Sep 2026): a slide photo at full size, tap/click
   anywhere or Esc to close. Deliberately simple — no pinch-zoom library,
   just a big readable image, since the ask was "zoom to read clear
   content", not a full image-viewer toolset. */
.img-lightbox {
  position: fixed; inset: 0; z-index: 9000; background: rgba(6, 10, 8, .92);
  display: flex; align-items: center; justify-content: center; padding: 40px 16px;
  cursor: zoom-out;
}
.img-lightbox[hidden] { display: none; }
.img-lightbox img {
  max-width: 100%; max-height: 100%; border-radius: 10px; cursor: default;
  box-shadow: 0 20px 60px rgba(0, 0, 0, .5);
}
.img-lightbox-close {
  position: absolute; top: 16px; right: 16px; width: 40px; height: 40px;
  border-radius: 50%; border: none; background: rgba(255, 255, 255, .12);
  color: #fff; font-size: 1.1rem; cursor: pointer;
}
.img-lightbox-close:hover { background: rgba(255, 255, 255, .22); }
@media (max-width: 760px) {
  .chooser-panel {
    left: 0; right: 0; bottom: 0; top: auto; width: 100%; max-width: none;
    max-height: min(72vh, calc(100vh - 24px)); border-radius: 20px 20px 0 0;
  }
}

/* Board stage cards (21 Sep 2026): a small "view" affordance travels with
   the "See this wall →" link — no extra markup, home.js just appends
   ?view=1 to every card's href. */

/* 21 Sep 2026: preview-your-spot in the chooser. One quiet line under a
   category row's existing detail line — the same row, not a new panel —
   filled in by home.js once the server's own placement preview answers.
   Hidden until then, and silently stays hidden on any failure. */
.chooser-row-preview { display: block; color: var(--green-deep); font-size: .78rem; margin-top: 2px; font-style: italic; }

/* ===== WALL PAGE, 21 Sep 2026: reveal-mode curiosity pass =====
   The ghost (drawRevealArtwork) itself now sharpens by opacity value, not
   by a class, so it needs no rule here. What DOES: the "just uncovered"
   filmstrip under #revealCap, its fade-in on a new crop, and the milestone
   caption pulse. Nothing above this block is touched. */

/* Filmstrip: a thin row of tiny artwork crops, sitting directly under the
   reveal caption — never a floating panel of its own. */
.reveal-filmstrip {
  display: flex; justify-content: center; gap: 6px; margin: 6px 0 0;
  flex-wrap: wrap;
}
.reveal-filmstrip .reveal-crop {
  width: 40px; height: 40px; border-radius: 8px; flex: none;
  border: 1px solid var(--line); box-shadow: var(--shadow-sm);
  background: var(--panel);
}
.reveal-filmstrip .reveal-crop-new { animation: pt-crop-in .5s ease both; }
@keyframes pt-crop-in {
  from { opacity: 0; transform: scale(.82); }
  to { opacity: 1; transform: scale(1); }
}

/* Milestone moment: a brief, tasteful pulse on the caption itself — no new
   modal, no new banner. Plays once per crossing (reveal.js). */
.stage-foot .reveal-cap.milestone-pulse { animation: pt-milestone-pulse 1.5s ease; }
@keyframes pt-milestone-pulse {
  0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(46,125,79,.35); }
  30% { transform: scale(1.045); box-shadow: 0 0 0 8px rgba(46,125,79,0); }
  100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(46,125,79,0); }
}
/* Milestone burst (22 Sep 2026, owner: "milestone bursts get a real
   visual moment") — a real particle moment on top of the caption pulse
   above, same technique as the Lucky Brand "found it" burst
   (lucky-brand.js's .pt-lucky-burst/-spark), reimplemented here rather
   than shared since that file is feature-flagged off by default and
   this must not depend on it. Centred on the stage, not the viewport —
   this is a moment about THIS wall, not the whole page. Now fires in
   either view (ad view or reveal-artwork), not reveal-artwork only —
   see playMilestoneBurst() in reveal.js. */
.milestone-burst { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 8; }
.milestone-spark { position: absolute; top: 50%; left: 50%; width: 8px; height: 8px; margin: -4px 0 0 -4px;
  border-radius: 50%; opacity: 0; animation: pt-milestone-spark .8s ease-out forwards; }
@keyframes pt-milestone-spark {
  0% { opacity: 0; transform: translate(0, 0) scale(.3); }
  14% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--mx), var(--my)) scale(1); }
}
@media (prefers-reduced-motion: reduce) { .milestone-burst { display: none; } }

/* Ambient shimmer on a near-empty board (22 Sep 2026, owner: "ambient
   motion on a near-empty board") — a few large, soft, slow-drifting
   blurred dots over the canvas, so a wall with "0.157% claimed" reads as
   quietly alive instead of a flat grey grid while it waits for its first
   claims. Same technique as the home page's impact-card haze. Hidden
   entirely once the board stops reading as empty (reveal.js). */
.stage-ambient { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 1; }
.stage-ambient-dot { position: absolute; border-radius: 50%; filter: blur(30px);
  background: radial-gradient(circle, rgba(46,125,79,.16), rgba(46,125,79,0) 70%);
  animation: stageAmbientDrift 22s ease-in-out infinite; }
.stage-ambient-dot.a1 { width: 220px; height: 220px; top: 12%; left: 10%; animation-duration: 26s; }
.stage-ambient-dot.a2 { width: 260px; height: 260px; top: 55%; left: 65%; animation-duration: 30s; animation-delay: -6s; }
.stage-ambient-dot.a3 { width: 180px; height: 180px; top: 70%; left: 20%; animation-duration: 24s; animation-delay: -14s; }
.stage-ambient-dot.a4 { width: 200px; height: 200px; top: 20%; left: 75%; animation-duration: 28s; animation-delay: -20s; }
@keyframes stageAmbientDrift {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(30px, -24px) scale(1.15); }
}
@media (prefers-reduced-motion: reduce) { .stage-ambient { display: none; } }

/* Green scoreboard's one-line top-supporter summary (renderSupporters). */
.leaderboard-top { margin-top: 8px; text-align: center; }

@media (prefers-reduced-motion: reduce) {
  .reveal-filmstrip .reveal-crop-new { animation: none; }
  .stage-foot .reveal-cap.milestone-pulse { animation: none; }
}

/* ===== WALL PAGE, 21 Sep 2026 (evening): the owner's phone review =====
   Six fixes from screenshots at 390px. Nothing above this block is touched
   except the .minimap-wrap phone rule and the removed zero-state panel. */

/* 1. "Show me" on the claim card rendered as a green pill with invisible
   text. Root cause: `.panel button:hover` (0,2,1, further down the file)
   ties with `.linklike, .panel button.linklike` (0,2,1, near the top) and
   wins on :hover — and iOS keeps :hover on a button after a tap. So the
   tapped link filled green-deep with green-deep text, in the 999px radius
   every button carries. This rule outranks both, in every state. */
.claim-card button.linklike, .claim-card button.linklike:hover, .claim-card button.linklike:active, .claim-card button.linklike:focus-visible {
  display: inline; width: auto; margin: 0; padding: 0; border: none; border-radius: 0; box-shadow: none;
  background: none; color: var(--green-deep); font-size: inherit; font-weight: 600; text-decoration: underline;
}
html[data-theme="dark"] .claim-card button.linklike { color: var(--green); }

/* 3. The empty-board note: one quiet line under the toolbar in the stage
   toast's own styling (.stage-toast), never a centred panel. Kept to the
   left of the zoom column on every width so the × and the + never share a
   pixel; hidden while the "just looking" chip holds the same slot. */
.stage-empty-line {
  left: 14px; right: 62px; transform: none; max-width: none; width: auto; top: 58px;
  display: flex; align-items: center; gap: 8px; text-align: left; pointer-events: auto;
  padding: 6px 6px 6px 12px; font-size: .78rem; font-weight: 500; border-color: var(--line); line-height: 1.35; color: #3a423d;
}
html[data-theme="dark"] .stage-empty-line { color: #d5dbd7; }
html[data-theme="dark"] .live-strip .ls-fig { color: #b9c4bd; }
.stage-empty-line.show { transform: none; }
.stage-empty-line > span { flex: 1; min-width: 0; }
.stage-empty-line .view-chip-close {
  flex: none; pointer-events: auto; width: 24px; height: 24px; margin: 0; padding: 0; border: none; border-radius: 50%;
  background: none; color: var(--muted); font-size: 1.15rem; line-height: 1;
}
.stage-empty-line .view-chip-close:hover { color: var(--ink); background: var(--grey-2); }
body.view-mode .stage-empty-line { display: none; }
@media (min-width: 761px) { .stage-empty-line, .stage-empty-line.show { left: 50%; right: auto; transform: translateX(-50%); max-width: min(560px, calc(100% - 140px)); top: 62px; } }
body.art-full .stage-empty-line { display: none; }

/* 5. The chip row: a gentle snap so a flick lands on a whole chip, not
   half of one. Proximity, not mandatory — a slow drag still stops anywhere. */
.stage-jump { scroll-snap-type: x proximity; scroll-padding-left: 14px; }
.stage-jump > * { scroll-snap-align: start; }

/* 2. "Show me" scrolls #stage to the top of the screen; the sticky nav is
   52px, so the stage lands under it rather than beneath it. */
.stage { scroll-margin-top: 52px; }

/* ===== 21 Sep 2026: nothing hides until it has data (the owner) =====
   The activity feed, the spotlight, the last-hour counter and the mission
   line stay on the page in a quiet empty state. Each one keeps its own
   container; these rules only dress the empty state. */
.recent-claims.hot::before { content: '🔥 '; }
.activity-empty { padding: 12px 16px; border-radius: 10px; background: var(--grey); color: var(--muted); }
.spotlight-img-empty { background: transparent; border: 1.5px dashed var(--line); }
/* The mission line now sits under the tiles, full width, instead of
   replacing them. */
.live-grid .mission { grid-column: 1 / -1; }

/* Your badge, before you pay (21 Sep 2026, growth plan lever 5). A second
   thumbnail row under the live block preview, inside the same .preview-row:
   the name field, then the badge (a 1080px canvas shown at 160px — the
   real drawBadge() output, not a mock) beside its label and the pack line.
   Before a name is typed the slot is a dashed medal outline with one line
   of copy, the same size, so nothing jumps when the badge appears. */
.preview-row { flex-wrap: wrap; }
.preview-row > .preview-note { flex: 1 1 0; min-width: 0; }   /* stays beside the block now the row wraps */
.badge-preview { flex: 1 1 100%; min-width: 0; }
.badge-preview .label { margin-top: 8px; }
.badge-row { display: flex; gap: 14px; align-items: center; margin-top: 12px; }
.badge-slot { position: relative; flex: none; width: 160px; height: 160px; }
.badge-slot canvas { display: block; width: 160px; height: 160px; }
.badge-empty { position: absolute; inset: 0; display: grid; place-items: center; margin: 0; padding: 22px; text-align: center; border: 1.5px dashed var(--line); border-radius: 50%; color: var(--muted); font-size: .78rem; line-height: 1.35; }
.badge-slot.on .badge-empty { display: none; }
.badge-text { min-width: 0; }
.badge-label { margin: 0; font-size: .9rem; font-weight: 600; color: var(--ink); line-height: 1.35; }
.badge-pack { margin-top: 4px; }

/* Business bundles (21 Sep 2026): a second row of the same pack chips inside
   the claim card's own <details> style. Open on desktop, closed on a phone
   (renderBundles sets `open` from a 761px match). */
.claim-card details.bundles { margin: 0 0 4px; padding: 0; border: 0; }
.claim-card details.bundles summary { position: relative; padding: 11px 36px 11px 14px; border: 1px solid var(--grey-2); border-radius: 12px; }
.claim-card details.bundles summary::after { float: none; position: absolute; right: 14px; top: 50%; transform: translateY(-50%); }
.bundles-sub { display: block; margin-top: 2px; font-size: .8rem; font-weight: 400; color: var(--muted); }
.packs.packs-bundles { margin: 10px 0 8px; }
.packs-bundles .pc-name { white-space: nowrap; }
.packs-bundles .pc-price { font-size: 1.25rem; }
.price-card .pc-use { color: var(--ink); margin-top: 2px; }

/* Lucky Brand of the Day (21 Sep 2026, public/lucky-brand.js). Feature-
   flagged off by default (see docs/business/lucky-brand-of-the-day.md) —
   this block only ever appears once window.PT_luckyBrand.checkTap() finds
   a match. Reuses the site's own green/gold tokens, no new colours. */
.pt-lucky-burst { position: fixed; inset: 0; pointer-events: none; z-index: 9998; overflow: hidden; }
.pt-lucky-spark { position: absolute; top: 0; left: 0; width: 7px; height: 7px; border-radius: 50%; opacity: 0; background: radial-gradient(circle, var(--gold-line), var(--green) 75%); animation: pt-lucky-pop .6s ease-out forwards; }
@keyframes pt-lucky-pop {
  0% { opacity: 0; transform: translate(0, 0) scale(.3); }
  12% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--lx), var(--ly)) scale(1); }
}
.pt-lucky-toast { position: fixed; left: 50%; bottom: 28px; transform: translate(-50%, 12px); z-index: 9999; max-width: 92vw; width: 360px; padding: 16px 18px; border-radius: var(--r); background: var(--panel); border: 1px solid var(--gold-line); box-shadow: var(--shadow); text-align: center; opacity: 0; transition: opacity .25s ease, transform .25s ease; }
.pt-lucky-toast.on { opacity: 1; transform: translate(-50%, 0); }
.pt-lucky-toast .pt-lucky-tree { display: block; margin: 0 auto 8px; }
.pt-lucky-toast .pt-lucky-msg { margin: 0; font-size: .95rem; font-weight: 600; color: var(--ink); line-height: 1.4; }
.pt-lucky-toast .pt-lucky-sub { margin: 4px 0 0; font-size: .82rem; font-weight: 400; color: var(--muted); }
.pt-lucky-trunk { stroke: #6b4a2b; stroke-width: 3; stroke-linecap: round; fill: none; stroke-dasharray: 26; stroke-dashoffset: 26; animation: pt-lucky-grow .5s ease-out forwards; }
.pt-lucky-leaf { fill: var(--green); opacity: 0; transform-origin: center; animation: pt-lucky-bloom .5s ease-out forwards; }
@keyframes pt-lucky-grow { to { stroke-dashoffset: 0; } }
@keyframes pt-lucky-bloom { 0% { opacity: 0; transform: scale(.2); } 100% { opacity: 1; transform: scale(1); } }
@media (prefers-reduced-motion: reduce) {
  .pt-lucky-burst { display: none; }
  .pt-lucky-trunk, .pt-lucky-leaf { animation: none; stroke-dashoffset: 0; opacity: 1; transform: none; }
}

/* "This info should blink... to gain user attention" (owner, 22 Sep 2026):
   a small pill INSIDE the sticky top nav (normal flow, never floated over
   the canvas — the wall page just had a real bug from overlays sitting on
   top of the board, so this deliberately does not repeat that pattern),
   shown only while GET /api/lucky-brand says the game is active today
   (lucky-brand.js) — inert and unrendered the rest of the time, same as
   every other Lucky Brand UI in this file. */
/* flex-shrink: 0 (24 Sep 2026) — same overlap bug as .tn-brand: without
   it, this pill shrank under the same pressure that made .tn-links
   scroll, and with no overflow handling of its own its text spilled past
   its own edge instead of staying readable. */
.pt-lucky-live { display: flex; align-items: center; gap: 6px; padding: 5px 10px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--gold-line); box-shadow: var(--shadow-sm);
  font-size: .78rem; font-weight: 600; color: var(--ink); white-space: nowrap; margin-left: 8px;
  text-decoration: none; flex-shrink: 0; }
.pt-lucky-live-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--green); flex: none;
  animation: pt-lucky-blink 1.1s ease-in-out infinite; }
@keyframes pt-lucky-blink { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) { .pt-lucky-live-dot { animation: none; } }
@media (max-width: 760px) { .pt-lucky-live span { display: none; } .pt-lucky-live { padding: 6px; } }
