/* ═══════════════════════════════════════════════════════════
   CANOPY — Radical Modern AI Aesthetic
   Claude / Opal / Apple Intelligence Vibe
   ═══════════════════════════════════════════════════════════ */

/* ── Fonts: Local Swansea Fonts ──
   Metric overrides: Swansea's TTF declares ascent 0.75em / descent 0.25em,
   but its cap/digit glyphs span ~0.71em above baseline vs ~0.02em below —
   so text rode ~1.7px HIGH inside any centered line box (every pill, every
   input, every button), regardless of the box model. These overrides re-split
   the em so the optical center of caps/digits lands on the line-box center
   (measured skew −0.31px, matching -apple-system's −0.27px), while descent
   24% still clears real descenders (g/y/p measured 0.229em).
   2026-07-13: the same split is now BURNED INTO the TTFs themselves via
   fontTools (hhea + sTypo*, ?v=2 — the originals shipped a spec-violating
   POSITIVE sTypoDescender of +229), so engines without override support
   (< iOS 17) get identical rendering from the file. The overrides stay as
   the declared contract; file and CSS carry the same numbers. */
@font-face {
  font-family: 'Swansea';
  src: url('../fonts/Swansea.ttf?v=2') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  ascent-override: 91.5%;
  descent-override: 24%;
  line-gap-override: 0%;
  /* Restrict to Basic Latin so symbols (·, ▲, ▼, →, etc.) fall through to -apple-system */
  unicode-range: U+0020-007E, U+00A0, U+00C0-00FF;
}
@font-face {
  font-family: 'Swansea';
  src: url('../fonts/Swansea-Bold.ttf?v=2') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  ascent-override: 91.5%;
  descent-override: 24%;
  line-gap-override: 0%;
  unicode-range: U+0020-007E, U+00A0, U+00C0-00FF;
}

:root {
  -webkit-tap-highlight-color: transparent;
  --bg: #000000;
  --surface: rgba(255, 255, 255, 0.02);
  --surface-elevated: rgba(255, 255, 255, 0.06);
  --surface-hover: rgba(255, 255, 255, 0.05);
  --border: rgba(255, 255, 255, 0.06);
  --border-soft: rgba(255, 255, 255, 0.04);
  --input-line-height: 20px; /* text-input contract — single source for field metrics */
  --text: #ffffff;
  --text-secondary: rgba(255, 255, 255, 0.5);
  --text-tertiary: rgba(255, 255, 255, 0.3);

  --accent: #E3E3E3;
  --gain: #1df28b;
  --loss: #ff4d4d;
  --amber: #ffb800;

  /* ─ Glassmorphism (Apple-esque) ─ */
  --glass-bg: rgba(10, 10, 12, 0.5);
  --glass-blur: 24px;
  --glass-saturate: 180%;
  --glass-border: rgba(255, 255, 255, 0.1);

  /* ─ Corner radii ─
     Value-named rungs: a behavior-preserving 1:1 tokenization of the radii
     already in use (single source of truth; a future pass can collapse rungs
     without hunting literals). --radius-pill is the fully-rounded value used by
     pills/chips (previously hardcoded as 100px and, in a few small pills, 999px
     — both render identically on short elements). One-off radii (2/3/4/5px,
     22px, device-frame radii in simulator.css) intentionally stay literal. */
  --radius-6: 6px;
  --radius-8: 8px;
  --radius-10: 10px;
  --radius-12: 12px;
  --radius-14: 14px;
  --radius-16: 16px;
  --radius-18: 18px;
  --radius-20: 20px;
  --radius-24: 24px;
  --radius-pill: 100px;

  /* ─ Apple Intelligence Colors ─ */
  --ai-purple: #BC82F3;
  --ai-pink: #F5B9EA;
  --ai-blue: #8D9FFF;
  --ai-deep-purple: #AA6EEE;
  --ai-coral: #FF6778;

  --font: 'Swansea', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  --heading: 'Swansea', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  --mono: 'Swansea', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-display: 'Space Grotesk', 'Inter', -apple-system, sans-serif;

  --safe-top: max(env(safe-area-inset-top, 0px), 14px);
  --safe-bottom: max(env(safe-area-inset-bottom, 0px), 34px);
  --header-control-h: 34px;
  --header-h: calc(var(--safe-top) + var(--header-control-h) + 18px);
  --tab-h: 70px;
  --page-px: 24px;

  /* ─ Motion tokens ─ */
  --ease: cubic-bezier(0.32, 0.72, 0, 1);
  --ease-out: cubic-bezier(0.32, 0.72, 0, 1);
  --ease-spring: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-ios: cubic-bezier(0.32, 0.72, 0, 1);
  --motion-fast: 0.18s;
  --motion-normal: 0.26s;
  --motion-slow: 0.42s;
}

@media (min-width: 500px) {
  .iphone-chassis {
    --safe-top: 59px;
  }
}

[data-theme="light"] {
  --bg: #f2f2f7;
  --surface: #ffffff;
  --surface-elevated: #ffffff;
  --surface-hover: rgba(0, 0, 0, 0.04);
  --border: rgba(0, 0, 0, 0.08);
  --border-soft: rgba(0, 0, 0, 0.05);
  --text: #1c1c1e;
  --text-secondary: rgba(0, 0, 0, 0.5);
  --text-tertiary: rgba(0, 0, 0, 0.3);
  --glass-bg: rgba(242, 242, 247, 0.75);
  --glass-border: rgba(0, 0, 0, 0.08);
  --gain: #00a86b;
  --loss: #e53935;
  color-scheme: light;
}

/* Light mode element overrides for hardcoded dark values */
[data-theme="light"] .mesh-bg { background: var(--bg); }
[data-theme="light"] .tab-btn.active { background: rgba(0,0,0,0.07); box-shadow: inset 0 1px 1px rgba(0,0,0,0.06); }
[data-theme="light"] .market-dot[data-state="closed"],
[data-theme="light"] .market-dot[data-state="holiday"] { background: rgba(0,0,0,0.25); }

html { transition: background-color var(--motion-normal) var(--ease), color var(--motion-normal) var(--ease); }

*, *::before, *::after {
  margin: 0; padding: 0; box-sizing: border-box;
}
* { -webkit-tap-highlight-color: transparent; }
body {-webkit-tap-highlight-color: transparent;
}

html, body {
  height: 100%; overflow: hidden; background: var(--bg); color: var(--text);
  font-family: var(--font); font-size: 16px; -webkit-font-smoothing: antialiased;
}

.ai-card,
.ai-surface {
  position: relative;
  border: 1px solid transparent;
  overflow: hidden;
}

.ai-card::before,
.ai-surface::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  padding: 1px;
  color: var(--text);
  background: conic-gradient(from 150deg, var(--ai-purple), var(--ai-pink), var(--ai-blue), var(--ai-deep-purple), var(--ai-purple));
  -webkit-mask: linear-gradient(currentColor 0 0) content-box, linear-gradient(currentColor 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  opacity: 0.58;
}

.ai-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  width: fit-content;
  padding: 4px 9px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--ai-purple) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--ai-purple) 24%, transparent);
  color: var(--text-secondary);
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.ai-badge::before {
  content: '✦';
  color: var(--ai-purple);
  font-size: 9px;
  line-height: 1;
}

.ai-shimmer {
  position: relative;
  overflow: hidden;
  background: linear-gradient(105deg,
    color-mix(in srgb, var(--surface-elevated) 84%, transparent) 0%,
    color-mix(in srgb, var(--ai-purple) 22%, transparent) 28%,
    color-mix(in srgb, var(--ai-pink) 20%, transparent) 48%,
    color-mix(in srgb, var(--ai-blue) 22%, transparent) 66%,
    color-mix(in srgb, var(--surface-elevated) 84%, transparent) 100%);
  background-size: 240% 100%;
  animation: aiShimmer 1.65s var(--ease) infinite;
}

@keyframes aiShimmer {
  from { background-position: 180% 50%; }
  to { background-position: -60% 50%; }
}

.mesh-bg {
  position: fixed; inset: 0; z-index: 0; overflow: hidden; background: #000; pointer-events: none;
}
.mesh-bg::before {
  content: ''; position: absolute; top: -20%; left: -10%; width: 70vw; height: 70vw;
  background: radial-gradient(circle, rgba(40,40,100,0.4) 0%, rgba(0,0,0,0) 70%);
  filter: blur(80px); animation: float1 20s infinite alternate ease-in-out;
}
.mesh-bg::after {
  content: ''; position: absolute; bottom: -10%; right: -20%; width: 80vw; height: 80vw;
  background: radial-gradient(circle, rgba(20,60,50,0.3) 0%, rgba(0,0,0,0) 70%);
  filter: blur(90px); animation: float2 25s infinite alternate ease-in-out;
}
@keyframes float1 { 0% { transform: translate(0,0); } 100% { transform: translate(10%, 15%); } }
@keyframes float2 { 0% { transform: translate(0,0); } 100% { transform: translate(-15%, -10%); } }

.num, .mono, [data-num] { font-feature-settings: "tnum" on, "lnum" on; }

.app {
  height: 100%;
  width: 100%;
  display: flex;
  flex-direction: column;
  position: relative;
  z-index: 1;
  overflow: hidden;
}

.header {
  min-height: var(--header-h);
  padding: calc(var(--safe-top) + 6px) max(var(--page-px), env(safe-area-inset-right, 0px)) 16px max(var(--page-px), env(safe-area-inset-left, 0px));
  display: grid;
  grid-template-columns: var(--header-control-h) minmax(0, 1fr);
  align-items: center;
  gap: 10px;
  background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  -webkit-transform: translateZ(0); transform: translateZ(0);
  border-bottom: 1px solid var(--glass-border);
  position: fixed; top: 0; left: 0; right: 0; z-index: 100;
  transition: background var(--motion-slow) var(--ease), backdrop-filter var(--motion-slow) var(--ease), border-color var(--motion-slow) var(--ease);
}
.header-left,
.header-right {
  display: flex;
  align-items: center;
  min-width: 0;
}
.header-left {
  justify-content: flex-start;
  gap: 0;
}
.header-right {
  display: none;
  justify-content: flex-end;
}

.header.ai-active {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom-color: transparent;
}

.logo-icon {
  width: 32px; height: 32px; border-radius: var(--radius-10);
  background: linear-gradient(135deg, rgba(255,255,255,0.4) 0%, rgba(255,255,255,0.05) 100%);
  border: 1px solid rgba(255,255,255,0.2); box-shadow: 0 4px 12px rgba(255,255,255,0.1);
  display: flex; align-items: center; justify-content: center;
}
.logo-icon::after {
  content: ''; width: 12px; height: 12px; border-radius: 50%;
  background: #fff; box-shadow: 0 0 10px #fff;
}

/* ── Text-input contract ────────────────────────────────────────────
   One box model for every text-entry control (same rules as .ai-input's
   caret doctrine in ai-assistant.css, with its border math corrected):
     1. Form controls do NOT inherit font-family. Without an explicit
        family they fall back to UA fonts with foreign metrics — the
        header search pill was rendering Arial 13.3px on web and SF on
        iOS, which is why its text never sat right in the box.
     2. Integer px line-height on text fields (--input-line-height) —
        unitless multipliers produce fractional line boxes (1.3 × 16 =
        20.8) that round differently per engine and float the caret
        off the visual center on WKWebView.
     3. No fixed heights on text fields: visual height = line-height
        + vertical padding + borders (border-box via the universal
        reset above). Padding does the centering.
   :where() keeps this rule at ZERO specificity, so any later rule —
   even a bare class like .comm-compose-input { line-height: 1.5 } —
   still wins the cascade. Without it the :not() chain scored (0,3,1)
   and silently flattened deliberate textarea spacing (caught in
   review). Marquee fields (.search-input, .ai-input, .txn-field)
   also declare the token explicitly, both as local documentation and
   as a fallback for engines without :where(). */
input, textarea, select, button { font-family: var(--font); }
:where(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), textarea) {
  line-height: var(--input-line-height);
}
/* Older WKWebView gives the placeholder box its own 'normal' line-height,
   floating placeholder text off the caret/value position — pin it to the
   field's own metrics so both always start at the same spot. Plain selectors:
   engines are picky about pseudo-elements behind :where() (verified live —
   the :where() form never applied in Chromium). */
input::placeholder,
textarea::placeholder {
  line-height: inherit;
}

.header-search-btn {
  width: 100%;
  height: var(--header-control-h);
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
  min-width: 0;
  padding: 0 14px;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-secondary);
  /* Explicit size — as a <button> this pill got UA type metrics
     (Arial 13.3px on Chrome, SF on iOS) before the input contract. */
  font-size: 14px;
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  box-shadow: 0 4px 16px rgba(0,0,0,0.2), inset 0 0.5px 0 rgba(255,255,255,0.1);
  cursor: pointer;
  transition: transform var(--motion-fast) var(--ease), color var(--motion-normal) var(--ease), background var(--motion-normal) var(--ease);
}
.header-search-btn svg {
  width: 18px;
  height: 18px;
  flex: 0 0 18px;
  stroke-width: 2;
}
.header-search-text {
  display: inline-flex;
  align-items: center;
  height: 18px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-secondary);
  font-family: var(--font);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0;
  line-height: 18px;
  transform: translateY(1.5px);
}
.header-search-btn:active {
  transform: scale(0.985);
  background: rgba(255,255,255,0.12);
  color: var(--text);
}

.market-status {
  display: flex; align-items: center; gap: 6px; justify-content: center; padding: 0 12px; height: var(--header-control-h); border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2), inset 0 0.5px 0 rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.1);
  transition: transform var(--motion-fast) var(--ease);
  white-space: nowrap;
  flex-shrink: 0;
  padding-top: 1px; /* Optical centering for top-heaviness */
}
.market-status:active { transform: scale(0.96); }

.market-status,
.header-context,
.header-search-btn {
  animation: statusPillFade 0.28s var(--ease) 80ms both;
}

@keyframes statusPillFade {
  from { opacity: 0; transform: translate3d(0, -4px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

.market-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.4);
  box-shadow: 0 0 6px rgba(255, 255, 255, 0.3);
  flex-shrink: 0;
}
.market-dot[data-state="open"]    { background: var(--gain); box-shadow: 0 0 8px var(--gain); animation: marketPulse 2.4s ease-in-out infinite; }
.market-dot[data-state="pre"]     { background: var(--ai-blue); box-shadow: 0 0 8px var(--ai-blue); }
.market-dot[data-state="after"]   { background: var(--amber); box-shadow: 0 0 8px var(--amber); }
.market-dot[data-state="closed"]  { background: rgba(255, 255, 255, 0.35); }
.market-dot[data-state="holiday"] { background: rgba(255, 255, 255, 0.35); }
@keyframes marketPulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.6; transform: scale(1.25); }
}

.market-label { font-family: var(--font); font-size: 12px; font-weight: 500; color: var(--text-secondary); transform: translateY(1px); }

/* ── Header Context Strip ─────────────────────────────────────────────────
 * The middle slot of the header. A flexible glass card that shows a rotating
 * contextual sentence (greeting, portfolio delta, trial countdown, etc.).
 * Tap → fires triggerAIQuery() with the contextually-paired prompt, so the
 * strip becomes a one-tap shortcut into the most relevant question.
 */
.header-context {
  width: 100%;
  max-width: none;
  min-width: 0;
  padding: 0 12px;
  height: var(--header-control-h);
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: 0 4px 16px rgba(0,0,0,0.2), inset 0 0.5px 0 rgba(255,255,255,0.1);
  display: none;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: auto;
  transition: border-color var(--motion-slow) var(--ease);
  -webkit-tap-highlight-color: transparent;
}

.header-context-inner {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0;
  width: 100%;
  justify-content: center;
  transition: opacity var(--motion-normal) var(--ease), transform var(--motion-slow) var(--ease);
  will-change: opacity, transform;
  padding-top: 1px; /* Optical centering for top-heaviness */
}
.header-context-pulse {
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--ai-blue);
  box-shadow: 0 0 6px var(--ai-blue);
  flex-shrink: 0;
  animation: ctxPulse 2.6s ease-in-out infinite;
}
@keyframes ctxPulse {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; box-shadow: 0 0 10px var(--ai-blue); }
}

/* Tone-driven pulse coloring — gain/loss/pro */
.header-context[data-tone="gain"] .header-context-pulse {
  background: var(--gain); box-shadow: 0 0 8px var(--gain);
}
.header-context[data-tone="loss"] .header-context-pulse {
  background: var(--loss); box-shadow: 0 0 8px var(--loss);
}
.header-context[data-tone="pro"]  .header-context-pulse {
  background: var(--ai-purple); box-shadow: 0 0 8px var(--ai-purple);
}

.header-context-text {
  font-family: var(--font);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transform: translateY(1px); /* Match dot optical baseline */
}
.header-context[data-tone="gain"] .header-context-text { color: var(--gain); }
.header-context[data-tone="loss"] .header-context-text { color: #ff8a82; }
.header-context[data-tone="pro"]  .header-context-text { color: rgba(220, 200, 255, 0.92); }

/* Note: .ai-native-toast position/visibility is owned by ai-assistant.css —
 * it's now absolutely positioned so it doesn't steal flex space from the
 * always-on header context strip. */

#screen-container { flex: 1; position: relative; overflow: hidden; width: 100vw; }

.screen-slider {
  display: flex;
  width: 400vw;
  height: 100%;
  transition: transform 0.42s var(--ease);
  will-change: transform;
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

/* ── Screen Transitions (iOS-style swipe) ──
   -webkit-overflow-scrolling: touch enables GPU momentum on iOS WKWebView.
   overscroll-behavior is intentionally 'auto' so iOS native rubber-band bounce
   is preserved — 'contain' made scrolling feel stiff and un-iOS. PTR still
   works because touchstart only activates at scrollTop <= 0. */
.screen {
  position: relative;
  width: 100vw;
  flex-shrink: 0;
  height: 100%;
  padding: calc(var(--header-h) + 10px) var(--page-px) calc(var(--tab-h) + var(--safe-bottom) + 80px);
  overflow-y: auto; overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-y: auto;
  opacity: 0;
  transition: opacity var(--motion-normal) var(--ease);
  will-change: scroll-position;
}
.screen.active { opacity: 1; }
.screen-slider.dragging .screen { opacity: 1; }
.screen::-webkit-scrollbar { display: none; }

@media (max-width: 499px) {
  .screen {
    padding-bottom: calc(var(--tab-h) + var(--safe-bottom) + 112px);
  }
}

/* data-source-badge removed */

/* ── Tab Bar (fluid morph active indicator) ── */
.tab-bar {
  position: fixed; bottom: calc(var(--safe-bottom) + 8px); left: 50%; transform: translateX(-50%);
  display: flex; gap: 4px; padding: 6px; 
  background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-border); border-radius: var(--radius-pill); z-index: 100;
  box-shadow: 0 20px 40px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.1);
}

.tab-btn {
  display: flex; align-items: center; justify-content: center; width: 50px; height: 50px;
  border-radius: 50px; background: transparent; border: none; color: var(--text-tertiary);
  cursor: pointer;
  transition: width 0.28s var(--ease), background var(--motion-normal) var(--ease), color var(--motion-normal) var(--ease), box-shadow var(--motion-normal) var(--ease), transform var(--motion-fast) var(--ease);
}
.tab-btn svg { width: 22px; height: 22px; stroke-width: 1.5px; transition: transform var(--motion-fast) var(--ease); }
.tab-btn:active { transform: scale(0.94); }
.tab-btn:active svg { transform: scale(0.9); }
.tab-btn.active { background: rgba(255,255,255,0.1); color: var(--text); width: 64px; box-shadow: inset 0 1px 1px rgba(255,255,255,0.1); }
.tab-label { display: none; }

/* ── Splash ── */
#splash-screen { position: fixed; inset: 0; background: #000 url('../assets/splash-bg-dark.png?v=2') 0 0 / 150px 150px repeat; z-index: 9999; display: flex; flex-direction: column; align-items: center; justify-content: center; transition: opacity 0.38s var(--ease), transform 0.42s var(--ease); transform: translate3d(0, 0, 0) scale(1); will-change: opacity, transform; }
#splash-screen.hidden { opacity: 0; transform: translate3d(0, -6px, 0) scale(0.985); pointer-events: none; }
.splash-app-icon { width: 64px; height: 64px; padding: 12px; display: flex; align-items: center; justify-content: center; margin: 0 auto 14px; border-radius: var(--radius-18); background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.08); box-shadow: 0 0 24px rgba(255,255,255,0.08), inset 0 1px 0 rgba(255,255,255,0.08); opacity: 0; transform: translateY(10px) scale(0.96); animation: splashIntro 0.8s var(--ease) forwards; overflow: hidden; }
.splash-logo-img { width: 40px; height: 40px; display: block; object-fit: contain; filter: none; }
.splash-logo { font-family: 'Swansea', var(--heading); font-size: 28px; font-weight: 700; letter-spacing: -0.03em; color: #fff; text-transform: none; opacity: 0; transform: translateY(10px); animation: splashIntro 0.8s var(--ease) 0.1s forwards; text-shadow: 0 0 28px rgba(255,255,255,0.28); }
.splash-pulse { width: 32px; height: 2px; background: #fff; margin-top: 14px; opacity: 0; transform-origin: left; animation: splashPulse 1.2s var(--ease) 0.2s forwards; box-shadow: 0 0 10px rgba(255,255,255,0.8); }
/* Tagline appears after the pulse fades. Subtle breathing during long
   first-install holds so the splash never feels stuck. Mono font + tight
   uppercase tracking reads as "data terminal" — reinforces the
   stealth-scraper / live-tape positioning rather than a generic logo wait. */
.splash-tagline { margin-top: 18px; font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 10.5px; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: rgba(255,255,255,0.42); opacity: 0; white-space: nowrap; animation: splashTaglineIn 0.7s var(--ease) 0.6s forwards, splashTaglineBreathe 2.4s ease-in-out 1.3s infinite; }
@keyframes splashIntro { to { opacity: 1; transform: translateY(0); } }
@keyframes splashPulse { 0% { opacity: 0; transform: scaleX(0); } 50% { opacity: 1; transform: scaleX(1); } 100% { opacity: 0; transform: scaleX(1); } }
@keyframes splashTaglineIn { to { opacity: 0.55; } }
@keyframes splashTaglineBreathe { 0%, 100% { opacity: 0.35; } 50% { opacity: 0.6; } }

/* ── Shared ── */
.card { background: rgba(255,255,255,0.02); backdrop-filter: blur(20px); border: 1px solid var(--border); border-radius: var(--radius-24); padding: 24px; transition: transform var(--motion-fast) var(--ease); }
.card:active { transform: scale(0.98); }
.gain { color: var(--gain); } .loss { color: var(--loss); } .neutral { color: var(--text-secondary); }
.section-title { font-size: 15px; font-weight: 700; color: var(--text-secondary); margin-bottom: 24px; letter-spacing: -0.2px; }
.divider { height: 1px; background: var(--border); margin: 32px 0; }

/* ── Pull-to-Refresh Indicator ──
   Both the indicator AND the screen's content translate off a single
   --ptr-offset custom property on #screen-portfolio. That way the content
   shifts down with the pull and the spinner appears in a fresh gap above
   it, instead of sliding *over* the index chips and header text. The
   .ptr-pulling class (set during the drag) suppresses the transition so
   the translate tracks the finger; removing it on release re-enables the
   spring so the return eases smoothly, not as a snap. */
#screen-portfolio > * {
  transform: translate3d(0, var(--ptr-offset, 0px), 0);
  transition: transform 0.58s var(--ease);
  will-change: transform;
}
#screen-portfolio.ptr-pulling > * { transition: none; }

/* PTR indicator sits ABOVE the screen (top: -60px) via absolute positioning
   instead of negative margin. The old margin-top:-60px trick made the
   indicator's flow position overlap the first content child, so the spinner
   rendered *on top of* the portfolio total instead of in the gap revealed
   by the pull. Absolute positioning keeps it fully out of flow; it still
   translates with the content via the `#screen-portfolio > *` rule above,
   so it rides down into view as the user pulls. */
.ptr-indicator {
  position: absolute;
  top: -60px;
  left: 0;
  right: 0;
  height: 60px;
  display: flex; justify-content: center; align-items: center;
  pointer-events: none;
  z-index: 1;
}
#screen-portfolio { position: relative; }

/* Two real nested elements so transform channels don't collide:
 *   .ptr-spinner        → entrance (opacity + scale)
 *   .ptr-spinner-ring   → the visible arc + continuous rotation
 * A single element can't carry both `transform: scale(1)` (from rule) and
 * `transform: rotate(Xdeg)` (from keyframe) at once; browser picks one and
 * the spinner sits static. Pseudo-elements (::before) animate unreliably
 * on iOS WKWebView when the parent has its own transform, so both layers
 * are real DOM nodes. */
.ptr-spinner {
  width: 22px; height: 22px;
  opacity: 0; transform: scale(0.6);
  transition: opacity 0.2s var(--ease), transform 0.3s var(--ease);
}
.ptr-spinner-ring {
  width: 100%; height: 100%;
  border: 2px solid rgba(255,255,255,0.12);
  border-top-color: var(--text);
  border-radius: 50%;
  box-sizing: border-box;
  animation: ptr-spin 0.8s linear infinite;
}
.ptr-indicator.pulling .ptr-spinner,
.ptr-indicator.refreshing .ptr-spinner { opacity: 1; transform: scale(1); }
@keyframes ptr-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* ── Context Menu ── */
.context-menu-overlay {
  position: fixed; inset: 0; z-index: 9990; background: rgba(0,0,0,0.5);
  opacity: 0; transition: opacity var(--motion-fast) var(--ease);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
}
.context-menu-overlay.visible { opacity: 1; }

.context-menu {
  position: fixed; z-index: 9991; min-width: 220px;
  background: var(--glass-bg); backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-border); border-radius: var(--radius-24);
  padding: 6px; box-shadow: 0 12px 40px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.08);
  opacity: 0; transform: scale(0.9);
  transition: opacity 0.2s var(--ease), transform 0.2s var(--ease-spring);
}
.context-menu.visible { opacity: 1; transform: scale(1); }

.context-menu-header {
  padding: 12px 14px 8px; font-size: 13px; font-weight: 700; color: var(--text-secondary);
  letter-spacing: 0.02em;
}

.context-menu-item {
  display: block; width: 100%; padding: 12px 14px; background: none; border: none;
  text-align: left; font-family: var(--font); font-size: 14px; font-weight: 500;
  color: var(--text); cursor: pointer; border-radius: var(--radius-pill);
  transition: background var(--motion-fast) var(--ease);
}
.context-menu-item:active { background: rgba(255,255,255,0.1); }

/* ── Staggered List Entrance Animations ── */
@keyframes fadeSlideUp {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ── Trending Cards Stagger ── */
.trending-grid .trending-card {
  animation: fadeSlideUp 0.35s var(--ease) both;
}
.trending-grid .trending-card:nth-child(1) { animation-delay: 0.03s; }
.trending-grid .trending-card:nth-child(2) { animation-delay: 0.06s; }
.trending-grid .trending-card:nth-child(3) { animation-delay: 0.09s; }
.trending-grid .trending-card:nth-child(4) { animation-delay: 0.12s; }
.trending-grid .trending-card:nth-child(5) { animation-delay: 0.15s; }
.trending-grid .trending-card:nth-child(6) { animation-delay: 0.18s; }
.trending-grid .trending-card:nth-child(7) { animation-delay: 0.21s; }
.trending-grid .trending-card:nth-child(8) { animation-delay: 0.24s; }

/* ── Alert Items Stagger ── */
.alert-item {
  animation: fadeSlideUp 0.35s var(--ease) both;
}
.alert-item:nth-child(1) { animation-delay: 0.05s; }
.alert-item:nth-child(2) { animation-delay: 0.10s; }
.alert-item:nth-child(3) { animation-delay: 0.15s; }
.alert-item:nth-child(4) { animation-delay: 0.20s; }
.alert-item:nth-child(5) { animation-delay: 0.25s; }
.alert-item:nth-child(6) { animation-delay: 0.30s; }

/* ── Value Counter Animation ── */
@keyframes countFadeIn {
  from { transform: translateY(-8px); filter: blur(3px); }
  to { transform: translateY(0); filter: blur(0); }
}

.portfolio-value-section {
  animation: countFadeIn 0.5s var(--ease);
}

/* ── Analog rolling-digit odometer ──
   Each digit is a .odo-d wrapper clipped to one line. Inside is an .odo-reel
   column stacking 0-9; translating the reel -N*10% reveals digit N in the
   visible slot. Non-digits ($ , . , ,) render as .odo-static inline. */
.odo-host {
  display: inline-flex;
  align-items: baseline;
  line-height: 1;
  /* tabular numerals so all digit columns are the same width */
  font-feature-settings: "tnum" on, "lnum" on;
  font-variant-numeric: tabular-nums;
}
.odo-d {
  display: inline-block;
  overflow: hidden;
  height: 1em;
  line-height: 1;
  vertical-align: top;
  /* Fade mask transitions in when .odo-rolling is applied */
  -webkit-mask-image: linear-gradient(to bottom, black 0%, black 100%);
  mask-image: linear-gradient(to bottom, black 0%, black 100%);
  transition: -webkit-mask-image var(--motion-normal) var(--ease), mask-image var(--motion-normal) var(--ease);
}
.odo-d.odo-rolling {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 8%, black 92%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, black 8%, black 92%, transparent 100%);
}
.odo-reel {
  display: flex;
  flex-direction: column;
  will-change: transform;
  transform: translate3d(0, 0, 0);
}
.odo-digit-slot {
  height: 1em;
  line-height: 1;
  display: block;
}
.odo-static {
  display: inline-block;
  line-height: 1;
}

/* .market-indices-bar entrance animation removed — it overrode the
   marqueeScroll keyframes defined in portfolio.css and froze the ticker.
   The chip row now relies solely on its infinite marquee animation. */

/* ── Settings rows stagger ── */
.settings-group {
  animation: fadeSlideUp 0.4s var(--ease) both;
}
.settings-group:nth-child(1) { animation-delay: 0s; }
.settings-group:nth-child(2) { animation-delay: 0.05s; }
.settings-group:nth-child(3) { animation-delay: 0.1s; }
.settings-group:nth-child(4) { animation-delay: 0.15s; }
.settings-group:nth-child(5) { animation-delay: 0.2s; }

@media (prefers-reduced-motion: reduce) {
  #splash-screen,
  .splash-tagline,
  .splash-pulse,
  .splash-app-icon,
  .splash-logo,
  .header-context-text,
  .header-search-btn,
  .market-status,
  .header-context,
  .settings-group,
  .trending-grid .trending-card,
  .alert-item,
  .portfolio-value-section {
    animation: none !important;
    transition: none !important;
  }
  /* Static visible state for reduced-motion splash so the tagline still
     reads at a comfortable contrast even with no fade-in. */
  .splash-tagline { opacity: 0.5; }
}

/* ── Odometer (Analog Number Lock) ── */
:root { --odo-h: 1em; }
.odo-wrap {
  display: inline-flex;
  align-items: baseline;
}
.odo-digit {
  display: inline-block;
  overflow: hidden;
  height: var(--odo-h);
  vertical-align: text-bottom;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 22%, black 78%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, black 22%, black 78%, transparent 100%);
}
.odo-track {
  display: flex;
  flex-direction: column;
  transition: transform 0.45s var(--ease);
  will-change: transform;
}
.odo-cell {
  display: block;
  height: var(--odo-h);
  line-height: var(--odo-h);
  text-align: center;
}
.odo-char {
  display: inline-block;
  line-height: var(--odo-h);
  vertical-align: text-bottom;
}
}

/* ── Light mode overrides ── */
[data-theme="light"] .header {
  background: var(--glass-bg);
  border-bottom-color: var(--glass-border);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.03);
}

[data-theme="light"] .header.ai-active {
  background: transparent;
  border-bottom-color: transparent;
  box-shadow: none;
}

[data-theme="light"] .logo-icon,
[data-theme="light"] .ai-orb {
  background: var(--surface-elevated);
  border-color: var(--border);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08), inset 0 1px 0 rgba(0, 0, 0, 0.03);
}

[data-theme="light"] .logo-icon::after {
  background: var(--text);
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.18);
}

[data-theme="light"] .market-status,
[data-theme="light"] .header-search-btn,
[data-theme="light"] .header-context {
  background: var(--surface-elevated);
  border-color: var(--border);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08), inset 0 1px 0 rgba(0, 0, 0, 0.03);
}

[data-theme="light"] .market-dot {
  background: rgba(0, 0, 0, 0.28);
  box-shadow: 0 0 6px rgba(0, 0, 0, 0.12);
}

[data-theme="light"] .header-context[data-tone="loss"] .header-context-text {
  color: var(--loss);
}

[data-theme="light"] .header-context[data-tone="pro"] .header-context-text {
  color: var(--ai-deep-purple);
}

[data-theme="light"] .tab-bar {
  background: var(--glass-bg);
  border-color: var(--glass-border);
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.14), inset 0 1px 0 rgba(0, 0, 0, 0.03);
}

[data-theme="light"] .tab-btn.active {
  background: var(--surface-hover);
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.06);
}

[data-theme="light"] #splash-screen {
  background: var(--bg) url('../assets/splash-bg-light.png?v=2') 0 0 / 150px 150px repeat;
}

[data-theme="light"] .splash-app-icon {
  padding: 0;
  background: none;
  border-color: transparent;
  box-shadow: none;
  overflow: visible;
}

[data-theme="light"] .splash-logo-img {
  width: 84px;
  height: 84px;
  filter:
    drop-shadow(0 2px 2px rgba(0, 0, 0, 0.32))
    drop-shadow(0 10px 18px rgba(0, 0, 0, 0.24))
    drop-shadow(0 18px 34px rgba(80, 86, 120, 0.20));
}

[data-theme="light"] .splash-logo {
  color: var(--text);
  text-shadow: 0 0 24px rgba(0, 0, 0, 0.12);
}

[data-theme="light"] .splash-pulse {
  background: var(--text);
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.22);
}

[data-theme="light"] .splash-tagline {
  color: rgba(0, 0, 0, 0.42);
}

[data-theme="light"] .card {
  background: var(--surface);
  border-color: var(--border);
}

[data-theme="light"] .card:active {
  background: var(--surface-hover);
}

[data-theme="light"] .section-title {
  color: var(--text-secondary);
}

[data-theme="light"] .ptr-spinner-ring {
  border-color: var(--border);
  border-top-color: var(--text);
}

[data-theme="light"] .context-menu {
  background: var(--surface-elevated);
  border-color: var(--border);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.16), inset 0 1px 0 rgba(0, 0, 0, 0.03);
}

[data-theme="light"] .context-menu-item:active {
  background: var(--surface-hover);
}

/* ── Scroll Focus Gallery Effect ── */
.scroll-focus-item {
  transform: scale(calc(0.90 + 0.10 * var(--focus-ratio, 1))) translateZ(0);
  opacity: calc(0.4 + 0.6 * var(--focus-ratio, 1));
  filter: brightness(calc(0.75 + 0.25 * var(--focus-ratio, 1))) blur(calc((1 - var(--focus-ratio, 1)) * 2px));
  will-change: transform, opacity, filter;
  /* Avoid CSS transitions on transform/opacity/filter to prevent fighting the rAF loop */
}

/* Glassy Specular Highlight for Cards */
.scroll-focus-item.swipe-container::after,
.scroll-focus-item .news-card::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  left: -100%; width: 300%;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(
    115deg,
    transparent 35%,
    rgba(255, 255, 255, 0.02) 45%,
    rgba(255, 255, 255, 0.15) 50%,
    rgba(255, 255, 255, 0.02) 55%,
    transparent 65%
  );
  z-index: 10;
  mix-blend-mode: overlay;
  /* Sweep the light across the card as you scroll past it */
  transform: translateX(calc( -20% * var(--scroll-y, 0) )) translateZ(0);
  /* Stronger shine when tilted (off-center) */
  opacity: calc(0.4 + 0.6 * (1 - var(--focus-ratio, 1)));
  will-change: transform, opacity;
}
@media (prefers-reduced-motion: reduce) {
  .scroll-focus-item {
    transform: none !important;
    opacity: 1 !important;
  }
}

/* ── Per-page vertical depth ──
   A very slight top-to-bottom wash so each screen reads as a lit surface
   rather than a flat black field. background-attachment stays at the
   default (scroll), so on these scrolling containers the wash is pinned to
   the screen box and does not slide away with the content. */
.screen {
  background-image: linear-gradient(180deg,
    rgba(255, 255, 255, 0.05) 0%,
    rgba(255, 255, 255, 0.018) 18%,
    rgba(255, 255, 255, 0) 46%);
}
[data-theme="light"] .screen {
  background-image: linear-gradient(180deg,
    rgba(0, 0, 0, 0.032) 0%,
    rgba(0, 0, 0, 0.010) 18%,
    rgba(0, 0, 0, 0) 46%);
}

/* Listing currency next to a ticker in search results (RY USD / RY.TO CAD). */
.ccy-pill {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 5px;
  border-radius: var(--radius-6);
  background: var(--surface-elevated);
  color: var(--text-secondary);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.3px;
  vertical-align: 2px;
}
