:root{
  --void:#000;
  --bg-app:#000;
  --surface:#1a1a1a;
  --surface-alt:#242424;
  --text-primary:#fff;
  --text-muted:#999;
  --text-faint:#666;
  /* Kept as tokens (rather than hardcoding #fff everywhere) so the handful of
     remaining accent-tinted spots stay easy to find/adjust, but both now
     resolve to white — the old purple/coral system is gone. Real color is
     reserved for exactly two spots via --gradient-ig, plus --danger for the
     like heart and error/recording states. */
  --accent:#fff;
  --accent-soft:rgba(255,255,255,0.08);
  --accent-warm:#fff;
  --danger:#FF3040;
  /* The ONE Instagram-style gradient in the whole app, reused wherever a
     spot needs to read as a real, tappable entry point rather than inert
     text/decoration: the "+" create button in the bottom nav, the ring
     around the user's own avatar on Profile, the Profile insight card,
     the token balance chip, the wait-screen nudge dot, active toggle
     switches, and the beta-feedback CTA. */
  --gradient-ig:linear-gradient(135deg,#833AB4,#FD1D1D,#FCB045);
  /* Shop palette A/B test (docs/SHOP_PALETTE_REDESIGN_SPEC.md) — two new,
     narrowly-scoped accent hues, extending the token system rather than
     replacing it. Direction A ("quiet ledger", calm periwinkle-blue) vs.
     Direction B ("premium currency", muted warm gold); shop.html assigns a
     visitor to one via a body-level .shop-variant-a/.shop-variant-b class.
     Used ONLY for the "best value" token-pack card border/badge — see
     .token-pack-card.best and .token-pack-badge below. */
  --accent-trust:#6C8CFF;
  --accent-trust-soft:rgba(108,140,255,.12);
  --accent-value:#D9A653;
  --accent-value-soft:rgba(217,166,83,.12);
  /* Muted sage green — introduced for tracker.html's "Waiting Growth"
     badge (see .tracker-waiting-badge below) specifically so it doesn't
     reuse --danger, which this codebase already reserves for real
     error/destructive states (see this file's own --danger comment and
     docs/SHOP_PALETTE_REDESIGN_SPEC.md) and which the badge this one
     replaces deliberately avoided for the exact same reason. */
  --accent-growth:#5FB88A;
  --border:rgba(255,255,255,0.08);
  --border-strong:rgba(255,255,255,0.16);
  --radius-lg:28px;
  --radius-md:18px;
  /* Fraunces serif dropped app-wide in favor of Manrope everywhere. Kept the
     --font-display variable name (rather than touching every rule that
     references it) but pointed it at the same sans-serif body font. */
  --font-body:'Manrope', sans-serif;
  --font-display:var(--font-body);
  /* Bar A / the "night dock" bottom nav (see .night-dock below) --
     how much bottom clearance a page's own scrolling content must
     reserve so the overlaid, translucent bar never hides real content
     or tap targets underneath it. Shared here (was previously defined
     page-scoped on profile.html only) now that every page with a
     bottom nav uses the same renderer -- see js/bottom-nav.js. */
  --nav-clearance:calc(60px + env(safe-area-inset-bottom,0px));
}
*{box-sizing:border-box; margin:0; padding:0;}
html,body{height:100%;}
body{
  font-family:var(--font-body);
  background:var(--void);
  color:var(--text-primary);
  -webkit-tap-highlight-color:transparent;
}

/* Each HTML file is one real page. #app is just that page's content column. */
#app{
  position:relative; z-index:1;
  width:100%; max-width:480px; margin:0 auto;
  min-height:100dvh;
  background:var(--bg-app);
  display:flex; flex-direction:column;
  animation:fadeIn .25s ease;
}
@keyframes fadeIn{ from{ opacity:0; transform:translateY(6px);} to{ opacity:1; transform:translateY(0);} }
@media (min-width:560px){
  body{ display:flex; justify-content:center; padding:28px 0;}
  #app{ min-height:min(860px, 92dvh); border-radius:32px; overflow:hidden; box-shadow:0 40px 90px -24px rgba(0,0,0,.65), 0 0 0 1px rgba(255,255,255,.04);}
}
/* Pages that need to feel like a full-bleed immersive screen (Result, Explore) */
#app.fill{ height:100dvh; overflow:hidden; }
/* Pages with an internally-scrolling .scroll-area between a fixed topbar/bottom-nav */
#app.scroll-shell{ height:100dvh; }

.px{ padding-left:24px; padding-right:24px;}
.topbar{ display:flex; align-items:center; justify-content:space-between; padding:calc(env(safe-area-inset-top,0px) + 14px) 20px 10px; flex:none;}
.topbar-logo{ font-weight:800; font-size:18px; letter-spacing:-.01em;}
.icon{ display:inline-flex; width:1em; height:1em;}
.icon svg{ width:100%; height:100%; display:block;}
.icon-btn{
  width:36px; height:36px; border-radius:50%; background:var(--surface); border:1px solid var(--border);
  display:flex; align-items:center; justify-content:center; color:var(--text-primary); cursor:pointer; font-size:18px; text-decoration:none;
  transition:background .18s ease, transform .12s ease, border-color .18s ease;
  -webkit-tap-highlight-color:transparent;
}
.icon-btn:hover{ background:var(--surface-alt); border-color:var(--border-strong);}
.icon-btn:active{ transform:scale(.9);}
.search-input{ flex:1; margin-right:10px; background:var(--surface); border:1px solid var(--border); border-radius:100px; padding:9px 16px; font-family:var(--font-body); color:var(--text-primary); font-size:14px;}
.search-input::placeholder{ color:var(--text-faint);}
.search-input:focus{ outline:2px solid var(--accent); border-color:transparent;}
.btn{
  font-family:var(--font-body); font-weight:700; font-size:14.5px; border:none; cursor:pointer;
  border-radius:100px; padding:15px 20px; display:flex; align-items:center; justify-content:center; gap:8px;
  transition:transform .15s cubic-bezier(.34,1.56,.64,1), opacity .15s ease, background .2s ease, box-shadow .2s ease, filter .15s ease, border-color .18s ease;
  text-decoration:none; -webkit-tap-highlight-color:transparent;
}
.btn:active{ transform:scale(.96);}
.btn:focus-visible{ outline:2px solid var(--accent); outline-offset:2px;}
.btn:disabled{ opacity:.4; cursor:not-allowed; transform:none;}
.btn:disabled:hover, .btn:disabled:active{ transform:none;}
/* Plain white-fill / black-text — the app's one "solid" button look. */
.btn-primary{ background:#fff; color:#000; box-shadow:0 4px 14px -6px rgba(0,0,0,.5);}
.btn-primary:hover{ background:#f0f0f0;}
.btn-primary:active{ background:#e2e2e2;}
.btn-primary:disabled{ opacity:.35; cursor:not-allowed; box-shadow:none;}
/* Outlined dark-surface look — the app's "secondary" button. */
.btn-secondary{ background:var(--surface-alt); color:var(--text-primary); border:1px solid var(--border-strong);}
.btn-secondary:hover{ background:var(--surface); border-color:var(--text-faint);}
.btn-ghost{ background:transparent; color:var(--text-muted); border:1px solid var(--border);}
.btn-ghost:hover{ background:var(--surface); color:var(--text-primary); border-color:var(--border-strong);}
/* Same solid white-fill look as primary — kept as its own class since it's
   used for Publish specifically, but the gradient is reserved elsewhere. */
.btn-warm{ background:#fff; color:#000; box-shadow:0 4px 14px -6px rgba(0,0,0,.5);}
.btn-warm:hover{ background:#f0f0f0;}
.btn-warm:active{ background:#e2e2e2;}
.btn-warm:disabled{ opacity:.5; cursor:not-allowed; box-shadow:none;}
.btn-block{ width:100%;}
.link-text{ font-size:13px; color:var(--text-muted); text-align:center; cursor:pointer; text-decoration:underline; text-underline-offset:3px;}
.link-text b{ color:var(--text-primary);}
.err-text{ font-size:12px; color:var(--danger); min-height:16px; margin:-4px 0 8px;}

/* ===== Welcome ===== */
.welcome-body{ flex:1; display:flex; flex-direction:column; justify-content:space-between; align-items:center; padding:calc(env(safe-area-inset-top,0px) + 56px) 28px 40px; text-align:center;}
.wm-visual{
  width:220px; height:220px; border-radius:38px; margin:18px auto;
  display:block; object-fit:contain;
  box-shadow:0 12px 32px -8px rgba(0,0,0,.6);
}
.wm-copy{ font-weight:800; font-size:24px; line-height:1.3;}
.wm-copy i{ font-style:normal;}
.wm-subcopy{ font-size:14px; color:var(--text-muted); margin-top:8px;}
@keyframes spin{ to{ transform:rotate(360deg);} }
.wm-foot{ width:100%; display:flex; flex-direction:column; gap:14px;}

/* ===== Login ===== */
.auth-head{ font-weight:800; font-size:22px; margin:18px 0 6px;}
.auth-logo{ display:flex; justify-content:center; margin:10px 0 4px;}
.auth-logo img{ width:52px; height:52px; border-radius:14px; box-shadow:0 8px 20px -8px rgba(0,0,0,.6);}
.auth-sub{ font-size:13px; color:var(--text-muted); line-height:1.5; margin:0 0 20px;}
.field{ background:var(--surface); border:1px solid var(--border); border-radius:14px; padding:14px 16px; font-family:var(--font-body); color:var(--text-primary); font-size:14px; width:100%; margin-bottom:12px;}
.field::placeholder{ color:var(--text-faint);}
.field:focus{ outline:2px solid var(--accent); border-color:transparent;}
.field-pw-wrap{ position:relative; margin-bottom:12px;}
.field-pw-wrap .field{ margin-bottom:0; padding-right:46px;}
.field-pw-toggle{
  position:absolute; right:4px; top:50%; transform:translateY(-50%); width:34px; height:34px;
  display:flex; align-items:center; justify-content:center; background:transparent; border:none;
  color:var(--text-faint); cursor:pointer; padding:0; -webkit-tap-highlight-color:transparent;
}
.field-pw-toggle:hover{ color:var(--text-muted);}
.field-pw-toggle svg{ width:18px; height:18px;}
.link-right{ display:block; text-align:right; font-size:12px; margin:-4px 0 14px;}
.auth-trust{
  display:flex; align-items:center; gap:10px; font-size:12px; color:var(--text-muted); line-height:1.4;
  background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:12px 14px; margin-top:8px;
}
.auth-trust .icon{ width:17px; height:17px; flex:none; color:var(--text-faint);}
.auth-notice{
  display:flex; align-items:flex-start; gap:10px; font-size:13px; color:var(--text-primary); line-height:1.5;
  background:var(--surface); border:1px solid var(--border); border-radius:14px; padding:16px; margin-bottom:12px;
}
.auth-notice .icon{ width:18px; height:18px; flex:none; color:var(--text-faint); margin-top:1px;}

/* ===== Story recap card (style.html / wizard.html Contact step) =====
   tracker item for-product-split-prompttext-storytext-f-yt5kc7 (founder-
   required, point 4): the pre-generation preview must show the FINAL,
   human-readable storyText the user will see everywhere else — never the
   chip flow's own camera-direction/style-modifier promptText, and never
   blank/a placeholder. See style.html's/wizard.html's own scripts for how
   this gets populated (an immediate deterministic value, opportunistically
   upgraded by an in-flight LLM rewrite before Generate/Continue is tapped). */
.story-recap-card{ background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-md); padding:14px 16px; margin-bottom:14px;}
.story-recap-label{ font-size:11px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--text-faint); margin-bottom:6px;}
.story-recap-text{ font-size:14px; line-height:1.5; font-weight:600; color:var(--text-primary);}

/* ===== Home / Profile ===== */
.section-label{ font-size:11px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--text-faint); margin:16px 0 10px;}
.gen-banner{ display:flex; align-items:center; gap:10px; margin:14px 20px 0; padding:12px 14px; background:var(--surface); border-radius:14px;}
.gen-banner-text{ font-size:13px; color:var(--text-muted);}
.scroll-area{ flex:1; min-height:0; overflow-y:auto; -webkit-overflow-scrolling:touch;}

/* ===================== Bar A — the anchored "night dock" =====================
   THE one shared bottom-nav visual system, rendered by js/bottom-nav.js on
   every page that has a bottom nav (home.html, explore.html, profile.html,
   result.html — tracker item
   for-product-urgent-founder-roll-the-new--8pyek3, "roll the NEW Profile
   bottom bar to home + explore", closing the open question left by
   for-product-bug-design-pass-find-bottom--g0m6ck). Originally shipped
   scoped to profile.html only (commit bb6224b) as one of two competing
   nav systems (the OLD in-flow `.bottom-nav`/`.nav-item`/`.nav-create` this
   replaces); moved here, unmodified, once the founder ordered the same
   bar rolled out everywhere and the OLD system retired for good.

   A translucent, blurred bar anchored to the bottom of #app that content
   scrolls/plays BEHIND (overlay positioning — position:absolute, not
   sticky/in-flow), with labels, an in-row 44px gradient "+" (not a
   protruding one), and the signed-in account's own avatar standing in for
   the Profile tab's icon (the YouTube "You" treatment). Home/Explore/+/
   Profile is the tab order everywhere (founder-approved, see
   js/bottom-nav.js's own header comment for the full decision trail). */
.night-dock{
  position:absolute; left:0; right:0; bottom:0; z-index:9;
  height:calc(60px + env(safe-area-inset-bottom,0px)); padding-bottom:env(safe-area-inset-bottom,0px);
  display:flex; align-items:center; justify-content:space-around;
  background:rgba(12,10,26,.72);
  backdrop-filter:blur(16px) saturate(1.2); -webkit-backdrop-filter:blur(16px) saturate(1.2);
  border-top:1px solid rgba(157,144,255,.22);
}
/* Blur is decorative, never load-bearing: without backdrop-filter support
   the bar must still be opaque enough to read against scrolling content. */
@supports not (backdrop-filter:blur(1px)){
  .night-dock{ background:rgba(12,10,26,.95); }
}
.night-dock-item{
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px;
  width:64px; min-height:44px; /* a real 44px tap target, not just the glyph */
  color:#8f88b3; font:700 10px var(--font-body); letter-spacing:.02em;
  text-decoration:none; cursor:pointer; transition:color .15s ease;
  -webkit-tap-highlight-color:transparent;
}
.night-dock-item .nav-icon{ font-size:22px; display:block; }
/* Hardcoded, not read from the page's own --home-ink/--home-line/--home-acc
   night-theme tokens (those only exist on home.html/profile.html's own
   page-scoped :root, not explore.html/result.html's) -- the dock is a
   fixed, page-agnostic visual system now, and must render byte-for-byte
   identically on all four pages regardless of which page-level theme
   variables happen to be defined. Values match profile.html's original
   theme exactly (#eae7fb/rgba(157,144,255,.32)/#9d90ff). */
.night-dock-item.active{ color:#eae7fb; }
.night-dock-item:active{ transform:scale(.92); }
.night-dock-create{
  width:44px; height:44px; border-radius:50%; flex:none; cursor:pointer;
  background:var(--gradient-ig); color:#fff; border:none; text-decoration:none;
  display:flex; align-items:center; justify-content:center;
  box-shadow:0 6px 16px -6px rgba(253,29,29,.35);
  transition:transform .15s cubic-bezier(.34,1.56,.64,1), filter .15s ease;
  -webkit-tap-highlight-color:transparent;
}
.night-dock-create .icon{ font-size:24px; }
.night-dock-create:hover{ filter:brightness(1.06); }
.night-dock-create:active{ transform:scale(.94); }
/* Avatar-as-Profile-icon (the YouTube "You" treatment) — the same Me
   character photo/initial the page's own identity header shows (where
   one exists), rendered at nav size. Computed by js/bottom-nav.js itself
   straight from DreamStore, so every page gets it, not just Profile. */
.night-dock-avatar{
  width:22px; height:22px; border-radius:50%; overflow:hidden; background:#2a2350;
  border:1.5px solid rgba(157,144,255,.32);
  display:flex; align-items:center; justify-content:center;
  font-style:normal; font-weight:800; font-size:9.5px; color:#eae7fb;
}
.night-dock-avatar img{ width:100%; height:100%; object-fit:cover; display:block; }
.night-dock-item.active .night-dock-avatar{ border-color:#9d90ff; }
/* New-likes badge dot (tracker item idea-notify-likes) — a plain dot on
   the Profile tab's icon, no count digit (stays legible at this size).
   DreamStore.getCachedNewLikesCount() drives its visibility on every
   page with a bottom nav (see js/store.js's own doc block for when this
   goes back to hidden). The wrap is a separate sibling element (not the
   .nav-icon/.night-dock-avatar element itself) because js/bottom-nav.js
   replaces those wholesale on every mount — a badge nested inside would
   get wiped out the moment that runs.
   [hidden] rather than an inline display:none so toggling it is a single
   attribute flip from js/bottom-nav.js's own mount(). */
.nav-icon-badge-wrap{ position:relative; display:inline-flex;}
.nav-badge-dot{
  position:absolute; top:-2px; right:-4px; width:9px; height:9px; border-radius:50%;
  background:var(--danger); border:1.5px solid var(--bg-app);
}
.nav-badge-dot[hidden]{ display:none;}

.video-grid{ display:grid; grid-template-columns:1fr 1fr; gap:16px 12px; padding:2px 20px 20px;}
.vcard{ cursor:pointer; text-decoration:none; color:inherit; display:block;}
.vcard-thumb{ position:relative; aspect-ratio:9/14; border-radius:14px; overflow:hidden; background:var(--surface);}
.vcard-thumb-bg{ position:absolute; inset:0;}
.vcard-video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover;}
.vcard-image{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover;}
.vcard-thumb .dur{ position:absolute; right:6px; bottom:6px; background:rgba(0,0,0,.6); color:#fff; font-size:10px; font-weight:700; padding:2px 6px; border-radius:6px;}
/* Bottom-left, not top-left — top-left is .vcard-delete's spot (Profile-only). */
.vcard-thumb .priv{ position:absolute; left:6px; bottom:6px; background:rgba(0,0,0,.65); color:#fff; font-size:9px; font-weight:700; padding:2px 6px; border-radius:6px; letter-spacing:.02em;}
.vcard-thumb .priv.pub{ background:rgba(255,255,255,.92); color:#000;}
.vcard-thumb .play-indicator{ position:absolute; top:8px; right:8px; color:#fff; width:13px; height:13px; opacity:.9;}
.vcard-delete{
  position:absolute; top:6px; left:6px; z-index:2; width:26px; height:26px; border-radius:50%;
  background:rgba(0,0,0,.55); backdrop-filter:blur(4px); border:none; color:#fff; cursor:pointer;
  display:flex; align-items:center; justify-content:center; font-size:13px; transition:background .15s ease, transform .15s ease;
}
.vcard-delete:hover{ background:rgba(255,48,64,.85);}
.vcard-delete:active{ transform:scale(.9);}
.vcard-title{ font-size:12.5px; font-weight:700; margin-top:8px; line-height:1.3;}
.vcard-meta{ font-size:11px; color:var(--text-faint); margin-top:2px;}
.grid-empty{ padding:30px 24px; text-align:center; color:var(--text-muted); font-size:13px; line-height:1.6;}

/* ===== Upcoming-night placeholder slots (js/dream-cards.js) =====
   Founder amendment on tracker item
   for-product-build-founder-approved-2026--to6ew2: an empty library slot
   stays a DIMMER placeholder square, and the dream journey shows through
   it so the tile reads as a next step ("Night 3 · tomorrow") rather than a
   blank gap. The first empty slot is slightly brighter, as "next".

   READ-ONLY BY DESIGN — these are <div>s, not links or buttons, and
   js/dream-cards.js attaches no handler to them. They mirror home.html's
   dream-log state; they are never a second place to claim or log
   anything. Same tall 9:14 footprint as a real .vcard-thumb, so the grid
   rhythm never breaks between real tiles and placeholders. */
.vcard-slot{ display:block; cursor:default;}
.vcard-slot-thumb{
  position:relative; aspect-ratio:9/14; border-radius:14px; overflow:hidden;
  background:var(--surface); border:1px dashed var(--border);
  display:flex; align-items:center; justify-content:center; opacity:.5;
}
.vcard-slot-mark{ font-size:20px; font-weight:300; line-height:1; color:var(--text-faint);}
.vcard-slot-label{ font-size:11px; color:var(--text-faint); margin-top:8px; opacity:.65;}
.vcard-slot.is-next .vcard-slot-thumb{ opacity:.8; border-style:solid; border-color:var(--border-strong);}
.vcard-slot.is-next .vcard-slot-label{ opacity:1; color:var(--text-muted);}

/* Home: single-column full-width feed cards (not a browse/search grid). */
.home-feed{ padding:2px 18px 18px; display:flex; flex-direction:column; gap:14px;}
.home-card{ position:relative; border-radius:16px; overflow:hidden; aspect-ratio:3/2; background:var(--surface); cursor:pointer; text-decoration:none; color:inherit; display:block;}
.home-card-art{ position:absolute; inset:0;}
.home-card-video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover;}
.home-card-scrim{ position:absolute; inset:0; background:linear-gradient(to top, rgba(0,0,0,.85), transparent 55%);}
.home-card-body{ position:absolute; left:14px; right:14px; bottom:12px; z-index:2; display:flex; justify-content:space-between; align-items:flex-end; gap:10px;}
.home-card-user{ font-size:11px; font-weight:600; color:var(--text-muted); margin-bottom:5px;}
.home-card-cap{ font-size:14.5px; font-weight:600; color:#fff; line-height:1.3;}
.home-card-like{ display:flex; align-items:center; gap:5px; font-size:12px; font-weight:700; color:#fff; flex:none;}
.home-card-like .icon{ width:14px; height:14px; color:var(--danger);}

.profile-head{ display:flex; flex-direction:column; align-items:center; text-align:center; padding:20px 24px 6px;}
/* The Instagram-style gradient's first appearance: a ring (padding reveals
   the ring thickness) around a plain dark inner avatar circle. */
.profile-avatar-ring{ width:76px; height:76px; border-radius:50%; padding:3px; background:var(--gradient-ig); margin-bottom:12px; position:relative; cursor:pointer;}
.profile-avatar{ width:100%; height:100%; border-radius:50%; background:var(--surface); display:flex; align-items:center; justify-content:center; font-size:26px; overflow:hidden;}
.profile-avatar img{ width:100%; height:100%; object-fit:cover; display:block;}
/* Small pencil badge overlaid on the avatar ring, the same "tap to edit"
   affordance the Me character photo doesn't otherwise have anywhere on
   this page — matches .icon-btn's circular surface look at a smaller size. */
.profile-avatar-edit-badge{
  position:absolute; right:-2px; bottom:-2px; width:24px; height:24px; border-radius:50%;
  background:var(--surface); border:1px solid var(--border-strong); color:var(--text-primary);
  display:flex; align-items:center; justify-content:center;
}
.profile-avatar-edit-badge svg{ width:12px; height:12px;}
.profile-name{ font-size:16px; font-weight:700; cursor:pointer;}
.profile-stats{ font-size:12.5px; color:var(--text-muted); margin-top:5px; max-width:240px; line-height:1.4;}
.profile-stats b{ color:var(--text-primary);}

/* Idea #4/#5 — pattern insight + milestone counter. Both read data
   already saved on the signed-in user's own dreams (see
   DreamStore.getDreamInsight/getDreamMilestone) and only ever render on
   the account's own profile page — there is no "view someone else's
   profile" route in this app, so no extra owner-check is needed here. */
.profile-milestone-row{ display:flex; gap:8px; padding:0 20px; margin-bottom:12px;}
.profile-milestone-chip{ flex:1; background:var(--surface); border:1px solid var(--border); border-radius:14px; padding:12px 8px; text-align:center;}
.profile-milestone-chip .pm-num{ font-size:19px; font-weight:800;}
.profile-milestone-chip .pm-label{ font-size:10.5px; color:var(--text-muted); margin-top:2px;}
.profile-milestone-chip .pm-icon{ width:18px; height:18px; margin:0 auto 2px;}
.profile-insight-card{ margin:0 20px 16px; padding:14px 16px; border-radius:16px; background:var(--gradient-ig); position:relative;}
.profile-insight-card::before{ content:""; position:absolute; inset:1px; border-radius:15px; background:var(--surface); opacity:.88;}
.profile-insight-card .pi-inner{ position:relative;}
.profile-insight-card .pi-label{ font-size:10px; font-weight:800; text-transform:uppercase; letter-spacing:.05em; color:#c9a8f0; margin-bottom:5px;}
.profile-insight-card p{ font-size:12.5px; line-height:1.5; color:#eee;}
.profile-insight-card b{ color:#fff;}

/* New-likes notification banner (tracker item idea-notify-likes) — plain
   surface card, not the gradient treatment .profile-insight-card uses,
   since this is a transient status line rather than a recurring feature
   card. */
.new-likes-banner{ margin:0 20px 16px; padding:12px 16px; border-radius:14px; background:var(--surface); border:1px solid var(--border); display:flex; align-items:center; gap:10px;}
.new-likes-banner .icon{ width:18px; height:18px; color:var(--danger); flex:none;}
.new-likes-banner span:last-child{ font-size:13px; font-weight:600; color:var(--text-primary); line-height:1.4;}

/* ===== Create ===== */
.choice-card{
  background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-md);
  padding:20px 20px 20px 18px; display:flex; align-items:center; gap:16px; cursor:pointer; margin-bottom:14px;
  transition:border-color .18s ease, background-color .18s ease, transform .12s ease;
}
.choice-card:hover{ border-color:var(--border-strong); background:var(--surface-alt);}
.choice-card:active{ transform:scale(0.985);}
.choice-icon{ width:50px; height:50px; border-radius:15px; display:flex; align-items:center; justify-content:center; font-size:22px; flex:none; background:var(--surface-alt); color:#fff;}
.choice-icon svg{ width:22px; height:22px;}
.choice-text{ flex:1; min-width:0;}
.choice-title{ font-weight:700; font-size:16px;}
.choice-sub{ font-size:12.5px; color:var(--text-muted); margin-top:4px;}
.choice-chevron{ font-size:20px; color:var(--text-faint); flex:none;}
textarea.field{ min-height:150px; resize:none; line-height:1.5; font-family:var(--font-body);}
/* Reclaims vertical space while Advanced is expanded, so the Continue
   button stays reachable without needing to scroll in the common case —
   see the matching #adv-toggle handler in create.html. */
#create-write.adv-open #dream-text{ min-height:70px;}
.char-count{ font-size:11px; color:var(--text-faint); text-align:right; margin-top:-6px; margin-bottom:14px;}
.rec-panel{ flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:22px; min-height:0; overflow-y:auto;}
.rec-error{ font-size:12px; color:var(--danger); text-align:center; padding:0 8px;}
.rec-timer{ font-size:34px; font-weight:700;}
.rec-dot{ width:120px; height:120px; border-radius:50%; background:radial-gradient(circle at 35% 30%, rgba(255,255,255,.2), transparent 45%), var(--danger); display:flex; align-items:center; justify-content:center; font-size:34px; color:#fff; box-shadow:0 0 0 10px rgba(255,48,64,.14), 0 0 0 22px rgba(255,48,64,.07); animation:pulse 1.6s ease-in-out infinite;}
@keyframes pulse{ 0%,100%{ transform:scale(1);} 50%{ transform:scale(1.05);} }
.wave{ display:flex; align-items:center; gap:3px; height:34px;}
.wave span{ width:3px; border-radius:2px; background:#fff; animation:wv 1.1s ease-in-out infinite;}
.wave span:nth-child(odd){ animation-delay:.15s;}
.wave span:nth-child(3n){ animation-delay:.3s;}
@keyframes wv{ 0%,100%{ height:6px;} 50%{ height:28px;} }
.wave.static span{ animation:none; background:var(--text-faint);}
#play-btn.playing{ background:var(--accent-soft); border-color:var(--text-primary); color:var(--text-primary);}

/* ===== Advanced (characters, and later camera/scenery) ===== */
.adv-section{ border:1px solid var(--border); border-radius:var(--radius-md); margin-top:14px; overflow:hidden;}
/* .rec-panel (the Record review screen) is a centered flex column, unlike
   the Write screen's block layout the section was designed for — stretch
   it to full width there instead of shrink-wrapping to content. */
.rec-panel .adv-section{ align-self:stretch; width:100%;}
.adv-toggle{
  display:flex; align-items:center; justify-content:space-between; padding:14px 16px; cursor:pointer;
  font-weight:700; font-size:14px; background:var(--surface); -webkit-tap-highlight-color:transparent;
}
.adv-toggle .icon{ width:16px; height:16px; color:var(--text-faint); transition:transform .2s ease;}
.adv-section.open .adv-toggle .icon{ transform:rotate(180deg);}
.adv-content{ display:none; padding:4px 16px 18px;}
.adv-section.open .adv-content{ display:block;}
.adv-sub-label{ font-size:11px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--text-faint); margin:14px 0 10px;}
.adv-sub-label:first-child{ margin-top:4px;}
.adv-hint{ font-size:12px; color:var(--text-muted); margin-top:-4px;}

.char-chip-row{ display:flex; flex-wrap:wrap; gap:8px;}
.char-chip{
  display:flex; align-items:center; gap:7px; padding:8px 12px; border-radius:100px;
  /* Per founder feedback the old --surface-alt (#242424) fill + 1px
     --border-strong edge read as "solid-black pills" that felt heavy.
     Note: void(#000) < surface(#1a1a1a) < surface-alt(#242424) in actual
     luminance, so --surface is technically darker than --surface-alt, not
     brighter — but against this screen's black (--bg-app) background it
     sits closer to the backdrop and reads as a subtler, lower-contrast
     pill rather than a stark block, which is the "lighter/less heavy"
     effect being asked for here (confirmed by eye against the real
     rendered page, not assumed). Paired with the softer --border (not
     --border-strong) for the same reason. */
  background:var(--surface); border:1px solid var(--border); cursor:pointer; font-size:13px; font-weight:600;
  transition:background .15s ease, border-color .15s ease;
}
.char-chip .chip-check{ width:15px; height:15px; border-radius:50%; border:1.5px solid var(--text-faint); display:flex; align-items:center; justify-content:center; flex:none;}
.char-chip .chip-check .icon{ width:9px; height:9px; opacity:0;}
.char-chip.selected{ background:#fff; color:#000; border-color:#fff;}
.char-chip.selected .chip-check{ background:#000; border-color:#000;}
.char-chip.selected .chip-check .icon{ opacity:1; color:#fff;}
/* Padding + matching negative margin grows the tap target around the name
   + pencil icon without changing the chip's visual size or its gap to the
   checkmark — the pencil alone was too small a target on a phone. A real
   mobile test (Facebook Messenger in-app browser) found the original 8px
   version (~32px effective tap height, given the chip's 13px font/~16px
   line height) still "very hard to tap" — bumped to comfortably clear the
   ~44px mobile tap-target guideline; the name text + pencil together were
   already the whole tap target (data-char-edit is on this element, not on
   the pencil icon alone), so this only had to grow the invisible padding,
   not change what element the click listener is bound to. */
.char-chip .chip-edit-area{ display:flex; align-items:center; gap:7px; padding:14px 10px 14px 0; margin:-14px -10px -14px 0;}
.char-chip .chip-edit{ color:var(--text-faint); width:13px; height:13px; flex:none;}
.char-chip.selected .chip-edit{ color:#000; opacity:.5;}
.char-chip-add{
  display:flex; align-items:center; gap:6px; padding:8px 14px; border-radius:100px;
  background:transparent; border:1px dashed var(--border-strong); color:var(--text-muted); cursor:pointer; font-size:13px; font-weight:600;
}
.char-chip-add .icon{ width:13px; height:13px;}
.char-chip-add:hover{ border-color:var(--text-faint); color:var(--text-primary);}

/* Single-select pill groups — camera view, scenery. Same visual language as
   .char-chip's selected state (white fill/black text) but no checkmark
   circle, since only one option in a group can ever be active. */
.opt-chip-row{ display:flex; flex-wrap:wrap; gap:8px;}
.opt-chip{
  /* Lighter/less-heavy treatment — same reasoning as .char-chip above. */
  padding:8px 14px; border-radius:100px; background:var(--surface); border:1px solid var(--border);
  color:var(--text-primary); cursor:pointer; font-size:13px; font-weight:600; transition:background .15s ease, border-color .15s ease;
}
.opt-chip.selected{ background:#fff; color:#000; border-color:#fff;}

/* "+N more" / "Show less" expander chip (Action step curation — tracker
   item for-product-wizard-step-3-has-too-many-c-lrg1ct). Dashed/
   transparent so it reads as a disclosure affordance, not a selectable
   option — same visual language as .char-chip-add's "+ Add character"
   tile above, not an invented look. */
.opt-chip-more{
  padding:8px 14px; border-radius:100px; background:transparent; border:1px dashed var(--border-strong);
  color:var(--text-muted); cursor:pointer; font-size:13px; font-weight:600;
}
.opt-chip-more:hover{ border-color:var(--text-faint); color:var(--text-primary);}

/* Character sheet: self-only Describe/Upload photo picker. */
.char-mode-row{ display:flex; gap:8px; margin-bottom:12px;}
.char-mode-btn{
  flex:1; padding:9px; border-radius:10px; background:var(--surface-alt); border:1px solid var(--border-strong);
  color:var(--text-muted); font-size:13px; font-weight:700; cursor:pointer; text-align:center; transition:background .15s ease, color .15s ease;
}
.char-mode-btn.active{ background:#fff; color:#000; border-color:#fff;}
.char-photo-area{ margin-bottom:12px;}
.char-photo-preview{
  width:120px; height:120px; border-radius:16px; overflow:hidden; background:var(--surface);
  margin:0 auto 12px; display:flex; align-items:center; justify-content:center; border:1px solid var(--border);
}
.char-photo-preview img{ width:100%; height:100%; object-fit:cover; display:block;}
.char-photo-empty{ color:var(--text-faint); font-size:11px; text-align:center; padding:0 12px;}
.char-photo-hint{ font-size:11px; color:var(--text-muted); text-align:center; line-height:1.4; margin:0 auto 12px; max-width:260px;}
.char-photo-remove{ display:block; text-align:center; font-size:12px; color:var(--danger); cursor:pointer; margin-top:8px;}

/* Image-vs-video picker (style.html) — new instance of the char-mode-row/
   char-mode-btn segmented-pill pattern above (Describe/Upload photo), new
   class names so this stays decoupled from the character sheet's own
   toggle even though the visual language matches on purpose. */
.media-type-row{ display:flex; gap:8px; margin-bottom:14px;}
.media-type-btn{
  flex:1; padding:10px; border-radius:10px; background:var(--surface-alt); border:1px solid var(--border-strong);
  color:var(--text-muted); font-size:13px; font-weight:700; cursor:pointer; text-align:center; transition:background .15s ease, color .15s ease;
}
.media-type-btn.active{ background:#fff; color:#000; border-color:#fff;}

/* ===== Style grid ===== */
.style-grid{ display:grid; grid-template-columns:1fr 1fr; gap:12px; margin:18px 0 24px;}
.style-card{ position:relative; border-radius:18px; height:140px; overflow:hidden; cursor:pointer; border:2px solid transparent;}
.style-card.selected{ border-color:#fff;}
.style-card .sc-bg{ position:absolute; inset:0;}
.style-card .sc-overlay{ position:absolute; inset:0; background:linear-gradient(to top, rgba(0,0,0,.85), rgba(0,0,0,0) 55%);}
.style-card .sc-content{ position:absolute; left:14px; bottom:12px; z-index:2;}
.style-card .sc-icon{ width:20px; height:20px; color:#fff;}
.style-card .sc-name{ font-weight:800; font-size:14px; margin-top:6px; color:#fff;}
.style-card .sc-check{ position:absolute; top:10px; right:10px; width:24px; height:24px; border-radius:50%; background:#fff; color:#000; display:none; align-items:center; justify-content:center; z-index:3;}
.style-card .sc-check .icon{ width:13px; height:13px;}
.style-card.selected .sc-check{ display:flex;}
/* Style preview thumbnails stay colorful on purpose — they represent the
   four distinct visual output styles a dream can be generated in, same as
   filter previews in any camera app; only the app's own UI chrome went
   black/white/gray. Real fal.ai FLUX-generated stills (~$0.003 each, a
   one-time cost — see assets/style-previews/), one per style, evoking a
   generic "flying dream" scene rendered the way that style would actually
   render it: a real preview, not decoration standing in for one. Resized
   to 480px and re-compressed for web (originals were 1024px/~250KB each;
   these are ~20-30KB). Re-generate deliberately, not automatically, if a
   style's actual model output ever drifts far from these. */
.style-card .sc-bg{ background-size:cover; background-position:center;}
.sc-cartoon{ background-image:url("../assets/style-previews/cartoon.jpg");}
.sc-cinematic{ background-image:url("../assets/style-previews/cinematic.jpg");}
.sc-anime{ background-image:url("../assets/style-previews/anime.jpg");}
.sc-realistic{ background-image:url("../assets/style-previews/realistic.jpg");}

/* ===== Processing ===== */
/* overflow-y:auto + min-height:0 -- #app is now height:100dvh (class=
   "scroll-shell", see processing.html), a real cap rather than the old
   min-height:100dvh floor (tracker item
   for-product-sheet-dismissal-app-wide-fix-rlay70, review round 1: the
   out-of-tokens purchase sheet mounts here too, and needed #app to be
   genuinely viewport-bounded for its own tap-outside/drag-dismiss to
   position correctly against the real screen, not an oversized #app
   that could grow past one viewport with the nudge card + preview row
   both showing). Without this, that real cap would just clip any
   content taller than one viewport with nothing to scroll it into view. */
.proc-wrap{ flex:1; display:flex; flex-direction:column; padding:40px 30px; overflow-y:auto; min-height:0;}
.proc-live{ flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; gap:22px;}
.proc-orb{ width:130px; height:130px; border-radius:50%; position:relative; background:conic-gradient(from 0deg, transparent, #fff); animation:spin 1.4s linear infinite;}
.proc-orb::after{ content:""; position:absolute; inset:10px; border-radius:50%; background:var(--bg-app);}
.proc-title{ font-size:20px; font-weight:800;}
.proc-sub{ font-size:13px; color:var(--text-muted); margin-top:-10px;}
/* Indeterminate — a translucent segment sweeps across the track on a fixed
   loop, never tied to any real percentage (there is no target to reach). */
.proc-bar-track{ width:100%; height:8px; background:var(--surface); border-radius:100px; overflow:hidden; position:relative;}
.proc-bar-fill{ position:absolute; top:0; left:-40%; height:100%; width:40%; border-radius:100px; background:linear-gradient(90deg, transparent, #fff, transparent); animation:proc-bar-sweep 1.6s ease-in-out infinite;}
@keyframes proc-bar-sweep{ 0%{ left:-40%;} 100%{ left:100%;} }
/* Friendly elapsed-time estimate under the bar — a fake, client-side-only
   countdown from a fixed per-mediaType estimate (see processing.html's
   startTimeEstimate/updateTimeLabel), NOT tied to the real (unknowable)
   fal.ai completion time. Purely reassurance copy alongside the existing
   indeterminate sweep above — first-timers reported the plain sweep alone
   read as "maybe stuck" with nothing telling them how much longer. */
.proc-time-label{ width:100%; text-align:right; font-size:11px; color:var(--text-faint); margin-top:-14px;}
.proc-checklist{ display:flex; flex-direction:column; gap:8px; align-items:flex-start; text-align:left;}
.proc-check-item{ display:flex; align-items:center; gap:8px; font-size:13px; color:var(--text-muted);}
.proc-check-item.current{ color:var(--text-primary); font-weight:700; animation:pulse-text 1.6s ease-in-out infinite;}
.proc-check-mark{ display:inline-flex; width:16px; height:16px; flex-shrink:0; color:var(--text-muted);}
.proc-check-mark svg{ width:16px; height:16px;}
.proc-check-item .spinner-inline{ flex-shrink:0;}
@keyframes pulse-text{ 0%,100%{ opacity:1;} 50%{ opacity:.6;} }
.proc-preview{ width:100%; margin-top:4px;}
.proc-preview-label{ font-size:11px; color:var(--text-muted); margin-bottom:8px;}
.proc-preview-row{ display:flex; gap:8px; overflow-x:auto; padding-bottom:2px; scrollbar-width:none;}
.proc-preview-row::-webkit-scrollbar{ display:none;}
.proc-preview-thumb{ position:relative; flex:0 0 auto; width:64px; aspect-ratio:9/14; border-radius:10px; overflow:hidden; background:var(--surface);}
.ppt-bg{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover;}
/* Post-signup FB/IG in-app-browser nudge (docs/design/inapp-browser-nudge.html,
   Frame 1 only — see processing.html's initInAppNudge). Shown only when the
   UA is Instagram's or Facebook's own in-app webview and only once signed
   in. ROUND-2 FOUNDER FIX (tracker item for-product-webview-notify-escape-
   nudge--5yray5): dismissing this card no longer makes it vanish forever —
   it collapses to the small .proc-nudge-chip pill below instead (see
   DreamStore.getInAppNudgeDismissed/dismissInAppNudge's own updated doc
   comment). Reuses the app's existing --accent-trust blue (already used
   for token-pack "best value" and the tracker's own "ron" badge) and
   --gradient-ig (the app's one Instagram-style gradient, already used for
   the "+" nav button and the profile avatar ring) rather than inventing
   new colors. */
.proc-nudge-card{ width:100%; text-align:left; position:relative; margin-top:6px; padding:13px 30px 13px 14px; border-radius:16px; background:var(--surface); border:1px solid rgba(108,140,255,.35);}
.proc-nudge-x{ position:absolute; top:9px; right:10px; width:22px; height:22px; display:flex; align-items:center; justify-content:center; color:var(--text-faint); background:none; border:none; font-size:16px; line-height:1; cursor:pointer; padding:0;}
.proc-nudge-head{ display:flex; align-items:center; gap:8px; font-size:12.5px; font-weight:800; color:var(--text-primary); margin-bottom:5px;}
.proc-nudge-dot{ flex:0 0 auto; width:22px; height:22px; border-radius:7px; background:var(--gradient-ig); display:flex; align-items:center; justify-content:center; color:#fff;}
.proc-nudge-dot svg{ width:13px; height:13px;}
.proc-nudge-body{ font-size:11.5px; color:var(--text-muted); line-height:1.5;}
.proc-nudge-btn{ margin-top:9px; width:100%; display:flex; align-items:center; justify-content:center; gap:6px; font-weight:700; font-size:12.5px; border-radius:100px; padding:9px 12px; background:var(--accent-trust); color:#0a1130; border:none; cursor:pointer;}
.proc-nudge-btn .icon{ width:14px; height:14px;}
.proc-nudge-hint{ font-size:10.5px; color:var(--text-faint); text-align:center; margin-top:7px; transition:color .3s ease;}
.proc-nudge-hint.proc-nudge-hint-emph{ color:var(--accent-trust); font-weight:700;}
/* Persistent post-copy instruction line — tracker item for-product-webview-
   notify-escape-nudge--5yray5 (founder, round 2): the pre-existing toast
   ("Link copied — paste it in Safari...") auto-dismisses after ~2.2s, so a
   visitor who looks back at the screen a few seconds later saw nothing
   telling them what to do next. This renders alongside the toast (doesn't
   replace it) and, unlike the toast, stays up for as long as the card/panel
   itself is visible — closer to the founder's own verbatim ask ("open your
   browser and paste the link there"). Shared by processing.html's
   #proc-nudge-copied-note and create.html's #rec-inapp-copied-note, same
   "one visual language, not two implementations" precedent as
   .menu-row-replica below. Hidden by default; shown once copyLinkForBrowser
   actually succeeds, not merely on button click (a failed copy shows its
   own distinct toast instead, see copyLinkForBrowser's own doc comment). */
.proc-nudge-copied-note{ display:none; font-size:11px; font-weight:700; color:var(--accent-trust); text-align:center; margin-top:8px; line-height:1.4;}
/* Collapsed re-entry pill — tracker item for-product-webview-notify-escape-
   nudge--5yray5 (founder, round 2): the X-dismiss on the in-app-webview
   nudge card used to make it vanish forever, the same one-shot-
   disappearance mistake fixed elsewhere for the original A2HS nudge (see
   js/install-nudge.js's own 2026-07-29 revision note) — but that fix's
   actual mechanism (a separate ALWAYS-shown home.html journey card, gated
   on a real "verified installed" signal, not on that small card's own
   dismiss state) doesn't transplant here: there's no equivalent "verified"
   signal for having escaped a webview, and this card doesn't live on a
   persistent dashboard page like home.html. So this is a new, small
   idiom — a low-profile pill that replaces the full card once dismissed
   (initInAppNudge renders this INSTEAD of the full card on any load where
   DreamStore.getInAppNudgeDismissed() is already true, and the dismiss
   button swaps to it in place rather than removing the card from the DOM)
   and re-expands the full card on tap. Deliberately small/quiet — this is a
   re-entry point for someone who already said "not now," not a second nudge
   competing for attention — sized/shaped like the app's existing small pill
   chips (.token-chip-compact) rather than inventing a new pill language. */
.proc-nudge-chip{ display:flex; align-items:center; gap:6px; width:max-content; max-width:100%; margin-top:6px; padding:7px 14px 7px 8px; border-radius:100px; background:var(--surface-alt); border:1px solid var(--border-strong); color:var(--text-muted); font-size:11px; font-weight:700; cursor:pointer;}
.proc-nudge-chip-dot{ flex:0 0 auto; width:18px; height:18px; border-radius:50%; background:var(--gradient-ig); display:flex; align-items:center; justify-content:center; color:#fff;}
.proc-nudge-chip-dot svg{ width:10px; height:10px;}
/* Static, non-interactive replica of the actual menu row a visitor has to
   find inside their host app's own "..." menu (Instagram's/Facebook's
   in-app-browser chrome) -- tracker item
   for-product-nudge-polish-founder-show-a--x72003 (founder feedback): a
   text-only hint left people unsure exactly what to look for, so this
   renders a small mockup of that real menu row -- icon + label, a light
   "system menu" card look deliberately distinct from this app's own dark
   surfaces and Manrope type, using the OS system-font stack so it reads as
   "not part of our UI" -- alongside the existing host-dynamic hint
   sentence. Shared by processing.html's post-signup nudge card (inside
   #proc-nudge-hint) and create.html's record-blocked panel (inside
   #rec-inapp-hint) -- one visual language for the same instruction in both
   places, not two separate implementations. aria-hidden in markup since
   the adjacent hint sentence already carries the same information for
   screen readers -- this is a decorative visual aid, not new content. */
.menu-row-replica{ display:inline-flex; align-items:center; gap:9px; margin:8px auto 0; padding:9px 16px; background:#f2f2f4; border:1px solid rgba(0,0,0,.08); border-radius:12px; box-shadow:0 1px 3px rgba(0,0,0,.16); text-align:left; max-width:100%;}
.menu-row-replica-icon{ flex:0 0 auto; width:18px; height:18px; display:flex; align-items:center; justify-content:center; color:#48484f;}
.menu-row-replica-icon svg{ width:16px; height:16px;}
.menu-row-replica-label{ font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; font-size:13px; font-weight:500; color:#1c1c1e;}
/* ===== Install (A2HS) nudge — tracker item for-product-build-stage-0-pwa-
   web-push-f-jbutt5, absorbing home-screen-shortcut-a2hs-nudge-founder--
   yylzoq. Rendered by js/install-nudge.js as a fixed bottom sheet-style
   card (unlike .proc-nudge-card above, which is inline in processing.html's
   own markup) since this nudge is shared across processing.html/
   result.html/profile.html, none of which have a purpose-built slot for
   it — same fixed-bottom-with-safe-area positioning as .toast, just a
   card instead of a pill. Reuses the exact same design language as
   .proc-nudge-card (surface background, --gradient-ig icon dot,
   --accent-trust CTA) intentionally, so this reads as "the same family of
   nudge" as the in-app-webview-escape card, not a visually distinct
   one-off. */
.install-nudge-card{ position:fixed; left:16px; right:16px; bottom:calc(16px + env(safe-area-inset-bottom,0px)); z-index:60; text-align:left; padding:14px 34px 14px 14px; border-radius:16px; background:var(--surface); border:1px solid rgba(108,140,255,.35); box-shadow:0 10px 30px -8px rgba(0,0,0,.6); max-width:420px; margin:0 auto;}
.install-nudge-x{ position:absolute; top:10px; right:11px; width:22px; height:22px; display:flex; align-items:center; justify-content:center; color:var(--text-faint); background:none; border:none; font-size:16px; line-height:1; cursor:pointer; padding:0;}
.install-nudge-head{ display:flex; align-items:center; gap:8px; font-size:13px; font-weight:800; color:var(--text-primary); margin-bottom:5px;}
.install-nudge-dot{ flex:0 0 auto; width:22px; height:22px; border-radius:7px; background:var(--gradient-ig); display:flex; align-items:center; justify-content:center; color:#fff;}
.install-nudge-dot svg{ width:13px; height:13px;}
.install-nudge-body{ font-size:12px; color:var(--text-muted); line-height:1.5;}
/* Step label (Step 2 of 2) -- tracker item for-product-install-first-door-
   founder-d-b60cls's guided two-step framing. Same small-caps convention as
   .section-label/.story-recap-label elsewhere in this file, just recolored
   with --accent-trust so it reads as "you're partway through a flow," not
   a generic section heading. */
.install-nudge-steplabel{ font-size:10px; font-weight:800; letter-spacing:.08em; text-transform:uppercase; color:var(--accent-trust); margin-bottom:4px;}
/* .install-nudge-btn doubles as a real <a href> (the Step 2 "Open in
   Safari" x-safari-https:// link, tracker item for-product-install-first-
   door-founder-d-b60cls) as well as a <button> -- text-decoration:none
   keeps an anchor from picking up the browser's default underline/blue
   link color the button variant never had. */
.install-nudge-btn{ margin-top:10px; width:100%; display:flex; align-items:center; justify-content:center; gap:6px; font-weight:700; font-size:13px; border-radius:100px; padding:10px 12px; background:var(--accent-trust); color:#0a1130; border:none; cursor:pointer; text-decoration:none;}
.install-nudge-btn .icon{ width:14px; height:14px;}
/* Secondary (copy-link fallback) variant -- same shape as the primary CTA
   above, softer fill so it doesn't compete with "Open in Safari" for
   attention while still being a real, tappable second option. */
.install-nudge-btn-secondary{ background:var(--surface-alt); color:var(--text-primary); border:1px solid var(--border-strong);}
/* iOS visual aid — tracker item for-product-a2hs-install-nudge-3-founder-
   vcofk7. Founder screenshot correction 2026-07-29: current Safari on iOS
   does NOT show a direct Share icon in its bottom toolbar — the toolbar
   is back-chevron / address-bar-pill / a "•••" (More) button at the
   bottom-RIGHT, and tapping that opens a small menu with Share as its
   first item, which THEN opens the real share sheet. The original build
   of this visual (a share icon centered in the toolbar) matched neither
   of Ron's real screenshots — rebuilt from those screenshots directly.
   Matches this app's own established precedent for "show a real OS-chrome
   mockup, don't just describe it in words" (see .menu-row-replica above,
   the in-app-webview nudge's own equivalent). Light system-chrome
   coloring (not this app's dark surfaces) so it reads as "a picture of
   your browser," same reasoning .menu-row-replica already documents.
   CSS/SVG only, no image asset. */
.install-nudge-visual{ margin:2px 0 10px; display:flex; flex-direction:column; align-items:center; gap:6px; }
.install-nudge-toolbar{ display:flex; align-items:center; gap:8px; width:100%; background:#f2f2f4; border:1px solid rgba(0,0,0,.08); border-radius:14px; padding:9px 12px; box-shadow:0 1px 3px rgba(0,0,0,.16); }
.install-nudge-toolbar-back{ flex:0 0 auto; width:18px; height:18px; color:#8e8e93; display:flex; align-items:center; justify-content:center; }
.install-nudge-toolbar-back svg{ width:14px; height:14px; }
.install-nudge-toolbar-url{ flex:1; min-width:0; background:#e2e2e6; border-radius:8px; padding:6px 10px; font-size:10.5px; color:#48484f; text-align:center; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* .install-nudge-toolbar-more (Safari's real "•••" More button, bottom-
   right) and .install-nudge-toolbar-highlight (Chrome's real Share icon,
   right of its address bar -- tracker item for-product-bug-research-
   founder-high-ad-vnda9t's browser-aware guidance; also reused for
   Android's real "⋮" overflow-menu icon in Step 2's menu-fallback visual,
   tracker item for-product-install-first-door-founder-d-b60cls -- same
   "highlighted circle pointing at a real button" role, just a third real
   button in a third real browser) are visually identical highlighted
   circles, just pointing at different real buttons -- one shared rule,
   shared class names. */
.install-nudge-toolbar-more, .install-nudge-toolbar-highlight{ flex:0 0 auto; width:28px; height:28px; border-radius:50%; background:#fff; border:2px solid var(--accent-trust); color:#0a63d6; display:flex; align-items:center; justify-content:center; box-shadow:0 0 0 4px rgba(20,120,230,.16); }
.install-nudge-toolbar-more svg, .install-nudge-toolbar-highlight svg{ width:15px; height:15px; }
.install-nudge-toolbar-arrow{ font-size:10.5px; font-weight:800; letter-spacing:.03em; color:var(--accent-trust); text-align:center; }
.install-nudge-menurow{ margin:8px auto 0; }
.install-nudge-note{ font-size:10.5px; color:var(--text-faint); margin-top:7px; line-height:1.4; }

/* ===== Push-subscribe ask — tracker item for-product-build-stage-0-pwa-
   web-push-f-jbutt5 part 3. Shown inline on processing.html, right after a
   first video starts generating (a genuinely high-intent moment — see
   js/push-subscribe.js), not as a fixed overlay — this one has an actual
   slot in processing.html's own wait-screen markup, unlike the install
   nudge above. Same visual family again (surface + --gradient-ig icon +
   --accent-trust CTA) for the same "one consistent nudge language"
   reason. */
.push-ask-card{ width:100%; text-align:left; position:relative; margin-top:6px; padding:13px 30px 13px 14px; border-radius:16px; background:var(--surface); border:1px solid rgba(108,140,255,.35);}
.push-ask-x{ position:absolute; top:9px; right:10px; width:22px; height:22px; display:flex; align-items:center; justify-content:center; color:var(--text-faint); background:none; border:none; font-size:16px; line-height:1; cursor:pointer; padding:0;}
.push-ask-head{ display:flex; align-items:center; gap:8px; font-size:12.5px; font-weight:800; color:var(--text-primary); margin-bottom:5px;}
.push-ask-dot{ flex:0 0 auto; width:22px; height:22px; border-radius:7px; background:var(--gradient-ig); display:flex; align-items:center; justify-content:center; color:#fff;}
.push-ask-dot svg{ width:13px; height:13px;}
.push-ask-body{ font-size:11.5px; color:var(--text-muted); line-height:1.5;}
.push-ask-btn{ margin-top:9px; width:100%; display:flex; align-items:center; justify-content:center; gap:6px; font-weight:700; font-size:12.5px; border-radius:100px; padding:9px 12px; background:var(--accent-trust); color:#0a1130; border:none; cursor:pointer;}
.push-ask-btn .icon{ width:14px; height:14px;}

.proc-fail{ flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; gap:16px;}
.proc-fail-icon{ width:42px; height:42px; color:var(--danger);}
.proc-fail-copy{ font-size:13px; color:var(--text-muted); line-height:1.5; max-width:260px;}
.proc-fail-reason{ font-size:13px; color:var(--text-primary); line-height:1.5; max-width:280px; background:var(--surface); border-radius:12px; padding:12px 16px; margin-top:-4px;}
.proc-fail-code{ font-size:10px; font-family:ui-monospace, 'SF Mono', Menlo, Consolas, monospace; color:var(--text-faint); word-break:break-word; margin-top:-8px;}

/* ===== Result =====
   The ritual-card rebuild (2026-07-31, tracker item for-product-build-
   founder-approved-mock--azvcea) moved this screen's own visual system
   entirely into result.html's own page-scoped <style> block (same
   per-page-override convention home.html/profile.html already use for
   their night themes) — .hcard/.dreamvideo/.playbadge/.watchpill/
   .tagrow/.statetag/.heropill/.quietrow/.player-overlay and friends all
   live there now, not here. What's left below is the handful of classes
   result.html keeps dual-classed onto its new markup so existing
   test/*.js coverage of them (test/ui-behavioral.test.js and others)
   didn't need a wholesale ID/class rewrite alongside the visual one —
   .play-badge/.badge-circle also stay because explore.html's own feed
   still uses them, unrelated to this page. */
.play-badge{ display:flex; align-items:center; justify-content:center; cursor:pointer; opacity:1; transition:opacity .2s ease;}
.play-badge .badge-circle{ width:56px; height:56px; border-radius:50%; background:rgba(0,0,0,.35); backdrop-filter:blur(4px); color:#fff; display:flex; align-items:center; justify-content:center;}
.play-badge .badge-circle .icon{ width:22px; height:22px;}
.play-badge.hidden{ opacity:0; pointer-events:none;}
/* Small font, clamped to 2 lines with tap-to-expand/collapse. */
.dream-quote{ font-size:13px; font-weight:600; margin-bottom:4px; line-height:1.45; cursor:pointer;}
.dream-quote.clamped{ display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; overflow:hidden;}
.quote-more-hint{ font-size:11px; font-weight:700; color:var(--text-muted); margin-bottom:8px; cursor:pointer;}

/* "Turn this into a video" upsell CTA's inner cost pill — the outer
   .turn-video-cta layout itself is defined in result.html's own <style>
   now, but this compound selector styles the "100 tokens" child pill
   specifically and has no equivalent there yet. Deliberately UNCHANGED/
   full-size — founder-explicit: this one CTA keeps its clearly visible
   token cost, never restyled into a slim/subtle pill. */
.turn-video-cta .feed-tag{ background:rgba(255,255,255,.14); backdrop-filter:blur(4px); padding:4px 11px; border-radius:100px; font-size:11.5px; font-weight:700;}

/* Quiet small-link row — Publish / Make another / Edit / Delete (order per
   the founder's final 2026-07-31 amendment — Edit moved next to Delete).
   Layout/typography stay here (dual-classed with result.html's own
   .quietrow/.quietlink so this doesn't regress); danger-tinting the
   Delete icon (only, never the label) now lives in result.html's own
   `.quietlink.danger` rule alongside its other quiet-row rules -- see
   tracker.html's for-product-result-quiet-row-v5-founder--4dnf5u. */
.result-quiet-links{ display:flex; justify-content:center; align-items:center; gap:9px; flex-wrap:wrap; padding-top:2px;}
.result-quiet-link{
  display:flex; align-items:center; gap:4px; font-family:var(--font-body); font-size:11px; font-weight:600;
  color:rgba(255,255,255,.75); background:none; border:none; cursor:pointer; padding:4px 2px; margin:0;
  text-decoration:none; -webkit-tap-highlight-color:transparent;
}
.result-quiet-link:hover{ color:#fff;}
.result-quiet-link:disabled{ opacity:.4; cursor:not-allowed;}
.result-quiet-link .icon{ width:13px; height:13px;}
.interp-loading-row{ display:flex; align-items:center; gap:10px; padding:8px 0 20px; color:var(--text-muted); font-size:14px;}
/* Founder walkthrough punch list (2026-08-01, tracker item
   for-product-founder-walkthrough-punch-li-t33k3y, item 8): "sat on the
   new 15px floor" (this was 14.5px) -- a modest bump, not a redesign. */
.interp-text{ font-size:16px; line-height:1.65; color:var(--text-primary); margin:2px 0 14px; white-space:pre-wrap;}
.interp-disclosure{ font-size:12px; color:var(--text-faint); line-height:1.4; margin:10px 0 16px;}
.interp-error-copy{ font-size:13px; color:var(--text-muted); line-height:1.5; margin:6px 0 16px;}

/* bottom sheet + modal + toast (used on Result) */
.sheet-overlay{ position:absolute; inset:0; overflow:hidden; background:rgba(0,0,0,.6); z-index:10; opacity:0; pointer-events:none; transition:opacity .25s;}
.sheet-overlay.open{ opacity:1; pointer-events:auto;}
/* max-height uses dvh (dynamic viewport height), matching #app's own
   sizing (see #app's height:100dvh/min-height:100dvh above) — NOT plain
   vh. On a real phone with the browser chrome/address-bar expanded,
   static vh is measured against the larger chrome-collapsed viewport, so
   a vh-based cap can end up MORE than 88% of #app's actual current
   on-screen height, leaving no practically-tappable backdrop above the
   sheet (tracker item for-product-sheet-dismissal-app-wide-fix-rlay70) —
   invisible in headless/desktop testing, where vh and dvh coincide. dvh
   tracks the same currently-visible viewport #app is sized against, so
   the tappable gap this cap exists for is actually there. */
.sheet{ position:absolute; left:0; right:0; bottom:0; background:var(--bg-app); border-top:1px solid var(--border-strong); border-radius:26px 26px 0 0; padding:12px 22px calc(26px + env(safe-area-inset-bottom,0px)); transform:translateY(100%); transition:transform .3s cubic-bezier(.32,.72,0,1); z-index:11; box-shadow:0 -20px 50px rgba(0,0,0,.4); max-height:88dvh; overflow-y:auto; -webkit-overflow-scrolling:touch;}
.sheet-overlay.open .sheet{ transform:translateY(0);}
.sheet-handle{ width:38px; height:4px; background:var(--border-strong); border-radius:10px; margin:4px auto 16px;}
.sheet-title{ font-size:18px; font-weight:700; margin-bottom:14px;}
.modal-overlay{ position:absolute; inset:0; background:rgba(0,0,0,.65); z-index:20; opacity:0; pointer-events:none; transition:opacity .2s; display:flex; align-items:center; justify-content:center; padding:30px;}
.modal-overlay.open{ opacity:1; pointer-events:auto;}
.modal-card{ background:var(--surface); border:1px solid var(--border-strong); border-radius:20px; padding:22px; width:100%; transform:scale(.92); transition:transform .2s;}
.modal-overlay.open .modal-card{ transform:scale(1);}
.modal-title{ font-size:17px; font-weight:700; margin-bottom:8px;}
.modal-body{ font-size:13px; color:var(--text-muted); line-height:1.5; margin-bottom:18px;}
.modal-row{ display:flex; gap:10px;}
/* Passive, non-interactive fine print under a modal's action row -- e.g.
   result.html's #modal-publish-fine (tracker item for-product-terms-
   republish-license-per--fhpcxk). Never a button/toggle target itself. */
.modal-fine{ font-size:11px; color:var(--text-faint); line-height:1.4; margin-top:12px; text-align:center;}
.toast{ position:absolute; left:50%; bottom:calc(26px + env(safe-area-inset-bottom,0px)); transform:translate(-50%,20px); background:#fff; color:#000; font-weight:700; font-size:13px; padding:12px 20px; border-radius:100px; opacity:0; transition:all .3s; z-index:30; pointer-events:none; white-space:nowrap;}
.toast.show{ opacity:1; transform:translate(-50%,0);}

/* ===== Explore feed (Reels-style) ===== */
.feed-scroll{ flex:1; overflow-y:auto; scroll-snap-type:y mandatory; background:#000;}
.feed-card{ width:100%; height:100%; scroll-snap-align:start; scroll-snap-stop:always; position:relative; overflow:hidden; background:#000;}
.feed-bg{ position:absolute; inset:0;}
.feed-video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover;}
.feed-image{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover;}
/* No bloom/gradient decoration — just the scrim needed for text legibility,
   full-bleed black video behind it. */
.feed-scrim{ position:absolute; inset:0; background:linear-gradient(to top, rgba(0,0,0,.78) 0%, rgba(0,0,0,.05) 45%, rgba(0,0,0,.4) 100%); pointer-events:none;}
.feed-back{
  position:absolute; top:calc(env(safe-area-inset-top,0px) + 16px); left:16px; z-index:6; width:24px; height:24px;
  display:flex; align-items:center; justify-content:center; color:#fff; text-decoration:none;
}
.feed-back svg{ width:20px; height:20px;}
/* Bottom offsets below (.feed-info/.feed-actions/.feed-swipe-hint) clear
   the overlaid night dock's own footprint (--nav-clearance) -- the dock
   sits ON TOP of the full-bleed video (z-index:9 beats these), so
   anything positioned near the card's bottom edge at the OLD safe-area-
   only offset ends up literally underneath it: unclickable (More/Like/
   Share) and, for feed-swipe-hint, invisible. Caught by
   report-block-behavioral.test.js after the Bar A rollout (tracker item
   for-product-urgent-founder-roll-the-new--8pyek3) started overlaying
   explore.html too -- .feed-scroll filling #app's full height doesn't by
   itself keep per-item overlay content out of the dock's footprint. */
.feed-info{ position:absolute; left:18px; bottom:calc(var(--nav-clearance) + 30px); right:80px; z-index:4;}
.feed-user-row{ display:flex; align-items:center; gap:8px; margin-bottom:8px;}
/* Tracker item for-product-ui-founder-directed-2026-07--djgjn0: this circle
   used to render permanently empty (background:var(--surface-alt) with no
   content) -- explore.html's avatarHTML() now always fills it with either a
   real photo (<img>, own dream backfilled from local Me photo, or the
   small avatar thumbnail synced at publish time) or the deterministic
   colored-initial fallback below, never blank. `background` here stays as
   a base-case placeholder underneath a loading <img>, and .feed-avatar-
   fallback overrides it with the per-username gradient. */
.feed-avatar{ width:28px; height:28px; border-radius:50%; background:var(--surface-alt); flex:none; overflow:hidden; display:flex; align-items:center; justify-content:center;}
.feed-avatar img{ width:100%; height:100%; object-fit:cover; display:block;}
.feed-avatar-fallback{ color:#fff; font-size:13px; font-weight:800; text-transform:uppercase; line-height:1;}
.feed-user{ font-weight:700; font-size:13.5px; cursor:pointer; width:fit-content;}
.feed-caption{ font-size:14px; line-height:1.4; margin-bottom:8px; cursor:pointer;}
.feed-caption.clamped{ display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:3; overflow:hidden;}
.feed-tag-row{ display:flex; align-items:center; gap:6px; font-size:11.5px; font-weight:600; color:#e2e2e2;}
.feed-tag-row .dot{ width:4px; height:4px; border-radius:50%; background:#888;}
.feed-tag-row [data-filter-style]{ cursor:pointer;}
.feed-actions{ position:absolute; right:14px; bottom:calc(var(--nav-clearance) + 36px); z-index:6; display:flex; flex-direction:column; align-items:center; gap:19px; color:#fff;}
.feed-action{ display:flex; flex-direction:column; align-items:center; gap:4px; cursor:pointer;}
.feed-action .icon{ width:27px; height:27px;}
.feed-action span{ font-size:12px; font-weight:600;}
.feed-action.liked .icon{ color:var(--danger);}
.feed-swipe-hint{ position:absolute; bottom:calc(var(--nav-clearance) + 8px); left:50%; transform:translateX(-50%); font-size:10px; color:rgba(255,255,255,.5); z-index:5; letter-spacing:.05em;}
/* Recovery affordance for when autoplay silently fails to start (or the
   video errors out mid-load) — without this, a card with a stuck video had
   no way at all to get it going, since the feed otherwise only auto-plays/
   pauses via IntersectionObserver with no manual control. Uses the same
   .play-badge/.badge-circle pattern as result.html's play toggle. Visible
   by default (no .hidden class in the markup) so it's already there as a
   fallback the instant a card renders, then hides itself once the
   'playing' event actually fires (not 'play' — see explore.html's
   wireVideoRecovery for why that distinction matters here). */
.feed-play{ position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); z-index:5;}
.explore-filter-chip{
  position:absolute; top:calc(env(safe-area-inset-top,0px) + 16px); left:50%; transform:translateX(-50%); z-index:50;
  align-items:center; gap:8px; background:rgba(0,0,0,.72); backdrop-filter:blur(6px);
  border:1px solid rgba(255,255,255,.14); border-radius:100px; padding:8px 14px; font-size:12px; font-weight:700; color:#fff; cursor:pointer;
  max-width:70%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.explore-filter-chip .fc-x{ opacity:.65; font-weight:900; margin-left:2px;}

/* Explore's own topbar token-chip mount (tracker item
   for-product-build-the-daily-token-claim--fngrwd, item 4: "ADD the chip
   to explore.html's topbar too -- it's currently the one page missing
   it"). explore.html has no traditional .topbar (it's a full-bleed video
   feed) — mounted as its own fixed top-left overlay instead, mirroring
   .explore-filter-chip's own floating-pill treatment but pinned to the
   opposite corner from the per-card mute button (top-right, see
   .feed-mute-btn inline styles in explore.html) so it never sits on top of
   it while scrolling. .dow-badge's own `left` was nudged from 14px to 84px
   (see that rule above) to leave this room without the two ever
   colliding. */
.explore-token-chip-slot{ position:absolute; top:calc(env(safe-area-inset-top,0px) + 16px); left:14px; z-index:56;}
.explore-token-chip-slot .token-chip-compact{ background:rgba(0,0,0,.72); backdrop-filter:blur(6px); border-color:rgba(255,255,255,.2);}
.explore-token-chip-slot .token-chip-compact .token-chip-balance{ color:#fff;}

/* Idea #3 — "Dream of the Day". Not a new page, just a badge on top of
   whichever dream the server picked as today's highlight (see
   get-feed.js's resolveDreamOfDay). pointer-events:none so it never
   blocks the like/share/mute controls or the tap-to-scroll gesture
   underneath it. */
.dow-badge{
  position:absolute; top:calc(env(safe-area-inset-top,0px) + 16px); left:84px; right:64px; z-index:6;
  border-radius:14px; overflow:hidden; padding:10px 13px; pointer-events:none;
  background:linear-gradient(135deg, rgba(131,58,180,.4), rgba(253,29,29,.28), rgba(252,176,69,.22)), rgba(20,20,22,.6);
  backdrop-filter:blur(6px); border:1px solid rgba(255,255,255,.18);
}
.dow-badge .dow-eyebrow{ font-size:9.5px; font-weight:800; text-transform:uppercase; letter-spacing:.06em; color:#fff; opacity:.9; margin-bottom:3px;}
.dow-badge .dow-title{ font-size:12.5px; font-weight:800; line-height:1.3; overflow:hidden; text-overflow:ellipsis; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;}
.dow-badge .dow-sub{ font-size:10.5px; color:#e8e8e8; margin-top:2px;}

/* ===== Admin (admin.html) ===== */
/* A single settings row — reuses .choice-card's card shell, no new
   container pattern needed for "one setting, one switch". */
.admin-row-sub{ font-size:12.5px; color:var(--text-muted); margin-top:4px; line-height:1.4;}
.admin-source-tag{ font-size:11px; font-weight:700; letter-spacing:.02em; color:var(--text-faint); margin-top:6px;}
.toggle-switch{
  position:relative; flex:none; width:50px; height:30px; border-radius:100px;
  background:var(--surface-alt); border:1px solid var(--border-strong); cursor:pointer;
  transition:background-color .18s ease, border-color .18s ease;
}
.toggle-switch::after{
  content:''; position:absolute; top:2px; left:2px; width:24px; height:24px; border-radius:50%;
  background:var(--text-faint); transition:transform .18s ease, background-color .18s ease;
}
.toggle-switch.on{ background:var(--gradient-ig); border-color:transparent;}
.toggle-switch.on::after{ transform:translateX(20px); background:#fff;}
.toggle-switch.disabled{ opacity:.5; cursor:default; pointer-events:none;}

/* ===== Token balance chip — shared base =====
   Shape only. The pill (border-radius:100px) reads as tappable the same
   way .btn does; each variant below owns its own fill.

   This base used to carry the --gradient-ig fill + warm shadow of the
   original full-size profile.html chip. profile.html moved to the plain
   variant in the 2026-07-30 night restyle (tracker item
   for-product-build-founder-approved-2026--to6ew2), leaving no consumer of
   the bare class at all — so those declarations were removed rather than
   left as an unreachable default that only looked like a live style. */
.token-chip{ display:flex; align-items:center; gap:8px; padding:9px 16px; border-radius:100px; text-decoration:none; max-width:max-content;}
.token-chip-balance{ font-size:13px; font-weight:800; color:var(--text-primary); white-space:nowrap;}
.token-chip-cta{ font-size:11px; font-weight:800; color:#fff; white-space:nowrap; border-left:1px solid rgba(255,255,255,.35); padding-left:8px;}

/* ===== Plain balance chip — js/purchase-sheet.js's mountBalanceChip
   `plain:true` variant (tracker item
   for-product-build-founder-approved-2026--to6ew2, Part 5).
   A quiet outlined pill reading "✦ 240 · Shop", and nothing else.
   Deliberately has NO claimable/low/healthy state rules at all: the
   founder's Profile-restyle spec removed every claim mechanic from that
   page (home.html is the sole claim/ritual surface now), so there is
   nothing left here for a claim state to style. A page can re-skin the
   colors in its own scoped <style> — profile.html does, for the night
   palette — while the structural rules stay here with the module that
   emits the markup, exactly as .token-chip-compact does. */
.token-chip-plain{ padding:7px 14px; gap:6px; background:var(--surface-alt); border:1px solid var(--border-strong);}
.token-chip-plain .token-chip-balance{ font-size:12.5px;}
.token-chip-plain .token-chip-star{ font-size:12px; line-height:1;}
.token-chip-plain .token-chip-sep{ font-size:12px; color:var(--text-faint); font-weight:400;}
.token-chip-plain .token-chip-cta{ border-left:none; padding-left:0; color:var(--text-muted);}

/* ===== Compact topbar balance chip (create.html/style.html/result.html) =====
   tracker item for-product-build-out-of-tokens-purchase-2y8hyw, item 3.
   Extends .token-chip above (same pill shape/class-name convention, see
   js/purchase-sheet.js's mountBalanceChip/renderBalanceChip) rather than a
   new design system — smaller for a topbar slot, and state-tinted
   (healthy/low) instead of the fixed --gradient-ig fill, per the
   founder-approved mock (docs/design/out-of-tokens-sheet.html, branch
   claude/manager-onboarding-xcq4ul): --accent-growth green once the
   balance is comfortable, --accent-value amber (the same "premium
   currency" gold this codebase already uses for the shop's "Best value"
   pack) once it drops under 100. Includes the small circled-plus
   affordance the founder explicitly added to that mock on 2026-07-26. */
.token-chip-compact{ padding:5px 5px 5px 12px; gap:6px; background:var(--surface-alt); border:1px solid var(--border-strong);}
.token-chip-compact .token-chip-balance{ font-size:12px;}
.token-chip-compact.healthy{ border-color:rgba(95,184,138,.5);}
.token-chip-compact.healthy .token-chip-balance{ color:#a8e0c2;}
.token-chip-compact.low{ border-color:rgba(217,166,83,.6);}
.token-chip-compact.low .token-chip-balance{ color:var(--accent-value);}
.token-chip-plus{ flex:none; width:16px; height:16px; border-radius:50%; background:rgba(255,255,255,.14); border:1px solid rgba(255,255,255,.25); color:#fff; font-size:11px; font-weight:800; display:flex; align-items:center; justify-content:center; line-height:1;}

/* ===== Out-of-tokens purchase sheet (js/purchase-sheet.js) =====
   tracker item for-product-build-out-of-tokens-purchase-2y8hyw. Reuses the
   existing .sheet-overlay/.sheet/.sheet-handle bottom-sheet pattern
   already used by result.html's edit/character/interp sheets, rather than
   the old centered .modal-overlay/.modal-card this replaces — matching
   the founder-approved mock (docs/design/out-of-tokens-sheet.html, branch
   claude/manager-onboarding-xcq4ul). Behavior is locked; only minor
   visual tweaks may follow. */
.purchase-sheet-title{ font-size:17px; font-weight:800; text-align:center; margin-bottom:6px;}
.purchase-sheet-body{ font-size:13px; color:var(--text-muted); text-align:center; line-height:1.5; margin-bottom:14px;}
.purchase-sheet-body b{ color:var(--text-primary);}
.purchase-meter{ height:8px; border-radius:100px; background:var(--surface-alt); margin-bottom:6px; overflow:hidden;}
.purchase-meter i{ display:block; height:100%; width:0%; background:linear-gradient(90deg,var(--accent-trust),#9ab0ff); border-radius:100px; transition:width .3s ease;}
.purchase-meter-label{ display:flex; justify-content:space-between; font-size:10.5px; color:var(--text-faint); margin-bottom:16px;}
.purchase-buy-btn{ width:100%; font-family:var(--font-body); font-weight:800; font-size:14.5px; border:none; border-radius:100px; padding:15px; background:#fff; color:#000; box-shadow:0 4px 14px -6px rgba(0,0,0,.5); margin-bottom:10px; cursor:pointer;}
.purchase-buy-btn:disabled{ opacity:.5; cursor:not-allowed;}
.purchase-error{ text-align:center; font-size:12px; color:var(--danger); margin-bottom:10px;}
.purchase-see-all{ text-align:center; font-size:12.5px; color:var(--accent-trust); font-weight:700; margin-bottom:14px; cursor:pointer;}
.purchase-wait-line{ text-align:center; font-size:12px; color:var(--text-muted); padding-top:12px; border-top:1px solid var(--border); margin-bottom:8px;}
.purchase-trust{ text-align:center; font-size:10.5px; color:var(--text-faint);}

/* ===== Daily token claim (tracker item
   for-product-build-the-daily-token-claim--fngrwd) =====
   (1) the compact topbar balance chip's pulsing "+N claimable" state
   (.token-chip-compact) — reuses the existing --accent-value gold this
   codebase already treats as its "premium currency"/reward color (see the
   shop's "Best value" pack badge) rather than introducing a new accent.
   The profile-page chip used to carry this state too; it no longer can —
   Profile's chip is the plain variant now and Home is the sole claim
   surface (tracker item for-product-build-founder-approved-2026--to6ew2),
   so those rules were removed rather than left as dead selectors.
   (2) the inline "Claim +N" button inside the out-of-tokens sheet
   (js/purchase-sheet.js's claimInline). (3) the dedicated claim sheet +
   its brief confetti burst. */
.token-chip-compact.claimable{ animation:pulse 1.8s ease-in-out infinite; box-shadow:0 0 0 0 rgba(217,166,83,.5); border-color:var(--accent-value);}
.token-chip-compact.claimable .token-chip-plus{ background:var(--accent-value); border-color:var(--accent-value); color:#1a1200;}

.claim-inline-btn{
  width:100%; font-family:var(--font-body); font-weight:800; font-size:13.5px; border:1.5px solid var(--accent-value);
  border-radius:100px; padding:12px; background:transparent; color:var(--accent-value); cursor:pointer; margin-bottom:10px;
}
.claim-inline-btn:disabled{ opacity:.5; cursor:not-allowed;}

/* No `position` override here on purpose -- this class is applied
   alongside the base .sheet class on the SAME element (class="sheet
   claim-sheet"), and .sheet already sets position:absolute (its bottom
   sheet anchoring: left/right/bottom:0, transform:translateY, etc). This
   used to redundantly set position:relative for .claim-sheet-confetti-
   host's position:absolute;inset:0 to anchor against -- but since both
   rules target the same element and this one comes later in the
   cascade, position:relative silently WON over .sheet's position:absolute
   (found via a real Playwright test failure: the claim sheet rendered
   pinned to the top of its overlay with zero bottom-anchoring, height
   equal to its own content instead of a proper bottom sheet, with no
   tappable backdrop around it at all -- tracker item
   for-product-sheet-dismissal-app-wide-fix-rlay70). position:absolute
   is just as valid a positioning ancestor for the confetti host's
   children, so simply not overriding it here fixes this with no other
   change needed. */
.claim-sheet{ text-align:center; overflow:hidden;}
.claim-sheet-badge{ font-size:40px; line-height:1; margin-bottom:6px;}
.claim-sheet-title{ font-size:17px; font-weight:800; margin-bottom:10px;}
.claim-sheet-amount{ font-size:40px; font-weight:900; color:var(--accent-value); margin-bottom:4px;}
.claim-sheet-streak{ font-size:12.5px; font-weight:700; color:var(--text-muted); margin-bottom:18px;}
/* Balance count-up display (Duolingo/mobile-game daily-reward pattern,
   founder directive 2026-07-28) — hidden until a claim actually succeeds
   (see js/purchase-sheet.js's runClaim), at which point it animates the
   account's balance old -> new. This replaces the old behavior of
   re-animating the reward amount itself a second time on success. */
.claim-sheet-balance{ font-size:14px; font-weight:700; color:var(--text-muted); margin-bottom:18px;}
.claim-sheet-balance #claim-sheet-balance-num{ color:var(--accent-value); font-weight:900;}
.claim-sheet-confetti-host{ position:absolute; inset:0; pointer-events:none; overflow:hidden;}
.claim-confetti-piece{
  position:absolute; top:-12px; width:8px; height:8px; border-radius:2px; opacity:0;
  animation:claim-confetti-fall 1.1s ease-in forwards;
}
@keyframes claim-confetti-fall{
  0%{ opacity:1; transform:translateY(0) rotate(0deg);}
  100%{ opacity:0; transform:translateY(220px) rotate(340deg);}
}

/* ===== Share mini-sheet (js/share-sheet.js) =====
   tracker item for-product-build-share-mini-sheet-share-r42tc4. Two
   stacked option rows (Share link / Save to device) rather than the
   purchase sheet's single centered CTA — reuses the same
   .sheet-overlay/.sheet/.sheet-handle/.sheet-title base this whole app's
   bottom sheets share. */
.share-sheet-option{ width:100%; display:flex; align-items:center; gap:14px; background:var(--surface); border:1px solid var(--border); border-radius:16px; padding:14px 16px; margin-bottom:10px; cursor:pointer; font-family:var(--font-body); text-align:left;}
.share-sheet-option:last-of-type{ margin-bottom:0;}
.share-sheet-option:disabled{ opacity:.5; cursor:not-allowed;}
.share-sheet-option .icon{ width:22px; height:22px; flex:none; color:var(--text-primary); display:flex;}
.share-sheet-option-label{ font-size:15px; font-weight:700; color:var(--text-primary);}
.share-sheet-error{ text-align:center; font-size:12px; color:var(--danger); margin-top:10px;}

/* ===== "Payment received" resume state (processing.html) =====
   Shown after a successful checkout return, while polling for the
   webhook's token credit (js/purchase-sheet.js's pollForCredit) — the
   founder-approved mock's Frame 2. Reuses .proc-wrap's existing full-
   screen wait-state layout rather than a new page. */
.proc-payment-resume{ flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:16px; padding:0 26px; text-align:center;}
.proc-payment-check{ width:64px; height:64px; border-radius:50%; background:rgba(95,184,138,.15); border:1.5px solid var(--accent-growth); display:flex; align-items:center; justify-content:center; color:var(--accent-growth); font-size:26px;}
.proc-payment-resume h2{ font-size:17px; font-weight:800; margin:0;}
.proc-payment-resume p{ font-size:12.5px; color:var(--text-muted); line-height:1.5; margin:0;}
.proc-payment-caption{ font-size:10.5px; color:var(--text-faint);}

/* ===== Token shop (shop.html) =====
   Palette per docs/SHOP_PALETTE_REDESIGN_SPEC.md — the shop used to be the
   one place besides the two documented --gradient-ig spots (bottom-nav "+",
   Profile avatar ring) that borrowed the hot IG gradient, which read as
   loud/urgent rather than calm. Baseline fix below (plain card border, plain
   white balance numeral matching .rec-timer's existing big-number pattern)
   applies to every visitor, no variant condition. Only the "best value" pack
   card's (pack700) border/badge color is a live in-product A/B test — see
   .token-pack-card.best / .token-pack-badge and the .shop-variant-a/
   .shop-variant-b rules further down, which shop.html's inline script
   assigns once per browser and persists in localStorage. The middle
   "Most popular" pack (pack300, added for Token Economy C's 3-pack
   lineup) uses its own fixed .token-pack-card.popular / .token-pack-
   badge-popular color, not part of that A/B test. */
.token-balance-card{ position:relative; background:var(--surface); border:1px solid var(--border-strong); border-radius:20px; padding:20px; margin:6px 0 22px;}
.token-balance-inner{ text-align:center;}
.token-balance-amt{ font-size:36px; font-weight:800; line-height:1.1; color:var(--text-primary);}
.token-balance-sub{ font-size:12.5px; color:var(--text-faint); margin-top:6px;}
.token-pack-card{ position:relative; background:var(--surface); border:1px solid var(--border-strong); border-radius:18px; padding:18px 20px; margin-bottom:14px; transition:border-color .15s ease;}
.token-pack-row{ display:flex; align-items:baseline; justify-content:space-between;}
.token-pack-amt{ font-weight:800; font-size:18px;}
.token-pack-price{ font-weight:800; font-size:20px;}
.token-pack-sub{ font-size:12.5px; color:var(--text-faint); margin-top:4px;}
.token-pack-badge{ position:absolute; top:-10px; right:16px; color:#fff; font-size:10px; font-weight:800; padding:4px 10px; border-radius:100px;}
.token-pack-card .btn{ margin-top:14px;}
/* "Most popular" badge (pack300, Token Economy C's 3-pack lineup) — fixed
   accent-growth color, deliberately NOT part of the "best value" A/B test
   below (that test is scoped to .token-pack-card.best specifically). */
.token-pack-card.popular{ border-color:var(--accent-growth);}
.token-pack-badge.token-pack-badge-popular{ background:var(--accent-growth);}
/* Direction A — "quiet ledger" (calm periwinkle-blue). */
.shop-variant-a .token-pack-card.best{ border-color:var(--accent-trust);}
.shop-variant-a .token-pack-card.best .token-pack-badge{ background:var(--accent-trust);}
/* Direction B — "premium currency" (muted warm gold). */
.shop-variant-b .token-pack-card.best{ border-color:var(--accent-value);}
.shop-variant-b .token-pack-card.best .token-pack-badge{ background:var(--accent-value);}

/* First-purchase +50% bonus callout (tracker item
   for-product-shop-first-purchase-50-bonus-bzx2d4) — promoted from a
   small/faint ".auth-sub" line to the most prominent element on the shop
   page per founder feedback (2026-07-27 live shop review): the research
   that introduced this bonus called it the single strongest payer-
   conversion lever on this page, so its visual weight now matches that —
   big bold headline, real border/background, not fine print. shop.html's
   script hides this by default (inline style="display:none;" in markup)
   and only reveals it once get-token-status.js's hasMadeFirstPurchase
   comes back explicitly false, so a repeat buyer who already redeemed the
   bonus never sees the claim again. Deliberately shares the SAME
   --accent-trust/--accent-value tokens as the "best value" pack's own
   .shop-variant-a/.shop-variant-b border above (base plain style here,
   two explicit per-variant overrides below) rather than introducing a
   third, untested color — so this callout reads as part of whichever
   palette direction this visitor's shop-palette A/B assignment already
   put them in, instead of becoming its own confound in that live test. */
.first-purchase-callout{ background:var(--surface); border:1.5px solid var(--border-strong); border-radius:18px; padding:16px 18px; margin-bottom:14px; text-align:center;}
.first-purchase-callout-headline{ font-size:19px; font-weight:800; line-height:1.3; color:var(--text-primary);}
.first-purchase-callout-sub{ font-size:12.5px; color:var(--text-muted); margin-top:5px;}
/* Direction A — "quiet ledger" (calm periwinkle-blue), matching .token-pack-card.best's own A/B color. */
.shop-variant-a .first-purchase-callout{ border-color:var(--accent-trust); background:var(--accent-trust-soft);}
.shop-variant-a .first-purchase-callout-headline{ color:var(--accent-trust);}
/* Direction B — "premium currency" (muted warm gold). */
.shop-variant-b .first-purchase-callout{ border-color:var(--accent-value); background:var(--accent-value-soft);}
.shop-variant-b .first-purchase-callout-headline{ color:var(--accent-value);}

/* ===== Tracker (tracker.html) ===== */
/* One item = one native <details>/<summary> — the summary row (always
   visible) holds the done button + priority buttons + title + expand
   chevron; the detail text, start control, and comments are the
   collapsible body. Done/priority/start/delete buttons stopPropagation()
   their own click in tracker.html's script so clicking them doesn't also
   toggle the item open/closed — only the title/whitespace area of the
   summary does that (native <summary> behavior, untouched). */
.tracker-item{ background:var(--surface); border:1px solid var(--border); border-radius:14px; margin-bottom:10px; overflow:hidden; transition:border-color .18s ease;}
.tracker-item[open]{ border-color:var(--border-strong);}
.tracker-item-summary{ list-style:none; cursor:pointer; display:flex; flex-direction:column; gap:10px; padding:14px; -webkit-tap-highlight-color:transparent;}
.tracker-item-summary::-webkit-details-marker{ display:none;}
.tracker-item-summary::marker{ content:'';}
/* Title sits on its own full-width line above the action controls
   (done/priority/delete) rather than squeezed into a narrow leftover
   column beside them — Ron's own feedback, since a long title used to
   wrap into many cramped lines fighting those buttons for horizontal
   space on a phone-width screen. */
.tracker-item-summary-top{ display:flex; align-items:flex-start; gap:10px;}
.tracker-item-summary-actions{ display:flex; align-items:center; gap:10px; flex-wrap:wrap;}
/* Replaced the old bare `<input type="checkbox" class="tracker-check">`
   with an explicitly labeled button — Ron's own feedback was that the
   plain checkbox square read as an ambiguous UI element, not something he
   could tell was a "mark as done" control at a glance. */
.tracker-done-btn{
  flex:none; display:flex; align-items:center; gap:4px; height:28px; padding:0 10px;
  border-radius:8px; border:1px solid var(--border-strong); background:var(--surface-alt);
  color:var(--text-muted); font-size:11px; font-weight:700; letter-spacing:.01em;
  cursor:pointer; font-family:var(--font-body); white-space:nowrap;
  transition:background .15s ease, color .15s ease, border-color .15s ease;
}
.tracker-done-btn:hover{ border-color:var(--text-faint); color:var(--text-primary);}
.tracker-done-btn.is-done{ background:#fff; border-color:#fff; color:#000;}
.tracker-done-btn .icon{ width:12px; height:12px; flex:none;}
.tracker-item-title{ flex:1; min-width:0; font-size:13.5px; font-weight:600; line-height:1.4;}
.tracker-category-badge{
  display:inline-block; font-size:9.5px; font-weight:800; letter-spacing:.04em; text-transform:uppercase;
  color:var(--text-faint); border:1px solid var(--border-strong); border-radius:6px; padding:2px 6px; margin-right:7px; vertical-align:middle;
}
.tracker-pri-group{ display:flex; gap:4px; flex:none;}
.tracker-pri-btn{
  width:24px; height:24px; border-radius:7px; border:1px solid var(--border-strong);
  background:var(--surface-alt); color:var(--text-faint); font-size:10.5px; font-weight:800;
  cursor:pointer; display:flex; align-items:center; justify-content:center; padding:0;
  font-family:var(--font-body); transition:background .15s ease, color .15s ease, border-color .15s ease;
}
.tracker-pri-btn:hover{ border-color:var(--text-faint);}
.tracker-pri-btn.active[data-priority="high"]{ background:var(--danger); border-color:var(--danger); color:#fff;}
.tracker-pri-btn.active[data-priority="medium"]{ background:#fff; border-color:#fff; color:#000;}
.tracker-pri-btn.active[data-priority="low"]{ background:var(--surface-alt); border-color:var(--text-muted); color:var(--text-primary);}
.tracker-chevron{ width:16px; height:16px; flex:none; color:var(--text-faint); transition:transform .18s ease;}
.tracker-item[open] .tracker-chevron{ transform:rotate(180deg);}
.tracker-item-detail{ padding:0 14px 12px 14px; font-size:12.5px; color:var(--text-muted); line-height:1.55;}
/* createdAt/startedAt/doneAt — omitted entirely (not rendered) whenever a
   given item has no real value for that field, rather than showing a
   fabricated date; see tracker-store.js's own SCHEMA CHANGE comment. */
.tracker-item-meta{ display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:8px; font-size:11px; color:var(--text-faint);}
.tracker-start-row{ padding:0 14px 14px;}
.tracker-start-btn{ padding:8px 14px; font-size:12px; font-weight:600;}
/* "Reviewed" control shares the start button's row styling — same
   secondary-button treatment, just a different one-way signal (see
   tracker.html's reviewControl/handleReviewChange). */
.tracker-review-btn{ padding:8px 14px; font-size:12px; font-weight:600;}
/* Small inline pill marking a done-but-not-yet-reviewed item, shown next
   to its title only within the Done section's "Needs review" group (see
   itemHTML's needsReviewBadge in tracker.html) — reuses the same accent
   red as the high-priority pill (.tracker-pri-btn.active[data-priority
   ="high"]) so it reads as "needs founder attention" at a glance. */
.tracker-needs-review-badge{
  display:inline-block; font-size:9.5px; font-weight:800; letter-spacing:.03em; text-transform:uppercase;
  color:#fff; background:var(--danger); border-radius:6px; padding:2px 6px; margin-right:7px; vertical-align:middle;
}
/* Small inline pill marking an OPEN item's "waiting for" status — Ron's
   own ask (tracker item for-product-tracker-add-a-visible-waitin-9zdkb8):
   "at-a-glance visibility of who each item is blocked on". Supersedes the
   earlier, narrower .tracker-needs-input-badge (removed) — see itemHTML's
   waitingForBadge in tracker.html for the full derivation logic (explicit
   item.waitingFor when set, else a display-only fallback from the item's
   title tag / last comment author for the ~35 items that predate this
   field). Purely additive/visual, does NOT change sort order (see
   sortItems — untouched by this feature), and never shown on a done item
   (that's needsReviewBadge's job above, an unrelated signal). Three
   distinct colors via data-for: --accent-trust (blue) for "ron" preserves
   the exact color the old .tracker-needs-input-badge already used for
   this exact case; --accent-value (gold) for "product"; --accent-growth
   (muted sage green, introduced above) for "growth" — deliberately NOT
   --danger/red, even though it was otherwise unused among the three:
   this page already uses --danger for .tracker-needs-review-badge (done
   items whose completion the founder hasn't looked at yet), and the old
   badge this one replaces explicitly chose blue over red for its own
   single case specifically so red keeps meaning "needs a look now"
   consistently across this page, not "waiting on Growth's normal turn". */
.tracker-waiting-badge{
  display:inline-block; font-size:9.5px; font-weight:800; letter-spacing:.03em; text-transform:uppercase;
  color:#fff; border-radius:6px; padding:2px 6px; margin-right:7px; vertical-align:middle;
}
.tracker-waiting-badge[data-for="ron"]{ background:var(--accent-trust);}
.tracker-waiting-badge[data-for="product"]{ background:var(--accent-value);}
.tracker-waiting-badge[data-for="growth"]{ background:var(--accent-growth);}
.tracker-comment-list{ padding:0 14px; display:flex; flex-direction:column; gap:8px; margin-bottom:14px;}
.tracker-comment-entry{ background:var(--surface-alt); border-radius:10px; padding:10px 12px;}
.tracker-comment-entry-head{ display:flex; align-items:baseline; gap:8px; margin-bottom:3px;}
.tracker-comment-author{ font-size:11px; font-weight:800; color:var(--text-primary);}
.tracker-comment-time{ font-size:10.5px; color:var(--text-faint);}
.tracker-comment-text{ font-size:12.5px; color:var(--text-muted); line-height:1.5; white-space:pre-wrap; word-break:break-word;}
.tracker-comment-empty{ font-size:12px; color:var(--text-faint); padding:0 14px 14px;}
/* Two compose areas, one per author ("Your comment" / "Claude's
   comment") — Ron's own request: "add a new comment area for me or for
   you", so his own notes and a build/review session's notes never
   overwrite each other (see tracker-store.js's SCHEMA CHANGE comment for
   the comment -> comments migration this replaced). */
.tracker-comment-compose-group{ padding:0 14px 16px; display:flex; flex-direction:column; gap:14px;}
.tracker-comment-compose-label{ font-size:10.5px; font-weight:700; color:var(--text-faint); text-transform:uppercase; letter-spacing:.04em; margin-bottom:6px;}
.tracker-comment-input{ min-height:60px; resize:vertical; font-size:12.5px; margin-bottom:8px; padding:10px 12px;}
.tracker-comment-save{ padding:8px 14px; font-size:12px; font-weight:600;}
.tracker-comment-save:disabled{ opacity:.55; cursor:default;}
/* ONE combined Done section spans both Tasks and Ideas — see
   doneSectionHTML() in tracker.html; this replaced two separate
   per-category done-<details> per Ron's own feedback. */
.tracker-done-details{ margin-top:2px; margin-bottom:18px;}
.tracker-done-summary{ list-style:none; cursor:pointer; display:flex; align-items:center; gap:8px; font-size:11.5px; font-weight:700; letter-spacing:.03em; color:var(--text-faint); padding:8px 4px; -webkit-tap-highlight-color:transparent;}
.tracker-done-summary::-webkit-details-marker{ display:none;}
.tracker-done-summary::marker{ content:'';}
/* "N new" pill next to the Done summary count, only rendered when at
   least one done item is still unreviewed (see doneSectionHTML). */
.tracker-review-count-badge{
  display:inline-block; font-size:9.5px; font-weight:800; letter-spacing:.03em; text-transform:uppercase;
  color:#fff; background:var(--danger); border-radius:100px; padding:2px 8px;
}
/* One-sentence completion digest ("N items done since your last
   review") — Ron's own lightweight-digest ask, deliberately just a
   count sentence rather than a richer report (see doneSectionHTML's own
   comment for why). Sits above the Needs review sub-section, inside the
   collapsible Done <details> so it doesn't clutter the page when
   collapsed. */
.tracker-review-digest{ font-size:12px; color:var(--text-muted); padding:2px 4px 10px;}
/* "Needs review" / "Reviewed" sub-section labels inside the Done list —
   same visual language as .section-label (Open Tasks/Ideas headings)
   but smaller, since these are one level deeper (sub-groups of Done,
   not top-level page sections). */
.tracker-done-subhead{ font-size:10.5px; font-weight:800; letter-spacing:.04em; text-transform:uppercase; color:var(--text-faint); padding:10px 4px 4px;}
.tracker-done-list{ margin-top:8px;}
.tracker-done-list .tracker-item{ opacity:.68;}
.tracker-done-list .tracker-item-title{ text-decoration:line-through;}
/* Needs-review items stay at full opacity (unlike every other done
   item, dimmed at .68 above) so a new completion the founder hasn't
   seen yet doesn't visually recede the same way an already-reviewed one
   does — the whole point of this sub-group is to stand out until it's
   cleared. */
.tracker-done-list .tracker-item.needs-review{ opacity:1;}
.tracker-empty{ font-size:12.5px; color:var(--text-faint); padding:2px 2px 16px;}
/* Delete button lives in the summary row alongside the priority buttons —
   same stopPropagation()-on-its-own-click treatment so clicking it doesn't
   also toggle the item open/closed. */
.tracker-delete-btn{
  width:24px; height:24px; flex:none; border:none; background:transparent; color:var(--text-faint);
  cursor:pointer; display:flex; align-items:center; justify-content:center; padding:0;
  transition:color .15s ease;
}
.tracker-delete-btn:hover{ color:var(--danger);}
/* "Read aloud" toggle (tracker.html's handleSpeakToggle) — same bare-icon
   sizing/hover treatment as .tracker-delete-btn right next to it, but its
   own accent color while active so the founder can tell at a glance which
   item (if any) is currently speaking and that clicking it again stops
   it. Reuses --accent-trust (blue), same color already used for the
   "Waiting Ron" badge elsewhere on this page — no new brand color needed
   for a purely-local playing/not-playing state. */
.tracker-speak-btn{
  width:24px; height:24px; flex:none; border:none; background:transparent; color:var(--text-faint);
  cursor:pointer; display:flex; align-items:center; justify-content:center; padding:0;
  transition:color .15s ease;
}
.tracker-speak-btn:hover{ color:var(--text-primary);}
.tracker-speak-btn.is-speaking{ color:var(--accent-trust);}
/* Add-item form sits above the Open Tasks/Ideas sections, inside the same
   owner-gated #tracker-content — a plain card using the same .field/.btn
   primitives every other form on this site already uses, not new form
   styling of its own. */
.tracker-add-form{ background:var(--surface); border:1px solid var(--border); border-radius:14px; padding:16px; margin-bottom:24px;}

.spinner-inline{ display:inline-block; width:16px; height:16px; border-radius:50%; border:2px solid rgba(255,255,255,.3); border-top-color:#fff; animation:spin .7s linear infinite;}

/* ===== Settings (profile.html's #sheet-account-overlay) ===== */
/* The sheet used to be one flat stack of fields/buttons with manual
   height-spacer divs and no visual grouping at all — this groups it into
   labeled sections (same .section-label already used for Profile's own
   Shared/Private grid headers) so Account / Your data / Tokens / Support
   & feedback read as distinct, scannable groups instead of one long list.
   Purely a spacing/grouping wrapper — every control inside still uses
   this app's existing .field/.btn/.auth-notice/.err-text primitives. */
.settings-section{ margin-bottom:22px;}
.settings-section:last-of-type{ margin-bottom:0;}
.settings-section .section-label{ margin-top:0;}

/* Support & feedback — the beta-feedback CTA reuses --gradient-ig, this
   app's established "special, inviting, tap me" treatment (already used
   for the token-balance chip and the profile insight card) rather than a
   plain muted row, since this is explicitly meant to entice a tap, not
   just sit there as one more settings option. */
.feedback-cta{
  display:flex; align-items:center; gap:12px; padding:14px 16px; border-radius:16px;
  background:var(--gradient-ig); cursor:pointer; margin-bottom:10px;
  box-shadow:0 10px 24px -12px rgba(253,29,29,.45);
  transition:transform .15s cubic-bezier(.34,1.56,.64,1), filter .15s ease;
}
.feedback-cta:hover{ filter:brightness(1.05);}
.feedback-cta:active{ transform:scale(.98);}
.feedback-cta-icon{ width:38px; height:38px; border-radius:50%; background:rgba(255,255,255,.22); display:flex; align-items:center; justify-content:center; flex:none; color:#fff;}
.feedback-cta-icon svg{ width:19px; height:19px;}
.feedback-cta-text{ flex:1; min-width:0;}
.feedback-cta-title{ font-weight:800; font-size:14px; color:#fff;}
.feedback-cta-sub{ font-size:11.5px; color:rgba(255,255,255,.88); margin-top:2px; line-height:1.35;}
.feedback-cta-chevron{ width:15px; height:15px; color:#fff; opacity:.85; flex:none; transform:rotate(-90deg);}

/* Contact support — a plainer row (same shell language as .choice-card,
   just compact) since this is a utility action, not the enticing one. */
.settings-row{
  display:flex; align-items:center; gap:12px; padding:13px 14px; border-radius:14px;
  background:var(--surface); border:1px solid var(--border); cursor:pointer;
  transition:border-color .15s ease, background-color .15s ease, transform .12s ease;
}
.settings-row:hover{ border-color:var(--border-strong); background:var(--surface-alt);}
.settings-row:active{ transform:scale(.99);}
.settings-row-icon{ width:36px; height:36px; border-radius:50%; background:var(--surface-alt); display:flex; align-items:center; justify-content:center; flex:none; color:var(--text-primary);}
.settings-row-icon svg{ width:17px; height:17px;}
.settings-row-text{ flex:1; min-width:0;}
.settings-row-title{ font-weight:700; font-size:13.5px;}
.settings-row-sub{ font-size:11.5px; color:var(--text-muted); margin-top:2px; line-height:1.35;}
.settings-row-chevron{ width:14px; height:14px; color:var(--text-faint); flex:none; transform:rotate(-90deg);}

/* Shared compose area both the feedback CTA and the support row open
   into (one form, a `type` distinction — see profile.html's script —
   rather than two separate pipelines/forms). */
.support-compose{ margin-top:12px;}

/* FAQ accordion (Settings > FAQ, tracker.html's idea-faq-section item) —
   each .faq-item toggles independently via its own .open class (siblings
   untouched), same open/close + rotating-chevron shape as create.html's
   .adv-section/.adv-toggle/.adv-content, just one row per question
   instead of one shared panel. */
.faq-item{ border:1px solid var(--border); border-radius:var(--radius-md); margin-bottom:8px; overflow:hidden;}
.faq-item:last-child{ margin-bottom:0;}
.faq-question{
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:13px 14px; cursor:pointer; font-size:13.5px; font-weight:700;
  transition:background-color .15s ease;
}
.faq-question:hover{ background:var(--surface-alt);}
.faq-chevron{ width:15px; height:15px; color:var(--text-faint); flex:none; transition:transform .2s ease;}
.faq-item.open .faq-chevron{ transform:rotate(180deg);}
.faq-answer{ display:none; padding:0 14px 14px; font-size:12.5px; color:var(--text-muted); line-height:1.5;}

/* ===== Interpretation Wave 1 — "The Interpreter's Chamber" (Direction B) =====
   docs/INTERPRETATION_WAVE1_SPEC.md, tracker item
   for-product-build-interpretation-wave-1--xuftyn. A full-screen
   conversational takeover (js/interpret-experience.js), NOT a bottom
   sheet — deliberately its own `.itp-*` prefix rather than reusing
   `.sheet`/`.sheet-overlay` (this is a fixed full-viewport surface, not an
   anchored-to-bottom panel), but it reuses this app's other established
   visual language throughout: `.style-card`'s portrait-fills-card/bottom-
   gradient-scrim grammar for the persona picker cards, `.opt-chip`'s pill
   shape for answer chips, and `.interp-text`/`.interp-disclosure`/
   `.auth-trust` verbatim for the reading card's body/disclaimer/trust line
   (imported by class name, not restyled).

   Mounted into `#app` (or document.body, same fallback
   js/purchase-sheet.js's ensureMounted already uses) as a single root
   `#itp-root.itp-overlay` — position:fixed so it truly takes over the
   viewport regardless of which host page's own #app it's appended into,
   independent of that page's own scroll position. */
.itp-overlay{
  position:fixed; inset:0; z-index:80; background:#050505;
  opacity:0; pointer-events:none; transition:opacity .2s ease;
  display:flex; flex-direction:column;
  font-family:var(--font-body); color:var(--text-primary);
}
.itp-overlay.open{ opacity:1; pointer-events:auto;}
/* Same reasoning as #app's own min(860px,92dvh)/32px-radius desktop
   treatment above — a full-bleed fixed overlay would otherwise stretch
   edge-to-edge on a wide viewport, which reads wrong next to every other
   page's centered "phone" card. */
@media (min-width:560px){
  .itp-overlay{ max-width:480px; left:50%; transform:translateX(-50%); border-radius:20px; overflow:hidden; box-shadow:0 40px 90px -24px rgba(0,0,0,.65);}
}

.itp-topbar{
  flex:none; display:flex; align-items:center; gap:10px;
  padding:calc(env(safe-area-inset-top,0px) + 14px) 16px 10px;
}
.itp-topbar-btn{
  width:36px; height:36px; border-radius:50%; background:rgba(255,255,255,.08); border:1px solid rgba(255,255,255,.14);
  display:flex; align-items:center; justify-content:center; color:#fff; cursor:pointer; flex:none;
  -webkit-tap-highlight-color:transparent;
}
.itp-topbar-btn .icon{ width:16px; height:16px;}
.itp-topbar-persona{ display:flex; align-items:center; gap:9px; min-width:0; flex:1;}
.itp-topbar-persona-name{ font-size:13.5px; font-weight:800; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
.itp-topbar-persona-sub{ font-size:10.5px; color:var(--text-faint); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}

/* Portrait — real illustration OR the documented accent-gradient+initial
   fallback (spec §12: "ship with a graceful fallback... so the branch
   never blocks on asset approval"). js/interpret-experience.js sets the
   fallback's background via inline style (persona.accent-derived
   gradient) since the color is per-persona DATA, not something a fixed
   CSS rule can express; this class only supplies layout/shape. The <img>
   and the fallback <div> are siblings — the img's own onerror (wired in
   JS) hides itself so the fallback beneath shows through, rather than
   this CSS trying to detect a broken image itself (no such selector
   exists). */
.itp-portrait{ position:relative; border-radius:50%; overflow:hidden; flex:none; background:var(--surface-alt);}
.itp-portrait img{ width:100%; height:100%; object-fit:cover; display:block;}
.itp-portrait-fallback{
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  font-weight:800; color:#fff; text-shadow:0 1px 4px rgba(0,0,0,.35);
}
.itp-topbar .itp-portrait{ width:32px; height:32px;}
.itp-topbar .itp-portrait-fallback{ font-size:13px;}

.itp-body{ flex:1; min-height:0; overflow-y:auto; -webkit-overflow-scrolling:touch; padding:4px 20px calc(20px + env(safe-area-inset-bottom,0px));}

/* ----- Dream-picker strip (tracker item for-product-chamber-dream-
   linkage-is-unc-00s2dr, founder verdict 2026-07-31 evening, overriding
   both directions docs/CHAMBER_DREAM_LINKAGE_SPEC.md had proposed): the
   Chamber's own dream picker is "the EXACT same videos-preview area as
   the homepage's My-dreams row" -- reuses js/dream-cards.js's shared
   dreamRowTileHTML markup/class names verbatim (js/interpret-
   experience.js's renderDreamStrip), present in every phase, above
   .itp-body.

   Scoped with the #itp-dream-strip ID (rather than styling the bare
   .dream-row-tile/.is-selected class names dreamRowTileHTML emits) so
   this looks identical regardless of which host page the overlay
   happens to be mounted from. home.html ALSO defines its own page-
   scoped `.dream-row-tile{...}` rule (150x100, its own dark-theme
   colors) in its own inline <style> block, at the same class-only
   specificity as anything added here — since that inline block loads
   after this shared stylesheet, a same-specificity rule here would lose
   to it there. An ID-anchored selector always outranks a class-only one
   regardless of source order, so the Chamber's own strip never
   accidentally inherits home.html's sizing when opened from that page,
   while home.html's own My Dreams row (which doesn't live under
   #itp-dream-strip) is completely untouched by any of this. */
.itp-dream-strip{
  flex:none; display:flex; gap:10px; overflow-x:auto; -webkit-overflow-scrolling:touch;
  scroll-snap-type:x mandatory; scrollbar-width:none;
  padding:0 16px 14px; margin-top:-4px;
}
.itp-dream-strip::-webkit-scrollbar{ display:none;}
#itp-dream-strip .dream-row-tile{
  flex:0 0 84px; width:84px; height:84px; border-radius:14px; padding:0;
  border:2px solid rgba(255,255,255,.14); scroll-snap-align:start;
  position:relative; overflow:hidden; background:var(--surface-alt);
  cursor:pointer; -webkit-tap-highlight-color:transparent; display:block;
}
#itp-dream-strip .dream-row-tile::after{ /* same footage scrim home.html's own row uses */
  content:""; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(180deg, transparent 55%, rgba(0,0,0,.4));
}
#itp-dream-strip .dream-row-tile.is-selected{ border-color:#fff; box-shadow:0 0 0 2px rgba(255,255,255,.55);}
#itp-dream-strip .dream-row-tile:active{ transform:scale(.96);}

/* ----- Picker phase ----- */
.itp-picker-title{ font-size:19px; font-weight:800; margin:6px 0 4px;}
.itp-picker-sub{ font-size:12.5px; color:var(--text-muted); margin-bottom:16px; line-height:1.4;}
.itp-carousel{
  display:flex; gap:12px; overflow-x:auto; scroll-snap-type:x mandatory; -webkit-overflow-scrolling:touch;
  padding:2px 2px 10px; margin:0 -2px;
}
.itp-carousel::-webkit-scrollbar{ display:none;}
/* Same portrait-fills-card/bottom-gradient-scrim grammar as .style-card,
   just wider (this is the picker's own showcase, not a small style
   swatch) and with an accent-tinted border ring instead of .style-card's
   plain white selected-ring (nothing here is ever "selected" mid-scroll —
   tapping a card advances immediately, spec §3.1). */
.itp-persona-card{
  position:relative; flex:none; width:72vw; max-width:300px; height:220px; border-radius:20px; overflow:hidden;
  scroll-snap-align:center; cursor:pointer; border:2px solid transparent; transition:border-color .15s ease;
  background:var(--surface-alt);
}
/* Fallback layer sits BELOW the real portrait image (spec §12: accent-
   gradient + initial, "so the branch never blocks on asset approval") —
   painted first, then the real `background-image` div stacks on top and
   simply shows nothing (transparent) when its own image 404s, letting
   this fallback show through untouched. No JS onerror/detection needed
   for the CARD background specifically (unlike the smaller circular
   .itp-portrait elsewhere, which DOES need a JS onerror since an <img>
   tag has no CSS equivalent of "fall through to what's underneath" the
   way a failed background-image naturally does). */
.itp-persona-card .itp-persona-card-fallback{ position:absolute; inset:0; display:flex; align-items:center; justify-content:center; font-size:64px; font-weight:800; color:rgba(255,255,255,.32);}
.itp-persona-card .itp-persona-card-bg{ position:absolute; inset:0; background-size:cover; background-position:center top;}
.itp-persona-card .itp-persona-card-scrim{ position:absolute; inset:0; background:linear-gradient(to top, rgba(0,0,0,.92), rgba(0,0,0,.15) 55%, rgba(0,0,0,.35));}
.itp-persona-card .itp-persona-card-content{ position:absolute; left:14px; right:14px; bottom:14px; z-index:2;}
.itp-persona-card .itp-persona-card-name{ font-weight:800; font-size:15.5px; color:#fff;}
.itp-persona-card .itp-persona-card-inspired{ font-size:10.5px; color:rgba(255,255,255,.6); margin-top:1px;}
.itp-persona-card .itp-persona-card-tagline{ font-size:11.5px; color:rgba(255,255,255,.88); margin-top:7px; line-height:1.35;}
/* Already-read badge (spec §3.1: personas the user already got a reading
   from on THIS dream show a small ✓ badge). */
.itp-persona-card-badge{
  position:absolute; top:10px; right:10px; z-index:3; width:22px; height:22px; border-radius:50%;
  background:#fff; color:#000; display:none; align-items:center; justify-content:center;
}
.itp-persona-card-badge .icon{ width:12px; height:12px;}
.itp-persona-card.read .itp-persona-card-badge{ display:flex;}
.itp-carousel-dots{ display:flex; justify-content:center; gap:6px; margin:2px 0 14px;}
.itp-carousel-dot{ width:6px; height:6px; border-radius:50%; background:rgba(255,255,255,.22); transition:background-color .15s ease, transform .15s ease;}
.itp-carousel-dot.active{ background:#fff; transform:scale(1.3);}
.itp-picker-footer{ font-size:11.5px; color:var(--text-faint); text-align:center; line-height:1.4; margin:2px 0 14px;}

/* ----- Loading phases (q_loading / r_loading) ----- */
.itp-loading-wrap{ display:flex; flex-direction:column; align-items:center; justify-content:center; gap:14px; padding:60px 10px; text-align:center;}
.itp-loading-wrap .itp-portrait{ width:64px; height:64px;}
.itp-loading-wrap .itp-portrait-fallback{ font-size:22px;}
.itp-loading-line{ display:flex; align-items:center; gap:9px; color:var(--text-muted); font-size:13.5px;}

/* ----- Questions phase — chat transcript ----- */
.itp-transcript{ display:flex; flex-direction:column; gap:12px; padding:6px 0 16px;}
.itp-bubble-row{ display:flex;}
.itp-bubble-row.mine{ justify-content:flex-end;}
.itp-bubble{
  max-width:82%; padding:11px 14px; border-radius:16px; font-size:13.5px; line-height:1.5;
}
/* Founder walkthrough punch list (2026-08-01, tracker item
   for-product-founder-walkthrough-punch-li-t33k3y, item 7): the QUESTION
   bubble used to inherit plain var(--text-primary) (#fff), the exact same
   color the answer chips below (.itp-chip) also use -- too similar to
   visually tell apart at a glance. Bold white for the question (it's the
   primary content of this screen) vs. the chips' muted gray + visible
   border below (secondary/interactive) is a real color+weight split, not
   just a font-size nudge. */
.itp-bubble.persona{ background:var(--surface-alt); border:1px solid var(--border); border-bottom-left-radius:5px; color:var(--text-primary); font-weight:700;}
.itp-bubble.mine{ background:#fff; color:#000; border-bottom-right-radius:5px; font-weight:600;}
.itp-typing-dots{ display:inline-flex; gap:3px; padding:2px 0;}
.itp-typing-dots i{ width:5px; height:5px; border-radius:50%; background:var(--text-faint); animation:itpTyping 1.1s ease-in-out infinite;}
.itp-typing-dots i:nth-child(2){ animation-delay:.15s;}
.itp-typing-dots i:nth-child(3){ animation-delay:.3s;}
@keyframes itpTyping{ 0%,60%,100%{ opacity:.3; transform:translateY(0);} 30%{ opacity:1; transform:translateY(-2px);} }

.itp-progress-dots{ display:flex; justify-content:center; gap:5px; margin:2px 0 10px;}
.itp-progress-dot{ width:5px; height:5px; border-radius:50%; background:rgba(255,255,255,.18);}
.itp-progress-dot.done{ background:rgba(255,255,255,.5);}
.itp-progress-dot.current{ background:#fff;}

.itp-chip-row{ display:flex; flex-wrap:wrap; gap:8px; margin:2px 0 10px;}
/* color changed from var(--text-primary) (#fff, same as the question
   bubble above) to var(--text-muted) -- see .itp-bubble.persona's own
   comment just above for why these two needed to visually diverge. */
.itp-chip{ padding:8px 14px; border-radius:100px; background:var(--surface); border:1px solid var(--border); color:var(--text-muted); cursor:pointer; font-size:12.5px; font-weight:600; transition:background .15s ease, border-color .15s ease, color .15s ease;}
.itp-chip:hover{ border-color:var(--text-faint); color:var(--text-primary);}
.itp-chip:active{ transform:scale(.96);}

/* Free-text "type your answer" field — moved INLINE (part of .itp-body's
   own scrolling content, right after the question/chips and BEFORE "Skip
   this question") and given a deliberately LIGHTER visual treatment than
   the old docked-bar composer had (founder walkthrough punch list,
   2026-08-01, tracker item for-product-founder-walkthrough-punch-li-t33k3y,
   item 7): a near-invisible tinted background instead of the same solid
   --surface every other field in the app uses, muted text instead of
   --text-primary, and a plain outline send affordance instead of a solid
   filled white circle -- this field is a secondary escape hatch here, not
   the primary answer mechanism (the chips are), and shouldn't visually
   compete with them. #itp-composer-field/#itp-composer-send keep their
   IDs unchanged (existing tests target them directly) even though they no
   longer live inside the old #itp-composer docked bar, which is removed
   entirely now that every phase this component has only ever needed it
   for (questions) renders it inline instead. */
.itp-inline-composer{ display:flex; align-items:center; gap:8px; margin:2px 0 12px;}
.itp-composer-field{
  flex:1; background:rgba(255,255,255,.04); border:1px solid var(--border); border-radius:100px; padding:10px 15px;
  font-family:var(--font-body); color:var(--text-muted); font-size:13px; font-weight:400; min-width:0;
}
.itp-composer-field::placeholder{ color:var(--text-faint);}
.itp-composer-field:focus{ color:var(--text-primary); outline:1.5px solid var(--border-strong); border-color:transparent;}
.itp-composer-send{
  width:34px; height:34px; border-radius:50%; background:transparent; color:var(--text-faint); border:1px solid var(--border); flex:none; cursor:pointer;
  display:flex; align-items:center; justify-content:center; -webkit-tap-highlight-color:transparent; transition:color .15s ease, border-color .15s ease;
}
.itp-composer-send:not(:disabled){ color:var(--text-primary); border-color:var(--border-strong);}
.itp-composer-send:disabled{ opacity:.5; cursor:not-allowed;}
.itp-composer-send .icon{ width:13px; height:13px; transform:rotate(-90deg);}

.itp-skip-row{ display:flex; justify-content:center; padding:2px 0 8px;}

/* Persistent "Just interpret it →" escape (spec §3.3, non-negotiable) —
   own class rather than reusing .link-text so it can carry the persona
   accent color (set inline per-persona in JS) without a specificity
   fight against .link-text's fixed --text-muted color. */
.itp-escape-link{ font-size:12.5px; font-weight:700; text-align:center; cursor:pointer; text-decoration:underline; text-underline-offset:3px; padding:6px 0;}

/* ----- Reading phase ----- */
.itp-reading-card{
  border:1px solid var(--border-strong); border-radius:18px; padding:18px 16px 14px; margin:6px 0 16px;
  background:var(--surface-alt); border-left-width:3px; /* left border-color set inline per-persona accent */
}
.itp-reading-persona-line{ display:flex; align-items:center; gap:8px; margin-bottom:10px;}
.itp-reading-persona-name{ font-size:12.5px; font-weight:800;}
/* .interp-text (reused verbatim) supplies the reading body's own
   typography — no .itp-reading-card override needed beyond spacing. */
.itp-reading-actions{ display:flex; flex-direction:column; gap:10px; align-items:center; margin-top:4px;}

.itp-error-copy{ font-size:13px; color:var(--text-muted); line-height:1.5; margin:16px 0; text-align:center;}
.faq-item.open .faq-answer{ display:block;}
