/*
 * Static landing-page styles. Hand-written equivalents of the React
 * landing's Tailwind classes — kept here so the marketing page can
 * render with no JS / no React. Match changes here when the React
 * design language shifts (the only React landing component remaining
 * is the murmuration background, now in /murmuration.js).
 */

/* FreightSansPro — same files the SPA loads via src/index.css. The
   landing page is static and doesn't import that stylesheet, so we
   re-declare here. Font files live at /_fonts/FreightSansPro/ in
   public/ and are served as-is by Cloudflare. */
@font-face {
  font-family: "FreightSansPro";
  src: url("/_fonts/FreightSansPro/FreigSanProLig.otf") format("opentype");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "FreightSansPro";
  src: url("/_fonts/FreightSansPro/FreigSanProLigIt.otf") format("opentype");
  font-weight: 300;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "FreightSansPro";
  src: url("/_fonts/FreightSansPro/FreigSanProBook.otf") format("opentype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "FreightSansPro";
  src: url("/_fonts/FreightSansPro/FreigSanProBookIt.otf") format("opentype");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "FreightSansPro";
  src: url("/_fonts/FreightSansPro/FreigSanProMed.otf") format("opentype");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "FreightSansPro";
  src: url("/_fonts/FreightSansPro/FreigSanProMedIt.otf") format("opentype");
  font-weight: 500;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "FreightSansPro";
  src: url("/_fonts/FreightSansPro/FreigSanProSem.otf") format("opentype");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "FreightSansPro";
  src: url("/_fonts/FreightSansPro/FreigSanProSemIt.otf") format("opentype");
  font-weight: 600;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "FreightSansPro";
  src: url("/_fonts/FreightSansPro/FreigSanProBold.otf") format("opentype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "FreightSansPro";
  src: url("/_fonts/FreightSansPro/FreigSanProBoldIt.otf") format("opentype");
  font-weight: 700;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "FreightSansPro";
  src: url("/_fonts/FreightSansPro/FreigSanProBlk.otf") format("opentype");
  font-weight: 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "FreightSansPro";
  src: url("/_fonts/FreightSansPro/FreigSanProBlkIt.otf") format("opentype");
  font-weight: 900;
  font-style: italic;
  font-display: swap;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  font-family: "FreightSansPro", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  /* Matches the aurora's sky-top color so iOS overscroll above the
     hero (and any visible body bg below the dark site-footer)
     continues the dark night-sky. Section-light sections bg uses
     rgba(255,255,255,0.88) so the body shows through slightly —
     dark body shifts those sections a touch cooler/darker but they
     still read as white prose surfaces. */
  background: #030404;
  color: #171717;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  min-width: 360px;
  overflow-x: hidden;
}
img { display: block; max-width: 100%; user-select: none; }
button { font: inherit; cursor: pointer; border: 0; background: transparent; }
a { color: inherit; }

/* Page container — mirrors PAGE_MAX_WIDTH + PAGE_PX */
.container {
  max-width: 1296px;
  margin: 0 auto;
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}
@media (min-width: 640px) { .container { padding-left: 3rem; padding-right: 3rem; } }
@media (min-width: 768px) { .container { padding-left: 4rem; padding-right: 4rem; } }
@media (min-width: 1024px) { .container { padding-left: 4.5rem; padding-right: 4.5rem; } }
@media (min-width: 1280px) { .container { padding-left: 6rem; padding-right: 6rem; } }

/* ===== Hero ===== */

.hero {
  position: relative;
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 100svh;
  padding: 2rem 1.5rem;
}
@media (min-width: 640px) {
  .hero { padding: 2.5rem 2rem; }
}
/* Aurora bg — fixed behind every section. Sized to 100lvh (largest
   possible viewport — iOS chrome retracted state) so chrome show /
   hide animations don't resize the canvas mid-frame. Body bg
   (#030404) matches the aurora's skyTop, so iOS rubber-band
   overscroll above the page reads as a continuation of the night
   sky. z-index: -1 sits over body bg but under every section — the
   standard "fixed bg" pattern. WebGL canvases get compositor-
   promoted in some browsers and z-index: 0 doesn't reliably keep
   them behind sibling positioned elements; z-index: -1 is
   unambiguous (negative z always behind static + auto-z elements). */
.aurora-bg {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh; /* fallback for browsers without lvh */
  height: 100lvh;
  z-index: -1;
  pointer-events: none;
  display: block;
}
.hero-inner {
  position: relative;
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Hero graphic — square vertical lockup. Centered, with a min size
   of 240×240 and a max of 512×512. Between those limits the width
   tracks `100vw - 2 × MIN_PAD`, where MIN_PAD is the spec'd minimum
   horizontal padding around the graphic at each breakpoint
   (48 / 64 / 72 / 96 px). The .hero already contributes part of that
   min padding via its own outer padding (1.5rem mobile, 2rem 640+);
   the remainder comes from the centering offset inside hero-inner.
   Aspect ratio is enforced explicitly so a non-square source still
   renders as 1:1 (object-fit: contain leaves the letterboxing inside
   the locked box, no distortion). */
.hero-title {
  margin: 0;
}
.hero-graphic {
  display: block;
  width: clamp(240px, calc(100vw - 96px), 512px);
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: contain;
}
@media (min-width: 640px) { .hero-graphic { width: clamp(240px, calc(100vw - 128px), 512px); } }
@media (min-width: 768px) { .hero-graphic { width: clamp(240px, calc(100vw - 144px), 512px); } }
@media (min-width: 1024px) { .hero-graphic { width: clamp(240px, calc(100vw - 192px), 512px); } }
.hero-tagline {
  margin-top: 1rem;
  text-align: center;
  font-weight: 400;
  font-size: 1.125rem;
  /* Light copy for legibility against the aurora's dark sky bg.
     Match changes here when the hero bg variant changes. */
  color: #fafafa;
}
/* sm matches mobile font-size (1.125rem) — only adjusts spacing. */
@media (min-width: 640px) { .hero-tagline { margin-top: 1.5rem; } }
@media (min-width: 768px) { .hero-tagline { font-size: 1.25rem; } }
/* lg + xl bumps mirror .section-light p so the hero tagline reads at
   the same scale as the wide single-column intro/CTA prose below. */
@media (min-width: 1024px) { .hero-tagline { font-size: 1.75rem; } }
@media (min-width: 1280px) { .hero-tagline { font-size: 2rem; } }

/* Hero pricing line ("$2.99/month · Try it free for 7 days") — no
   bg, just brand decorative cyan (#00bde0) on the aurora. Sits below
   the tagline as its own row via display:block + margin-top.
   Intentionally a different visual treatment from the SPA's Profile
   pill (FREE_ACCESS_PILL_CLASS, dark cyan-on-cyan badge) — don't
   try to keep them in lockstep. The Profile pill is a structured
   status badge inside a dark panel; this line is an accent under a
   hero tagline. */
.hero-pill {
  margin-top: 1rem;
  display: inline-block;
  font-size: 1rem;
  font-weight: 500;
  color: #00bde0;
}
@media (min-width: 640px) {
  .hero-pill { margin-top: 1.5rem; padding: 0.375rem 1.25rem; font-size: 1.125rem; }
}

/* Primary CTA — Continue with Google. White pill with dark text so
   the button reads as a solid affordance against the dark aurora bg.
   The Google "G" stays full-color via inline SVG fill values inside
   the button. */
.cta {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  background: #fff;
  color: #171717;
  border-radius: 9999px;
  padding: 0.75rem 1.5rem;
  font-size: 0.875rem;
  font-weight: 700;
  transition: background-color 150ms;
  text-decoration: none;
}
.cta:hover { background: #e5e5e5; }
.cta:active { transform: scale(0.95); }

/* Inside light prose sections (section-light's mostly-white bg) the
   default white CTA would disappear. Flip back to the dark-pill
   variant: bg #171717, white text, hover gray-800. */
.section-light .cta {
  background: #171717;
  color: #fff;
}
.section-light .cta:hover { background: #262626; }
.cta-svg { width: 1rem; height: 1rem; }
@media (min-width: 640px) {
  .cta { gap: 0.75rem; padding: 0.875rem 2rem; font-size: 1rem; }
  .cta-svg { width: 1.25rem; height: 1.25rem; }
}
@media (min-width: 768px) {
  .cta { padding: 1rem 2.5rem; font-size: 1.125rem; }
}
.cta-large {
  margin-top: 2.5rem;
  gap: 0.75rem;
  padding: 1rem 2rem;
  font-size: 1.125rem;
}
@media (min-width: 640px) {
  .cta-large { font-size: 1.25rem; }
}
.cta-large .cta-svg { width: 1.25rem; height: 1.25rem; }
@media (min-width: 640px) {
  .cta-large .cta-svg { width: 1.5rem; height: 1.5rem; }
}

/* Sticky nav (appears past hero) — mirrors the in-app StickyNav so the
   landing and dashboard navs read as the same UI element. Uses its own
   class set rather than reusing the hero's .cta so the rules can't drift
   from each other or pick up unintended overrides. */
.sticky-nav {
  position: fixed;
  /* var(--nav-top) tracks visualViewport.offsetTop so the nav stays
     flush below the iPad/iOS browser chrome when it auto-shows on
     scroll-up. Set by the inline script at the bottom of index.html.
     Falls back to 0 on browsers without Visual Viewport API. */
  top: 0;
  top: var(--nav-top, 0);
  left: 0;
  right: 0;
  z-index: 50;
  background: rgba(23, 23, 23, 0.80); /* gray-900 @ 80% */
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  transform: translateY(-100%);
  /* transform-only transition — top updates need to be instant so
     the nav tracks the chrome animation precisely (chrome animates
     in ~250ms; tweening top would lag visibly behind it). */
  transition: transform 250ms ease;
}
.sticky-nav.visible { transform: translateY(0); }
.sticky-nav-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-top: 1rem;
  padding-bottom: 1rem;
}
@media (min-width: 768px) {
  .sticky-nav-row {
    padding-top: 1.25rem;
    padding-bottom: 1.25rem;
  }
}
.sticky-nav-brand {
  display: inline-flex;
  align-items: center;
  gap: 1rem;
  text-decoration: none;
  user-select: none;
  min-width: 0;
}
.sticky-nav-wordmark {
  display: block;
  height: 1.25rem;
  width: auto;
  flex-shrink: 0;
}
@media (min-width: 640px) { .sticky-nav-wordmark { height: 1.5rem; } }
@media (min-width: 1024px) { .sticky-nav-wordmark { height: 1.75rem; } }

.sticky-nav-action {
  display: inline-flex;
  align-items: center;
  gap: 0;
  background: #fff;
  color: #171717;
  border: 0;
  border-radius: 9999px;
  padding: 0.375rem;
  font-size: 0.875rem;
  font-weight: 700;
  white-space: nowrap;
  flex-shrink: 0;
  cursor: pointer;
  transition: background-color 150ms;
}
.sticky-nav-action:hover { background: #e5e5e5; }
.sticky-nav-icon {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  display: block;
}
.sticky-nav-label { display: none; }
@media (min-width: 640px) {
  .sticky-nav-action {
    gap: 0.5rem;
    padding: 0.5rem 1rem;
    font-size: 0.875rem;
  }
  .sticky-nav-label { display: inline; }
}

/* ===== Light content sections ===== */

.section-light {
  position: relative;
  z-index: 10;
  width: 100%;
  /* Opaque white — content sits above the fixed aurora canvas; no
     transparency here so the aurora doesn't bleed through prose. */
  background: #fff;
}
.section-light .container {
  padding-top: 5rem;
  padding-bottom: 5rem;
}
@media (min-width: 640px) {
  .section-light .container { padding-top: 7rem; padding-bottom: 7rem; }
}
@media (min-width: 768px) {
  .section-light .container { padding-top: 8rem; padding-bottom: 8rem; }
}
.section-light h2 {
  font-size: 1.875rem;
  font-weight: 700;
  color: #171717;
  line-height: 1.15;
}
@media (min-width: 640px) { .section-light h2 { font-size: 2.25rem; } }
@media (min-width: 768px) { .section-light h2 { font-size: 3rem; } }
@media (min-width: 1024px) { .section-light h2 { font-size: 3.75rem; } }
.section-light p {
  margin-top: 1.5rem;
  font-size: 1.125rem;
  line-height: 1.625;
  color: #404040;
}
@media (min-width: 640px) { .section-light p { font-size: 1.25rem; } }
@media (min-width: 768px) { .section-light p { font-size: 1.5rem; } }
/* lg + xl bumps so the wide single-column intro/CTA prose holds its
   own against the giant section-light h2 (3.75rem at lg+) without
   reading as cramped or under-weighted. */
@media (min-width: 1024px) { .section-light p { font-size: 1.75rem; } }
@media (min-width: 1280px) { .section-light p { font-size: 2rem; } }
.section-light p.bold {
  margin-top: 1.5rem;
  font-weight: 600;
  color: #171717;
}
.section-light p.medium {
  margin-top: 1.5rem;
  font-weight: 500;
  color: #171717;
}
.section-light p.tight {
  margin-top: 1rem;
}

/* Modifier on .section-light that flips the whole section to a dark
   theme — gray-800 (#1f2937) bg, white h2, gray-100 (#f3f4f6) prose
   and lists, gray-500 (#6b7280) bullet dots so they stay visible on
   the dark bg. Used by the "Build your portfolio" section. Applied
   as a sibling class — padding, layout, and the .intro-screenshot
   :has() bottom-padding reset still inherit from .section-light;
   only the color theme overrides. */
.section-light.section-light-dark {
  background: #1f2937;
}
.section-light.section-light-dark h2 { color: #fff; }
.section-light.section-light-dark p,
.section-light.section-light-dark ul {
  color: #f3f4f6;
}
.section-light.section-light-dark p.bold {
  /* .section-light p.bold sets color: #171717 — override back to
     white so the bolded line stays the most prominent prose color
     against the dark bg. */
  color: #fff;
}
.section-light.section-light-dark li::before {
  background: #6b7280;
}

/* Bulleted lists inside .section-light prose sections. Flex-row
   layout with a small gray dot prefix: dot sits on #737373 (readable
   on white/gray-100), text color matches .section-light p (#404040).
   Font-size ramp matches .section-light p so the list reads at the
   same hierarchy as the surrounding prose at every breakpoint. */
.section-light ul {
  margin-top: 1.5rem;
  list-style: none;
  font-size: 1.125rem;
  line-height: 1.625;
  color: #404040;
}
@media (min-width: 640px) { .section-light ul { font-size: 1.25rem; } }
@media (min-width: 768px) { .section-light ul { font-size: 1.5rem; } }
@media (min-width: 1024px) { .section-light ul { font-size: 1.75rem; } }
@media (min-width: 1280px) { .section-light ul { font-size: 2rem; } }
.section-light li {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  margin-top: 0.75rem;
}
.section-light li:first-child { margin-top: 0; }
.section-light li::before {
  content: "";
  display: inline-block;
  /* Bullet width/height in em, NOT rem, so the dot scales with the
     text size at each breakpoint. The previous shape used 0.5rem
     (constant 8px at every breakpoint) which broke vertical
     alignment with the first line of text at larger font sizes:
     because the dot didn't grow, its em-based margin-top no longer
     centered the dot at the x-height — the offset stayed visually
     constant in pixels, but pixel constancy reads as "drifting
     higher" relative to ever-larger text. Em-based dimensions
     scale uniformly with the 1.125rem → 2rem font ramp. Dot grows
     from ~7px (mobile) to ~13px (xl) — visually balanced rather
     than undersized on big text.

     margin-top base is 0.6em (centers at x-height MIDDLE
     mathematically), with a per-breakpoint pixel offset added on
     top. The pixel offset compensates for two effects that
     accumulate with text size: (1) FreightSansPro's optical
     center sits visually lower than the strict x-height middle,
     so a dot mathematically centered reads as "high"; (2) the
     bigger the text, the more pronounced the perceived gap. User
     tuning: 0px at base, ramping to +4px at xl. */
  width: 0.4em;
  height: 0.4em;
  margin-top: calc(0.6em + 2px);
  background: #737373;
  border-radius: 50%;
  flex-shrink: 0;
}
/* Per-breakpoint pixel offset ramp is non-monotonic (2,1,2,1,0)
   because user-perceived optical alignment doesn't follow a clean
   em or pixel scale here. Values are hand-tuned: mobile +2px and
   xl +0px were specified directly; 640/768 stay at the prior +1/+2
   ("about right"); 1024 interpolates between 768 and 1280. */
@media (min-width: 640px) {
  .section-light li::before { margin-top: calc(0.6em + 1px); }
}
@media (min-width: 768px) {
  .section-light li::before { margin-top: calc(0.6em + 2px); }
}
@media (min-width: 1024px) {
  .section-light li::before { margin-top: calc(0.6em + 1px); }
}
@media (min-width: 1280px) {
  .section-light li::before { margin-top: 0.6em; }
}

/* Looping intraday portfolio chart at the bottom of section-light.
   Full-width, fixed 320px tall, flush against the section's left,
   right, and bottom edges. Transparent fill — the section's white bg
   shows through, and the same canvas can be reused on a dark surface
   without re-colouring. */
#intraday-chart {
  display: block;
  width: 100%;
  height: 320px;
  background: transparent;
}

/* Halve the container's bottom padding ONLY when the container is
   immediately followed by the chart canvas. Other .section-light
   blocks (which have no chart sibling) keep the original symmetric
   padding-top/bottom. Uses :has() — supported in every browser the
   app already targets (Safari 15.4+ / Chrome 105+ / Firefox 121+). */
.section-light .container:has(+ #intraday-chart) {
  padding-bottom: 2.5rem;
}

/* App-preview screenshot used by three sections (intro / Real-time
   data / Build your portfolio). Sits at the bottom of its container,
   fills the container's content width (= prose column width), and
   is gutter-aligned with the prose above it. Class name kept as
   .intro-screenshot for historical continuity; conceptually it's a
   "framed section screenshot." <picture> swaps the image per
   breakpoint (see index.html); each variant has its own intrinsic
   aspect ratio (mobile portrait, tablet portrait, desktop landscape)
   — width:100%/height:auto on the rendered <img> scales every
   variant to the available width while preserving its native ratio.

   Margin-top ramp 3 → 5 → 6 → 7rem mirrors the row-gap of
   .section-how .steps so the gap between this image and the prose
   above it reads as the same "major-row" vertical rhythm as the
   gap between How-it-works step rows.

   The hosting .container's bottom padding is zeroed (via the
   :has() override below) so the image's bottom edge sits flush
   against the next section. Applies in any .section-light variant
   (white or section-light-gray modifier). */
.intro-screenshot {
  display: block;
  margin-top: 3rem;
  /* Hairline + top-only radii mirror .step-frame so the screenshot
     reads as part of the same framed-card system as the four "How
     it works" mocks. Bottom-border-only (no top/left/right) keeps
     the screenshot's left/right edges flush with the section gutter
     and lets the bottom hairline visually separate the image from
     the dark section below. overflow:hidden clips the inner <img>
     to the rounded top corners — without it the corner radii apply
     to the picture box only and the image still paints to the
     square corners. Radii ramp mirrors .step-frame at every
     breakpoint (1.25 → 1.5 → 1.75 → 2 → 2.25rem). */
  overflow: hidden;
  border-bottom: 1px solid #374151;
  border-top-left-radius: 1.25rem;
  border-top-right-radius: 1.25rem;
}
.intro-screenshot img {
  display: block;
  width: 100%;
  height: auto;
}
@media (min-width: 640px) {
  .intro-screenshot {
    margin-top: 5rem;
    border-top-left-radius: 1.5rem;
    border-top-right-radius: 1.5rem;
  }
}
@media (min-width: 768px) {
  .intro-screenshot {
    margin-top: 6rem;
    border-top-left-radius: 1.75rem;
    border-top-right-radius: 1.75rem;
  }
}
@media (min-width: 1024px) {
  .intro-screenshot {
    margin-top: 7rem;
    border-top-left-radius: 2rem;
    border-top-right-radius: 2rem;
  }
}
@media (min-width: 1280px) {
  .intro-screenshot {
    border-top-left-radius: 2.25rem;
    border-top-right-radius: 2.25rem;
  }
}

/* Zero the container's bottom padding ONLY when the intro
   screenshot is present. Mirrors the same :has() pattern used for
   #intraday-chart above; scoped so other .section-light blocks
   keep their symmetric padding. */
.section-light .container:has(> .intro-screenshot) {
  padding-bottom: 0;
}
@media (min-width: 640px) {
  .section-light .container:has(+ #intraday-chart) { padding-bottom: 3.5rem; }
}
@media (min-width: 768px) {
  .section-light .container:has(+ #intraday-chart) { padding-bottom: 4rem; }
}

/* ===== How it works section =====
   Single-column h2 + 4 icon-led steps. Transparent bg so the aurora
   reads through — light text on the night-sky gradient. Icons are
   inlined Lucide SVGs in the HTML; .step-icon picks up currentColor
   for easy palette swaps. */
.section-how {
  position: relative;
  z-index: 10;
  width: 100%;
}
.section-how .container {
  /* Symmetric pt/pb at every breakpoint. The global * { margin: 0 }
     reset zeroes h3/p browser defaults, so the last row's <p> ends
     at the text baseline — no inherent bottom margin to compensate
     for; pb = pt produces the same visual whitespace above the h2
     as below the last p. */
  padding-top: 5rem;
  padding-bottom: 5rem;
}
@media (min-width: 640px) {
  .section-how .container { padding-top: 7rem; padding-bottom: 7rem; }
}
@media (min-width: 768px) {
  .section-how .container { padding-top: 8rem; padding-bottom: 8rem; }
}

/* Direct-child selector — scope to the section's own "How it works"
   h2 only. Without > the rule leaked into mock-tk-symbol (which is
   an <h2>) inside the step-frame mock and rendered "VOO" at 60px
   on desktop instead of the mock's intended 36px text-4xl. */
.section-how > .container > h2 {
  font-size: 1.875rem;
  font-weight: 700;
  color: #fff;
  line-height: 1.15;
}
@media (min-width: 640px) { .section-how > .container > h2 { font-size: 2.25rem; } }
@media (min-width: 768px) { .section-how > .container > h2 { font-size: 3rem; } }
@media (min-width: 1024px) { .section-how > .container > h2 { font-size: 3.75rem; } }

.section-how .steps {
  margin-top: 2.5rem;
  list-style: none;
  display: grid;
  grid-template-columns: 1fr;
  gap: 3rem;
}
@media (min-width: 640px) {
  .section-how .steps {
    /* 2-col at every viewport ≥640px. Earlier iterations went to
       repeat(4, 1fr) at 1024+; reverted to keep 2 columns so each
       step shows a larger framed screen mockup alongside its copy
       (the icons were swapped for inset square screens). */
    grid-template-columns: 1fr 1fr;
    gap: 5rem 2.5rem;
    margin-top: 3rem;
  }
}
@media (min-width: 768px) {
  .section-how .steps { gap: 6rem 3rem; margin-top: 4rem; }
}
@media (min-width: 1024px) {
  .section-how .steps { gap: 7rem 4rem; }
}

/* Step list item — always a vertical stack now that the icon was
   replaced by a square framed screen (the icon-on-left mobile
   variant from the earlier version doesn't fit a screen-sized image
   on a narrow viewport). Image on top, h3 below, p below that. */
.section-how .steps li {
  display: block;
}

/* Per-step h3 type ramp: 1.5/1.875/1.875/2.25rem at base/640/768/1024,
   font-weight 700, white. Line-height inherits body's 1.5 so wrapped
   subheadings ramp consistently across breakpoints.
   Direct-child selector — scope to the step's own subheading only,
   not the mock-tk-datehead <h3> "Fri, May 8" inside the framed
   ticker mock. */
.section-how .steps > li > h3 {
  font-size: 1.5rem;
  font-weight: 700;
  color: #fff;
}
@media (min-width: 640px) { .section-how .steps > li > h3 { font-size: 1.875rem; } }
@media (min-width: 768px) { .section-how .steps > li > h3 { font-size: 1.875rem; } }
@media (min-width: 1024px) { .section-how .steps > li > h3 { font-size: 2.25rem; } }

/* Direct-child selector — targets only the step's own description
   paragraph (the one-line sentence under each step's h3). Without
   the > combinator the rule leaked into every <p> element inside
   the step-frame mock (mock-pf-balance, mock-tk-price, mock-ap-hint,
   etc.) and overrode their mock-specific font sizes, making the
   landing's framed-mock text read significantly larger than the
   same mock on /illustrations. The > scopes it to one <p> per
   step — the step's description — and lets the mock's internal
   typography ramp through. */
.section-how .steps > li > p {
  margin-top: 0.75rem; /* mobile — tightened gap from the h3 above */
  font-size: 1.125rem;
  line-height: 1.625;
  color: #d4d4d4;
}
@media (min-width: 640px) { .section-how .steps > li > p { font-size: 1.25rem; margin-top: 1.5rem; } }
@media (min-width: 768px) { .section-how .steps > li > p { font-size: 1.125rem; } }
@media (min-width: 1024px) { .section-how .steps > li > p { font-size: 1.25rem; } }

/* Step frame — square inset image that replaces the per-step icon.
   Wraps one of the .mock-{dp,tk,ap,pf} mockups and clips it to its
   top square portion (the visually distinctive 400×400 header of
   each 400×720 mock). The frame scales responsively via container
   queries: the inner mock's transform: scale() resolves to (frame
   width) / (mock intrinsic width), so the visible content stays
   the same composition at every breakpoint.

   Animation hook: this section will animate the framed mocks soon
   — the .mock children are static HTML/CSS with queryable DOM nodes
   inside (no Shadow DOM, no canvas), so a future JS pass can pin
   onto specific .mock-pf-balance / .mock-tk-price / etc. nodes for
   choreographed reveals. */
.step-frame {
  container-type: inline-size;
  width: 100%;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  position: relative;
  /* gray-800 — matches mock-dp + mock-tk's intrinsic panel color so
     the frame bg blends with the mock content. Important for mock-tk
     in particular, which sits with `top: 8cqi` (32 mock pixels of
     empty space above VOO); the visible top strip would otherwise
     read as a darker "modal-header" band against the mock body. */
  background: #1f2937;
  /* Hairline outline — same gray-700 hex used by dividers throughout
     this file. Separates the frame from the dark page bg so the four
     framed mocks read as a unified card system. */
  border: 1px solid #374151;
  border-radius: 1.25rem; /* 20px — mobile */
  margin-bottom: 1rem; /* mobile — tightened so the h3 sits closer to the frame */
}
@media (min-width: 640px) {
  .step-frame { border-radius: 1.5rem; margin-bottom: 2.5rem; }
}
@media (min-width: 768px) {
  .step-frame { border-radius: 1.75rem; }
}
@media (min-width: 1024px) {
  .step-frame { border-radius: 2rem; margin-bottom: 3rem; }
}
@media (min-width: 1280px) {
  .step-frame { border-radius: 2.25rem; }
}

/* Scale the intrinsically 400px-wide mock to match the square
   frame's width. The 720-tall mock then becomes (W × 1.8) tall —
   the bottom portion below the frame's W tall square is clipped
   by .step-frame's overflow: hidden. Content density inside the
   visible top square reads at the same scale ratio at every
   viewport: text/inputs/charts inside the mock are at intrinsic
   400px-wide-mock proportions, just visually scaled with the
   frame.

   Math: scale = 100cqi / 400px (since 100cqi = frame width and the
   un-scaled mock is 400px wide). Mock anchors top-left so the top
   square portion (y=0 to y=400 in mock coords) is the visible
   region.

   Compound specificity here (.step-frame .mock) wins over the bare
   .mock rule's @media (max-width: 480px) transform — that scale was
   designed for the standalone /illustrations preview page where the
   mock floats free in the viewport. Inside a step-frame the frame
   itself handles all responsive sizing. */
.step-frame .mock {
  position: absolute;
  top: 0;
  left: 0;
  transform: scale(calc(100cqi / 400px));
  transform-origin: top left;
  margin-bottom: 0;
  border-radius: 0; /* outer .step-frame supplies the radius; clipping the inner mock at 0 makes the frame's corners read as the visible corners */
  box-shadow: none; /* drop shadow hidden by .step-frame's overflow:hidden anyway, but removing it explicitly avoids paint cost */
}

/* Modal-sheet headers (X close + title row) hidden inside step-frame
   for the three modal-style mocks so each frame opens directly onto
   the screen content — not the generic close-button chrome. mock-pf
   has no modal header (its greeting IS the screen-level content)
   so it isn't included here.
   Using display: none (rather than a `top` offset on the mock)
   makes the body the first flex child of the .mock flex column,
   so the body slides up naturally to fill the freed space — no
   manual offset math needed and no risk of misalignment when the
   mock's child sizes change. */
.step-frame .mock-tk-header,
.step-frame .mock-dp-header,
.step-frame .mock-ap-header {
  display: none;
}

/* mock-dp + mock-ap pill alignment — both segmented pill rows sit
   at the same y (16px from mock top, ie. pt-4) so the two adjacent
   framed mocks visually rhyme. mock-ap-typestrip's padding is
   already pt-4 from the standalone illustrations design; mock-dp
   was pt-6 (24px) and shifted to pt-4 here to align with it. */
.step-frame .mock-dp-form {
  padding-top: 1rem; /* pt-4 — was pt-6, shifts pills up to align with mock-ap */
}

/* mock-dp — show the Deposit CTA at the bottom of the framed view.
   Intrinsic .mock height is 720px (designed for a phone aspect on
   the standalone illustrations page); shrinking to 400px makes the
   mock the same aspect as the square frame, so the entire mock
   (segpills + amount box + footer with Deposit $500.00 CTA) fits
   the frame without any clipping. Keypad is hidden — it doesn't
   represent app UI (it's a stand-in for the OS keyboard on the
   illustrations preview) and removing it lets the form flex grow
   into the freed space, keeping the CTA pinned to the bottom. */
.step-frame .mock-dp {
  height: 400px;
}
.step-frame .mock-dp-keypad {
  display: none;
}

/* mock-tk — tune the body's vertical position so the empty space
   above VOO matches the empty space below the chart. With the modal
   header AND the timeframe tabs hidden, the body content begins at
   mock y≈8 (VOO symbol) and the chart wrap ends at mock y≈368. The
   32 mock pixels of "slack" between mock body height (368) and
   frame height (400) gets split evenly via top: 3cqi — 12 mock px
   above VOO + 12 mock px below chart, with another ~8 mock px of
   inherent top padding inside the body. */
.step-frame .mock-tk {
  top: 3cqi;
}
.step-frame .mock-tk-tabs {
  display: none;
}

/* mock-pf — lead with the balance hero, not the eyebrow. The
   greeting row is hidden (rule below) and the mock is then shifted
   up by 7cqi so the "Portfolio balance" eyebrow (mock y=0–21)
   crops off the frame top entirely, the $48,234.12 balance becomes
   the first visible element flush with the top, and the first
   stats row ends with ~24 mock px of breathing room before the
   frame bottom. The +4px is a small visual nudge to give the
   balance a touch of breathing room above it. */
.step-frame .mock-pf {
  top: calc(-7cqi + 4px);
}

/* mock-pf — hide the greeting row + action-button row so the
   framed view opens directly at the PORTFOLIO BALANCE eyebrow.
   page padding-top dropped to 0 too — gap-0.75rem above the
   section would still apply between flex children, but with the
   greeting row removed there's only one child left, so the gap
   has no effect.
   Background unified at gray-800 (#1f2937) — overrides both the
   .mock-pf base (gray-900) and the inner .mock-pf-section's own
   gray-900 fill so the framed view sits at the same panel color
   as the other three mocks. The .mock-pf-section border-bottom
   becomes invisible inside the frame (frame's overflow clips it
   anyway). */
.step-frame .mock-pf-greeting-row {
  display: none;
}
.step-frame .mock-pf-page {
  padding-top: 0;
}
.step-frame .mock-pf,
.step-frame .mock-pf-section {
  background: #1f2937;
}

/* Add-past mock — match the floating-tier panel color used by the
   other three framed mocks (gray-800). The .mock base default of
   gray-900 reads as a noticeably darker square next to mock-dp +
   mock-tk + the newly-unified mock-pf. */
.step-frame .mock-ap {
  background: #1f2937;
}

/* Add-past mock — the form's overflow-x: hidden originally guarded
   against horizontal overflow on the standalone illustrations page,
   but the flex layout (form: flex 1) plus content-taller-than-
   allocated-space combines with overflow-x: hidden to implicitly
   trigger overflow-y: auto in some browsers, producing a scroll
   surface inside the form. Inside a step-frame the outer overflow:
   hidden already clips any genuine overflow, so the form needs no
   overflow rule of its own here. Disabling it removes the
   browser's implicit scrollability. */
.step-frame .mock-ap-form {
  overflow: visible;
}

/* ===== Mobile UI mockups (used by /illustrations preview page) =====
   These styles render the 400×720 phone-screen mockups of the mobile
   app's authed UI. The mockups live on a separate /illustrations
   stub page (not linked from anywhere) so they're easy to refine
   without polluting the landing layout. Pure HTML+CSS — designed to
   be easy to animate later via vanilla JS (no framework, queryable
   DOM nodes inside). */

/* ── Mock chrome (shared) ──────────────────────────────────────────
   Phone-screen-shaped panel: dark gray-900 fill, rounded corners,
   subtle drop shadow, FreightSansPro inherited from body. Fixed
   400×720 — on viewports narrower than that the mock scales down via
   transform so it never overflows the container. */

.mock {
  width: 400px;
  height: 720px;
  flex-shrink: 0;
  border-radius: 28px;
  background: #111827; /* gray-900 */
  color: #fff;
  overflow: hidden;
  position: relative;
  display: flex;
  flex-direction: column;
  box-shadow:
    0 30px 60px -15px rgba(0, 0, 0, 0.6),
    0 0 0 1px rgba(255, 255, 255, 0.06);
}

@media (max-width: 480px) {
  /* Below 480px the mock would overflow most containers — scale it
     down. transform-origin top center keeps it visually anchored at
     the top of its slot; negative margins compensate for the empty
     space the scale leaves behind. 0.7 fits a ~280px wide viewport. */
  .mock {
    transform: scale(0.7);
    transform-origin: top center;
    margin-bottom: -216px; /* 720 * (1 - 0.7) */
  }
}

/* Header bar at the top of every mock — close icon on the left,
   optional title in the middle, optional right-action on the right.
   Mirrors MenuHeader's compact 32px close button + px-5 py-4. */
.mock-header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid #374151; /* gray-700 */
}
.mock-header-title {
  flex: 1;
  text-align: center;
  font-size: 1.125rem;
  font-weight: 700;
  color: #fff;
}
.mock-header-spacer { flex: 1; }
.mock-icon-btn {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  background: transparent;
  border: 0;
  color: #9ca3af; /* gray-400 */
  cursor: default;
  padding: 0;
}
.mock-icon-btn svg { width: 1rem; height: 1rem; display: block; }

/* Watchlist toggle pill — outlined dark circle with bookmark icon. */
.mock-watchlist-pill {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  background: transparent;
  border: 1px solid #4b5563; /* gray-600 */
  color: #fff;
  cursor: default;
  padding: 0;
}
.mock-watchlist-pill svg { width: 1rem; height: 1rem; display: block; }

/* Body slot — flex column, fills remaining height between header
   and any sticky footer. Default body is centered-content with
   horizontal padding; specific mocks override as needed. */
.mock-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 1.25rem 1.25rem 0;
  min-height: 0;
}
.mock-body-ticker {
  padding: 1rem 1.25rem 0;
}
.mock-body-portfolio {
  padding: 1.25rem 1.25rem 1.5rem;
}

/* ── Segmented pill switcher (Buy/Sell, Deposit/Withdraw, etc.) ── */
.mock-segpills {
  display: flex;
  gap: 0.125rem;
  padding: 0.125rem;
  border-radius: 9999px;
  background: #374151; /* gray-700 */
  height: 44px;
  margin-bottom: 1rem;
}
.mock-segpill {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border-radius: 9999px;
  background: transparent;
  border: 0;
  color: #d1d5db; /* gray-300 */
  font-family: inherit;
  font-size: 1rem;
  font-weight: 600;
  cursor: default;
  padding: 0 1rem;
}
.mock-segpill-active {
  background: #0e7490; /* cyan-700 — matches PillSwitcher selection-fill */
  color: #fff;
}
.mock-segpill-dot {
  width: 8px;
  height: 8px;
  border-radius: 9999px;
  background: #00bde0; /* decorative brand cyan */
}
.mock-segpills-4 .mock-segpill {
  font-size: 0.875rem;
  padding: 0 0.5rem;
}

/* ── Recessed amount-input box (TradeModal / TransferModal pattern) ── */
.mock-amount-box {
  margin-top: 0.5rem;
  border-radius: 1rem;
  background: #030712; /* gray-950-ish — one shade darker than the mock panel */
  padding: 1.5rem 1.5rem;
  text-align: center;
}
.mock-eyebrow {
  font-size: 0.75rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #9ca3af;
  margin: 0;
}
.mock-eyebrow-large {
  font-size: 0.875rem;
}
.mock-amount {
  font-size: 3.5rem;
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: #fff;
  margin: 0.75rem 0 0;
}
.mock-helper {
  font-size: 0.875rem;
  color: #9ca3af;
  margin: 0.75rem 0 0;
}

/* CTA button at the bottom of the body — sticky-footer style without
   the actual sticky positioning. */
.mock-cta {
  margin-top: auto;
  margin-bottom: 1rem;
  width: 100%;
  height: 48px;
  border-radius: 9999px;
  background: #0093b2; /* brand CTA cyan */
  color: #fff;
  font-family: inherit;
  font-weight: 600;
  font-size: 1rem;
  border: 0;
  cursor: default;
}

/* ── Numeric keypad — iOS-style 4×3 grid of large keys. ── */
.mock-keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.5rem;
  padding: 0.75rem 1rem 1.25rem;
  background: #1f2937; /* gray-800 — keypad sits on a slightly lighter strip */
  border-top: 1px solid #374151;
}
.mock-key {
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 0.75rem;
  background: transparent; /* keys are flat text-only on the keypad strip */
  border: 0;
  color: #fff;
  font-family: inherit;
  font-size: 1.5rem;
  font-weight: 600;
  cursor: default;
  padding: 0;
}
.mock-key svg { width: 1.5rem; height: 1.5rem; display: block; color: #fff; }

/* ── Ticker modal: symbol + price + chart + tabs + stats + 52w bar ── */
.mock-ticker-symbol { margin-top: 0.5rem; }
.mock-h2 {
  font-size: 2.25rem;
  font-weight: 900;
  letter-spacing: 0.025em;
  color: #fff;
  margin: 0;
}
.mock-subtle {
  font-size: 0.875rem;
  color: #9ca3af;
  margin: 0.125rem 0 0;
}
.mock-hero-line {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.625rem;
  margin-top: 0.75rem;
}
.mock-hero-price {
  font-size: 2rem;
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  color: #fff;
  margin: 0;
}
.mock-change {
  font-size: 0.875rem;
  font-weight: 600;
  margin: 0;
}
.mock-change-up { color: #16a34a; /* green-600 */ }
.mock-change-large { font-size: 1rem; }

.mock-chart {
  display: block;
  width: 100%;
  height: 110px;
  margin-top: 0.5rem;
}
.mock-chart-portfolio { height: 130px; margin-top: 0.75rem; }

/* Tabs row (timeframe pills) */
.mock-tabs {
  display: flex;
  justify-content: space-between;
  gap: 0.125rem;
  margin-top: 0.5rem;
}
.mock-tab {
  flex: 1;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  background: transparent;
  border: 0;
  color: #22d3ee; /* cyan-400 */
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  cursor: default;
  padding: 0;
}
.mock-tab-active {
  background: #0e7490; /* cyan-700 */
  color: #fff;
}

/* Stats grid — 2×2, hairline dividers between cells. */
.mock-stat-grid {
  margin-top: 1rem;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}
.mock-stat { padding: 0; }
.mock-stat-label {
  font-size: 0.75rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #9ca3af;
  margin: 0;
}
.mock-stat-value {
  font-size: 1.125rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: #fff;
  margin: 0.25rem 0 0;
}
.mock-positive { color: #16a34a; }

/* 52w range bar — slim version with marker */
.mock-52w {
  margin-top: 1rem;
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.mock-52w-label {
  font-size: 0.625rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #9ca3af;
}
.mock-52w-bar {
  flex: 1;
  position: relative;
  height: 12px;
  border-radius: 9999px;
  background: #374151; /* gray-700 */
}
.mock-52w-marker {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 16px;
  height: 16px;
  border-radius: 9999px;
  background: #16a34a;
  border: 2px solid #fff;
}

/* ── Add past transaction inputs ── */
.mock-input-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.875rem 0;
  border-bottom: 1px solid #1f2937; /* gray-800 */
}
.mock-input-label {
  font-size: 0.875rem;
  color: #9ca3af;
  margin: 0;
}
.mock-input-value {
  font-size: 1rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: #fff;
  margin: 0;
  text-align: right;
}
.mock-input-sub {
  display: block;
  font-size: 0.75rem;
  font-weight: 400;
  color: #9ca3af;
}

.mock-recessed {
  margin-top: 0.75rem;
  border-radius: 1rem;
  background: #030712;
  padding: 1rem 1.25rem;
}
.mock-row-between {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 1rem;
  color: #fff;
}
.mock-bold { font-weight: 600; }
.mock-row-sub { color: #9ca3af; font-size: 0.875rem; margin-top: 0.5rem; }

/* ────────────────────────────────────────────────────────────────────
   Refined dashboard mock (.mock-pf-*) — written to match the real
   mobile dashboard's typography and spacing as closely as possible.
   References: StickyNav.tsx, AuthenticatedNav.tsx, NavAvatar.tsx,
   NavBell.tsx, Dashboard.tsx, PortfolioSummary.tsx. The earlier
   .mock-portfolio rules below are unused (left in place; can be
   stripped once all 4 mocks are refined).
   ──────────────────────────────────────────────────────────────────── */

.mock-pf {
  background: #111827; /* gray-900 — matches real page bg on mobile */
  display: flex;
  flex-direction: column;
}

/* ── Sticky nav (StickyNav.tsx)
   Real chrome: rgba(23,23,23,0.80) bg with backdrop-blur, white/10
   bottom border, py-4 (mobile). Logo h-5 (20px), avatar + bell h-8 w-8
   (32px) with border-gray-600. The mock renders this as a static bar
   at the top of the panel. */
.mock-pf-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1.5rem; /* py-3 px-6 — slightly tighter than real py-4 to fit the 720px budget */
  background: rgba(23, 23, 23, 0.96);
  border-bottom: 1px solid rgba(255, 255, 255, 0.10);
}
.mock-pf-wordmark {
  display: block;
  height: 20px; /* h-5 — matches real mobile size */
  width: auto;
}
.mock-pf-nav-right {
  display: flex;
  align-items: center;
  gap: 1rem; /* gap-4 — matches real mobile gap */
}
.mock-pf-bell {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 9999px;
  border: 1px solid #4b5563; /* gray-600 */
  color: #fff;
}
.mock-pf-bell svg {
  width: 16px;
  height: 16px; /* h-4 w-4 */
  display: block;
}
.mock-pf-bell-badge {
  position: absolute;
  top: 0;
  right: 0;
  width: 8px;
  height: 8px; /* h-2 w-2 */
  border-radius: 9999px;
  background: #ef4444; /* red-500 */
  pointer-events: none;
}
.mock-pf-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 9999px;
  border: 1px solid #4b5563; /* gray-600 */
  background: #4b5563; /* gray-600 — initial-on-gray fallback */
  color: #fff;
  font-size: 0.875rem; /* text-sm */
  font-weight: 700;
}

/* ── Page content
   Sticky nav removed in v2 of this mock — page now starts directly
   inside the panel. Padding-top tightened to 1rem (16px) in v4 so
   the 3rd row of stats sits clearly above the panel's bottom edge
   with breathing room (was 1.5rem in v3 — the row landed cleanly
   inside but was visibly pressed against the bottom corner).
   gap is 0.75rem (12px) so the action-buttons row sits close to
   the PortfolioSummary section below it. */
.mock-pf-page {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding-top: 1rem;
}

/* Greeting + action buttons row.
   Real Dashboard mobile: flex flex-col gap-3 px-5. Buttons in a
   `flex w-full gap-3` row with each `flex-1`. */
.mock-pf-greeting-row {
  display: flex;
  flex-direction: column;
  gap: 0.75rem; /* gap-3 */
  padding: 0 1.25rem; /* px-5 */
}
.mock-pf-greeting {
  /* h1 text-3xl font-black text-white. Real also includes a tiny
     dev-build-hash tag for samhoang's account; skipped here. */
  font-size: 1.875rem; /* text-3xl */
  font-weight: 900;
  color: #fff;
  margin: 0;
  line-height: 1.1;
}
.mock-pf-actions {
  display: flex;
  width: 100%;
  gap: 0.75rem; /* gap-3 */
}
.mock-pf-pill-btn {
  /* PageActionButton — h-10 black pill, white text + leading icon.
     Mobile flex-1 so the pair fills the row width. */
  flex: 1;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem; /* gap-1.5 — matches PageActionButton */
  border-radius: 9999px;
  background: #000;
  color: #fff;
  font-family: inherit;
  font-size: 0.875rem; /* text-sm */
  font-weight: 600;
  border: 0;
  padding: 0 0.875rem;
  white-space: nowrap;
}
.mock-pf-pill-btn svg {
  width: 16px;
  height: 16px;
  display: block;
}

/* ── PortfolioSummary section
   Real: border-b border-gray-700 bg-gray-900 py-6, px-5 inside.
   On mobile the section blends with the page bg (both gray-900);
   only the bottom hairline border visually divides it from the
   next section below. */
.mock-pf-section {
  background: #111827; /* gray-900 — matches page bg */
  border-bottom: 1px solid #374151; /* gray-700 */
  /* Top padding dropped to 0 so the hero (Portfolio balance + value
     + change line) sits flush against the page-level gap above it
     — i.e. close to the Transfer money / Trade stocks action row.
     The earlier py-5 created a double-gap (page gap + section
     padding) that pushed the hero too far below the buttons. */
  padding: 0 0 1.25rem; /* pb-5 only */
}

/* Hero block: eyebrow + balance + change line. */
.mock-pf-hero {
  padding: 0 1.25rem; /* px-5 */
}
.mock-pf-eyebrow {
  /* "Portfolio balance" — text-sm font-medium uppercase tracking-widest
     text-gray-400. */
  font-size: 0.875rem; /* text-sm */
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em; /* tracking-widest */
  color: #9ca3af; /* gray-400 */
  margin: 0;
}
.mock-pf-balance {
  /* Hero $: text-6xl font-black tabular-nums leading-none white.
     Bumped one step from text-5xl (48px) to text-6xl (60px) so the
     balance reads as the primary hero — the screen's most visually
     dominant number, rather than competing with the chart and stats
     below for emphasis. */
  font-size: 3.75rem; /* text-6xl = 60px */
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  line-height: 1; /* leading-none */
  color: #fff;
  margin: 0.5rem 0 0; /* mt-2 — matches real */
}
.mock-pf-change {
  /* Change line: text-sm font-semibold green-600. mt-3 from real.
     Includes ▲/▼ arrow + signed currency + percent + tfLabel. */
  font-size: 0.875rem;
  font-weight: 600;
  color: #16a34a; /* green-600 */
  margin: 0.75rem 0 0; /* mt-3 */
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}
.mock-pf-change-suffix {
  /* "today" / "today · 2:34 PM ET" suffix carries the same green
     since the whole change line is one button in real code. */
  font-weight: 600;
}
.mock-pf-info-icon {
  width: 16px;
  height: 16px;
  margin-left: 0.125rem;
  color: #22d3ee; /* cyan-400 — info-icon convention */
  vertical-align: text-bottom;
  display: inline-block;
}

/* Chart wrapper + svg.
   Real: mt-6 h-48 (192px) w-full; sm:px-4 inside.
   Mock uses h-35 (140px) to fit the 720px height budget — compressed
   further from h-40 (160px) so the 3rd stats row clears the panel's
   bottom edge with breathing room. */
.mock-pf-chart-wrap {
  margin-top: 1.5rem; /* mt-6 */
  height: 140px; /* compressed from h-48 → h-40 → h-35 */
  padding: 0 1rem; /* sm:px-4 */
}
.mock-pf-chart {
  display: block;
  width: 100%;
  height: 100%;
}

/* Timeframe pills (PillSwitcher tabs variant).
   Real: 7 tabs, evenly spaced via gap-x-1 sm:gap-x-2 ramp, each
   min-w-10 sm:min-w-12 floor. text-cyan-400 inactive,
   bg-cyan-700 text-white active. */
.mock-pf-tabs {
  display: flex;
  gap: 0.125rem;
  margin-top: 1rem; /* mt-4 */
  padding: 0 1.25rem; /* px-5 */
}
.mock-pf-tab {
  flex: 1;
  min-width: 40px; /* min-w-10 */
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  background: transparent;
  color: #22d3ee; /* cyan-400 */
  font-family: inherit;
  font-size: 0.875rem; /* text-sm */
  font-weight: 600;
  border: 0;
  padding: 0 0.625rem; /* px-2.5 */
}
.mock-pf-tab-active {
  background: #0e7490; /* cyan-700 */
  color: #fff;
}

/* 8-stat grid (size="sm" Stat tiles).
   Real: mt-8 grid-cols-2 gap-6 px-5 lg:grid-cols-4 lg:gap-8. The mock
   renders the first 6 (Cash, Stocks & ETFs, Net deposits, Net
   dividends received, All-time realized P&L, Unrealized P&L)
   above-the-fold; the bottom 2 (YTD realized + unrealized P&L) sit
   just below the 720px panel edge. mt-6 tightens the gap between
   chart pills and stats so the 3rd stats row clears the panel
   bottom with breathing room. */
.mock-pf-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem; /* gap-6 */
  margin-top: 1.5rem; /* mt-6 — tightened from mt-8 */
  padding: 0 1.25rem; /* px-5 */
}
.mock-pf-stat-label {
  /* Stat size="sm" label: text-xs font-medium uppercase
     tracking-widest text-gray-400 (sm:text-sm bumps later). */
  font-size: 0.75rem; /* text-xs */
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #9ca3af;
  margin: 0;
}
.mock-pf-stat-value {
  /* Stat size="sm" value: mt-1 text-base font-semibold tabular-nums
     (sm:text-xl bumps later). */
  font-size: 1rem; /* text-base */
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: #fff;
  margin: 0.25rem 0 0; /* mt-1 */
}
/* Realized + unrealized P&L tiles surface green/red sign colors
   (real Stat uses tone="signed" + the colorClass helper). */
.mock-pf-positive { color: #16a34a; }
.mock-pf-negative { color: #ef4444; }

/* ────────────────────────────────────────────────────────────────────
   Refined Deposit Money mock (.mock-dp-*) — built against
   TransferModal.tsx in DEPOSIT mode. Modal-on-mobile: bordered
   header, body with [PillSwitcher Deposit/Withdraw + recessed amount
   block + hint], sticky footer with value-echoing PrimaryCta. Below
   the modal: a fake iOS-style numeric keypad (the real app uses the
   OS keyboard; mock includes it for visual context).
   ──────────────────────────────────────────────────────────────────── */

.mock-dp {
  background: #1f2937; /* gray-800 — floating-tier modal panel */
}

/* Header — same chrome as add-past header. */
.mock-dp-header {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.25rem; /* py-4 px-5 */
  border-bottom: 1px solid #1f2937; /* gray-800 — same color as panel; effectively invisible. Real has gray-800 against gray-800/80. */
  background: rgba(31, 41, 55, 0.6); /* slight tint so header reads as distinct from body */
}
.mock-dp-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 9999px;
  background: transparent;
  border: 0;
  color: #9ca3af;
  padding: 0;
}
.mock-dp-x svg { width: 20px; height: 20px; display: block; }
.mock-dp-title {
  font-size: 1rem; /* text-base mobile */
  font-weight: 600;
  color: #fff;
  margin: 0;
}
.mock-dp-spacer { width: 36px; height: 36px; display: block; }

/* Form body (px-5 pt-6 pb-10 flex-1). */
.mock-dp-form {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 1.5rem 1.25rem 2.5rem;
  min-height: 0;
}

/* Switcher: PillSwitcher size="md" segmented variant. h-11 (44px)
   gray-700 shell with 2px inner padding (p-0.5), 2px gap. Inside
   buttons stretch to 40px. Active = bg-cyan-700 text-white with
   brand-cyan dot to the left of label. */
.mock-dp-segpills {
  display: flex;
  gap: 2px;
  height: 44px;
  padding: 2px;
  border-radius: 9999px;
  background: #374151; /* gray-700 */
}
.mock-dp-segpill {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border-radius: 9999px;
  background: transparent;
  border: 0;
  color: #d1d5db;
  font-family: inherit;
  font-size: 1rem; /* text-base — md size */
  font-weight: 600;
  padding: 0 1rem;
}
.mock-dp-segpill-active {
  background: #0e7490; /* cyan-700 */
  color: #fff;
}
.mock-dp-segpill-dot {
  width: 8px;
  height: 8px;
  border-radius: 9999px;
  background: #00bde0;
  flex-shrink: 0;
}

/* Recessed amount block — mt-6 rounded-2xl bg-gray-900 p-5. Inside
   on a gray-800 panel, gray-900 reads as slightly recessed (one
   shade darker). */
.mock-dp-amount-box {
  margin-top: 1.5rem; /* mt-6 */
  padding: 1.25rem; /* p-5 */
  border-radius: 1rem; /* rounded-2xl */
  background: #111827; /* gray-900 */
}
.mock-dp-eyebrow {
  /* "Amount" — text-center text-sm font-medium uppercase
     tracking-widest text-gray-400. */
  text-align: center;
  font-size: 0.875rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #9ca3af;
  margin: 0;
}
/* Amount row — flex items-baseline justify-center gap-1. The "$" is
   ONE step smaller than the value: real code uses text-4xl prefix
   vs text-5xl input on mobile.

   min-height pins the row's height to the TYPED state (the text-5xl
   value's full line-box at 3rem) even when the value text is empty.
   Without this, the row collapses to ~40px when the deposit
   animation clears the value to "" between loop iterations, then
   grows back to ~48px when typing resumes — visually the input box
   AND the helper text below ("Deposit limit: $1,000,000.00") jump
   down 8px every cycle. Locking to 3rem keeps both stationary
   throughout the animation. */
.mock-dp-amount-row {
  margin-top: 0.75rem; /* mt-3 */
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 0.25rem;
  position: relative;
  min-height: 3rem;
}
.mock-dp-amount-prefix {
  font-size: 2.25rem; /* text-4xl */
  font-weight: 900;
  color: #9ca3af; /* gray-400 — recedes against the value */
  line-height: 1;
}
.mock-dp-amount-value {
  font-size: 3rem; /* text-5xl */
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  color: #fff;
  line-height: 1;
}
/* Subtle blinking cursor after the value to read as "actively
   typing." 1px wide × matches caret height.

   translateY pushes the cursor down ~9 mock px. The step-frame
   transforms the entire mock with scale(100cqi / 400px), so at
   widest breakpoints (~520px frame width → ~1.3× scale) the
   cursor lands ~12 actual px lower. Without this offset the
   cursor's bottom (its baseline, since it's an empty inline-block)
   sits flush with the text baseline of the "$500" row, which
   reads as the cursor "floating" above the digit's center
   — pushing it down anchors the cursor visually inside the digit
   like a real typing caret. */
.mock-dp-amount-cursor {
  display: inline-block;
  width: 2px;
  height: 2.5rem; /* roughly text-5xl cap-height */
  background: #fff;
  margin-left: 0.125rem;
  transform: translateY(9px);
  animation: mock-cursor-blink 1.1s step-end infinite;
}
@keyframes mock-cursor-blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}
/* Hint line — mt-4 text-center text-lg gray-400. */
.mock-dp-hint {
  margin: 1rem 0 0; /* mt-4 */
  text-align: center;
  font-size: 1.125rem; /* text-lg */
  color: #9ca3af;
}

/* Footer (border-t border-gray-700 px-5 pt-3 pb-4). PrimaryCta. */
.mock-dp-footer {
  flex-shrink: 0;
  padding: 0.75rem 1.25rem 1rem;
  border-top: 1px solid #374151;
}
.mock-dp-cta {
  width: 100%;
  height: 48px;
  border-radius: 9999px;
  background: #0093b2;
  color: #fff;
  font-family: inherit;
  font-size: 1rem;
  font-weight: 600;
  border: 0;
  white-space: nowrap;
  cursor: default;
}

/* iOS-style numeric keypad — sits below the modal as if the OS
   keyboard slid up. Dark mode look: bg gray-700 strip, keys gray-600
   with subtle 1px highlight on top edge for the "raised" feel. 4
   rows × 3 cols. Each key: number on top, optional letter sub-row
   beneath (matches iPhone keypad). The "." and backspace use
   slightly muted bg to read as meta keys. */
.mock-dp-keypad {
  flex-shrink: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  padding: 0.625rem 0.5rem 0.875rem;
  background: #111827; /* gray-900 strip — slightly darker than panel */
  border-top: 1px solid #374151;
}
.mock-dp-key {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  height: 44px;
  border-radius: 8px;
  background: transparent; /* flat text-only keys, no key-cap chrome */
  border: 0;
  color: #fff;
  font-family: inherit;
  font-size: 1.5rem; /* iOS-ish number size */
  font-weight: 400;
  padding: 0;
  cursor: default;
}
.mock-dp-key svg {
  width: 22px;
  height: 22px;
  display: block;
  color: #fff;
}
.mock-dp-key-sub {
  font-size: 0.5rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  color: rgba(255, 255, 255, 0.6);
  line-height: 1;
}
/* Meta keys (".", backspace) — same flat treatment as number keys
   now that the keypad is bg-less. */
.mock-dp-key-meta {
  background: transparent;
}

/* ────────────────────────────────────────────────────────────────────
   Refined Stock Ticker Details mock (.mock-tk-*) — built against
   TickerModal.tsx. Modal-on-mobile chrome: gray-800 panel (floating
   tier per CLAUDE.md), bordered header, scrollable body with summary
   block + chart + tabs + bordered stats wrapper containing the 52W
   infographic.
   ──────────────────────────────────────────────────────────────────── */

.mock-tk {
  /* Override the shared .mock background — TickerModal is a floating-
     tier surface (bg-gray-800/80 in real code) so panel reads at a
     lighter shade than the page-tier dashboard mock. */
  background: #1f2937; /* gray-800 */
}

/* Header (px-5 py-4 border-b border-gray-700).
   Floating-tier modals use border-gray-700 dividers (one shade
   brighter than the page-tier border-gray-800 the add-past modal
   header uses; both are correct per the CLAUDE.md tier rule). */
.mock-tk-header {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 1rem 1.25rem; /* py-4 px-5 */
  border-bottom: 1px solid #374151; /* gray-700 — floating-tier */
}
.mock-tk-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px; /* h-9 w-9 */
  height: 36px;
  border-radius: 9999px;
  background: transparent;
  border: 0;
  color: #9ca3af; /* gray-400 */
  padding: 0;
}
.mock-tk-x svg {
  width: 20px; /* h-5 w-5 */
  height: 20px;
  display: block;
}
.mock-tk-header-right {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
/* WatchlistPill (mobile state): h-10 w-10 rounded-full border
   border-gray-600 bg-transparent text-white. Bookmark icon h-4 w-4
   strokeWidth=3. */
.mock-tk-watch {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 9999px;
  border: 1px solid #4b5563; /* gray-600 */
  background: transparent;
  color: #fff;
  padding: 0;
}
.mock-tk-watch svg {
  width: 16px;
  height: 16px;
  display: block;
}
/* Trade button: h-10 cyan pill with ArrowLeftRight icon + "Trade".
   bg-[#0093b2] hover:bg-[#007e9b]. */
.mock-tk-trade {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  height: 40px;
  border-radius: 9999px;
  background: #0093b2;
  color: #fff;
  border: 0;
  padding: 0 1.25rem;
  font-family: inherit;
  font-size: 0.875rem; /* text-sm — sm:text-base bumps later */
  font-weight: 600;
}
.mock-tk-trade svg {
  width: 16px;
  height: 16px;
  display: block;
}

/* Body — flex-1 below the header. Real overflow scrolls; mock
   doesn't need to. */
.mock-tk-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 0.5rem 1.25rem 0; /* pt-2 px-5 */
  overflow: hidden;
}

/* Summary block: symbol h2 + company name + price/change row +
   exchange line. */
.mock-tk-summary { padding-top: 0; }
.mock-tk-symbol {
  font-size: 2.25rem; /* text-4xl mobile (sm:text-5xl) */
  font-weight: 900;
  letter-spacing: -0.025em; /* tracking-tight */
  color: #fff;
  margin: 0;
  line-height: 1;
}
.mock-tk-company {
  /* mt-1 text-xl font-medium text-gray-400 — mobile size. */
  font-size: 1.25rem; /* text-xl */
  font-weight: 500;
  color: #9ca3af;
  margin: 0.25rem 0 0; /* mt-1 */
  line-height: 1.2;
}
.mock-tk-price-row {
  margin-top: 1.25rem; /* mt-5 */
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 0.75rem; /* gap-x-3 */
  row-gap: 0.25rem; /* gap-y-1 */
}
.mock-tk-price {
  /* text-3xl font-black white — mobile size. */
  font-size: 1.875rem; /* text-3xl */
  font-weight: 900;
  color: #fff;
  margin: 0;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.mock-tk-change {
  /* text-sm font-semibold green-600 (or red-500 if down). */
  font-size: 0.875rem; /* text-sm */
  font-weight: 600;
  color: #16a34a;
  margin: 0;
}
.mock-tk-exchange {
  /* mt-2 text-sm font-medium uppercase tracking-widest gray-400. */
  margin: 0.5rem 0 0;
  font-size: 0.875rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #9ca3af;
}

/* Chart wrap (mt-6, compressed h-48). */
.mock-tk-chart-wrap {
  margin-top: 1.5rem; /* mt-6 */
  height: 192px; /* h-48 — compressed from real h-64 mobile */
  width: 100%;
}
.mock-tk-chart {
  display: block;
  width: 100%;
  height: 100%;
}

/* Timeframe pills (mt-4 px-0 inside body which already has px-5).
   Same PillSwitcher tabs styling as the dashboard mock. */
.mock-tk-tabs {
  display: flex;
  gap: 0.125rem;
  margin-top: 1rem; /* mt-4 */
}
.mock-tk-tab {
  flex: 1;
  min-width: 40px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  background: transparent;
  color: #22d3ee; /* cyan-400 */
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  border: 0;
  padding: 0 0.625rem;
}
.mock-tk-tab-active {
  background: #0e7490; /* cyan-700 */
  color: #fff;
}

/* Stats wrapper (mt-4 border-t border-gray-700 pt-4 pb-5). Real
   wrapper holds [date header → 52W viz → 12-stat grid]; mock shows
   the date head + 52W viz only. Bottom padding gives the unit room
   below — keeps the 52W viz from feeling crammed against the panel
   edge. */
.mock-tk-statwrap {
  margin-top: 1rem; /* mt-4 */
  border-top: 1px solid #374151; /* gray-700 */
  padding-top: 1rem; /* pt-4 */
  padding-bottom: 1.25rem; /* pb-5 — matches real wrapper */
}
.mock-tk-datehead {
  /* h3 text-2xl font-black white. */
  font-size: 1.5rem; /* text-2xl mobile */
  font-weight: 900;
  color: #fff;
  margin: 0;
  line-height: 1.1;
}

/* 52W range infographic. 3-col grid (auto | 1fr | auto), 3 rows:
   row 1 = absolute-positioned price label + connector line over the
   middle column; row 2 = LOW / bar / HIGH; row 3 col-span-3 =
   delta lines. Top margin sized for the price label + connector
   line stack to clear the date head above. */
.mock-tk-52w {
  margin-top: 1rem; /* tightened further from mt-7 → mt-4 (16px); date
                       label sits directly above the price-tag, slider,
                       and deltas as one compact unit */
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  column-gap: 0.75rem;
  row-gap: 0.75rem;
  position: relative;
}
/* Price label + connector line block — sits absolute above the bar.
   Spans the bar's column visually via the same `left: progress%`
   anchor that the marker uses. */
.mock-tk-52w-pricewrap {
  grid-column: 2 / 3;
  grid-row: 1 / 2;
  position: relative;
  height: 28px;
}
.mock-tk-52w-price {
  position: absolute;
  bottom: 0;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: 1rem; /* text-base — matches Stat size="sm" value */
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: #fff;
  margin: 0;
}
.mock-tk-52w-line {
  position: absolute;
  width: 1px;
  height: 12px;
  background: #fff;
  bottom: -14px; /* line bottom touches circle top (-2 from bar top) */
  transform: translateX(-50%);
}
/* Side labels (52W LOW / 52W HIGH). text-xs uppercase tracking-widest
   text-gray-400 — same eyebrow style as the dashboard's stat labels. */
.mock-tk-52w-side {
  grid-row: 2 / 3;
  font-size: 0.75rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #9ca3af;
  white-space: nowrap;
}
.mock-tk-52w-side:first-of-type { grid-column: 1; }
.mock-tk-52w-side:nth-of-type(2) { grid-column: 3; }
.mock-tk-52w-bar {
  grid-column: 2 / 3;
  grid-row: 2 / 3;
  position: relative;
  height: 12px;
  border-radius: 9999px;
  background: #374151; /* gray-700 — same as PillSwitcher segmented shell */
}
.mock-tk-52w-marker {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 16px; /* h-4 w-4 — matches real marker */
  height: 16px;
  border-radius: 9999px;
  border: 2px solid #fff;
  background: #16a34a; /* green-600 — direction-up color */
}
.mock-tk-52w-deltas {
  grid-column: 1 / -1;
  grid-row: 3 / 4;
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  column-gap: 0.75rem;
  row-gap: 0.25rem;
  font-size: 0.875rem; /* text-sm — sm:text-base later */
  font-variant-numeric: tabular-nums;
  color: #9ca3af;
  text-align: center;
  margin-top: 0.5rem;
}
.mock-tk-52w-deltaitem {
  white-space: nowrap;
}
.mock-tk-52w-num { color: #fff; }

/* ────────────────────────────────────────────────────────────────────
   Refined Add Past Transaction mock (.mock-ap-*) — built against
   AddPastTransactionModal.tsx (the BUY mode is the canonical case).
   Modal-on-mobile rendering: bordered header strip + bordered type-
   pill strip + scrollable form + sticky footer with PrimaryCta.
   ──────────────────────────────────────────────────────────────────── */

/* Header (px-5 py-4 border-b border-gray-800).
   IconButton h-9 w-9 close on left, centered title, h-9 w-9 spacer
   on right so the title sits on the geometric middle. */
.mock-ap-header {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.25rem; /* py-4 px-5 */
  border-bottom: 1px solid #1f2937; /* gray-800 — floating-tier */
}
.mock-ap-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px; /* h-9 w-9 — IconButton */
  height: 36px;
  border-radius: 9999px;
  background: transparent;
  border: 0;
  color: #9ca3af; /* gray-400 */
  padding: 0;
}
.mock-ap-x svg {
  width: 20px; /* h-5 w-5 */
  height: 20px;
  display: block;
}
.mock-ap-title {
  font-size: 1rem; /* text-base on mobile (sm:text-lg later) */
  font-weight: 600;
  color: #fff;
  margin: 0;
}
.mock-ap-spacer {
  width: 36px;
  height: 36px;
  display: block;
}

/* Type switcher strip (px-5 py-4 border-b border-gray-800).
   Real PillSwitcher size="md": shell h-11 (44px) bg-gray-700 with
   2px inner padding (p-0.5), 0.5 gap between buttons. Inner pills
   stretch to (44 - 4) = 40px (h-10). Active pill bg-cyan-700,
   inactive transparent + text-gray-300. Active pill includes the
   2-unit decorative-cyan dot (#00bde0) to the left of its label. */
.mock-ap-typestrip {
  flex-shrink: 0;
  padding: 1rem 1.25rem; /* py-4 px-5 */
  border-bottom: 1px solid #1f2937; /* gray-800 */
}
.mock-ap-typepills {
  display: flex;
  gap: 2px;
  height: 44px;
  padding: 2px;
  border-radius: 9999px;
  background: #374151; /* gray-700 — segmented shell */
}
.mock-ap-typepill {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border-radius: 9999px;
  background: transparent;
  border: 0;
  color: #d1d5db; /* gray-300 — inactive */
  font-family: inherit;
  font-size: 0.875rem; /* compressed from text-base/sm:text-base since 4 pills crowd at 360px content width */
  font-weight: 600;
  padding: 0 0.625rem;
  min-width: 0;
}
.mock-ap-typepill-active {
  background: #0e7490; /* cyan-700 — selection-fill */
  color: #fff;
}
.mock-ap-typepill-dot {
  width: 8px;
  height: 8px;
  border-radius: 9999px;
  background: #00bde0; /* decorative cyan */
  flex-shrink: 0;
}

/* Form body — flex-1 so it absorbs remaining vertical space between
   the type-pill strip and the sticky footer. */
.mock-ap-form {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 1.5rem 1.25rem 2.5rem; /* pt-6 px-5 pb-10 */
  overflow-x: hidden;
}

/* 2×2 grid: row 1 = Date | Ticker (with company-name hint),
   row 2 = Shares | Price / share. items-start so the ticker hint can
   grow into its cell without disturbing Date's row. */
.mock-ap-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: start;
  gap: 1rem; /* gap-4 */
}
.mock-ap-field { display: flex; flex-direction: column; }
.mock-ap-label {
  /* text-sm font-medium uppercase tracking-widest text-gray-400 */
  font-size: 0.875rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #9ca3af;
}
.mock-ap-input {
  /* mt-2 flex items-center gap-2 rounded-xl border bg-gray-900
     px-4 py-3 border-gray-700 — focus-within would change to
     gray-400 in real code; mock shows the resting state.
     Real code uses a real <input> element which the browser
     aligns to a font baseline that visually centers within the
     box. Our mock uses <span>s with explicit line-height: 1
     below + symmetric vertical padding to achieve the same
     optical centering. */
  margin-top: 0.5rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6875rem 1rem 1.0625rem; /* 11px top / 16px sides / 17px bottom — asymmetric to nudge text optically up by ~3px against FreightSansPro's low-baseline metric. Total height stays at 44px (matches py-3.5 outer height). */
  border-radius: 0.75rem; /* rounded-xl */
  border: 1px solid #374151; /* gray-700 */
  background: #111827; /* gray-900 */
}
.mock-ap-input-text {
  font-size: 1rem; /* text-base */
  line-height: 1; /* override default 1.5; flex align-items: center
                     does the optical-center work directly on the
                     glyph height */
  color: #fff;
  flex: 1;
  font-variant-numeric: tabular-nums;
  /* Color transition supports the framed-view's typing animation:
     when illustrations.js removes the --hint modifier on the price
     input, the gray hint color tweens to white over 600ms. Most
     state changes (ticker swap, shares swap) are instant — the
     transition is invisible because the value text content changes
     in lockstep with the class flip. */
  transition: color 600ms ease;
}
/* Hint state for typed inputs in the framed mock-ap animation —
   illustrations.js adds this class to indicate "placeholder-like"
   text (the gray hint shown before the user has typed). */
.mock-ap-input-text--hint {
  color: #6b7280; /* gray-500 — between input-text white and label gray-400 so the hint reads as faded but legible */
}
/* Ticker input gets distinct typography: bold + tracking-wide so the
   symbol reads with weight. */
.mock-ap-ticker {
  font-weight: 700;
  letter-spacing: 0.025em;
}
/* Numeric inputs (Shares, Price/share value) get tabular-nums but no
   bold treatment. */
.mock-ap-num { font-weight: 400; }
/* "$" prefix on the price input — gray-400 so it reads as a label
   for the value, not as part of the value. line-height: 1 so it
   shares the same vertical centering as the .mock-ap-input-text
   sibling. */
.mock-ap-prefix {
  color: #9ca3af; /* gray-400 */
  font-size: 1rem;
  line-height: 1;
}
/* Trailing icons inside an input wrapper (calendar glyph on Date,
   green check on a valid Ticker). */
.mock-ap-trail-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: #9ca3af; /* gray-400 default */
  display: block;
}
.mock-ap-check {
  color: #16a34a; /* green-600 */
  /* Framed-view animation: hidden state fades to visible when
     illustrations.js validates the ticker. */
  transition: opacity 300ms ease;
}
/* --hidden modifier used by illustrations.js to hide the checkmark
   before the ticker animation has typed VOO. Layout preserved via
   opacity (visibility:hidden would also work but opacity transitions). */
.mock-ap-check--hidden { opacity: 0; }
/* Date field — looks like a button (text-left + trailing calendar).
   Real DateField has the same chrome as the input wrapper. */
.mock-ap-input-button { cursor: default; }

/* Hint line below the ticker input — mt-1 text-xs gray-400.
   Tightened from mt-1.5 text-sm so the company-name hint reads as a
   subtle annotation closer to the Ticker input rather than as its
   own labelled row. min-height reserves a line so an empty/loading
   /valid state doesn't push the surrounding row layout. */
.mock-ap-hint {
  margin: 0.25rem 0 0; /* mt-1 */
  font-size: 0.75rem; /* text-xs */
  color: #9ca3af;
  min-height: 1rem;
  /* Framed-view animation: company-name hint fades in once the
     ticker animation validates "VOO". */
  transition: opacity 300ms ease;
}
.mock-ap-hint--hidden { opacity: 0; }

/* Total + cash-availability lines (mt-6 below the grid). text-lg
   text-gray-400 (red-500 when cost exceeds available cash; mock
   shows the all-good state). space-y-1 between lines. */
.mock-ap-totals {
  margin-top: 1.5rem; /* mt-6 */
}
.mock-ap-total-line {
  font-size: 1.125rem; /* text-lg */
  color: #9ca3af;
  margin: 0;
  font-variant-numeric: tabular-nums;
  /* Framed-view animation: the "Total: $X" line reveals once the
     price has been confirmed (faded to white). The
     "Cash available on X: $Y" line is informational and stays
     visible from the start — illustrations.js only adds --hidden
     to the first total line. */
  transition: opacity 300ms ease;
}
.mock-ap-total-line--hidden { opacity: 0; }

/* Pulsing ring around the chart's "live now" dot — animates the
   outer ring's radius outward while fading opacity, then snaps
   back. Mirrors the real product's PulsingChartDot
   (animate-ping outer ring expanding outward every 1s; see CLAUDE.md).
   Applied to both mock-tk and mock-pf outer ring circles.
   SVG `r` and `fill-opacity` are CSS-animatable in every browser
   the app targets (Chrome 78+, Safari 13.1+, Firefox 71+). The
   keyframes use these properties directly so the static fallback
   r="10" / fill-opacity="0.40" from the SVG attributes is what
   the ring renders at the 0% keyframe — identical pose to no-
   animation rendering for a clean restart each cycle. */
@keyframes mock-chart-pulse {
  0% {
    r: 10;
    fill-opacity: 0.40;
  }
  100% {
    r: 18;
    fill-opacity: 0;
  }
}
.mock-chart-pulse-ring {
  animation: mock-chart-pulse 1.6s ease-out infinite;
}
.mock-ap-total-line + .mock-ap-total-line {
  margin-top: 0.25rem; /* space-y-1 */
}

/* Per-type explainer note (mt-6 text-sm gray-400). */
.mock-ap-note {
  margin-top: 1.5rem; /* mt-6 */
  font-size: 0.875rem;
  color: #9ca3af;
  line-height: 1.5;
}

/* Sticky footer — PrimaryCta (h-12 w-full rounded-full bg-[#0093b2]).
   border-t border-gray-700 above. Real footer pads heavily on the
   bottom for iOS safe-area; mock uses a clean 16px bottom. */
.mock-ap-footer {
  flex-shrink: 0;
  padding: 0.75rem 1.25rem 1rem; /* pt-3 px-5 pb-4 */
  border-top: 1px solid #374151; /* gray-700 */
}
.mock-ap-cta {
  width: 100%;
  height: 48px; /* h-12 */
  border-radius: 9999px;
  background: #0093b2; /* brand CTA cyan */
  color: #fff;
  font-family: inherit;
  font-size: 1rem;
  font-weight: 600;
  border: 0;
  white-space: nowrap;
  cursor: default;
}

/* ────────────────────────────────────────────────────────────────────
   Below: original .mock-portfolio rules from the v1 mock — kept while
   the other 3 mocks still depend on shared chrome + chart + tabs +
   stats classes. Cleanup pass once all 4 mocks have refined .mock-pf-
   /.mock-tk- /.mock-ap- /.mock-dp- equivalents.
   ──────────────────────────────────────────────────────────────────── */

/* ── Dashboard: sticky nav + greeting + balance ── */
.mock-stickynav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1.25rem;
  border-bottom: 1px solid #374151;
}
.mock-stickynav-brand {
  font-size: 0.875rem;
  font-weight: 900;
  letter-spacing: 0.05em;
  color: #00bde0;
}
.mock-stickynav-right {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.mock-avatar {
  width: 32px;
  height: 32px;
  border-radius: 9999px;
  background: #0e7490;
  color: #fff;
  font-size: 0.875rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mock-greeting {
  font-size: 1rem;
  color: #d1d5db;
  margin: 0 0 1rem;
}
.mock-portfolio-balance {
  font-size: 3rem;
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: #fff;
  margin: 0.5rem 0 0.5rem;
}

/* ===== Footer ===== */

.site-footer {
  position: relative;
  z-index: 10;
  width: 100%;
  /* Solid gray-900 — needs to be explicit because this stylesheet
     is shared with the legal pages (/privacy, /terms) where body bg
     is #fff. A transparent footer there falls through to white,
     which broke the dark-footer look. On the landing, body bg is
     #030404 (almost the same), so the visual change is minimal —
     footer area now reads as a flat gray-900 surface instead of
     the aurora-animated darkness behind a transparent footer. */
  background: #171717;
}
.site-footer .container {
  padding-top: 8rem;
  padding-bottom: 8rem;
}
@media (min-width: 640px) {
  .site-footer .container { padding-top: 10rem; padding-bottom: 10rem; }
}
@media (min-width: 768px) {
  .site-footer .container { padding-top: 12rem; padding-bottom: 12rem; }
}
.footer-brand {
  display: block;
  text-decoration: none;
}
.footer-brand img {
  display: block;
  width: 100%;
  height: auto;
}
.footer-bottom {
  margin-top: 2.5rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.5rem;
  border-top: 1px solid #262626;
  padding-top: 2.5rem;
}
.footer-link {
  font-size: 0.875rem;
  color: #d4d4d4;
  text-decoration: none;
  transition: color 150ms;
}
.footer-link:hover { color: #f5f5f5; }
.footer-copy {
  font-size: 0.875rem;
  color: #d4d4d4;
}
@media (min-width: 640px) {
  .footer-copy { margin-left: auto; }
}

/* ===== Legal pages (/privacy, /terms) =====
   Static pages share this stylesheet — they reuse the .sticky-nav-*
   layout (under a non-sticky .page-nav wrapper) and the .site-footer
   block so the nav and footer match the landing's at every breakpoint.
   Body gets `class="legal"` to flip the bg from #f5f5f5 (landing) to
   white (legal pages have prose under the nav). */

body.legal { background: #fff; }

.page-nav {
  background: #fff;
  border-bottom: 1px solid #e5e5e5;
}

.main {
  max-width: 800px;
  margin: 0 auto;
  padding: 4rem 1.5rem;
}
@media (min-width: 640px) { .main { padding: 6rem 2rem; } }
@media (min-width: 768px) { .main { padding-left: 3rem; padding-right: 3rem; } }

.eyebrow {
  font-size: 0.875rem;
  font-weight: 500;
  color: #737373;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
h1.title {
  margin-top: 0.75rem;
  font-size: 2.25rem;
  font-weight: 900;
  color: #171717;
  line-height: 1.1;
}
@media (min-width: 640px) { h1.title { font-size: 3rem; } }
.updated {
  margin-top: 1rem;
  font-size: 1rem;
  color: #737373;
}

.prose {
  margin-top: 3rem;
  font-size: 1.125rem;
  line-height: 1.7;
  color: #404040;
}
.prose section + section { margin-top: 2.5rem; }
.prose h2 {
  font-size: 1.25rem;
  font-weight: 700;
  color: #171717;
}
@media (min-width: 640px) { .prose h2 { font-size: 1.5rem; } }
.prose p { margin-top: 1rem; }
.prose strong { color: #171717; font-weight: 700; }
.prose a {
  color: #171717;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.prose a:hover { color: #525252; }
.prose ul {
  margin-top: 1rem;
  list-style: none;
}
.prose li {
  margin-top: 0.75rem;
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
}
.prose li::before {
  content: "";
  display: inline-block;
  width: 0.375rem;
  height: 0.375rem;
  margin-top: 0.7em;
  background: #a3a3a3;
  border-radius: 50%;
  flex-shrink: 0;
}
